From: Antonio Jimenez Pastor Date: Thu, 8 Mar 2018 10:39:24 +0000 (+0100) Subject: Setting up the public repository for the SAGE implementation Differentially Definable... X-Git-Url: http://git.risc.jku.at/gitweb/?a=commitdiff_plain;h=da67015149d99a8d64e49c1bb16a681130a7af9e;p=ajpastor%2Fdiff_defined_functions.git Setting up the public repository for the SAGE implementation Differentially Definable Functions. --- da67015149d99a8d64e49c1bb16a681130a7af9e diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..83b59bb --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +SHELL:=/bin/bash +BASE=some +ZIP=diff_defined_functions# ZIP name +VERSION=$(shell cat ./package-version.txt) + +all: + sage -sh sage-pip-install . + +create: + @echo "Creating main directories..." + @mkdir -p ./$(BASE) + @mkdir -p ./releases/old + @echo "from pkgutil import extend_path;" > ./$(BASE)/__init__.py + @echo "__path__ = extend_path(__path__, __name__);" >> ./$(BASE)/__init__.py + +zip: + @echo "Compressing the project into file" $(ZIP)".zip"... + @zip -r ./releases/old/$(ZIP)__$(VERSION)__`date +'%y.%m.%d_%H:%M:%S'`.zip $(BASE) + @rm -f ./releases/$(ZIP)__$(VERSION).zip + zip -r ./releases/$(ZIP)__$(VERSION).zip $(BASE) + @echo $(VERSION) + diff --git a/SPKG.txt b/SPKG.txt new file mode 100644 index 0000000..6ce83f5 --- /dev/null +++ b/SPKG.txt @@ -0,0 +1,30 @@ += diff_defined_functions = + +== Description == + +A Sage implementation of Differentially Definable Functions. + +Main features for the Diff. Definable functions. Allows the user to compute +the closure properties of these class of functions where the coefficients of +the differential equations belong to an arbitrary differential ring. + +Give access to the objects DFinite and DDFinite, that are representations +of the D-Finite and DD-Finite differential rings. + +== License == + +Distributed under the terms of the GNU General Public License (GPL) + +http://www.gnu.org/licenses/ + +== Upstream Contact == + +Antonio Jimenez-Pastor + +== Dependencies == + +none. + +== Special Update/Build Instructions == + +none. diff --git a/ajpastor/__init__.py b/ajpastor/__init__.py new file mode 100644 index 0000000..6e3977c --- /dev/null +++ b/ajpastor/__init__.py @@ -0,0 +1,2 @@ +from pkgutil import extend_path; +__path__ = extend_path(__path__, __name__); diff --git a/ajpastor/dd_functions/__init__.py b/ajpastor/dd_functions/__init__.py new file mode 100644 index 0000000..b793718 --- /dev/null +++ b/ajpastor/dd_functions/__init__.py @@ -0,0 +1,16 @@ +try: + from .ddFunction import * +except Exception: + print "Error loading module dd_functions.ddFunction"; +try: + from .ddExamples import *; +except Exception: + print "Error loading module dd_functions.ddExamples"; +try: + from .symbolic import *; +except Exception: + print "Error loading module dd_functions.symbolic"; + +from pkgutil import extend_path; +__path__ = extend_path(__path__, __name__); + diff --git a/ajpastor/dd_functions/ddExamples.py b/ajpastor/dd_functions/ddExamples.py new file mode 100644 index 0000000..e1f10d3 --- /dev/null +++ b/ajpastor/dd_functions/ddExamples.py @@ -0,0 +1,499 @@ + +# This file was *autogenerated* from the file ./ddExamples.sage +from sage.all_cmdline import * # import sage library + +_sage_const_3 = Integer(3); _sage_const_2 = Integer(2); _sage_const_1 = Integer(1); _sage_const_0 = Integer(0); _sage_const_6 = Integer(6) +from ajpastor.dd_functions.ddFunction import *; + +from ajpastor.misc.dinamic_string import *; +#from ajpastor.dd_functions.ddParametrizedFunction import *; + +## Global variables (PUBLIC) +DFinite_examples = {}; +DDFinite_examples = {}; + +## Global variables (PRIVATE) +__example_names = {}; + +################################################################################## +################################################################################## +### +### Predefined examples +### +################################################################################## +################################################################################## +def DD_EXAMPLES_LOAD(): + global DFinite_examples; global DDFinite_examples; + global __example_names; + + s = DFinite.element([_sage_const_1 ,_sage_const_0 ,_sage_const_1 ],[_sage_const_0 ,_sage_const_1 ], name=DinamicString("sin(_1)", "x")); + c = DFinite.element([_sage_const_1 ,_sage_const_0 ,_sage_const_1 ],[_sage_const_1 ,_sage_const_0 ], name=DinamicString("cos(_1)", "x")); + sh = DFinite.element([-_sage_const_1 ,_sage_const_0 ,_sage_const_1 ],[_sage_const_0 ,_sage_const_1 ], name=DinamicString("sinh(_1)", "x")); + ch = DFinite.element([-_sage_const_1 ,_sage_const_0 ,_sage_const_1 ],[_sage_const_1 ,_sage_const_0 ], name=DinamicString("cosh(_1)", "x")); + ln = DFinite.element([_sage_const_1 ,_sage_const_0 ,(x+_sage_const_1 )],[_sage_const_0 ,_sage_const_1 ], name=DinamicString("log(_1-1)", "x")); + e = DFinite.element([-_sage_const_1 ,_sage_const_1 ],[_sage_const_1 ], name=DinamicString("exp(_1)", "x")); + tan = DDFinite.element([-_sage_const_2 ,_sage_const_0 ,c**_sage_const_2 ],[_sage_const_0 ,_sage_const_1 ], name=DinamicString("tan(_1)", "x")); + + ## Defining D-Finite Examples + DFinite_examples['e'] = e; + DFinite_examples['ln'] = ln; + DFinite_examples['sin'] = s; + DFinite_examples['cos'] = c; + DFinite_examples['sinh'] = sh; + DFinite_examples['cosh'] = ch; + P = DFiniteP.parameters()[_sage_const_0 ]; + DFinite_examples['bessel'] = DFiniteP.element([x**_sage_const_2 -P**_sage_const_2 ,x,x**_sage_const_2 ], name=DinamicString("bessel_J(_1,_2)", ["P","x"])); + DFinite_examples['legendre'] = DFiniteP.element([P*(P+_sage_const_1 ), -_sage_const_2 *x,_sage_const_1 -x**_sage_const_2 ], name=DinamicString("legendre_P(_1,_2)", ["P","x"])); + DFinite_examples['chebyshev1'] = DFiniteP.element([P**_sage_const_2 ,-x,(_sage_const_1 -x**_sage_const_2 )], name=DinamicString("chebyshev_T(_1,_2)", ["P","x"])); + DFinite_examples['chebyshev2'] = DFiniteP.element([P*(P+_sage_const_2 ),-_sage_const_3 *x,_sage_const_1 -x**_sage_const_2 ], name=DinamicString("chebyshev_U(_1,_2)", ["P","x"])); + + ## Defining DD-Finite Examples + DDFinite_examples['esin'] = DDFinite.element([_sage_const_1 ,-c], [_sage_const_1 ], name=DinamicString("exp(sin(_1))", "x")); + DDFinite_examples['sine'] = DDFinite.element([e**_sage_const_2 ,-_sage_const_1 ,_sage_const_1 ],[_sage_const_0 ,_sage_const_1 ], name=DinamicString("sin(exp(_1))", "x")); + DDFinite_examples['tan'] = [tan, DDFinite.element([_sage_const_0 ,-_sage_const_2 *s*c,c**_sage_const_2 ], [_sage_const_0 ,_sage_const_1 ], name=DinamicString("tan(_1)", "x"))]; + DDFinite_examples['bernoulli'] = DDFinite.element([x*e-e+_sage_const_1 ,x*(e-_sage_const_1 )],[_sage_const_1 ,-_sage_const_1 /_sage_const_2 ,_sage_const_1 /_sage_const_6 ,_sage_const_0 ]); + + ## Defining some names + __example_names['exp'] = 'e'; + __example_names['log'] = 'ln'; + __example_names['sen'] = 'sin'; + __example_names['tg'] = 'tan'; + +def DFinite_example(input, n=_sage_const_0 ): + if(DFinite_examples.has_key(input)): + res = DFinite_examples[input]; + elif (__example_names.has_key(input) and DFinite_examples.has_key(__example_names[input])): + res = DFinite_examples[__example_names[input]]; + else: + raise ValueError('The DD-Function by name %s does not exist' %(input)); + + if(type(res)==list): + return res[n]; + else: + return res; + + +def DDFinite_example(input, n = _sage_const_0 ): + if(DDFinite_examples.has_key(input)): + res = DDFinite_examples[input]; + elif (__example_names.has_key(input) and DDFinite_examples.has_key(__example_names[input])): + res = DDFinite_examples[__example_names[input]]; + else: + raise ValueError('The DD-Function by name %s does not exist' %(input)); + + if(type(res)==list): + return res[n]; + else: + return res; + +def DDFunction_example(input, n=_sage_const_0 ): + try: + return DFinite_example(input, n); + except Exception: + pass; + try: + return DDFinite_example(input, n); + except Exception: + pass; + + raise ValueError('No DD-Function by name %s exist' %(input)); + +################################################################################## +################################################################################## +### +### Trigonometric and Hyperbolic trigonometric Functions +### +################################################################################## +################################################################################## +@cached_function +def Sin(input, ddR = None): + from ajpastor.dd_functions.ddFunction import DDFunction; + if(isinstance(input, DDFunction)): + return Sin(x)(input); + f,dR = __decide_parent(input, ddR); + + evaluate = lambda p : dR.getSequenceElement(p,_sage_const_0 ); + if(evaluate(f) != _sage_const_0 ): + raise ValueError("Impossible to compute sin(f) with f(0) != 0"); + + df = dR.base_derivation(f); + df2 = dR.base_derivation(df); + + newName = repr(f); + if(hasattr(f, "_DDFunction__name") and (not(f._DDFunction__name is None))): + newName = f._DDFunction__name; + + return dR.element([df**_sage_const_3 ,-df2,df],[_sage_const_0 ,evaluate(df),evaluate(df2)], name=DinamicString("sin(_1)", newName)); + +@cached_function +def Cos(input, ddR = None): + from ajpastor.dd_functions.ddFunction import DDFunction; + if(isinstance(input, DDFunction)): + return Cos(x)(input); + f,dR = __decide_parent(input, ddR); + + evaluate = lambda p : dR.getSequenceElement(p,_sage_const_0 ); + if(evaluate(f) != _sage_const_0 ): + raise ValueError("Impossible to compute cos(f) with f(0) != 0"); + + df = dR.base_derivation(f); + df2 = dR.base_derivation(df); + + newName = repr(f); + if(hasattr(f, "_DDFunction__name") and (not(f._DDFunction__name is None))): + newName = f._DDFunction__name; + + return dR.element([df**_sage_const_3 ,-df2,df],[_sage_const_1 ,_sage_const_0 ,-evaluate(df)**_sage_const_2 ], name=DinamicString("cos(_1)",newName)); + +@cached_function +def Tan(input, ddR = None): + from ajpastor.dd_functions.ddFunction import DDFunction; + if(isinstance(input, DDFunction)): + return Tan(x)(input); + if(input == x): + return DDFinite_example('tan'); + g, dR = __decide_parent(input, ddR,_sage_const_2 ); + + + evaluate = lambda p : dR.getSequenceElement(p,_sage_const_0 ); + if(evaluate(g) != _sage_const_0 ): + raise ValueError("Impossible to compute tan(f) with f(0) != 0"); + + dg = dR.base_derivation(g); ddg = dR.base_derivation(dg); + a = Cos(input)**_sage_const_2 ; b = dR.base().zero(); c = dR.base()(-_sage_const_2 ); + + ### First we compute the new linear differential operator + newOperator = dR.element([dg**_sage_const_3 *c,dg**_sage_const_2 *b-ddg*a,dg*a]).equation; + + ### Now, we compute the initial values required + required = newOperator.get_jp_fo()+_sage_const_1 ; + + init_tan = Tan(x).getInitialValueList(required); + init_input = [factorial(i)*dR.base().getSequenceElement(g,i) for i in range(required)]; + + newInit = [init_tan[_sage_const_0 ]]+[sum([init_tan[j]*bell_polynomial(i,j)(*init_input[_sage_const_1 :i-j+_sage_const_2 ]) for j in range(_sage_const_1 ,i+_sage_const_1 )]) for i in range(_sage_const_1 ,required)]; ## See Faa di Bruno's formula + + result = dR.element(newOperator,newInit); + + newName = repr(input); + if(hasattr(input, "_DDFunction__name") and (not(input._DDFunction__name is None))): + newName = input._DDFunction__name; + + result._DDFunction__name = DinamicString("tan(_1)",newName); + return result; + +@cached_function +def Sinh(input, ddR = None): + from ajpastor.dd_functions.ddFunction import DDFunction; + if(isinstance(input, DDFunction)): + return Sinh(x)(input); + f,dR = __decide_parent(input, ddR); + + evaluate = lambda p : dR.getSequenceElement(p,_sage_const_0 ); + if(evaluate(f) != _sage_const_0 ): + raise ValueError("Impossible to compute sin(f) with f(0) != 0"); + + df = dR.base_derivation(f); + df2 = dR.base_derivation(df); + + newName = repr(f); + if(hasattr(f, "_DDFunction__name") and (not(f._DDFunction__name is None))): + newName = f._DDFunction__name; + + return dR.element([-df**_sage_const_3 ,-df2,df],[_sage_const_0 ,evaluate(df),evaluate(df2)], name=DinamicString("sinh(_1)",newName)); + +@cached_function +def Cosh(input, ddR = None): + from ajpastor.dd_functions.ddFunction import DDFunction; + if(isinstance(input, DDFunction)): + return Cosh(x)(input); + f,dR = __decide_parent(input, ddR); + + evaluate = lambda p : dR.getSequenceElement(p,_sage_const_0 ); + if(evaluate(f) != _sage_const_0 ): + raise ValueError("Impossible to compute cos(f) with f(0) != 0"); + + df = dR.base_derivation(f); + df2 = dR.base_derivation(df); + + newName = repr(f); + if(hasattr(f, "_DDFunction__name") and (not(f._DDFunction__name is None))): + newName = f._DDFunction__name; + + return dR.element([-df**_sage_const_3 ,-df2,df],[_sage_const_1 ,_sage_const_0 ,evaluate(df)**_sage_const_2 ], name=DinamicString("cosh(_1)", newName)); + +################################################################################## +################################################################################## +### +### Exponential and Logarithm Functions +### +################################################################################## +################################################################################## +@cached_function +def Log(input, ddR = None): + from ajpastor.dd_functions.ddFunction import DDFunction; + if(isinstance(input, DDFunction)): + return Log(x+_sage_const_1 )(input); + f,dR = __decide_parent(input, ddR); + + evaluate = lambda p : dR.getSequenceElement(p,_sage_const_0 ); + if(evaluate(f) != _sage_const_1 ): + raise ValueError("Impossible to compute ln(f) with f(0) != 1"); + + df = dR.base_derivation(f); + df2 = dR.base_derivation(df); + + newName = repr(f); + if(hasattr(f, "_DDFunction__name") and (not(f._DDFunction__name is None))): + newName = f._DDFunction__name; + + return dR.element([_sage_const_0 ,df**_sage_const_2 -df2*f,df*f],[_sage_const_0 ,evaluate(df), evaluate(df2)-evaluate(df)**_sage_const_2 ], name=DinamicString("log(_1)",newName)); + +@cached_function +def Log1(input, ddR = None): + from ajpastor.dd_functions.ddFunction import DDFunction; + if(isinstance(input, DDFunction)): + return Log1(x)(input); + f,dR = __decide_parent(input, ddR); + + evaluate = lambda p : dR.getSequenceElement(p,_sage_const_0 ); + if(evaluate(f) != _sage_const_0 ): + raise ValueError("Impossible to compute cos(f) with f(0) != 0"); + + df = dR.base_derivation(f); + df2 = dR.base_derivation(df); + + f1 = f+_sage_const_1 ; + + newName = repr(f); + if(hasattr(f, "_DDFunction__name") and (not(f._DDFunction__name is None))): + newName = f._DDFunction__name; + + return dR.element([_sage_const_0 ,df**_sage_const_2 -df2*f1,df*f1],[_sage_const_0 ,evaluate(df), evaluate(df2)-evaluate(df)**_sage_const_2 ], name=DinamicString("log(_1+1)", newName)); + +@cached_function +def Exp(input, ddR = None): + from ajpastor.dd_functions.ddFunction import DDFunction; + if(isinstance(input, DDFunction)): + return Exp(x)(input); + f,dR = __decide_parent(input, ddR); + + evaluate = lambda p : dR.getSequenceElement(p,_sage_const_0 ); + if(evaluate(f) != _sage_const_0 ): + raise ValueError("Impossible to compute exp(f) with f(0) != 0"); + + newName = repr(f); + if(hasattr(f, "_DDFunction__name") and (not(f._DDFunction__name is None))): + newName = f._DDFunction__name; + + return dR.element([-dR.base_derivation(f),_sage_const_1 ],[_sage_const_1 ], name=DinamicString("exp(_1)", newName)); + +################################################################################## +################################################################################## +### +### Special Functions +### +################################################################################## +################################################################################## +### Bessel Functions +@cached_function +def BesselD(input, kind = _sage_const_1 ): + if(input is None): + return DDFunction_example('bessel'); + elif(kind == _sage_const_1 ): + try: + alpha = QQ(input); + if(alpha < _sage_const_0 ): + raise ValueError("Impossible to manage Bessel functions of first kind with negative order"); + + P = DFiniteP.parameters()[_sage_const_0 ]; + + func = DDFunction_example('bessel')(**{str(P):alpha}); + if(alpha in ZZ): + return func.change_init_values([_sage_const_0 for i in range(alpha)] + [_sage_const_1 /_sage_const_2 **alpha, _sage_const_0 , -((alpha+_sage_const_2 )/(_sage_const_2 **(alpha+_sage_const_2 )))], name = "bessel_J(%d,x)" %input); + else: + return func.change_init_values([_sage_const_0 ], name = DinamicString("bessel_J(_1,_2)", [str(input), "x"])); + except TypeError: + raise TypeError("Impossible to manage Bessel functions of first kind with irrational order"); + else: + raise ValueError("Impossible to manage Bessel functions of %dth kind" %(kind)); + +### Legendre Polynomials +__legendre_initials = [[_sage_const_1 ,_sage_const_0 ],[_sage_const_0 ,_sage_const_1 ]]; +@cached_function +def LegendreD(input): + global __legendre_initials; + if(input is None): + return DDFunction_example('legendre'); + + try: + n = ZZ(input); + if(n < _sage_const_0 ): + raise ValueError("Impossible to create a Legendre polynomial of negative index"); + + P = DFiniteP.parameters()[_sage_const_0 ]; + func = DDFunction_example('legendre')(**{str(P):n}); + for i in range(len(__legendre_initials), n+_sage_const_1 ): + prev = __legendre_initials[-_sage_const_1 ]; + prev2 = __legendre_initials[-_sage_const_2 ]; + __legendre_initials += [[-(i-_sage_const_1 )*prev2[_sage_const_0 ]/i,((_sage_const_2 *i-_sage_const_1 )*prev[_sage_const_0 ] - (i-_sage_const_1 )*prev2[_sage_const_1 ])/i]]; + return func.change_init_values(__legendre_initials[n], name=DinamicString("legendre_P(_1,_2)", [str(input), "x"])); + except TypeError as e: + #raise TypeError("Impossible to create a Legendre polynomial of rational index"); + raise e; + +### Chebyshev Polynomials +__chebyshev_initials = [[],[[_sage_const_1 ,_sage_const_0 ],[_sage_const_0 ,_sage_const_1 ]],[[_sage_const_1 ,_sage_const_0 ],[_sage_const_0 ,_sage_const_2 ]]]; +@cached_function +def ChebyshevD(input, kind = _sage_const_1 ): + global __chebyshev_initials; + if(input is None): + return DDFunction_example('chebyshev%d' %kind); + + try: + n = ZZ(input); + if(n < _sage_const_0 ): + raise ValueError("Impossible to create a Legendre polynomial of negative index"); + + P = DFiniteP.parameters()[_sage_const_0 ]; + ## Building the differential equation + name = None; + if(kind == _sage_const_1 ): + func = DDFunction_example('chebyshev1')(**{str(P):n}); + name = DinamicString("chebyshev_T(_1,_2)", [str(input), "x"]); + elif(kind == _sage_const_2 ): + func = DDFunction_example('chebyshev2')(**{str(P):n}); + name = DinamicString("chebyshev_U(_1,_2)", [str(input), "x"]); + else: + raise ValueError("Impossible to manage Chebyshev polynomial of %d-th kind" %(kind)); + + ## Computing initial values + for i in range(len(__chebyshev_initials[kind]), n+_sage_const_1 ): + prev = __chebyshev_initials[kind][-_sage_const_1 ]; + prev2 = __chebyshev_initials[kind][-_sage_const_2 ]; + __chebyshev_initials[kind] += [[-prev2[_sage_const_0 ], _sage_const_2 *prev[_sage_const_0 ]-prev2[_sage_const_1 ]]]; + return func.change_init_values(__chebyshev_initials[kind][n],name); + except TypeError as e: + raise e; + +### Hypergeometric Functions +__CACHED_HYPERGEOMETRIC = {}; + +def HypergeometricFunction(a,b,c, init = _sage_const_1 ): + return GenericHypergeometricFunction([a,b],[c],init); + +def GenericHypergeometricFunction(num=[],den=[],init=_sage_const_1 ): + if (not (isinstance(num,list) or isinstance(num,set) or isinstance(num,tuple))): + num = [num]; + else: + num = list(num); + if (not (isinstance(den,list) or isinstance(den,set) or isinstance(den,tuple))): + den = [den]; + else: + den = list(den); + + ## Cleaning repeated values + i = _sage_const_0 ; + while(i < len(num) and len(den) > _sage_const_0 ): + if(num[i] in den): + den.remove(num[i]); + num.remove(num[i]); + else: + i += _sage_const_1 ; + + ## Sort list for cannonical input + num.sort(); den.sort(); + + ## Casting to tuples to have hash + num = tuple(num); den = tuple(den); + + ## Checking the function is cached + global __CACHED_HYPERGEOMETRIC; + if(not((num,den,init) in __CACHED_HYPERGEOMETRIC)): + ## Building differential operator + get_op = lambda p : DFinite.element(p).equation; + op_num = x*prod(get_op([el,x]) for el in num); + op_den = x*get_op([_sage_const_0 ,_sage_const_1 ])*prod(get_op([el-_sage_const_1 ,x]) for el in den); + op = op_num - op_den; + + f = DFinite.element(op); + + initVals = [init]; + + if(init == _sage_const_1 ): + __CACHED_HYPERGEOMETRIC[(num,den,init)] = f.change_init_values([_sage_const_1 ],name=DinamicString("hypergeometric(_1,_2,_3)", [str(num),str(den),"x"])); + else: + __CACHED_HYPERGEOMETRIC[(num,den,init)] = f.change_init_values([init],name=DinamicString("%d*(hypergeometric(_1,_2,_3))", [str(num),str(den),"x"])); + + ## Return the cached element + return __CACHED_HYPERGEOMETRIC[(num,den,init)]; + +### Mathieu's Functions +@cached_function +def MathieuD(a=None,q=None,init=()): + params =[]; + if(a is None): + params += ['a']; + if(q is None): + params += ['q']; + + destiny_ring = DDFinite; ra = a; rq = q; + if(len(params) > _sage_const_0 ): + destiny_ring = ParametrizedDDRing(DDFinite, params); + if(len(params) == _sage_const_2 ): + ra,rq = destiny_ring.parameters(); + elif('a' in params): + ra = destiny_ring.parameters()[_sage_const_0 ]; rq = q; + else: + rq = destiny_ring.parameters()[_sage_const_0 ]; ra = a; + + return destiny_ring.element([ra-_sage_const_2 *rq*Cos(_sage_const_2 *x), _sage_const_0 , _sage_const_1 ], init, name=DinamicString("Mathieu(_1,_2;_3)(_4)", [repr(ra),repr(rq),str(list(init[:_sage_const_2 ])),"x"])); + +@cached_function +def MathieuSin(a=None,q=None): + return MathieuD(a,q,(_sage_const_0 ,_sage_const_1 )); + +@cached_function +def MathieuCos(a=None,q=None): + return MathieuD(a,q,(_sage_const_1 ,_sage_const_0 )); + +################################################################################## +################################################################################## +### +### Particular differential Equations +### +################################################################################## +################################################################################## +### Federschwinger // Swing with mass +## f'' + 2a f' + b^2f = ksin(cx) +@cached_function +def Federschwinger(a,b,c,k,init=(_sage_const_0 ,_sage_const_0 )): + return DDFinite.element([b**_sage_const_2 ,_sage_const_2 *a,_sage_const_1 ], init, k*Sin(c*x)); + +################################################################################## +################################################################################## +### +### Private methods +### +################################################################################## +################################################################################## +def __decide_parent(input, parent = None, depth = _sage_const_1 ): + if(parent is None): + R = input.parent(); + if(isinstance(R, sage.symbolic.ring.SymbolicRing)): + parameters = set([str(el) for el in input.variables()])-set(['x']); + if(len(parameters) > _sage_const_0 ): + parent = ParametrizedDDRing(DDRing(DFinite, depth=depth), parameters); + else: + parent = DDRing(PolynomialRing(QQ,x), depth=depth); + else: + try: + parent = DDRing(R, depth = depth); + except Exception: + raise TypeError("The object provided is not in a valid Parent"); + + return parent.base()(input), parent; + +#### Usual running after defining everything +DD_EXAMPLES_LOAD(); + diff --git a/ajpastor/dd_functions/ddFunction.py b/ajpastor/dd_functions/ddFunction.py new file mode 100644 index 0000000..206f9e9 --- /dev/null +++ b/ajpastor/dd_functions/ddFunction.py @@ -0,0 +1,2065 @@ + +# This file was *autogenerated* from the file ./ddFunction.sage +from sage.all_cmdline import * # import sage library + +_sage_const_3 = Integer(3); _sage_const_2 = Integer(2); _sage_const_1 = Integer(1); _sage_const_0 = Integer(0); _sage_const_5 = Integer(5); _sage_const_4 = Integer(4); _sage_const_100 = Integer(100); _sage_const_12 = Integer(12); _sage_const_11 = Integer(11); _sage_const_1en10 = RealNumber('1e-10') + +# Python imports +import warnings; + +#SAGE imports +from sage.rings.ring import IntegralDomain; +from sage.rings.polynomial.polynomial_ring import is_PolynomialRing as isPolynomial; +from sage.structure.element import IntegralDomainElement; +from sage.categories.integral_domains import IntegralDomains; +from sage.categories.pushout import ConstructionFunctor; + +#ajpastor imports +from ajpastor.operator.operator import Operator; +from ajpastor.operator.oreOperator import w_OreOperator; +from ajpastor.operator.directStepOperator import DirectStepOperator; +from ajpastor.operator.fullLazyOperator import FullLazyOperator; + +from ajpastor.misc.dinamic_string import *; +from ajpastor.misc.cached_property import derived_property; +from ajpastor.misc.ring_w_sequence import Ring_w_Sequence; +from ajpastor.misc.ring_w_sequence import Wrap_w_Sequence_Ring; +from ajpastor.misc.ring_w_sequence import sequence; + + +##################################################### +### Definition of the particular warnings we are interested to raise +##################################################### +class DDFunctionWarning(RuntimeWarning): + pass; + +class MergingRingWarning(DDFunctionWarning): + pass; + +class TooDeepWarning(DDFunctionWarning): + pass; + +class NoOreAlgebraWarning(DDFunctionWarning): + pass; + +warnings.simplefilter('always', DDFunctionWarning); + + +## Auxiliar derivative function +def _aux_derivation(p): + try: + R = p.parent(); + return derivative(p,R(x)); + except Exception: + return _sage_const_0 ; + +##################################################### +### Class for DD-Rings +##################################################### +class DDRing (Ring_w_Sequence, IntegralDomain): + Element = None; + + _Default_Base = PolynomialRing(QQ,x); + _Default_Depth = _sage_const_1 ; + _Default_Base_Field = QQ; + _Default_Invertibility = None; + _Default_Derivation = None; + _Default_Operator = None; + _Default_Parameters = [ + ("base", _Default_Base), + ("depth", _Default_Depth), + ("base_field", _Default_Base_Field), + ("invertibility", _Default_Invertibility), + ("derivation", _Default_Derivation), + ("default_operator", _Default_Operator)]; + + _CACHED_DD_RINGS = {}; + + _Default_variable = 'x'; + + ################################################# + ### Static methods + ################################################# + @staticmethod + def __get_gens__(parent): + current = parent; + const = parent.construction(); + gens = [el for el in parent.gens()]; + + while((not(const is None)) and (not (_sage_const_1 in gens))): + current = const[_sage_const_1 ]; + gens += [el for el in current.gens()]; + const = current.construction(); + + while(_sage_const_1 in gens): + gens.remove(_sage_const_1 ); + + return tuple(set(gens)); + + ################################################# + ### Builder + ################################################# + @staticmethod + def __classcall__(cls, *args, **kwds): + final_args = []; + + ## We first compile the parameters the user send + current = _sage_const_0 ; + if(len(args) != _sage_const_1 or args[_sage_const_0 ] != None): + for i in range(len(args)): + final_args += [[DDRing._Default_Parameters[i][_sage_const_0 ], args[i]]]; + current = len(args); + + for i in range(current, len(DDRing._Default_Parameters)): + final_args += [[DDRing._Default_Parameters[i][_sage_const_0 ], kwds.get(DDRing._Default_Parameters[i][_sage_const_0 ],DDRing._Default_Parameters[i][_sage_const_1 ])]]; + + ## Managing the depth over DDRings + if(isinstance(final_args[_sage_const_0 ][_sage_const_1 ], DDRing)): + final_args[_sage_const_1 ][_sage_const_1 ] = final_args[_sage_const_1 ][_sage_const_1 ]+final_args[_sage_const_0 ][_sage_const_1 ].depth(); + final_args[_sage_const_0 ][_sage_const_1 ] = final_args[_sage_const_0 ][_sage_const_1 ]._DDRing__original; + + ## Casting to tuple (so is hashable) + to_hash = tuple(tuple(el) for el in final_args[:_sage_const_3 ]); + final_args = tuple(tuple(el) for el in final_args); + + if(final_args[_sage_const_1 ][_sage_const_1 ] == _sage_const_0 ): + return final_args[_sage_const_0 ][_sage_const_1 ]; + + if(not cls in DDRing._CACHED_DD_RINGS): + DDRing._CACHED_DD_RINGS[cls] = {}; + if(not (to_hash in DDRing._CACHED_DD_RINGS[cls])): + tmp = IntegralDomain.__new__(cls); + DDRing.__init__(tmp,**dict(final_args)); + DDRing._CACHED_DD_RINGS[cls][to_hash] = tmp; + + return DDRing._CACHED_DD_RINGS[cls][to_hash]; + + def __init__(self, base=_Default_Base, depth = _Default_Depth, base_field = _Default_Base_Field, invertibility = _Default_Invertibility, derivation = _Default_Derivation, default_operator = _Default_Operator): + ''' + Class representing the Ring of Differentially Definable Functions with coefficients over a base ring. It can be built using different parameter configurations: + - ``base``: is the ring where the coefficients of the differential equation will be. It is set by default as `\mathbb{Q}[x]`. + - ``invertibility``: a function to decide if the argument is a unit in the ring we are building. + - ``derivation``: a function to derivate elements in the coefficients ring. + + More formally, ``(base,derivation)`` should be a Differential Ring and the result (represented by this class) will be another Differential Ring which extends the original. + ''' + ## Other attributes initialization + self.__variables = None; + + if(depth > _sage_const_1 ): + DDRing.__init__(self,DDRing(base, depth-_sage_const_1 , base_field, invertibility, derivation, default_operator), _sage_const_1 , base_field, invertibility, derivation, default_operator); + else: + ### We call the builders of the superclasses + Ring_w_Sequence.__init__(self, base, method = lambda p,n : self(p).getSequenceElement(n)); + IntegralDomain.__init__(self, base, category=IntegralDomains()); + + ### We assign the operator class + if(default_operator is None): + ## In this case we add an default Operator + if(isinstance(base, DDRing)): + self.default_operator = FullLazyOperator; + else: + self.default_operator = DirectStepOperator; + elif(issubclass(default_operator, Operator)): + self.default_operator = default_operator; + else: + raise TypeError("Invalid type for Operators in this ring. Must inherit from class Operator."); + + ### In order to get Initial Values, we keep the original field base + # If the base ring is already a DDRing, we take the correspond ring from base + if isinstance(base, DDRing): + self.base_field = base.base_field; + self.__depth = base.__depth + _sage_const_1 ; + self.__original = base.__original; + # Otherwise, we set this simplest ring as our current coefficient ring + else: + self.base_field = base_field; + self.__depth = _sage_const_1 ; + self.__original = base; + + ### Saving the invertibility criteria + if(invertibility is None): + try: + self_var = self.variables(True,False)[_sage_const_0 ]; + self.base_inversionCriteria = lambda p : p(**{self_var : _sage_const_0 }) == _sage_const_0 ; + except IndexError: + self.base_inversionCriteria = lambda p : self.base()(p)==_sage_const_0 ; + else: + self.base_inversionCriteria = invertibility; + + ### Saving the base derivation + if(derivation is None): + try: + self_var = self.variables(True,False)[_sage_const_0 ]; + self.base_derivation = lambda p : p.derivative(self_var); + except IndexError: + self.base_derivation = lambda p : _sage_const_0 ; + else: + self.base_derivation = derivation; + + + + ### Setting new coercions + current = self; + while(not(current.base() == current)): + current = current.base(); + morph = DDSimpleMorphism(self, current); + current.register_conversion(morph); + + + + ################################################# + ### Coercion methods + ################################################# + def _coerce_map_from_(self, S): + coer = None; + if(isinstance(S, DDRing)): + coer = self.base()._coerce_map_from_(S.base()); + elif(S == self.base()): + coer = True; + elif(isinstance(S, sage.symbolic.ring.SymbolicRing)): + coer = True; + else: + coer = self.base()._coerce_map_from_(S); + + if(not(coer is False) and not(coer is None)): + return True; + return None; + + def __is_polynomial(self, S): + from sage.rings.polynomial.polynomial_ring import is_PolynomialRing as isUniPolynomial; + from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing as isMPolynomial; + + return isUniPolynomial(S) or isMPolynomial(S); + + def _pushout_(self, S): + if(isinstance(S, DDRing)): + from sage.categories.pushout import pushout; + if(isinstance(S, ParametrizedDDRing) and isinstance(self, ParametrizedDDRing)): + if(len(set(self.parameters(True)).intersection(S.parameters(True))) > _sage_const_0 ): + import traceback; + entry = traceback.extract_stack(limit=_sage_const_3 )[-_sage_const_3 ]; + warnings.warn_explicit("Merging rings with common parameters. \n\t* %s\n\t* %s\n--Are you sure they are the same?" %(repr(self),repr(S)), MergingRingWarning, *(list(entry[:_sage_const_3 ]))); + pushout_ddRing = pushout(self.base_ddRing(),S.base_ddRing()); + return ParametrizedDDRing(pushout_ddRing, set(self.parameters(True)).union(S.parameters(True))); + elif(isinstance(S, ParametrizedDDRing)): + return S._pushout_(self); + elif(isinstance(self, ParametrizedDDRing)): + pushout_ddRing = pushout(self.base_ddRing(),S); + return ParametrizedDDRing(pushout_ddRing, self.parameters(True)); + else: + pushout_base = pushout(self.original_ring(),S.original_ring()); + pushout_field = pushout(self.base_field, S.base_field); + return DDRing(pushout_base,depth = max(self.depth(),S.depth()),base_field=pushout_field); + + const = S.construction(); + if((not(const is None)) and (isinstance(const[_sage_const_0 ], sage.categories.pushout.FractionField))): + S = S.base(); + if(self.__is_polynomial(S)): + all_params = set(str(el) for el in DDRing.__get_gens__(S)); + params = all_params - set([str(el) for el in self.variables(True)]); + variables = all_params - params; + + base_ring = self; + if(len(variables) > _sage_const_0 ): + base_ring = DDRing(PolynomialRing(self.base_field, list(variables)), depth=self.depth()); + + if(len(params) > _sage_const_0 ): + return ParametrizedDDRing(base_ring, params); + else: + return base_ring; + + return None; + + def _has_coerce_map_from(self, S): + coer = self._coerce_map_from_(S); + return (not(coer is False) and not(coer is None)); + + def _element_constructor_(self, *args, **kwds): + el_class = self.element_class; + if(len(args) < _sage_const_1 ): + raise ValueError("Impossible to build a lazy element without arguments"); + + if(args[_sage_const_0 ] is self): + X = args[_sage_const_1 ]; + else: + X = args[_sage_const_0 ]; + + try: + if(isinstance(X, DDFunction)): + if(X.parent() is self): + return X; + else: + return self.element([coeff for coeff in X.equation.getCoefficients()], X.getInitialValueList(X.equation.get_jp_fo()+_sage_const_1 ), name=X._DDFunction__name); + else: + try: + X = self.base()(X); + el = self.element([-self.base_derivation(X), X]); + name = str(X); + try: + name = X._DDFunction__name; + except: + pass; + return el.change_init_values([sequence(X,i)*factorial(i) for i in range(el.equation.get_jp_fo()+_sage_const_1 )], name=name); + except AttributeError: + return self.element([_sage_const_1 ],[], self.base()(X), name=str(X)); + except TypeError as e: + #try: + # if(isinstance(X.parent(),sage.symbolic.ring.SymbolicRing)): + # #print "Casting a symbolic" + # new_parent = ParametrizedDDRing(self,X.variables()) + # el = new_parent(X); + # print new_parent; + # print el; + # return el + #except: + raise TypeError("Cannot cast an element to a DD-Function of (%s):\n\tElement: %s (%s)\n\tReason: %s" %(self, X, type(X), e)); + + def gens(self): + return (); + + def ngens(self): + return _sage_const_0 ; + + def construction(self): + return (DDRingFunctor(self.depth(), self.base_field), self.__original); + + def __contains__(self, X): + try: + return (X.parent() is self) or (self._has_coerce_map_from(X.parent())); + except AttributeError: + try: + self(X) + return True; + except Exception: + return False; + + ################################################# + ### Magic python methods + ################################################# + def __eq__(self, other): + if(isinstance(other, DDRing)): + return self._has_coerce_map_from(other) and other._has_coerce_map_from(self); + return False; + + ## Other magic methods + def _repr_(self): + ''' + Method implementing the __repr__ magic python method. Returns a string telling that self is a DDRing and which Ring is its base. + ''' + return "DD-Ring over (%s)" %(self.base()); + + def _to_command_(self): + return "DDRing(%s)" %command(self.base()); + + ################################################# + ### Integral Domain and Ring methods + ################################################# + def _an_element_(self): + ''' + Method inherited from Ring SAGE class. It returns an example of object that is in the DDRing. It simply returns the 1 element. + ''' + return self.one(); + + def characteristic(self): + ''' + Method inherited from the Ring SAGE class. It returns the characteristic of the coefficient ring. + ''' + return self.base().characteristic(); + + def base_ring(self): + return self.base_field; + + def original_ring(self): + return self.__original; + + def depth(self): + return self.__depth; + + def to_depth(self, dest): + return DDRing(self.original_ring(), depth = dest, base_field = self.base_field, invertibility = self.base_inversionCriteria, derivation = self.base_derivation, default_operator = self.default_operator); + + def is_field(self): + return False; + + def is_finite(self): + return False; + + def is_noetherian(self): + return True; + + ################################################# + ### DDRings methods + ################################################# + def invertible(self,x): + ''' + Method to make easier call the inversibility criteria specified when a DDRing was created. + + INPUT: + - ``x``: an element of self. + OUTPUT: + - True if ``x`` is in self and it is a unit. + - False otherwise. + ''' + return self.base_inversionCriteria(x); + + def element(self,coefficients=[],init=[],inhomogeneous=_sage_const_0 , check_init=True, name=None): + ''' + Method to create a DDFunction contained in self with some coefficients, inhomogeneous term and initial values. This method just call the builder of DDFunction just puting the base argument as self. + ''' + return DDFunction(self,coefficients,init,inhomogeneous, check_init=check_init, name=name); + + def eval(self, element, X=None, **input): + if not element in self: + raise TypeError('The element we want to evaluate (%s) is not an element of this ring (%s)'%(element,self)); + element = self(element); + + rx = X; + self_var = str(self.variables(True)[_sage_const_0 ]); + if((rx is None) and (self_var in input)): + rx = input.pop(self_var); + if not (rx is None): + if(rx == _sage_const_0 ): + return element.getInitialValue(_sage_const_0 ); + elif(rx in self.base_field): + return element.numerical_solution(rx,**input)[_sage_const_0 ]; + try: + return element.compose(rx); + except ValueError: + pass; + elif(len(input) == _sage_const_0 ): + return element; + + raise NotImplementedError("Not implemented evaluation of an element of this ring (%s) with the parameters %s and %s" %(self,rx,input)); + + + def variables(self, as_symbolic=False, fill = True): + if(self.__variables is None): + self.__variables = []; + current = self.base(); + while(current != self.base_field): + self.__variables += [var(str(el)) for el in current.gens()]; + current = current.base(); + self.__variables = tuple(set(self.__variables)); + + if(as_symbolic): + if(len(self.__variables) == _sage_const_0 and fill): + return tuple([var(DDRing._Default_variable)]); + return self.__variables; + else: + if(len(self.__variables) == _sage_const_0 and fill): + return tuple([self.base()(var(DDRing._Default_variable))]); + return tuple(self.base()(el) for el in self.__variables); + + #def getBaseMatrixSpace(self,nrows, ncols): + # ''' + # Method to get a MatrixSpace with coefficients in the coefficient Ring of the current DDRing with an specified dimension. + # + # INPUT: + # - ``nrows``: number or rows of the new MatrixSpace. + # - ``rcols``: number of columns of the new MatrixSpace. + # ''' + # return self.matrixSpace.matrix_space(nrows,ncols); + + +############################################################################# +### +### Ring class for Parametrized DD Functions +### +############################################################################# +class ParametrizedDDRing(DDRing): + + @staticmethod + def __classcall__(cls, *args, **kwds): + ## In order to call the __classcall__ of DDRing we treat the arguments received + if(len(args) != _sage_const_2 ): + raise TypeError("Incompatible number of arguments for ParametrizedDDRing"); + + base_ddRing = args[_sage_const_0 ]; parameters = args[_sage_const_1 ]; + + ## First we get the new variables + if ((not(type(parameters) == tuple)) and (not(type(parameters) == list)) and (not(type(parameters) == set))): + parameters = [parameters]; + else: + parameters = list(parameters); + if(len(parameters) == _sage_const_0 ): + raise TypeError("The list of variables can not be empty"); + + for i in range(len(parameters)): + if(type(parameters[i]) == str): + parameters[i] = var(parameters[i]); + elif(type(parameters[i]) != sage.symbolic.expression.Expression): + raise TypeError("All values of the second argument must be strings or SAGE symbolic variables"); + parameters = tuple(set(parameters)); + + ## We consider not the case the base ring is already parametrized + if(isinstance(base_ddRing, ParametrizedDDRing)): + parameters = tuple(set(parameters).union(base_ddRing.parameters(True))); + base_ddRing = base_ddRing.base_ddRing(); + + ## We rebuild now the base ring for the DDRing operator + base_field = base_ddRing.base_field; + constructions = [base_ddRing.construction()]; # Here is the DD-Ring operator + + while(constructions[-_sage_const_1 ][_sage_const_1 ] != base_field): + constructions += [constructions[-_sage_const_1 ][_sage_const_1 ].construction()]; + + new_basic_field = PolynomialRing(base_field, parameters).fraction_field(); + current = new_basic_field; + for i in range(_sage_const_1 ,len(constructions)): + current = constructions[-i][_sage_const_0 ](current); + + if(constructions[_sage_const_0 ][_sage_const_0 ].depth() > _sage_const_1 ): + base_ring = ParametrizedDDRing(DDRing(base_ddRing.original_ring(),depth=constructions[_sage_const_0 ][_sage_const_0 ].depth()-_sage_const_1 ), parameters); + ring = DDRing.__classcall__(cls, base_ring, _sage_const_1 , base_field = new_basic_field, default_operator = base_ddRing.default_operator); + Ring_w_Sequence.__init__(ring, base_ring, method = lambda p,n : ring(p).getSequenceElement(n)); + IntegralDomain.__init__(ring, base_ring, category=IntegralDomains()); + else: + ring = DDRing.__classcall__(cls, current, depth = constructions[_sage_const_0 ][_sage_const_0 ].depth(), base_field = new_basic_field, default_operator = base_ddRing.default_operator); + + ring.__init__(base_ddRing, parameters); + return ring; + + def __init__(self, base_ddRing, parameters): + ''' + This class represent a generilized concept of DDRing. If `R` is a domain of the power series space (`K[[x]]`), and `D(R)` is its associated DDRing, then we can consider new constants elements and consider `D(R)` but with the basic field be `K(var_1,...,var_m)` + + INPUT: + - base_ddRing: Base DDRing where this new ring will be based. This parameter can be got using method ``getBaRing``. + - variables: a list of variables or strings which will be the names of the new variables. + ''' + ## Checking the first argument + if ((not(isinstance(base_ddRing, DDRing))) or (isinstance(base_ddRing, ParametrizedDDRing))): + raise TypeError("Needed a DDRing as first argument for create a Parametrized DDRing"); + + self.__parameters = tuple(set(parameters)); + + self.__baseDDRing = base_ddRing; + + def _coerce_map_from_(self, S): + coer = super(ParametrizedDDRing, self)._coerce_map_from_(S); + if(not(coer)): + coer = self.__baseDDRing._coerce_map_from_(S); + + return not(not(coer)); + + def construction(self): + return (ParametrizedDDRingFunctor(self.depth(), self.base_field, set(self.__parameters)), self.__baseDDRing); + + def base_ddRing(self): + ''' + Method that retrieves the original DDRing where this parametrized space is based on. + + OUTPUT: + - DDRing where elements of ``self`` would be if we substitute the parameters by elements of the base ring. + ''' + return self.__baseDDRing; + + def _repr_(self): + res = "(%s" %str(self.parameters()[_sage_const_0 ]); + for i in range(_sage_const_1 ,len(self.parameters())): + res += ", " + str(self.parameters()[i]); + res += ")"; + + if(len(self.parameters()) > _sage_const_1 ): + return "%s with parameters %s" %(self.base_ddRing(),res); + else: + return "%s with parameter %s" %(self.base_ddRing(),res); + + @cached_method + def parameters(self, as_symbolic = False): + if(as_symbolic): + return self.__parameters; + else: + return [self.base_field(el) for el in self.__parameters]; + + def gens(self): + return self.parameters(True); + + def ngens(self): + return len(self.parameters()); + + #def variables(self, as_symbolic=False): + # return self.base_ddRing().variables(as_symbolic); + + # Override to_depth method from DDRing + def to_depth(self, dest): + return ParametrizedDDRing(self.base_ddRing().to_depth(dest), self.parameters(True)); + + # Override eval method from DDRing + def eval(self, element, X=None, **input): + rx = X; + self_var = str(self.variables(True)[_sage_const_0 ]); + if(X is None and self_var in input): + rx = input.pop(self_var); + ### If X is not None, then we evaluate the variable of the ring + if(not (rx is None)): + if(len(input) > _sage_const_0 ): + return self.eval(element, **input)(**{self_var:rx}); + else: + return super(ParametrizedDDRing, self).eval(element, rx); + elif(len(input) > _sage_const_0 ): + ### Otherwise, we evaluate the parameters + element = self(element); + + ### Getting the final parameters + original_parameters = set(str(el) for el in self.parameters()); + used_parameters = set(input.keys()); + got_parameters = reduce(lambda p,q : p.union(q), [set(str(el) for el in SR(value).variables()) for value in input.values()]); + + destiny_parameters = (original_parameters - used_parameters).union(got_parameters); + + if(self_var in destiny_parameters): + raise TypeError("Parameters can only be evaluated to constants.\n\t- Got: %s" %(input)); + + if(len(destiny_parameters) == _sage_const_0 ): + destiny_ring = self.base_ddRing(); + else: + destiny_ring = ParametrizedDDRing(self.base_ddRing(), tuple(destiny_parameters)); + + new_equation = destiny_ring.element([el(**input) for el in element.equation.getCoefficients()]).equation; + + new_init = [el(**input) for el in element.getInitialValueList(new_equation.get_jp_fo()+_sage_const_1 )]; + new_name = None; + if(element._DDFunction__name is not None): + new_name = din_m_replace(element._DDFunction__name, {key: str(input[key]) for key in input}, True); + return destiny_ring.element(new_equation,new_init,name=new_name); + return element; + + +##################################################### +### Class for DD-Functions +##################################################### +class DDFunction (IntegralDomainElement): + ##################################### + ### Init and Interface methods + ##################################### + def __init__(self, parent, input = _sage_const_0 , init_values = [], inhomogeneous = _sage_const_0 , check_init = True, name = None): + ''' + Method to initialize a DD-Function insade the DD-Ring given in 'parent' + + The object will represent a function 'f' such that + - input (f) = inhomogeneous; + - f^{(i)}(0) = init_values[i] + ''' + # We check that the argument is a DDRing + if(not isinstance(parent, DDRing)): + raise TypeError("A DD-Function must be an element of a DD-Ring"); + ### We call superclass builder + IntegralDomainElement.__init__(self, parent); + + ## Checking we have some input (if not, we assume the input for zero) + ## We take care of the following arguments + ## input -> equation + ## init_values -> init + ## inhomogeneous -> inhom + zero = False; + if((type(input) == list and len(input) == _sage_const_0 ) or input == _sage_const_0 ): + zero = True; + elif(type(input) == list and all(el == _sage_const_0 for el in input)): + zero = True; + elif((type(input) == list and len(input) == _sage_const_1 ) or (isinstance(input, Operator) and input.getOrder() == _sage_const_0 )): + zero = (inhomogeneous == _sage_const_0 ); + + if(zero): + ### The equation is zero, we need inhomogeneous equals to zero + if(not inhomogeneous == _sage_const_0 ): + raise ValueError("Incompatible equation (%s) and inhomogeneous term (%s)" %(input, inhomogeneous)); + else: + ### Also, all the initial values must be zero + for el in init_values: + if (not el == _sage_const_0 ): + raise ValueError("Incompatible equation (%s) and initial values (%s)" %(input, init_values)); + init = [_sage_const_0 ]; + equation = [_sage_const_0 ,_sage_const_1 ]; + inhom = _sage_const_0 ; + else: + equation = input; + init = [el for el in init_values]; + inhom = inhomogeneous; + + ### Cached elements + self.__pows = {_sage_const_0 :_sage_const_1 , _sage_const_1 :self}; # Powers-cache + self.__derivative = None; # The derivative of a function + + ### Assigning the differential operator + ### We will save the leading coefficient of the equation (lc) to future uses. + # We create the operator using the structure given by our DDRing + try: + self.equation = self.__buildOperator(equation); + except Exception as e: + print e; + raise TypeError("The input for this operator is not valid"); + + ### Managing the inhomogeneous term + # We cast the inhomogeneous term to an element of self.parent() + # If that is not zero, then we compute the new operator and initial values needed + # to define the equation. + if(inhom != _sage_const_0 ): + ## Getting the basic elements + inhom = self.parent()(inhom); + field = parent.base_field; + + ## Getting the homogeneous differential equation + new_eq = inhom.equation*self.equation; + + ## Get the number of elements to check + ## If init is too big, we use all the elements for a better checking + n_init = new_eq.get_jp_fo()+_sage_const_1 ; + to_check = max(n_init, len(init)); + + ## Getting the matrix of the current equation + M = Matrix(field, self.equation.get_recursion_matrix(to_check-_sage_const_1 -self.equation.forward_order)); + v = vector(field, inhom.getSequenceList(M.nrows())); + + ## Solving the system MX = v + X = M * BackslashOperator() * v; + ker = M.right_kernel_matrix(); + + ## Choosing a solution such X[i]==init[i] + diff = vector(field, [init[i]-X[i] for i in range(len(init))]); + N = Matrix(field, [[v[i] for i in range(len(init))] for v in ker]).transpose(); + + try: + to_sum = N * BackslashOperator() * diff; + except Exception: + raise ValueError("There is no function satisfying\n(%s)(f) == %s\nwith initial values %s" %(self.equation,inhom,init)); + + ## Putting the new values for the equation and initial values + init = X+sum([to_sum[i]*ker[i] for i in range(len(to_sum))]); + init = [init[i]*factorial(i) for i in range(len(init))]; + self.equation = new_eq; + + + ## After creating the original operator, we check we can not extract an "x" factor + coeff_gcd = _sage_const_1 ; + if(isPolynomial(self.parent().base())): + l = []; + for el in self.equation.getCoefficients(): + l += el.coefficients(x); + + coeff_gcd = gcd(l); + if(coeff_gcd == _sage_const_0 ): + coeff_gcd = _sage_const_1 ; + g = coeff_gcd*gcd(self.equation.getCoefficients()); + if(g != _sage_const_1 and g != _sage_const_0 ): + coeffs = [coeff/g for coeff in self.equation.getCoefficients()]; + self.equation = self.__buildOperator(coeffs); + + ### Managing the initial values + init = [self.parent().base_field(str(el)) for el in init]; + if(check_init): + self.__calculatedSequence = {}; + if(len(init) > self.equation.get_jp_fo()): + initSequence = [init[i]/factorial(i) for i in range(len(init))]; + M = self.equation.get_recursion_matrix(len(initSequence)-self.equation.forward_order-_sage_const_1 ); + if(M*vector(initSequence) == _sage_const_0 ): + for i in range(len(initSequence)): + self.__calculatedSequence[i] = initSequence[i]; + else: + raise ValueError("There is no such function satisfying %s whith initial values %s"%(self.equation,init)); + else: + for i in range(len(init)): + try: + get_init = self.getInitialValue(i); + if(not (get_init == init[i])): + raise ValueError("There is no such function satisfying %s whith such initial values (index %d:%s)"%(self.equation,i,init[i])); + except TypeError: + self.__calculatedSequence[i] = init[i]/factorial(i); + else: + self.__calculatedSequence = {i:init[i]/factorial(i) for i in range(len(init))}; + + + ### Other attributes for DDFunctions + ### Setting up the name for the function + self.__name = name; + self.__built = None; + + def __buildOperator(self, coeffs): + if(isinstance(coeffs, self.parent().default_operator)): + return coeffs; + elif(type(coeffs) == list): + new_coeffs = []; + for el in coeffs: + try: + new_coeffs += [self.parent().base()(el)]; + except TypeError: + try: + new_coeffs += [self.parent().base()(str(el))]; + except: + new_coeffs += [el]; + coeffs = new_coeffs; + + return self.parent().default_operator(self.parent().base(), coeffs, self.parent().base_derivation); + + def getOperator(self): + ''' + Getter method for the Linear Differential operator associated with the DDFunction. + ''' + return self.equation; + + def getOrder(self): + ''' + Getter method for the order of the equation that defines the DDFunction. + ''' + return self.equation.getOrder(); + + @derived_property + def ps_order(self): + if(self.is_null): + return -_sage_const_1 ; + else: + i = _sage_const_0 ; + while(self.getInitialValue(i) == _sage_const_0 ): + i += _sage_const_1 ; + + return i; + + def getSequenceElement(self, n): + try: + ## If we have computed it, we just return + return self.__calculatedSequence[n]; + except KeyError: + ## Otherwise, we compute such element + + + if(n > self.equation.get_jp_fo()): + ## If the required value is "too far" we can get quickly the equation + rec = self.equation.get_recursion_row(n-self.equation.forward_order); + else: + ## Otherwise, we try to get as usual the value + d = self.getOrder(); + i = max(n-d,_sage_const_0 ); + rec = self.equation.get_recursion_row(i); + while(rec[n] == _sage_const_0 and i <= self.equation.jp_value): + i += _sage_const_1 ; + rec = self.equation.get_recursion_row(i); + if(rec[n] == _sage_const_0 ): + raise TypeError("Impossible to compute recursively the required value"); + ## Checking that we only need previous elements + for i in range(n+_sage_const_1 , len(rec)): + if(not (rec[i] == _sage_const_0 )): + raise TypeError("Impossible to compute recursively the required value"); + + ## We do this operation in a loop to avoid computing initial values + ## if they are not needed + res = self.parent().base_field.zero(); + for i in range(n): + if(not (rec[i] == _sage_const_0 )): + res -= rec[i]*(self.getSequenceElement(i)); + + self.__calculatedSequence[n] = self.parent().base_field(res/rec[n]); + + return self.__calculatedSequence[n]; + + def getSequenceList(self, n): + ''' + Extra method that returns the list of the first `n` initial coefficients of the power-serie expansion of the function. If it is not possible to get so many values, the method DO NOT return an error. It returns just a truncated list. + ''' + ### We check the argument is correct + if n < _sage_const_0 : + raise ValueError("The argument must be a non-negative integer"); + + res = []; + for i in range(n): + try: + res += [self.getSequenceElement(i)]; + except TypeError: + break; + return res; + + def getInitialValue(self, n): + ''' + Getter method for an initial value. + - ``n``: order of the initial value expected to obtain, i.e. the return will be `D^n(f)(0)`. + + This method can raise an error if the initial value has not been provided and it is impossible to get it. + ''' + return self.getSequenceElement(n)*factorial(n); + + def getInitialValueList(self, n): + ''' + Extra method that returns the list of the first `n` initial values for the function. If it is not possible to get so many values, the method DO NOT return an error. It returns just a truncated list. + ''' + ### We check the argument is correct + if n < _sage_const_0 : + raise ValueError("The argument must be a non-negative integer"); + + res = []; + for i in range(n): + try: + res += [self.getInitialValue(i)]; + except TypeError: + break; + return res; + + @cached_method + def size(self): + to_sum = [self.getOrder()]; + if(isinstance(self.parent().base(), DDRing)): + to_sum += [el.size() for el in self.equation.getCoefficients()]; + else: + for coeff in self.equation.getCoefficients(): + try: + if(coeff != _sage_const_0 ): + to_sum += [coeff.degree()] + except Exception: + pass; + return sum(to_sum); + + def built(self): + return self.__built; + + def change_built(self, type, data): + if(type == "derivative"): + ## Nothing to check + pass; + elif(type == "polynomial"): + ## Check that the polynomial has appropriate variables + polynomial, variables = data; + vars_in_pol = None; + if(polynomial.parent().is_field()): + poly_ring = polynomial.parent().base(); + vars_in_pol = tuple(set(poly_ring(polynomial.numerator()).variables()).union(set(poly_ring(polynomial.denominator()).variables()))); + else: + vars_in_pol = polynomial.variables(); + + for var in vars_in_pol: + if(not str(var) in variables): + raise TypeError("The variables in the polynomial does not appear in the given map.\n\t- Polynomial: %s\n\t- Map: %s" %(polynomial, variables)); + + self.__built = tuple([type,data]); + + def change_name(self, new_name): + self.__name = new_name; + + ##################################### + ### Arithmetic methods + ##################################### + def scale_operator(self, r): + r = self.parent().base()(r); + + return self.parent().element(r*self.getOperator(), self.getInitialValueList(self.getOrder()), check_init=False); + + def change_init_values(self,newInit,name=None): + newElement = self.parent().element(self.getOperator(), newInit,name=name); + + ## Returning the new created element + return newElement; + + @derived_property + def inverse(self): + ''' + Method that compute and return a DD-Function `f` such `f*self == 1`, i.e. this method computes the multiplicative inverse of `self`. + ''' + if(self.getInitialValue(_sage_const_0 ) == _sage_const_0 ): + raise ValueError("Can not invert a function with initial value 0 --> That is not a power serie"); + + coeffs = self.getOperator().getCoefficients(); + + ### Computing the new name + newName = None; + if(not(self.__name is None)): + newName = DinamicString("1/(_1)",self.__name); + if(self.getOrder() == _sage_const_0 ): + raise ZeroDivisionError("Impossible to invert the zero function"); + elif(self.getOrder() == _sage_const_1 ): + return self.parent().element([-coeffs[_sage_const_0 ],coeffs[_sage_const_1 ]], [_sage_const_1 /self.getInitialValue(_sage_const_0 )], check_init=False, name=newName); + else: + newDDRing = DDRing(self.parent()); + return newDDRing.element([self.derivative(), self], [_sage_const_1 /self.getInitialValue(_sage_const_0 )], check_init=False, name=newName); + + def add(self, other): + ''' + Method that adds two DDFunctions. + + INPUT: + - ``other``: a DDFunction which will be added to `self`. + + WARNING: + - This method usually returns a new object different from `self` or `other`. But if one of the parameters is zero, then the output will be the other function, the SAME object. + - Whenever an error occurs, a NotImplemented error will be returned in order to Python can make the correspondent call to _radd_ method of `other` if necessary. + ''' + ### We check some simplifications: if one of the functions is zero, then we can return the other + if(self.is_null): + return other; + elif (other.is_null): + return self; + + ### Computing the new name + newName = None; + if(not(self.__name is None) and (not(other.__name is None))): + if(other.__name[_sage_const_0 ] == '-'): + newName = DinamicString("_1_2", [self.__name, other.__name]); + else: + newName = DinamicString("_1+_2", [self.__name, other.__name]); + + + ## We check other simplifications: if the elements are constants + if(self.is_constant or other.is_constant): + result = None; + if(self.is_constant and other.is_constant): + parent = self.parent(); + newOperator = [_sage_const_0 ,_sage_const_1 ]; + newInit = [self(_sage_const_0 )+other(_sage_const_0 )]; + result = parent.element(newOperator, newInit, check_init = False, name=newName); + elif(other.is_constant): + parent = self.parent(); + newOperator = parent.element(self.equation, inhomogeneous=other(_sage_const_0 )*self.equation.getCoefficient(_sage_const_0 )).equation; + newInit = [self(_sage_const_0 )+other(_sage_const_0 )] + [self.getInitialValue(i) for i in range(_sage_const_1 ,newOperator.get_jp_fo()+_sage_const_1 )]; + result = parent.element(newOperator, newInit, check_init = False, name=newName); + result.change_built("polynomial", (PolynomialRing(self.parent().base_field,'x1')("x1+%s" %other(_sage_const_0 )), {'x1':self})); + elif(self.is_constant): + parent = other.parent(); + newOperator = parent.element(other.equation, inhomogeneous=self(_sage_const_0 )*other.equation.getCoefficient(_sage_const_0 )).equation; + newInit = [self(_sage_const_0 )+other(_sage_const_0 )] + [other.getInitialValue(i) for i in range(_sage_const_1 ,newOperator.get_jp_fo()+_sage_const_1 )]; + result = parent.element(newOperator, newInit, check_init = False, name=newName) + result.change_built("polynomial", (PolynomialRing(self.parent().base_field,'x1')("x1+%s" %self(_sage_const_0 )), {'x1':other})); + return result; + + ## We build the new operator + if(self.equation == other.equation): + newOperator = self.equation; + else: + newOperator = self.equation.add_solution(other.equation); + + ### Getting the needed initial values for the solution + needed_initial = newOperator.get_jp_fo()+_sage_const_1 ; + + ### Getting as many initial values as posible until the new order + op1Init = self.getInitialValueList(needed_initial); + op2Init = other.getInitialValueList(needed_initial); + newInit = [op1Init[i] + op2Init[i] for i in range(min(len(op1Init),len(op2Init)))]; + + result = self.parent().element(newOperator, newInit, check_init=False, name=newName); + result.change_built("polynomial", (PolynomialRing(self.parent().base_field,['x1','x2'])('x1+x2'), {'x1':self, 'x2': other})); + return result; + + def sub(self, other): + ''' + Method that substract two DDFunctions. + + INPUT: + - ``other``: a DDFunction which will be substracted to `self`. + + WARNING: + - Whenever an error occurs, a NotImplemented error will be returned in order to Python can make the correspondent call to _rsub_ method of `other` if necessary. + ''' + if(self.is_null): + return -other; + elif (other.is_null): + return self; + + + ### Computing the new name + newName = None; + if(not(self.__name is None) and (not(other.__name is None))): + newName = DinamicString("_1-_2", [self.__name, other.__name]); + + ## We check other simplifications: if the elements are constants + if(self.is_constant or other.is_constant): + result = None; + if(self.is_constant and other.is_constant): + parent = self.parent(); + newOperator = [_sage_const_0 ,_sage_const_1 ]; + newInit = [self(_sage_const_0 )-other(_sage_const_0 )]; + result = parent.element(newOperator, newInit, check_init = False, name=newName); + elif(other.is_constant): + parent = self.parent(); + newOperator = parent.element(self.equation, inhomogeneous=other(_sage_const_0 )*self.equation.getCoefficient(_sage_const_0 )).equation; + newInit = [self(_sage_const_0 )-other(_sage_const_0 )] + [self.getInitialValue(i) for i in range(_sage_const_1 ,newOperator.get_jp_fo()+_sage_const_1 )]; + result = parent.element(newOperator, newInit, check_init = False, name=newName); + result.change_built("polynomial", (PolynomialRing(self.parent().base_field,'x1')("x1-%s" %other(_sage_const_0 )), {'x1':self})); + elif(self.is_constant): + parent = other.parent(); + newOperator = parent.element(other.equation, inhomogeneous=self(_sage_const_0 )*other.equation.getCoefficient(_sage_const_0 )).equation; + newInit = [self(_sage_const_0 )-other(_sage_const_0 )] + [-other.getInitialValue(i) for i in range(_sage_const_1 ,newOperator.get_jp_fo()+_sage_const_1 )]; + result = parent.element(newOperator, newInit, check_init = False, name=newName) + result.change_built("polynomial", (PolynomialRing(self.parent().base_field,'x1')("-x1+%s" %self(_sage_const_0 )), {'x1':other})); + return result; + + ## We build the new operator + if(self.equation == other.equation): + newOperator = self.equation; + else: + newOperator = self.equation.add_solution(other.equation); + + ### Getting the needed initial values for the solution + needed_initial = newOperator.get_jp_fo()+_sage_const_1 ; + + ### Getting as many initial values as posible until the new order + op1Init = self.getInitialValueList(needed_initial); + op2Init = other.getInitialValueList(needed_initial); + newInit = [op1Init[i] - op2Init[i] for i in range(min(len(op1Init),len(op2Init)))]; + + result = self.parent().element(newOperator, newInit, check_init=False, name=newName); + result.change_built("polynomial", (PolynomialRing(self.parent().base_field,['x1','x2'])('x1-x2'), {'x1':self, 'x2': other})); + return result; + + def mult(self, other): + ''' + Method that calculate the product of two DDFunctions. + + INPUT: + - ``other``: a DDFunction which will be multiplied to `self`. + + WARNING: + - This method always returns a new object different from `self` or `other`. + - Whenever an error occurs, a NotImplemented error will be returned in order to Python can make the correspondent call to _rmult_ method of `other` if necessary. + ''' + ### We check some simplifications: if one of the functions is zero, then we can return directly 0 + if ((other.is_null) or (self.is_null)): + return _sage_const_0 ; + if(self.is_one): + return other; + elif(other.is_one): + return self; + elif(self.is_constant and other.is_constant): + return self.getInitialValue(_sage_const_0 )*other.getInitialValue(_sage_const_0 ); + elif(self.is_constant): + return other.scalar(self.getInitialValue(_sage_const_0 )); + elif(other.is_constant): + return self.scalar(other.getInitialValue(_sage_const_0 )); + + ### We build the new operator + newOperator = self.equation.mult_solution(other.equation); + + ### Getting the needed initial values for the solution + needed_initial = newOperator.get_jp_fo()+_sage_const_1 ; + + ### Getting as many initial values as posible until the new order + op1Init = self.getInitialValueList(needed_initial); + op2Init = other.getInitialValueList(needed_initial); + newInit = [sum([binomial(i,j)*op1Init[j] * op2Init[i-j] for j in range(i+_sage_const_1 )]) for i in range(min(len(op1Init),len(op2Init)))]; + + ### Computing the new name + newName = None; + if(not(self.__name is None) and (not(other.__name is None))): + newName = DinamicString("(_1)*(_2)", [self.__name, other.__name]); + + result = self.parent().element(newOperator, newInit, check_init=False, name=newName); + result.change_built("polynomial", (PolynomialRing(self.parent().base_field,['x1','x2'])('x1*x2'), {'x1':self, 'x2': other})); + return result; + + def scalar(self, r): + ''' + This method returns the original function multiplied by a constant, i.e. `D(k) = 0`. + + INPUT: + - ``k``: scalar from the coefficient ring of `self`. It MUST be a constant. + + OUTPUT: + This method will raise TypeError if the value is not a constant. In order to do so, this method will cast the argument into the coefficient ring and try to derivate it. Just if the result is zero, the method will return a new DDFunction representing the function `k\cdot(self)`. + + INFO: + - This method must return always true for the following statements: + - ``self.scalar(k) == self.mult(DDFunction.getConstantFunction(k))``. + ''' + ### We first check if the scalar is zero, because the return is direct. + if(r == _sage_const_0 ): + return self.parent().zero(); + + ### Otherwise, we check that the element is a constant or not because the algorithm is + ### much easier + r = self.parent().base()(r); + if(self.parent().base_derivation(r) == _sage_const_0 ): + if(r == _sage_const_1 ): + return self; + n = self.equation.get_jp_fo()+_sage_const_1 ; + coeffs = self.getOperator().getCoefficients(); + init = self.getInitialValueList(n); + + if(isinstance(r, DDFunction)): + r = r.getInitialValue(_sage_const_0 ); + + ### Computing the new name + newName = None; + if(not(self.__name is None)): + if(r == -_sage_const_1 ): + newName = DinamicString("-(_1)" ,self.__name); + else: + newName = DinamicString("(_1)*(_2)", [repr(r), self.__name]); + + result = self.parent().element(self.equation, [r*el for el in init], check_init=False, name=newName); + result.change_built("polynomial", (PolynomialRing(self.parent().base_field,['x1'])('(%s)*x1' %repr(r)), {'x1':self})); + return result; + else: + return self.mult(self.parent()(r)); + + @derived_property + def zero_extraction(self): + if(self == self.parent().zero()): + return (_sage_const_0 ,self); + else: + n = _sage_const_0 ; + while(self.getInitialValue(n) == _sage_const_0 ): + n = n+_sage_const_1 ; + + if(n == _sage_const_0 ): + return (_sage_const_0 ,self); + else: + d = self.getOrder(); + coeffs = self.getOperator().getCoefficients(); + newEquation = self.parent().element([sum([coeffs[j+l]*(binomial(j+l, j)*falling_factorial(n,l)*x**(n-l)) for l in range(min(n,d-j)+_sage_const_1 )]) for j in range(d+_sage_const_1 )], check_init = False).equation; + newInit = [factorial(i)*self.getSequenceElement(i+n) for i in range(newEquation.get_jp_fo()+_sage_const_1 )]; + + ### Computing the new name + newName = None; + if(not(self.__name is None)): + newName = DinamicString("(_1)/(_2^%d)" %n, [self.__name, "x"]); + + result = self.parent().element(newEquation, newInit, check_init=False, name=newName); + result.change_built("polynomial", (PolynomialRing(self.parent().base_field,['x','x1']).fraction_field()('x1/(x^%d)' %n), {'x':self.parent()(x),'x1':self})); + return (n,result); + + def divide(self, other): + if(self.is_null): + return self.parent().zero(); + if(other.is_constant): + return self.scalar(_sage_const_1 /other.getInitialValue(_sage_const_0 )); + + s_ze = self.zero_extraction; + o_ze = other.zero_extraction; + + if(s_ze[_sage_const_0 ] >= o_ze[_sage_const_0 ]): + result = self.parent()(x)**(s_ze[_sage_const_0 ]-o_ze[_sage_const_0 ])*(s_ze[_sage_const_1 ]*o_ze[_sage_const_1 ].inverse); + + ### Computing the new name + newName=None; + if(not(self.__name is None) and (not(other.__name is None))): + newName = DinamicString("(_1)/(_2)", [self.__name, other.__name]); + + result.__name = newName; + return result; + else: + raise ValueError("Impossible perform a division if the x factor of the denominator (%d) is greater than in the numerator (%d)"%(o_ze[_sage_const_0 ],s_ze[_sage_const_0 ])); + + def gcd(self, other): + if(other in self.parent()): + other = self.parent()(other); + elif(self in other.parent()): + return other.gcd(self); + + if(self.is_null): + return other; + elif(other.is_null): + return self; + + se = self.zero_extraction; + oe = other.zero_extraction; + + return self.parent()(x**min(se[_sage_const_0 ],oe[_sage_const_0 ])*gcd(se[_sage_const_1 ].getInitialValue(_sage_const_0 ),oe[_sage_const_1 ].getInitialValue(_sage_const_0 ))); + + ##################################### + ### Differential methods + ##################################### + def derivative(self, *args): + ''' + Method to get a DDFunction `g` that satisfies `D(self) = g`. + + INPUT: + - ``args``: ignored input + ''' + if(self.__derivative is None): + if(self.is_constant): + ### Special case: is a constant + self.__derivative = self.parent()(_sage_const_0 ); + else: + ### We get the new operator + newOperator = self.equation.derivative_solution(); + + ### We get the required initial values (depending of the order of the next derivative) + initList = self.getInitialValueList(newOperator.get_jp_fo()+_sage_const_2 ); + newInit = [initList[i+_sage_const_1 ] for i in range(min(len(initList)-_sage_const_1 ,newOperator.get_jp_fo()+_sage_const_1 ))]; + + ### Computing the new name + newName = None; + if(not(self.__name is None)): + if(self.__name[-_sage_const_1 ] == "'"): + newName = DinamicString("_1'", self.__name); + else: + newName = DinamicString("(_1)'", self.__name); + + ### We create the next derivative with the equation, initial values + self.__derivative = self.parent().element(newOperator, newInit, check_init=False, name=newName); + self.__derivative.change_built("derivative",tuple([self])); + + + return self.__derivative; + + def integrate(self, constant=_sage_const_0 ): + ''' + Method to get a DDFunction `g` that satisfies `D(g) = self` and `g(0) = constant`. + + INPUT: + - ``constant``: element which will define the initial value of the returned primitive. + ''' + ### First we calculate the new linear differential operator + newOperator = self.equation.integral_solution(); + + ### We get the initial values for the integral just adding at the begining of the list the constant value + # If not enough initial values can be given, we create the integral with so many initial values we have + newInit = [self.parent().base_field(constant)] + self.getInitialValueList(newOperator.get_jp_fo()+_sage_const_1 ); + + ### Computing the new name + newName = None; + if(not(self.__name is None)): + if(constant == _sage_const_0 ): + newName = DinamicString("int(_1)", self.__name); + else: + newName = DinamicString("int(_1) + _2", [self.__name, repr(constant)]); + + return self.parent().element(newOperator, newInit, check_init=False, name=newName); + + def compose(self, element): + ''' + Method to get a DDFunction `g` that is the composition of `self` with `other`. It return the composition in the corresponding ring. + ''' + ## Checking we fall in teh cases already implemented + if((not (element in self.parent().original_ring())) and (self.getOrder() > _sage_const_3 )): + raise NotImplementedError("Composition not yet implemented with elements not in %s for functions of order greater than 2 (got %d)" %(self.parent().original_ring(),self.getOrder())); + + ## If we have the basic function we return the same element + ## Also, if self is a constant, the composition is again the constant + self_var = self.parent().variables(True)[_sage_const_0 ]; + if(self_var == element or self.is_constant): + return self; + + ## Otherwise, we check that the value at 0 is zero + init = element(**{str(self_var):_sage_const_0 }); + if(init != _sage_const_0 ): + raise ValueError("Impossible to compose a power series with other that has non-zero initial value.\n\t- Get: %s" %init); + + ## We compute the differential operator case by case + newOperator = None; + destiny_ring = None; + if(not(element in self.parent().original_ring())): + ## Particular cases: we have a formula + ## First, we get the pushout of both parents + ## This will be a DDRing always + from sage.categories.pushout import pushout; + mix_ring = pushout(self.parent(), element.parent()); + + ## We compute the depth in the DDRing structure of 'element' + element_depth = _sage_const_0 ; + if(isinstance(element.parent(),DDRing)): + element_depth = element.parent().depth(); + + ## We build the destiny DDRing + destiny_ring = mix_ring.to_depth(self.parent().depth() + element_depth); + if(destiny_ring.depth() >= _sage_const_3 ): + warnings.warn("Reached a too high depth in the recursion (%d). This may not finish." %destiny_ring.depth(), TooDeepWarning, stacklevel=_sage_const_2 ); + #element = destiny_ring.base()(element); + + if(element in destiny_ring.original_ring()): + return destiny_ring(self).compose(element); + elif(self.getOrder() == _sage_const_0 ): + raise ValueError("Impossible Error: Non-constant element of order zero!!"); + elif(self.getOrder() == _sage_const_1 ): + a = self.equation.getCoefficient(_sage_const_1 )(x=element); + b = self.equation.getCoefficient(_sage_const_0 )(x=element); + dg = destiny_ring.base_derivation(element); + newOperator = destiny_ring.element([dg*b,a]).equation; + elif(self.getOrder() == _sage_const_2 ): + a = self.equation.getCoefficient(_sage_const_2 )(x=element); + b = self.equation.getCoefficient(_sage_const_1 )(x=element); + c = self.equation.getCoefficient(_sage_const_0 )(x=element); + dg = destiny_ring.base_derivation(element); + ddg = destiny_ring.base_derivation(dg); + newOperator = destiny_ring.element([dg**_sage_const_3 *c,dg**_sage_const_2 *b-ddg*a,dg*a]).equation; + elif(self.getOrder() == _sage_const_3 ): + a = self.equation.getCoefficient(_sage_const_3 )(x=element); + b = self.equation.getCoefficient(_sage_const_2 )(x=element); + c = self.equation.getCoefficient(_sage_const_1 )(x=element); + d = self.equation.getCoefficient(_sage_const_0 )(x=element); + dg = destiny_ring.base_derivation(element); + ddg = destiny_ring.base_derivation(dg); + dddg = destiny_ring.base_derivation(ddg); + newOperator = destiny_ring.element([dg**_sage_const_5 *d, dg**_sage_const_4 *c-ddg*dg**_sage_const_2 *b-dg*dddg*a-_sage_const_3 *ddg**_sage_const_2 *a,dg**_sage_const_3 *b-_sage_const_3 *ddg*dg*a,dg**_sage_const_2 *a]).equation; + else: + newOperator = self.equation.compose_solution(element); + destiny_ring = self.parent(); + + + ### We compute the initial values required + required = newOperator.get_jp_fo()+_sage_const_1 ; + + f = self.getInitialValueList(required); + try: + ## Case element is simpler than self + g = [factorial(i)*self.parent().getSequenceElement(element,i) for i in range(required)]; + except TypeError: + ## Case element has higher complexity than self + g = [factorial(i)*element.parent().getSequenceElement(element,i) for i in range(required)]; + + newInit = [f[_sage_const_0 ]]+[sum([f[j]*bell_polynomial(i,j)(*g[_sage_const_1 :i-j+_sage_const_2 ]) for j in range(_sage_const_1 ,i+_sage_const_1 )]) for i in range(_sage_const_1 ,required)]; ## See Faa di Bruno's formula + + ### Computing the new name + newName = None; + if(not(self.__name is None)): + # if(isinstance(element, DDFunction) and not(element.__name is None)): + # newName = self.__name.replace("exp", "####").replace("x", element.__name).replace("####", "exp"); + # else: + # newName = self.__name.replace("exp", "####").replace("x", repr(element)).replace("####", "exp"); + if(isinstance(element, DDFunction) and (not (element.__name is None))): + newName = din_m_replace(self.__name, {"x":element.__name}, True); + elif(not isinstance(element, DDFunction)): + newName = din_m_replace(self.__name, {"x":repr(element)}, True); + + return destiny_ring.element(newOperator, newInit, check_init=False, name=newName); + + ##################################### + ### Equality methods + ##################################### + @derived_property + def is_null(self): + ''' + Cached property to check whether self is zero in its ring or not. + + The method used is comparing the first initial values of the function and check they are zero. If some initial value can not be computed, then False is returned. + ''' + return self.is_fully_defined and all(self.getInitialValue(i) == _sage_const_0 for i in range(self.equation.get_jp_fo()+_sage_const_1 )); + + @derived_property + def is_one(self): + ''' + Cached property to check whether self is one on its ring or not. + + The metod used is checking that the element is a constant and its first initial value is one. + ''' + return (self.is_constant and self.getInitialValue(_sage_const_0 ) == _sage_const_1 ); + + @derived_property + def is_constant(self): + ''' + Cached property to check whether self is a constant or not. + + We check enought initial values to know (without computing the derivative) if it is zero or not. + ''' + ### OPTION 1 + ## Test zero and if not, check if a constant can be solution + try: + if(not self.is_null): + return self.equation[_sage_const_0 ] == _sage_const_0 and all(self.getInitialValue(i) == _sage_const_0 for i in range(_sage_const_1 , self.equation.get_jp_fo()+_sage_const_1 )); + return True; + except TypeError: + return False; + ### OPTION 2 (nor proved) + ## Test only initial values for 'self' + #try: + # return all(self.getInitialValue(i) == 0 for i in range(1,self.equation.get_jp_fo()+3)); + #except TypeError: + # return False; + ### OPTION 3 (too costly) + ## Test the derivative + #return self.derivative() == 0; + ### OPTION 4 (too costly) + ## Test the difference with the cosntant term + #return (self - self(0)) == 0 + + @derived_property + def is_fully_defined(self): + ''' + Cached property yo check whether the function is fully defined or not. + + This means that, given some initial values and the differential equation, the solution of such problem is unique (True) or not (False) + ''' + max_init = self.equation.get_jp_fo()+_sage_const_1 ; + return len(self.getInitialValueList(max_init)) == max_init; + + def equals(self,other): ### TO REVIEW + ''' + Method that checks if two DDFunctions are equal (as power-series). In orther to do so, we substract one to the other and check if the result is zero. + + INPUT: + - ``other``: a DDFunction that can be substracted of ``self``. + + OUTPUT: + - True if the object ``other`` represents the same function as ``self``. + - False otherwise. + ''' + try: + if(not isinstance(other, DDFunction)): + other = self.parent()(other); + + ## Quick check of initial values + if(not self.quick_equals(other)): + return False; + + ## Special case: trying equality with zero + if(other.is_null): + return self.is_null; + elif(self.is_null): + return False; + + ## Special case with constants + if(other.is_constant): + return self.is_constant and self(_sage_const_0 ) == other(_sage_const_0 ); + elif(self.is_constant): + return False; + + if(not (self.is_fully_defined and other.is_fully_defined)): + return (self.equation == other.equation) and (self.getInitialValueList(self.equation.get_jp_fo()+_sage_const_1 ) == other.getInitialValueList(other.equation.get_jp_fo()+_sage_const_1 )); + + ### THREE OPTIONS + ## 1. Compare with the JP-Values of each function + #m = self.equation.get_jp_fo()+other.equation.get_jp_fo()+1; + #if(not all(self.getInitialValue(i) == other.getInitialValue(i) for i in range(m))): + # return False; + + ## 2. Substract and check zero equality + return (self-other).is_null; + + ## 3. Check adding orders and if they are equal, substract operators and check as many initial values as needed + #m = self.getOrder()+other.getOrder()+1; + #if(not all(self.getInitialValue(i) == other.getInitialValue(i) for i in range(m))): + # return False; + # + #M = self.equation.add_solution(other.equation).get_jp_fo()+1; + #return all(self.getInitialValue(i) == other.getInitialValue(i) for i in range(m,M)); + except Exception: + ### If an error occur, then other can not be substracted from self, so they can not be equals + return False; + + def numerical_solution(self, value, delta=_sage_const_1en10 , max_depth=_sage_const_100 ): + try: + ## We try to delegate the computation to the operator (in case of OreOperators) + if(self.equation.getCoefficients()[-_sage_const_1 ](_sage_const_0 ) != _sage_const_0 ): + sol = self.equation.operator.numerical_solution(self.getSequenceList(self.getOrder()), [_sage_const_0 ,value],delta); + return sol.center(), sol.diameter(); + else: + return self.__basic_numerical_solution(value, delta, max_depth); + except AttributeError: + ## The operator has no numerical solution method (i.e. it is not an OreOperator + ## We compute the basic numerical approximation + return self.__basic_numerical_solution(value, delta, max_depth); + + def __basic_numerical_solution(self, value, delta,max_depth): + res = _sage_const_0 ; + to_mult = _sage_const_1 ; + to_sum = _sage_const_0 ; + step = _sage_const_0 ; + find = _sage_const_0 ; + while(find < _sage_const_5 and step < max_depth): + to_sum = self.getSequenceElement(step)*to_mult; + res += to_sum; + if(self.getSequenceElement(step) != _sage_const_0 and abs(to_sum) < delta): + find += _sage_const_1 ; + elif(self.getSequenceElement(step) != _sage_const_0 ): + find = _sage_const_0 ; + + step += _sage_const_1 ; + to_mult *= value; + return float(res),float(abs(to_sum)); + + def to_symbolic(self): + evaluation = sage_eval(str(self.__name).replace("'", ".derivative()").replace("^", "**"), locals=globals()); + if(isinstance(evaluation, sage.symbolic.expression.Expression)): + evaluation = evaluation.simplify_full(); + + return evaluation; + + def quick_equals(self,other): ### TO REVIEW + ''' + Method that checks if two DDFunctions are equal (as power-series). In orther to do so, we substract one to the other and check if the result is zero. + + INPUT: + - ``other``: a DDFunction that can be substracted of ``self``. + + OUTPUT: + - True if the object ``other`` could represent the same function as ``self``. + - False if we are sure ``self`` is different from ``other``. + ''' + try: + if(not isinstance(other, DDFunction)): + other = self.parent()(other); + + m = self.equation.get_jp_fo()+other.equation.get_jp_fo()+_sage_const_1 ; + return self.getInitialValueList(m) == other.getInitialValueList(m); + except Exception: + return False + + ##################################### + ### Magic Python methods + ##################################### + ### Magic arithmetic + ## Special case for Simbolic Ring + def __check_symbolic(self, other): + try: + if(other.parent() is SR): + try: + return self.parent()(other); + except: + return ParametrizedDDRing(self.parent(),other.variables())(other); + except Exception as e: + pass; + return other; + + def __add__(self, other): + return super(DDFunction,self).__add__(self.__check_symbolic(other)); + + def __sub__(self, other): + return super(DDFunction,self).__sub__(self.__check_symbolic(other)); + + def __mul__(self, other): + return super(DDFunction,self).__mul__(self.__check_symbolic(other)); + + def __div__(self, other): + return super(DDFunction,self).__div__(self.__check_symbolic(other)); + + def __radd__(self, other): + return super(DDFunction,self).__add__(self.__check_symbolic(other)); + + def __rsub__(self, other): + return super(DDFunction,self).__sub__(self.__check_symbolic(other)); + + def __rmul__(self, other): + return super(DDFunction,self).__mul__(self.__check_symbolic(other)); + + def __rdiv__(self, other): + return super(DDFunction,self).__div__(self.__check_symbolic(other)); + + def __iadd__(self, other): + return super(DDFunction,self).__add__(self.__check_symbolic(other)); + + def __isub__(self, other): + return super(DDFunction,self).__sub__(self.__check_symbolic(other)); + + def __imul__(self, other): + return super(DDFunction,self).__mul__(self.__check_symbolic(other)); + + def __idiv__(self, other): + return super(DDFunction,self).__div__(self.__check_symbolic(other)); + + + ## Addition + + def _add_(self, other): + try: + return self.add(other); + except Exception: + return NotImplemented; + + ## Substraction + def _sub_(self, other): + try: + return self.sub(other); + except Exception: + return NotImplemented; + + ## Multiplication + def _mul_(self, other): + try: + if(not isinstance(other, DDFunction)): + return self.scalar(other); + + return self.mult(other); + except Exception: + return NotImplemented; + + def _div_(self, other): + try: + return self.divide(other); + except ValueError: + return NotImplemented; + + # Integer powering + def __pow__(self, other): + try: + return self.__pows[other]; + except KeyError: + f = self; + if(f.is_null): ## Trivial case when order is 0 + self.__pows[other] = f; + elif(other in ZZ): ## Trying integer power + other = int(other); + if(other >= _sage_const_0 ): + a = other >> _sage_const_1 ; + b = a+(other&_sage_const_1 ); + self.__pows[other] = self.__pow__(a)*self.__pow__(b); + newName = None; + if(not(self.__name is None)): + newName = DinamicString("(_1)^%d" %(other), self.__name); + self.__pows[other].__name = newName; + else: + try: + inverse = self.inverse; + except Exception: + raise ZeroDivisionError("Impossible to compute the inverse"); + return inverse.__pow__(-other); + else: ## Trying a power on the basic field + try: + newDDRing = DDRing(self.parent()); + other = self.parent().base_ring()(other); + self.__pows[other] = newDDRing.element([(-other)*f.derivative(),f], [el**other for el in f.getInitialValueList(_sage_const_1 )], check_init=False); + + newName = None; + if(not(self.__name is None)): + newName = DinamicString("(_1)^%s" %(other), self.__name); + self.__pows[other].__name = newName; + except TypeError: + raise TypeError("Impossible to compute (%s)^(%s) within the basic field %s" %(f.getInitialValue(_sage_const_0 ), other, f.parent().base_ring())); + except ValueError: + raise NotImplementedError("Powering to an element of %s not implemented" %(other.parent())); + return self.__pows[other]; + + + ### Magic equality + def __eq__(self,other): + if (other is self): + return True; + if((type(other) == sage.rings.integer.Integer or type(other) == int) and other == _sage_const_0 ): + return self.is_null; + return self.equals(other); + + ### Magic use + def __call__(self, X=None, **input): + ''' + Method that return the value of the function in the point given. As the function as a power-serie may not converge, this method only works if the argument is 0. + Further implementation can be done for DDFunctions that we know about the convergence. + ''' + if ((not(X is None)) and X == _sage_const_0 ): + return self.getInitialValue(_sage_const_0 ); + + return self.parent().eval(self, X, **input); + + ### Magic representation + def __hash__(self): + ''' + Hash method for DDFunctions. + ''' + return int("%d%d%d" %(self.getOrder(),self.equation.get_jp_fo(),self.size())); + #return sum([hash(coeff) for coeff in self.getOperator().getCoefficients()]); + + def __getitem__(self, key): + ''' + GetItem method for DDFunctions. It allows the user do ``self[i]`` for `i \geq -1`. + + INPUT: + - ``key``: an integer. + + RETURN: + - The result is the coefficient of `D^{key}(f)` on the equation. + ''' + return self.getOperator().getCoefficient(key); + + def __missing__(self, key): + ''' + Missing method for DDFuntions. + ''' + return _sage_const_0 ; + + def __str__(self, detail=True): + ''' + String method for DDFunctions. It prints all information of the DDFunction. + ''' + #if(self.is_constant): + # return (self.getInitialValue(0)).__str__(); + + if(detail and (not(self.__name is None))): + res = "%s %s in %s:\n" %(self.__critical_numbers__(),repr(self),self.parent()); + else: + res = "%s" %(repr(self)); + if(res[_sage_const_0 ] != '('): + res += " in %s" %(self.parent()); + res += ":\n"; + res += '-------------------------------------------------\n\t'; + res += '-- Equation (self = f):\n' + j = _sage_const_0 ; + while ((j < self.getOrder()) and (self.getOperator().getCoefficient(j) == _sage_const_0 )): + j += _sage_const_1 ; + res += self.__get_line__(self.getOperator().getCoefficient(j), j, detail, True); + for i in range(j+_sage_const_1 ,self.getOrder()+_sage_const_1 ): + if(self.getOperator().getCoefficient(i) != _sage_const_0 ): + res += '\n'; + res += self.__get_line__(self.getOperator().getCoefficient(i), i, detail); + + res += '\n\t\t = 0 \n\t'; + + res += '-- Initial values:\n\t' + res += format(self.getInitialValueList(self.equation.get_jp_fo()+_sage_const_1 )); + + res += '\n-------------------------------------------------'; + + return res; + + def __get_line__(self, element, der, detail, first = False): + res = ""; + string = repr(element); + crit = None; + + ## Getting the basic elements for the line + if(isinstance(element, DDFunction) and (not(element.__name is None))): + crit = element.__critical_numbers__(); + else: + ind = string.find(")")+_sage_const_1 ; + crit = string[:ind]; + string = string[ind:]; + + ## Printing critical numbers if detail is required + if(detail and len(crit) > _sage_const_0 ): + res += crit + "\t"; + if(len(res.expandtabs()) < len("\t\t".expandtabs())): + res += "\t"; + else: + res += "\t\t"; + + ## Adding the arithmetic symbol + if(not(first) and string[_sage_const_0 ] != '-'): + res += '+ '; + elif(string[_sage_const_0 ] == '-'): + res += '- '; + if(string[_sage_const_1 ] == '('): + string = string[_sage_const_1 :-_sage_const_1 ]; + else: + string = string[_sage_const_1 :]; + else: + res += ' '; + + ## Adding the function (with its derivative if needed) + if(der > _sage_const_1 ): + res += "D^%d(f) " %der; + elif(der == _sage_const_1 ): + res += " D(f) "; + else: + res += " f "; + + ## Adding the coefficient + if(string != "1"): + res += "* (%s)" %string; + + return res; + + def __repr__(self): + ''' + Representation method for DDFunctions. It prints basic information of the DDFunction. + ''' + if(self.is_constant): + return str(self.getInitialValue(_sage_const_0 )); + if(self.__name is None): + return "(%s:%s:%s)DD-Function in (%s)" %(self.getOrder(),self.equation.get_jp_fo(),self.size(),self.parent()); + else: + return str(self.__name); + + def __critical_numbers__(self): + return "(%d:%d:%d)" %(self.getOrder(),self.equation.get_jp_fo(),self.size()); + + def _to_command_(self): + if(self.__name is None): + return "%s.element(%s,%s)" %(command(self.parent()), _command_list(self.getOperator().getCoefficients()), self.getInitialValueList(self.getOrder())); + else: + return "%s.element(%s,%s,name='%s')" %(command(self.parent()), _command_list(self.getOperator().getCoefficients()), self.getInitialValueList(self.getOrder()),str(self.__name)); + + ##################################### + ### Other methods + ##################################### + def __nonzero__(self): + return not (self.is_null); + +##################################################### +### Construction Functor for DD-Ring +##################################################### +class DDRingFunctor (ConstructionFunctor): + def __init__(self, depth, base_field): + self.rank = _sage_const_11 ; + self.__depth = depth; + self.__base_field = base_field; + super(DDRingFunctor, self).__init__(IntegralDomains(),IntegralDomains()); + + ### Methods to implement + def _coerce_into_domain(self, x): + if(x not in self.domain()): + raise TypeError("The object [%s] is not an element of [%s]" %(x, self.domain())); + if(isinstance(x, DDRing)): + return x.base(); + return x; + + def _apply_functor(self, x): + return DDRing(x,self.__depth,self.__base_field); + + def _repr_(self): + return "DDRing(*,%d,%s)" %(self.__depth, self.__base_field); + + def depth(self): + return self.__depth; + +class ParametrizedDDRingFunctor (DDRingFunctor): + def __init__(self, depth, base_field, var = {}): + self.rank = _sage_const_12 ; + self.__vars = var; + super(ParametrizedDDRingFunctor, self).__init__(depth, base_field); + + ### Methods to implement + def _coerce_into_domain(self, x): + if(x not in self.domain()): + raise TypeError("The object [%s] is not an element of [%s]" %(x, self.domain())); + return x; + + def _apply_functor(self, x): + return ParametrizedDDRing(x, self.__vars); + + def merge(self, other): + if(isinstance(other, ParametrizedDDRingFunctor)): + return ParametrizedDDRingFunctor(self.__vars.union(other.__vars)); + return None; + + def __repr__(self): + return super(ParametrizedDDRingFunctor, self).__repr__() + " - " + repr(self.__vars); + +##################################################### +### General Morphism for return to basic rings +##################################################### +class DDSimpleMorphism (sage.categories.map.Map): + def __init__(self, domain, codomain): + super(DDSimpleMorphism, self).__init__(domain, codomain); + + def _call_(self, p): + if(p.is_constant): + return self.codomain()(p.getInitialValue(_sage_const_0 )); + + raise ValueError("Impossible perform this coercion: non-constant element"); + + + +##################################### +### Changes to fit the SAGE Categories Framework +##################################### +DDRing.Element = DDFunction; + +def zero_extraction(el): + try: + R = el.parent(); + if(isinstance(R, DDRing)): + return el.zero_extraction; + elif(x in R.gens()): + n = _sage_const_0 ; + val = el(**{'x':_sage_const_0 }); + while(el != R.zero() and val == _sage_const_0 ): + n += _sage_const_1 ; + el = R(el/x); + val = el(**{'x':_sage_const_0 }); + + return (n, el); + except AttributeError as e: + return (_sage_const_0 ,el); + +################################################################################################### +### PRIVAME MODULE METHODS +################################################################################################### +def _indices(string, sep): + try: + index = string.index(sep); + return [index] + [el+index+len(sep) for el in _indices(string[index+len(sep):],sep)]; + except ValueError: + return []; + +def _m_indices(string, *seps): + ## We assume no possible overlapping can occur between elements in seps + all_index = []; + for sep in seps: + all_index += [(el,sep) for el in _indices(string,sep)]; + all_index.sort(); + + return all_index; + +def _m_replace(string, to_replace, escape=("(",")")): + ## Checking if ther are scape characters + if(not escape is None): + pos_init = string.find(escape[_sage_const_0 ]); + if(pos_init >= _sage_const_0 ): + ## Escape initial character found, skipping outside the escape + pos_end = len(string); + pos_end = string.rfind(escape[_sage_const_1 ]); + if(pos_end <= pos_init): + pos_end = len(string); + + return string[:pos_init+_sage_const_1 ] + _m_replace(string[pos_init+_sage_const_1 :pos_end], to_replace, None) + string[pos_end:]; + + ## No escape characters: doing normal computation + all_index = _m_indices(string, *to_replace.keys()); + res = ""; current_index = _sage_const_0 ; + for el in all_index: + res += string[current_index:el[_sage_const_0 ]] + to_replace[el[_sage_const_1 ]]; + current_index = el[_sage_const_0 ]+len(el[_sage_const_1 ]); + res += string[current_index:] + return res; + +################################################################################################### +### COMMAND CONVERSION OF DD_FUNCTIONS +################################################################################################### +def command(e): + try: + return e._to_command_(); + except AttributeError: + from sage.rings.polynomial import polynomial_ring as Uni_Polynomial; + if(e in IntegralDomains()): + if(e is QQ): + return "QQ"; + if(Uni_Polynomial.is_PolynomialRing(e)): + return "PolynomialRing(%s,%s)" %(command(e.base()), [str(var) for var in e.gens()]); + return str(e); + +def _command_list(elements): + res = "["; + for i in range(len(elements)-_sage_const_1 ): + res += command(elements[i]) + ", "; + if(len(elements) > _sage_const_0 ): + res += command(elements[-_sage_const_1 ]); + res += "]"; + + return res; + +################################################################################################### +### STANDARD PACKAGES VARIABLES & GETTERS +################################################################################################### + +# Some global pre-defined DD-Rings +DFinite = None; +try: + DFinite = DDRing(PolynomialRing(QQ,x), default_operator=w_OreOperator); +except ImportError: + ## No ore_algebra package found + warnings.warn("Package ore_algebra was not found. It is hightly recommended to get this package built by M. Kauers and M. Mezzaroba (http://marc.mezzarobba.net/code/ore_algebra-analytic/ or http://www.kauers.de/software.html)", NoOreAlgebraWarning); + DFinite = DDRing(PolynomialRing(QQ,x)); +DDFinite = DDRing(DFinite); +#CFinite = DDRing(QQ, default_operator=w_OreOperator); +DFiniteP = ParametrizedDDRing(DFinite, [var('P')]); + +def __get_recurrence(f): + if(not f in DFinite): + raise TypeError("The function must be holonomic"); + + f = DFinite(f); + + op = f.equation; + m = max([el.degree() for el in op.getCoefficients()]); + bound = op.getOrder() + m; + + num_of_bck = bound - op.getOrder(); + m = None; + for i in range(num_of_bck, _sage_const_0 , -_sage_const_1 ): + if(op.backward(i) != _sage_const_0 ): + m = -i; + break; + + if(m is None): + for i in range(op.getOrder()+_sage_const_1 ): + if(op.forward(i) != _sage_const_0 ): + m = i; + break; + + n = op._Operator__polynomialRing.gens()[_sage_const_0 ]; + + rec = [op.get_recursion_polynomial(i)(n=n+m) for i in range(m,op.forward_order + _sage_const_1 )]; + rec_gcd = gcd(rec); + rec = [el/rec_gcd for el in rec]; + + return (rec, f.getSequenceList(op.forward_order-m)); +DFinite.get_recurrence = __get_recurrence; + +################################################################################################### +### PACKAGE ENVIRONMENT VARIABLES +################################################################################################### +__all__ = ["DDRing", "DFinite", "DDFinite", "command", "zero_extraction" , "ParametrizedDDRing", "DFiniteP"]; + + diff --git a/ajpastor/dd_functions/symbolic.py b/ajpastor/dd_functions/symbolic.py new file mode 100644 index 0000000..8e7e773 --- /dev/null +++ b/ajpastor/dd_functions/symbolic.py @@ -0,0 +1,153 @@ + +# This file was *autogenerated* from the file ./symbolic.sage +from sage.all_cmdline import * # import sage library + +_sage_const_2 = Integer(2); _sage_const_1 = Integer(1); _sage_const_0 = Integer(0) + +from .ddFunction import *; +from .ddFunction import DDFunction; +from .ddExamples import *; + +from ajpastor.misc.verbose import *; + +################################################################################################### +### SYMBOLIC CONVERSION OF DD_FUNCTIONS +################################################################################################### +def symbolic(function): + if(isinstance(function, DDFunction)): + try: + return function.to_symbolic(); + except Exception as e: + return e; + return None; + +#@wverbose +def from_symbolic(exp, dR = DFinite): + exp = SR(exp); + ## Base case: The expression is a polynomial + try: + polynomial = dR.original_ring()(str(exp)); + #sverbose("A polynomial found"); + return polynomial; + except Exception: + pass; + + ## Other cases: We choose between operands + try: + import types; + operator = exp.operator(); + if(operator is None): + raise ValueError("Impossible to cast: reach bottom without polynomial"); + + ## Getting the name + name = None; + try: + name = operator.__name__; + except AttributeError: + name = operator.name(); + + #sverbose("Operator name: %s" %name); + + ## Converting the operands + operands = [from_symbolic(el, dR) for el in exp.operands()]; + + ## Choosing the operation + ### Python types + if(name == "list"): + #sverbose("Returning list"); + return operands; + elif(name == "tuple"): + #sverbose("Returning tuple"); + return tuple(operands); + #sverbose("Returning set"); + elif(name == "set"): + return set(operands); + ### Arithmetic + elif(name == "add_vararg"): + #sverbose("Returning sum"); + return sum(operands); + elif(name == "mul_vararg"): + #sverbose("Returning mult"); + for el in operands: + print el + return prod(operands); + elif(name == "pow"): + try: + exponent = int(operands[_sage_const_1 ]); + #if(exponent < 0 and (not(isinstance(operands[0], DDFunction)))): + # operands[0] = dR(operands[0]); + except ValueError: + pass; + #sverbose("Returning pow"); + return pow(operands[_sage_const_0 ],operands[_sage_const_1 ]); + ### Trigonometric + elif(name == "sin"): + #sverbose("Returning sin"); + #sverbose("%s" %str(operands[0])); + return Sin(operands[_sage_const_0 ]); + elif(name == "cos"): + #sverbose("Returning cos"); + return Cos(operands[_sage_const_0 ]); + elif(name == "sinh"): + #sverbose("Returning sinh"); + return Sinh(operands[_sage_const_0 ]); + elif(name == "cosh"): + #sverbose("Returning cosh"); + return Cosh(operands[_sage_const_0 ]); + elif(name == "tan"): + #sverbose("Returning tan"); + return Tan(operands[_sage_const_0 ]); + ### Logarithmic and exponential + elif(name == "log"): + #sverbose("Returning log"); + return Log(operands[_sage_const_0 ]); + elif(name == "exp"): + #sverbose("Returning exp"); + return Exp(operands[_sage_const_0 ]); + ### Special functions + elif(name == "bessel_J"): + return BesselD(operands[_sage_const_0 ])(operands[_sage_const_1 ]); + elif(name == "legendre_P"): + return LegendreD(operands[_sage_const_0 ])(operands[_sage_const_1 ]); + elif(name == "chebyshev_T"): + return ChebyshevD(operands[_sage_const_0 ],_sage_const_1 )(operands[_sage_const_1 ]); + elif(name == "chebyshev_U"): + return ChebyshevD(operands[_sage_const_1 ],_sage_const_2 )(operands[_sage_const_1 ]); + ### Hypergeometric functions + elif(name == "hypergeometric"): + return GenericHypergeometricFunction(operands[_sage_const_0 ],operands[_sage_const_1 ])(operands[_sage_const_2 ]); + + raise TypeError("No valid operator found"); + except AttributeError: + return None; # Impossible to get the name of the operator + +def symbolic_simplify(function): + ''' + An inplace simplification using the symbolic SAGE expressions + ''' + ## If the input is not a DDFunction, we just return it + if(isinstance(function, DDFunction)): + ## First, we check the function itself + symbolic = from_symbolic(function.to_symbolic(), function.parent()); + if(not isinstance(symbolic, DDFunction)): + symbolic = function.parent()(symbolic); + + if(symbolic.size() < function.size()): + function.equation = symbolic.equation; + if(len(repr(symbolic)) < len(repr(function))): + function._DDFunction__name = repr(symbolic); + + ## Now we check the coefficients of the equation + symbolic_simplify_coefficients(function); + return function; + +def symbolic_simplify_coefficients(function): + if(isinstance(function, DDFunction)): + for i in range(function.getOrder()+_sage_const_1 ): + symbolic_simplify(function.equation.getCoefficient(i)); + +################################################################################################### +### PACKAGE ENVIRONMENT VARIABLES +################################################################################################### +__all__ = ["symbolic", "from_symbolic", "symbolic_simplify", "symbolic_simplify_coefficients"]; + diff --git a/ajpastor/lazy/__init__.py b/ajpastor/lazy/__init__.py new file mode 100644 index 0000000..e689411 --- /dev/null +++ b/ajpastor/lazy/__init__.py @@ -0,0 +1,4 @@ +from lazyToPoly import LazyToPoly; + +from pkgutil import extend_path; +__path__ = extend_path(__path__, __name__); diff --git a/ajpastor/lazy/conversion.py b/ajpastor/lazy/conversion.py new file mode 100644 index 0000000..80c3ef8 --- /dev/null +++ b/ajpastor/lazy/conversion.py @@ -0,0 +1,321 @@ + +# This file was *autogenerated* from the file ./conversion.sage +from sage.all_cmdline import * # import sage library + +_sage_const_1 = Integer(1); _sage_const_0 = Integer(0) +from sage.rings.polynomial.polynomial_ring import is_PolynomialRing as isUniPolynomial; +from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing as isMPolynomial; + +class ConversionSystem(object): + ## Main bulder + def __init__(self, base): + ''' + Builder for a Conversion system. + + Take the following input: + - `base`: a ring (checked or raise a TypeError) + ''' + + ## Starting the method + if(not base in IntegralDomains()): + raise TypeError("Imposible to build a conversion systom from something that is not a Ring"); + + ## Initializing variables + self.__base = base; + + self.__relations = None; + self.__rel_ideal = None; + + ## Public getters + def base(self): + return self.__base; + + def is_polynomial(self): + ''' + Returns a Boolean value that show if there are variables in this conversion system. + ''' + return (isUniPolynomial(self.poly_ring()) or isMPolynomial(self.poly_ring())); + + def poly_ring(self): + ''' + Returns the polynomial ring where the conversion system works. + ''' + raise NotImplementedError("Abstract method not implemented 'poly_ring()'"); + + def poly_field(self): + ''' + Returns the polynomial fraction field where the conversion system works. + ''' + raise NotImplementedError("Abstract method not implemented 'poly_field()'"); + + def map_of_vars(self): + ''' + Returns a Python dictionary which maps the variables of `self.poly_ring()` to the real elements of `self.base()` + ''' + raise NotImplementedError("Abstract method not implemented 'map_of_vars()'"); + + ## Pulbic methods + def add_relations(self, *relations): + if(self.is_polynomial()): + try: + ## Adding the new relations + self._relations(); # We make sure the relations are initialized + + self.__add_relation(relations); + + ## Changing the ideal and computing a groebner basis + if(len(self.__relations) >= _sage_const_1 ): + self.__rel_ideal = ideal(self.poly_ring(), self.__relations); + self.__relations = self.__rel_ideal.groebner_basis(); + except TypeError as e: + raise e; + + def clean_relations(self): + self.__relations = []; + + def to_poly(self, element): + ''' + This method cast an element in `self.base()` or `self.base().fraction_field()` to a polynomial in the conversion system. This method can receive different types on elments. + + The types allowed for the argument poly are: + - Elements in `self.base()` + - Elements in `self.base().fraction_field()` + - List, Sets or Tuples + - Matrices with polynomials recognized + - Vectors with polynomials recognized + + Returns an element in `self.poly_ring()` or `self.poly_field()`. + + If the type is not valid, a TypeError exception will be risen. + If it is not possible to cast the element, a ValueError exception will be risen. + ''' + if(element in self.base()): + return self._to_poly_element(element); + elif(element in self.base().fraction_field()): + n = self.to_poly(element.numerator()); + d = self.to_poly(element.denominator()); + return self.poly_field()(n/d); + elif(isinstance(element, sage.matrix.matrix.Matrix)): + R = self.poly_ring(); + if(element.parent().base().is_field()): + R = self.poly_field(); + return Matrix(R, [self.to_poly(row) for row in element]); + elif(isinstance(element, sage.modules.free_module_element.FreeModuleElement)): + R = self.poly_ring(); + if(element.parent().base().is_field()): + R = self.poly_field(); + return vector(R, [self.to_poly(el) for el in element]); + elif(isinstance(element, list)): + return [self.to_poly(el) for el in element]; + elif(isinstance(element, set)): + return set([self.to_poly(el) for el in element]); + elif(isinstance(relation, tuple)): + return tuple([self.to_poly(el) for el in element]); + else: + raise TypeError("Wrong type: Impossible to get polynomial value of element (%s)" %(element)); + + def to_real(self, poly): + ''' + This method cast a polynomial recognized in the conversion system to a real element in `self.base()`. This method can receive different types on elments. + + The types allowed for the argument poly are: + - Elements in `self.poly_ring()` + - Elements in `self.poly_field()` + - List, Sets or Tuples + - Matrices with polynomials recognized + - Vectors with polynomials recognized + + Returns an element in `self.base()` or `self.base().fraction_field()`. + + If the type is not valid, a TypeError exception will be risen. + ''' + if(poly in self.poly_ring()): + if(not self.is_polynomial()): + return self.base()(poly); + return self._to_real_element(self.poly_ring()(poly)); + elif(poly in self.poly_field()): + n = self.to_real(poly.numerator()); + d = self.to_real(poly.denominator()); + return n/d; + elif(isinstance(poly, sage.matrix.matrix.Matrix)): + R = self.base(); + if(poly.parent().base().is_field()): + R = R.fraction_field(); + return Matrix(R, [self.to_real(row) for row in poly]); + elif(isinstance(poly, sage.modules.free_module_element.FreeModuleElement)): + R = self.base(); + if(poly.parent().base().is_field()): + R = R.fraction_field(); + return vector(R, [self.to_real(el) for el in poly]); + elif(isinstance(poly, list)): + return [self.to_real(el) for el in poly]; + elif(isinstance(poly, set)): + return set([self.to_real(el) for el in poly]); + elif(isinstance(poly, tuple)): + return tuple([self.to_real(el) for el in poly]); + else: + raise TypeError("Wrong type: Impossible to get real value of element (%s)" %(poly)); + + def simplify(self, element): + ''' + Simplify the element receive using the relations known for the Conversion System. + + Several types of input are allowed: + - An element of `self.poly_ring()` or `self.poly_field()`. + - A List, a Set or a Tuple + - Matrices or vectors with polynomials rocignized by the Conversion system + - Elements in `self.base()` + ''' + if(element in self.poly_ring()): + try: + return self.poly_ring()(element).reduce(self._relations()); + except AttributeError: + return self.poly_ring()(element); + elif(element in self.poly_field()): + element = self.poly_field()(element); + n = self.simplify(element.numerator()); + d = self.simplify(element.denominator()); + return n/d; + elif(isinstance(element, list)): + return [self.simplify(el) for el in element]; + elif(isinstance(element, set)): + return set([self.simplify(el) for el in element]); + elif(isinstance(element, tuple)): + return tuple([self.simplify(el) for el in element]); + elif(isinstance(element, sage.matrix.matrix.Matrix)): + R = self.poly_ring(); + if(element.parent().base().is_field()): + R = self.poly_field(); + return Matrix(R, [[self.simplify(el) for el in row] for row in element]); + elif(isinstance(element, sage.modules.free_module_element.FreeModuleElement)): + R = self.poly_ring(); + if(element.parent().base().is_field()): + R = self.poly_field(); + return vector(R, [self.simplify(el) for el in element]); + elif(element in self.base()): + return self.to_real(self.simplify(self.to_poly(element))); + else: + return element; + + def mix_conversion(self, conversion): + ''' + Method that mixes two conversion system of the same class. It is also allowed to mix `self` with an element of `self.base().fraction_field()` + ''' + if(isinstance(conversion, self.__class__) or (conversion in self.base().fraction_field())): + return self._mix_conversion(conversion); + elif(isinstance(conversion, ConversionSystem) and isinstance(self, conversion.__class__)): + return conversion._mix_conversion(self); + + ## Protected methods + def _change_poly_ring(self, new_ring): + if(not (self.poly_ring() is new_ring)): + if(not(self.__relations is None)): + self.__relations = [new_ring(el) for el in self.__relations]; + self.__rel_ideal = ideal(self.poly_ring(), []); + + def _relations(self): + ''' + Returns a Groebner Basis of the relations ideals known in this conversion system. + ''' + if(self.__relations is None): + self.__relations = []; + self.__rel_ideal = ideal(self.poly_ring(), []); + + return self.__relations; + + def _rel_ideal(self): + ''' + Returns the ideal object of relations known in this conversion system. + ''' + return self.__rel_ideal; + + def _to_poly_element(self, element): + ''' + Method that cast an element in `self.base()` to a polynomial in the conversion system. + + This method must be implemented in each specific type of conversion system. + + Raise a ValueError if the element can not be casted. + ''' + raise NotImplementedError("Abstract method not implemented '_to_poly_element(element)'"); + + def _to_real_element(self, polynomial): + ''' + Method that receives a polynomial in the variables on the conversion system and plug in the real values of those variables. + + It returns an element in self.base(). + + This method can be overwritten if needed. + ''' + variables = polynomial.parent().gens(); + multi = (len(variables) > _sage_const_1 ); + res = self.base().zero(); + for (k,v) in polynomial.dict().items(): + term = self.base().one(); + ## We distinguish between several variables and just one + ## because the return of poly.dict() is different + if(multi): + for i in range(len(variables)): + term *= (self.map_of_vars()[str(variables[i])]**k[i]); + else: + term *= self.map_of_vars()[str(variables[_sage_const_0 ])]**k; + + res += term*self.base()(v); + + return res; + + def _mix_conversion(self, conversion): + ''' + Method that, assuming `conversion` is a compatible Conversion System, mix `self` with `conversion` to a new Conversion System of the same type of `self` that can represent objects in any conversion system. + + This method must be implemented in each particular class extending ConversionSystem. + ''' + raise NotImplementedError("Abstract method not implemented '_mix_conversion(conversion)'"); + + ## Private methods + def __add_relation(self, relation): + ''' + General method for adding relations that accepts any kind of argument posible. + + Allowed input types: + - List, Set or Tuple + - Polynomials. + - Fractions of polynomials. + - Any element that can be converted using `self`. + ''' + if(isinstance(relation, list) or isinstance(relation, set) or isinstance(relation, tuple)): + for el in relation: + self.__add_relation(el); + elif(relation in self.poly_ring()): + reduced = self.simplify(relation); + if(reduced != _sage_const_0 ): + self.__relations += [reduced]; + elif(relation in self.poly_field()): + reduced = self.simplify(relation.numerator()); + if(reduced != _sage_const_0 ): + self.__relations += [reduced]; + else: + try: + self.__add_relation(self.to_poly(relation)); + except Exception: + raise TypeError("Impossible to add a non-polynomical relation"); + + ## Magic Python methods + def __repr__(self): + if(self.is_polynomial()): + return "Conversion system with %d variables" %self.poly_ring().ngens(); + else: + return "(Empty) Conversion system"; + + def __str__(self): + out = ""; + out += repr(self) + "\n"; + if(self.is_polynomial()): + out += "\tFrom: %s\n" %self.base(); + out += "\tTo : %s\n" %self.poly_ring(); + out += "Map of the variables:\n"; + for gen in self.poly_ring().gens(): + out += "%s\t->\t%s\n" %(gen, repr(self.map_of_vars()[str(gen)])); + return out; + + diff --git a/ajpastor/lazy/lazyFracField.py b/ajpastor/lazy/lazyFracField.py new file mode 100644 index 0000000..4a156f2 --- /dev/null +++ b/ajpastor/lazy/lazyFracField.py @@ -0,0 +1,324 @@ + +# This file was *autogenerated* from the file ./lazyFracField.sage +from sage.all_cmdline import * # import sage library + +_sage_const_2 = Integer(2); _sage_const_1 = Integer(1); _sage_const_0 = Integer(0); _sage_const_25 = Integer(25) + +# Some needed imports +from sage.structure.element import FieldElement; +from sage.rings.ring import Field; +from sage.structure.unique_representation import UniqueRepresentation; +from sage.categories.quotient_fields import QuotientFields; +from sage.categories.pushout import ConstructionFunctor; + +# risc.ajpastor imports +from .lazyIDElements import *; + +##################################################### +### Class for Lazy Fraction Field Elements +##################################################### +class LazyFFElement(FieldElement): + def __init__(self, parent,n,d=None): + if(not isinstance(parent, LazyFracField)): + raise TypeError("The parent of a fraction must be a Fraction Field"); + + B = parent.base(); # B is the lazy domain + ### Checking/Adapting numerator + if(not(n in B)): + raise TypeError("Element for numerator must be in %s" %(repr(B))); + elif(not (isinstance(n, LazyIDElement))): + n = B(n); + + ### Checking/Adapting denominator + if(d is None): + d = B.one(); + elif(not (d in B)): + raise TypeError("Element for denominator must be in %s" %(repr(B))); + elif(d == B.zero()): + raise ZeroDivisionError("The denominator can not be zero"); + elif(not (isinstance(d, LazyIDElement))): + d = B(d); + elif(isinstance(d, SumLIDElement)): + #raise TypeError("No additions are allowed as denominators in a Lazy Fraction Field"); + #d = B(d.raw()); + pass; + + self.__n = n; + self.__d = d; + FieldElement.__init__(self,parent); + + def numerator(self): + return self.__n; + + def denominator(self): + return self.__d; + + def _repr_(self): + return "(%s):(%s)"%(repr(self.numerator()),repr(self.denominator())); + + def __str__(self): + return "(%s):(%s)"%(str(self.numerator()),str(self.denominator())); + + def _add_(self, other): + if(not(other in self.parent())): + return NotImplemented; + if(not isinstance(other, LazyFFElement)): + other = self.parent()(other); + + N = self.numerator()*other.denominator()+self.denominator()*other.numerator(); + D = self.denominator()*other.denominator(); + return LazyFFElement(self.parent(), N, D).simplify(); + + def _sub_(self, other): + if(not(other in self.parent())): + return NotImplemented; + if(not isinstance(other, LazyFFElement)): + other = self.parent()(other); + + N = self.numerator()*other.denominator()-self.denominator()*other.numerator(); + D = self.denominator()*other.denominator(); + return LazyFFElement(self.parent(), N, D).simplify(); + + def _neg_(self): + return LazyFFElement(self.parent(), -self.numerator(), self.denominator()); + + def _mul_(self, other): + if(not(other in self.parent())): + return NotImplemented; + if(not isinstance(other, LazyFFElement)): + other = self.parent()(other); + + N = self.numerator()*other.numerator(); + D = self.denominator()*other.denominator(); + return LazyFFElement(self.parent(), N, D).simplify(); + + def _div_(self, other): + if(not(other in self.parent())): + return NotImplemented; + if(not isinstance(other, LazyFFElement)): + other = self.parent()(other); + + N = self.numerator()*other.denominator(); + D = self.denominator()*other.numerator(); + return LazyFFElement(self.parent(), N, D).simplify(); + + ############################### + ### Equality methods + ############################### + def __eq__(self, other): + self.simplify(); + try: + if(isinstance(other, LazyFFElement)): + other.simplify(); + return (self.numerator() == other.numerator()) and (self.denominator() == other.denominator()); + elif(isinstance(other, LazyIDElement)): + other = other.simplify(); + + return (self.numerator() == other) and (self.denominator() == self.parent().base().one()); + except Exception as e: + return False; + + + ############################### + ### Other methods + ############################### + def raw(self): + self.simplify(); + if(self.denominator().raw() == _sage_const_1 ): + return self.numerator().raw(); + raise ValueError("Impossible to compute an element within the domain: non 1 denominator"); + + def simplify(self): + n = self.numerator().simplify(); + d = self.denominator().simplify(); + + ## Special case: numerator is zero --> Return the zero element + if(n == self.parent().base().zero()): + self.__n = self.parent().base().zero(); + self.__d = self.parent().base().one(); + return self; + + ## Special case: numerator and denominator are the same --> Return the one element + if(n == d): + self.__n = self.parent().base().one(); + self.__d = self.parent().base().one(); + return self; + + ## Generic algorithm: compute gcd of numerator and denominator and divide by it + num_divisor = n.max_divisor(); + den_divisor = d.max_divisor(); + common = num_divisor.gcd(den_divisor); + + n = n.divide(common); + d = d.divide(common); + + ## Special case for sign: check if the denominator has all negative. In that case + ## change the sign of the numerator. + if(isinstance(d, SumLIDElement)): + toChange = True; + for key in d.__struct__(): + toChange = (d.__struct__()[key] < _sage_const_0 ); + if(not toChange): + break; + + if(toChange): + n = -n; + d = -d; + + self.__n = n; + self.__d = d; + return self; + + def reduce(self): + return self.simplify(); + + def derivative(self, *input): + try: + N = self.numerator().derivative(*input)*self.denominator()-self.denominator().derivative(*input)*self.numerator(); + D = self.denominator()**_sage_const_2 ; + + return LazyFFElement(self.parent(), N, D).simplify(); + except AttributeError: + raise AttributeError("Impossible derivate elements of %s" %(self.parent().base())); + + def get_basic_elements(self): + return self.numerator().get_basic_elements().union(self.denominator().get_basic_elements()); + +##################################################### +### Class for Lazy Fraction Fields +##################################################### +class LazyFracField(UniqueRepresentation, Field): + Element = LazyFFElement; + + def __init__(self, base): + if base not in IntegralDomains(): + raise ValueError("%s is no integral domain" % base); + if (not isinstance(base, LazyIntegralDomain)): + base = GetLazyDomain(base); + Field.__init__(self, base, category=QuotientFields()); + + self.base().register_conversion(LFFSimpleMorphism(self, self.base())); + self.base().base().register_conversion(LFFSimpleMorphism(self, self.base().base())); + try: + self.base().base().fraction_field().register_conversion(LFFSimpleMorphism(self, self.base().base().fraction_field())); + except Exception: + pass; + + ### Coercion methods + def _coerce_map_from_(self, S): + coer = None; + if(isinstance(S, LazyFracField)): + coer = self.base()._coerce_map_from_(S.base()); + elif(S in QuotientFields()): + coer = self.base()._coerce_map_from_(S.base()); + elif(S == self.base()): + coer = True; + else: + coer = self.base()._coerce_map_from_(S); + + if(not(coer is False) and not(coer is None)): + return True; + return None; + + def _has_coerce_map_from(self, S): + coer = self._coerce_map_from_(S); + return (not(coer is False) and not(coer is None)); + + def _element_constructor_(self, *args, **kwds): + el_class = self.element_class; + if(len(args) < _sage_const_1 ): + raise ValueError("Impossible to build a lazy element without arguments"); + + i = _sage_const_0 ; + if(args[_sage_const_0 ] is self): + i = _sage_const_1 ; + + X = args[i]; + Y = None; + + if(len(args) > i+_sage_const_1 ): + Y = args[i+_sage_const_1 ]; + + try: + if(isinstance(X, el_class)): + if((not(Y is None)) and (Y in self)): + if(isinstance(Y, el_class)): + return X/Y; + else: + return X/(el_class(self,_sage_const_1 ,Y)); + else: + try: + if(X.parent() in QuotientFields()): + return self._element_constructor_(X.numerator(), X.denominator()); + except AttributeError: + pass; + return el_class(self, X,Y); + except TypeError as e: + raise TypeError("Can not cast %s to a Lazy Fraction of %s because:\n\t- %s" %(repr(X), repr(self.base()), e)); + + def construction(self): + return (LazyFracFieldFunctor(), self.base()); + + def __contains__(self, X): + try: + return (X.parent() is self) or (self._has_coerce_map_from(X.parent())); + except AttributeError: + try: + self(X) + return True; + except Exception: + return False; + + def _repr_(self): + return "LazyFractionField for(%s)"%repr(self.base().base()); + + def base_ring(self): + return self.base().base_ring(); + + def characteristic(self): + return self.base().characteristic(); + + def _an_element_(self): + return self.one(); + + def element(self, n,d = None): + return LazyFFElement(self, n,d); + +## Changes in LazyIntegralDomain + +LazyIntegralDomain.Fraction_Field = LazyFracField; + +##################################################### +### Construction Functor for LID +##################################################### +class LazyFracFieldFunctor (ConstructionFunctor): + def __init__(self): + self.rank = _sage_const_25 ; + super(LazyFracFieldFunctor, self).__init__(IntegralDomains(),QuotientFields()); + + ### Methods to implement + def _coerce_into_domain(self, x): + if(x not in self.domain()): + raise TypeError("The object [%s] is not an element of [%s]" %(x, self.domain())); + if(isinstance(x, LazyFracField)): + return x.base(); + return x; + + def _apply_functor(self, x): + return LazyIntegralDomain(x); + +##################################################### +### General Morphism for return to basic rings +##################################################### +class LFFSimpleMorphism (sage.categories.map.Map): + def __init__(self, domain, codomain): + super(LFFSimpleMorphism, self).__init__(domain, codomain); + + def _call_(self, p): + if(self.codomain() in QuotientFields()): + return self.codomain()(p.numerator().raw(), p.denominator().raw()); + elif(self.codomain() in IntegralDomains()): + return self.codomain()((p.numerator().divide(p.denominator())).raw()); + + raise TypeError("Impossible the conversion of %s to %s" (p, self.codomain())); + diff --git a/ajpastor/lazy/lazyIDElements.py b/ajpastor/lazy/lazyIDElements.py new file mode 100644 index 0000000..790ec61 --- /dev/null +++ b/ajpastor/lazy/lazyIDElements.py @@ -0,0 +1,1082 @@ + +# This file was *autogenerated* from the file ./lazyIDElements.sage +from sage.all_cmdline import * # import sage library + +_sage_const_2 = Integer(2); _sage_const_1 = Integer(1); _sage_const_10 = Integer(10); _sage_const_0 = Integer(0); _sage_const_20 = Integer(20) + +# Some needed imports +from sage.rings.ring import IntegralDomain; +from sage.structure.element import IntegralDomainElement; +from sage.categories.integral_domains import IntegralDomains; +from sage.categories.pushout import ConstructionFunctor; + +_MAX_INTEGER = Integer(_sage_const_2 **_sage_const_10 -_sage_const_1 ); + +##################################################### +### Class for Lazy Integral Domain Elements +##################################################### +class LazyIDElement(IntegralDomainElement): + def __init__(self, parent): + if(not (isinstance(parent, LazyIntegralDomain))): + parent = GetLazyDomain(parent); + + self.__raw = None; + self.__max_divisor = None; + + IntegralDomainElement.__init__(self, parent); + + ############################### + ### Public methods + ############################### + def base(self): + return self.parent().base(); + + def raw(self): + ''' + Method that computes (if needed) and returns an element of `self.base()` that is equal to `self`. + ''' + if(self.__raw is None): + self.__raw = self.base()(self.__compute_raw_element__()); + + return self.__raw; + + def max_divisor(self): + ''' + Method that computes (if needed) and returns a SumLIDElement which we know we can divide `self`. + ''' + if(self.__max_divisor is None): + self.__max_divisor = self.__compute_max_divisor__(); + + return self.__max_divisor; + + def gcd(self,*input): + ''' + Method that computes a SumLIDElement which we know we can divide `self` and every element on `input`. + ''' + return self.max_divisor().gcd(*input); + + def lcm(self, *input): + ''' + Method that computes a lazyElement such every element of input and self are divisors + ''' + ## Checking the arguments + if(len(input) == _sage_const_1 and type(input[_sage_const_0 ]) == list): + input = input[_sage_const_0 ]; + + if(len(input) == _sage_const_0 ): + return self; + + smallLcm = (self*input[_sage_const_0 ]).divide(self.gcd(input[_sage_const_0 ])); + if(len(input) == _sage_const_1 ): + return smallLcm; + + return smallLcm.lcm(input[_sage_const_1 :]); + + def is_multiple_of(self,element): + ''' + Method that returns whether `self` can be divided by `element`. + ''' + if(self == element or element == self.base().one()): + return True; + + return self.__inner_is_multiple__(element); + + def divide(self,element): + ''' + Methdo that performs (if possible) the division of `self` by `element`. + ''' + if((not element in self.base()) and (not isinstance(element, LazyIDElement))): + raise TypeError("Impossible divide by a %s" %(type(element))); + if(not self.is_multiple_of(element)): + raise ValueError("Impossible divide [%s] by [%s] within the domain" %(self, element)); + + element = self.parent()(element); + + if(self == self.parent().zero()): + return self; + elif(self == element): + return self.parent().one(); + elif(element == self.base().one()): + return self; + + return self.__compute_division__(element); + + def simplify(self): + ''' + Method that simplifies (if possible) the current element. It is a change inside the structure and the return is the result of the changes + ''' + return self; + + def derivative(self, *input): + ''' + Method that computes the lazy derivative of an element. It assumes that the parent ring has a derivation and the arguments for such derivation are provided in 'input'. + ''' + raise AttributeError("Method not implemented"); + + def is_zero(self): + if(self.__raw is None): + return self.__inner_is_zero__(); + return self.raw() == self.base().zero(); + + def is_one(self): + if(self.__raw is None): + return self.__inner_is_one__(); + return self.raw() == self.base().one(); + + ############################### + ### Arithmetic methods + ############################### + def _add_(self, other): + if(isinstance(other, LazyIDElement)): + if(other.is_zero()): + return self; + try: + return self.__inner_add__(other); + except NotImplementedError as e: + pass; + + return NotImplemented; + + def _sub_(self,other): + return self.__add__(-other); + + def _neg_(self): + try: + return self.__inner_neg__(); + except NotImplementedError: + pass; + + return NotImplemented; + + def _mul_(self,other): + if(isinstance(other, LazyIDElement)): + if(other.is_zero()): + return self.parent().zero(); + if(other.is_one()): + return self; + try: + return self.__inner_mul__(other); + except NotImplementedError: + pass; + + return NotImplemented; + + def _pow_(self,i): + try: + return self.__inner_pow__(i); + except NotImplementedError: + return NotImplemented; + + ############################### + ### Representation methods + ############################### + def _repr_(self): + return self.__inner_printing__(repr); + + def __str__(self): + return self.__inner_printing__(str); + + def __inner_printing__(self, method): + return "LazyElement(%s)" %(method(self.raw())); + + ############################### + ### Abstract methods definition + ############################### + def get_basic_elements(self): + raise NotImplementedError("This method has not been implemented for this type of LazyElement"); + + def __compute_raw_element__(self): + raise NotImplementedError("This method has not been implemented for this type of LazyElement"); + + def __change_domain__(self, newParent): + raise NotImplementedError("This method has not been implemented for this type of LazyElement"); + + def __compute_max_divisor__(self): + raise NotImplementedError("This method has not been implemented for this type of LazyElement"); + + def __struct__(self): + raise NotImplementedError("This method has not been implemented for this type of LazyElement"); + + def __inner_is_multiple__(self, element): + raise NotImplementedError("This method has not been implemented for this type of LazyElement"); + + def __compute_division__(self, element): + raise NotImplementedError("This method has not been implemented for this type of LazyElement"); + + def __inner_is_zero__(self): + raise NotImplementedError("This method has not been implemented for this type of LazyElement"); + + def __inner_is_one__(self): + raise NotImplementedError("This method has not been implemented for this type of LazyElement"); + + def __inner_add__(self, other): + raise NotImplementedError("This method has not been implemented for this type of LazyElement"); + + def __inner_neg__(self): + raise NotImplementedError("This method has not been implemented for this type of LazyElement"); + + def __inner_mul__(self, other): + raise NotImplementedError("This method has not been implemented for this type of LazyElement"); + + def __inner_pow__(self, other): + raise NotImplementedError("This method has not been implemented for this type of LazyElement"); + + ############################### + ### Private methods definition + ############################### + def _is_pure_in_base(self,element): + return ((not isinstance(element, LazyIDElement)) and (element in self.base())); + +##################################################### +### Class for Simple LazyIDElements +##################################################### +class SimpleLIDElement(LazyIDElement): + def __init__(self, parent, element): + super(SimpleLIDElement, self).__init__(parent); + + if(isinstance(element, LazyIDElement)): + element = element.raw(); + + self.__element = element; + self.raw(); + + ############################### + ### Overriden methods + ############################### + def derivative(self, *input): + return SimpleLIDElement(self.parent(), self.raw().derivative(*input)); + + ############################### + ### Abstract methods implementation + ############################### + def get_basic_elements(self): + try: + self.parent().base_ring()(self.raw()); + except Exception: + return {self}; + return set(); + + def __compute_raw_element__(self): + return self.__element; + + def __change_domain__(self, newParent): + if(newParent == self.parent()): + return self; + return SimpleLIDElement(newParent, self.raw()); + + def __compute_max_divisor__(self): + return SumLIDElement(self.parent(), ProductLIDElement(self.parent(),self)); + + def __struct__(self): + return self.raw(); + + def __inner_is_multiple__(self, element): + return False; + + def __compute_division__(self, element): + if(isinstance(element, SimpleLIDElement)): + inner = element; + number = _sage_const_1 ; + elif(isinstance(element, ProductLIDElement)): + inner = element; + number = _sage_const_1 ; + elif(isinstance(element, SumLIDElement)): + inner = element.__struct__().keys()[_sage_const_0 ]; + number = element.__struct__()[inner]; + + if(number == _sage_const_1 ): + if(inner == self.parent().one()): + return self; + else: + return self.parent().one(); + elif(number == -_sage_const_1 ): + if(inner == self.parent().one()): + return -self; + else: + return -self.parent().one(); + raise ValueError("Impossible Exception: can not perform division"); + + def __inner_add__(self, other): + if(isinstance(other, SumLIDElement)): + return SumLIDElement(self.parent(), self, other.__struct__()); + else: + return SumLIDElement(self.parent(), self, other); + + def __inner_neg__(self): + return SumLIDElement(self.parent(), {self:-_sage_const_1 }); + + def __inner_mul__(self, other): + if(isinstance(other, SumLIDElement)): + other_dic = other.__struct__(); + new_dic = {}; + for key in other_dic: + new_dic[key*self] = other_dic[key]; + + return SumLIDElement(self.parent(), new_dic); + elif(isinstance(other, ProductLIDElement)): + return ProductLIDElement(self.parent(), self, other.__struct__()); + else: + return ProductLIDElement(self.parent(), self, other); + + def __inner_pow__(self, other): + return ProductLIDElement(self.parent(), {self:other}); + + ############################### + ### Equality methods + ############################### + def __eq__(self, other): + if(self._is_pure_in_base(other)): + other = self.parent()(other); + if(isinstance(other, LazyIDElement)): + if(isinstance(other, SimpleLIDElement)): + return self.raw() == other.raw(); + else: + return other.__eq__(self); + + return False; + + def __hash__(self): + return hash(self.raw())%_MAX_INTEGER; + + ############################### + ### Representation methods + ############################### + def __inner_printing__(self, method): + return "Simple(%s)" %(method(self.raw())); + +##################################################### +### Class for Product of LazyIDElements +##################################################### +class ProductLIDElement(LazyIDElement): + def __init__(self, parent, *input): + super(ProductLIDElement, self).__init__(parent); + + self.__factors = {}; + + for el in input: + self.__add_element(el); + self.__simplified = False; + #self.simplify(); + + def __add_element(self, el, n=_sage_const_1 ): + ## Checking the n argument + if(n == _sage_const_0 ): + return; + elif(n < _sage_const_0 ): + raise ValueError("Imposible have a negative exponent"); + + ## If we are zero, we do nothing + zero = self.parent().zero(); + one = self.parent().one(); + if(zero in self.__struct__()): + return; + + ## Other structures checking + if(isinstance(el,list)): + for aux in el: + self.__add_element(aux,n); + elif(isinstance(el, dict)): + for aux in el: + self.__add_element(aux,el[aux]); + ## Checking if the element is zero 9simplify operations + elif(isinstance(el,LazyIDElement) and zero == el): + self.__factors = {zero : _sage_const_1 }; + return; + elif((not isinstance(el, LazyIDElement)) and zero.raw() == el): + self.__factors = {zero : _sage_const_1 }; + return; + ## We do nothing if we are adding +1 + elif(isinstance(el,LazyIDElement) and one == el): + return; + elif((not isinstance(el, LazyIDElement)) and one.raw() == el): + return; + ## Otherwise, we do as usual + elif(self._is_pure_in_base(el)): + self.__add_element(SimpleLIDElement(self.parent(),el),n); + elif(isinstance(el,SimpleLIDElement)): + self.__factors[el] = self.__factors.get(el,_sage_const_0 )+n; + else: + raise Exception("Impossible to put a %s in a Product Lazy Element" %type(el)); + + ############################### + ### Overriden methods + ############################### + def simplify(self): + if(not self.__simplified): + self.__simplified = True; + current_dic = self.__struct__(); + + new_dic = {}; + + for key in current_dic: + current = current_dic[key]; + s_key = key.simplify(); + if(s_key.is_zero()): + self.__factors = {self.parent().zero():_sage_const_1 }; + return self.parent().zero(); + if((not (s_key.is_one())) and (current > _sage_const_0 )): + new_dic[s_key] = new_dic.get(s_key, _sage_const_0 ) + current_dic[key]; + + mone = SimpleLIDElement(self.parent(),-self.base().one()); + if(new_dic.has_key(mone)): + new_dic[mone] = new_dic[mone]%_sage_const_2 ; + + self.__factors = new_dic; + + if(len(self.__factors) == _sage_const_0 ): + return self.parent().one(); + + return self; + + def derivative(self, *input): + self.simplify(); + + resList = []; + aux_dic = {}; + current_dic = self.__struct__(); + + #Preparing the aux_dic variable + for key in current_dic: + aux_dic[key] = current_dic[key]; + + # Iterating for each element + for key in current_dic: + # Creating the new element to the sum + der = key.derivative(*input); + aux_dic[key] = max(_sage_const_0 ,aux_dic[key]-_sage_const_1 ); + aux_dic[der] = aux_dic.get(der, _sage_const_0 )+_sage_const_1 ; + resList += [{ProductLIDElement(self.parent(), aux_dic) : current_dic[key]}]; + + #Restoring the aux_dic + aux_dic[key] = current_dic[key]; + if(current_dic.has_key(der)): + aux_dic[der] = aux_dic[der]-_sage_const_1 ; + else: + del aux_dic[der]; + + return SumLIDElement(self.parent(), resList); + + ############################### + ### Abstract methods implementation + ############################### + def get_basic_elements(self): + res = set(); + for key in self.__struct__().keys(): + res = res.union(key.get_basic_elements()); + return res; + + def __compute_raw_element__(self): + res = self.base().one(); + + current_dic = self.__struct__(); + for key in current_dic: + res *= (key.raw())**current_dic[key]; + + return res; + + def __change_domain__(self, newParent): + if(newParent == self.parent()): + return self; + + new_dic = {}; + current_dic = self.__struct__(); + + for key in current_dic: + new_dic[key.__change_domain(newParent)] = current_dic[key]; + + return ProductLIDElement(newParent, new_dic); + + def __compute_max_divisor__(self): + return SumLIDElement(self.parent(), self); + + def __struct__(self): + return self.__factors; + + def __inner_is_multiple__(self, element): + if(self._is_pure_in_base(element)): + element = self.parent()(element); + + if(isinstance(element, LazyIDElement)): + if(element.is_one()): + return True; + elif(isinstance(element, SimpleLIDElement)): + return not(self.__struct__().get(element) is None); + elif(isinstance(element, ProductLIDElement)): + return self.gcd(element) == element; + elif(isinstance(element, SumLIDElement)): + el_dic = element.__struct__(); + if(len(el_dic) == _sage_const_1 ): + key = el_dic.keys()[_sage_const_0 ]; + if(el_dic[key] == _sage_const_1 ): + return self.is_multiple_of(key); + + return False; + + def __compute_division__(self, element): + if(not isinstance(element, LazyIDElement)): + return self.__compute_division__(SimpleLIDElement(self.parent(),element)); + + if(isinstance(element, SimpleLIDElement)): + inner = element; + number = _sage_const_1 ; + elif(isinstance(element, ProductLIDElement)): + inner = element; + number = _sage_const_1 ; + elif(isinstance(element, SumLIDElement)): + inner = element.__struct__().keys()[_sage_const_0 ]; + number = element.__struct__()[inner]; + + if(number != _sage_const_1 and number != -_sage_const_1 ): + raise ValueError("Impossible Exception: can not perform division"); + + new_dic = {}; + current_dic = self.__struct__(); + if(isinstance(inner, SimpleLIDElement)): + for key in current_dic: + new_dic[key] = current_dic[key]; + new_dic[inner] = new_dic.get(inner,_sage_const_0 )-_sage_const_1 ; + if(new_dic[inner] == _sage_const_0 ): + del new_dic[inner]; + elif(isinstance(inner, ProductLIDElement)): + for key in current_dic: + value = current_dic[key]-inner.__struct__().get(key,_sage_const_0 ); + if(value != _sage_const_0 ): + new_dic[key] = value; + else: + raise TypeError("Error with types wile dividing in a Produc Lazy Element (%s)" %type(element)); + + if(number == _sage_const_1 ): + return ProductLIDElement(self.parent(), new_dic).simplify(); + else: ## number == -1 + return (-ProductLIDElement(self.parent(), new_dic)).simplify(); + + def __inner_is_zero__(self): + zero = self.parent().zero(); + return self.__struct__().has_key(zero); + + def __inner_is_one__(self): + self.simplify(); + return (len(self.__struct__()) == _sage_const_0 ); + + + def __inner_add__(self, other): + if(isinstance(other, SumLIDElement)): + return SumLIDElement(self.parent(), self, other.__struct__()); + else: + return SumLIDElement(self.parent(), self, other); + + def __inner_neg__(self): + return SumLIDElement(self.parent(), {self:-_sage_const_1 }); + #return self*(-self.parent().one()); + + def __inner_mul__(self, other): + if(isinstance(other, SumLIDElement)): + other_dic = other.__struct__(); + new_dic = {}; + for key in other_dic: + new_dic[key*self] = other_dic[key]; + + return SumLIDElement(self.parent(), new_dic); + elif(isinstance(other, ProductLIDElement)): + return ProductLIDElement(self.parent(), self.__struct__(), other.__struct__()); + else: + return ProductLIDElement(self.parent(), self.__struct__(), other); + + def __inner_pow__(self, other): + current_dic = self.__struct__(); + new_dic = {}; + + for key in current_dic: + new_dic[key] = current_dic[key]*other; + return ProductLIDElement(self.parent(), new_dic); + + ############################### + ### Equality methods + ############################### + def __eq__(self, other): + if(self._is_pure_in_base(other)): + other = self.parent()(other); + + current_dic = self.__struct__(); + if(len(current_dic) == _sage_const_0 ): + return other == _sage_const_1 ; + if(isinstance(other, LazyIDElement)): + if(isinstance(other, SimpleLIDElement)): + return (len(current_dic) == _sage_const_1 and current_dic.get(other,_sage_const_0 ) == _sage_const_1 ); + elif(isinstance(other, ProductLIDElement)): + other_dir = other.__struct__(); + if(len(other_dir) == len(current_dic)): + for key in current_dic: + if(not(current_dic[key] == other_dir.get(key,_sage_const_0 ))): + return False; + return True; + elif(isinstance(other, SumLIDElement)): + return other.__eq__(self); + + return False; + + def __hash__(self): + res = _sage_const_1 ; + current_dic = self.__struct__(); + + for key in current_dic: + res *= (hash(key)**current_dic[key] % _MAX_INTEGER); + return res; + + ############################### + ### Representation methods + ############################### + def __inner_printing__(self, method): + res = "ProductOf("; + current_dic = self.__struct__(); + if(len(current_dic) != _sage_const_0 ): + first = True; + for key in current_dic: + if(not first): + res += " * "; + else: + first = False; + res += "(%s)^%s" %(method(key),current_dic[key]); + res += ")"; + return res; + +##################################################### +### Class for Sum of LazyIDElements +##################################################### +class SumLIDElement(LazyIDElement): + def __init__(self, parent, *input): + super(SumLIDElement, self).__init__(parent); + + self.__summands = {}; + self.__simplified = False; + + for el in input: + self.__add_element(el); + #self.simplify(); + + def __add_element(self, el, n=_sage_const_1 ): + ## Checking the n argument + if(n == _sage_const_0 ): + return; + + zero = self.parent().zero(); + + ## Checking other structures + if(isinstance(el,list)): + for aux in el: + self.__add_element(aux,n); + elif(isinstance(el, dict)): + for aux in el: + self.__add_element(aux,el[aux]*n); + ## We do nothing if we want to add the zero element + elif(isinstance(el,LazyIDElement) and zero == el): + return; + elif((not isinstance(el, LazyIDElement)) and zero.raw() == el): + return; + ## Otherwise, we do as usual + elif(self._is_pure_in_base(el)): + self.__add_element(SimpleLIDElement(self.parent(),el),n); + elif(isinstance(el,SimpleLIDElement) or isinstance(el, ProductLIDElement)): + self.__summands[el] = self.__summands.get(el,_sage_const_0 )+n; + elif(isinstance(el, SumLIDElement)): + self.__add_element(el.__struct__(),n); + else: + raise Exception("Impossible to put a %s in a Sum Lazy Element" %type(el)); + + ############################### + ### Overriden methods + ############################### + def gcd(self,*input): + max_divisors = []; + for el in input: + if(self._is_pure_in_base(el)): + el = self.parent()(el); + + if(isinstance(el, LazyIDElement)): + max_divisors += [el.max_divisor()]; + else: + raise TypeError("Impossible compute gcd with %s" %(type(el))); + + myProd = self.__struct__().keys()[_sage_const_0 ]; + products = [el.__struct__().keys()[_sage_const_0 ] for el in max_divisors]; + vals = [self.__struct__()[myProd]] + [max_divisors[i].__struct__()[products[i]] for i in range(len(products))]; + val_gcd = gcd(vals); + + new_dic = {} + current_dic = myProd.__struct__(); + for key in current_dic: + minimum = current_dic[key]; + for el in products: + minimum = min(minimum, el.__struct__().get(key,_sage_const_0 )); + if(minimum == _sage_const_0 ): + break; + if(minimum > _sage_const_0 ): + new_dic[key] = minimum; + return SumLIDElement(self.parent(), {ProductLIDElement(self.parent(),new_dic) : val_gcd}); + + + def simplify(self): + if(not self.__simplified): + self.__simplified = True; + current_dic = self.__struct__(); + + new_dic = {}; + for key in current_dic: + current = current_dic[key]; + s_key = key.simplify(); + if((not (s_key.is_zero())) and (not(current == _sage_const_0 ))): + new_dic[s_key] = new_dic.get(s_key,_sage_const_0 ) + current; + + self.__summands = new_dic; + + if(len(self.__summands) == _sage_const_0 ): + return self.parent().zero(); + + return self; + + def derivative(self, *input): + current_dic = self.__struct__(); + new_dic = {}; + + for key in current_dic: + der = key.derivative(*input); + new_dic[der] = new_dic.get(der,_sage_const_0 )+current_dic[key]; + + return SumLIDElement(self.parent(), new_dic); + + ############################### + ### Abstract methods implementation + ############################### + def get_basic_elements(self): + res = set(); + for key in self.__struct__().keys(): + res = res.union(key.get_basic_elements()); + return res; + + def __compute_raw_element__(self): + res = self.base().zero(); + + current_dic = self.__struct__(); + for key in current_dic: + res += (key.raw())*current_dic[key]; + + return res; + + def __change_domain__(self, newParent): + if(newParent == self.parent()): + return self; + + new_dic = {}; + current_dic = self.__struct__(); + + for key in current_dic: + new_dic[key.__change_domain(newParent)] = current_dic[key]; + + return SumLIDElement(newParent, new_dic); + + def __compute_max_divisor__(self): + current_dic = self.__struct__(); + max_divisors = [SumLIDElement(self.parent(), {key.max_divisor() : current_dic[key]}) for key in current_dic]; + + if(len(max_divisors) > _sage_const_0 ): + return max_divisors[_sage_const_0 ].gcd(*max_divisors[_sage_const_1 :]); + + return SumLIDElement(self.parent(), ProductLIDElement(self.parent())); + + def __struct__(self): + return self.__summands; + + def __inner_is_multiple__(self, element): + max_divisor = self.max_divisor(); + inner = max_divisor.__struct__().keys()[_sage_const_0 ]; + if(isinstance(element, SimpleLIDElement)): + return inner.is_multiple_of(element); + elif(isinstance(element, ProductLIDElement)): + return inner.is_multiple_of(element); + elif(isinstance(element, SumLIDElement)): + if(len(element.__struct__()) == _sage_const_1 ): + number = max_divisor.__struct__()[inner]; + other_inner = element.__struct__().keys()[_sage_const_0 ]; + other_number = element.__struct__()[other_inner]; + + return (Integer(other_number).divides(number)) and (inner.is_multiple_of(other_inner)); + + return False; + + def __compute_division__(self, element): + if(isinstance(element, SimpleLIDElement)): + inner = element; + number = _sage_const_1 ; + elif(isinstance(element, ProductLIDElement)): + inner = element; + number = _sage_const_1 ; + elif(isinstance(element, SumLIDElement)): + inner = element.__struct__().keys()[_sage_const_0 ]; + number = element.__struct__()[inner]; + + new_dic = {}; + current_dic = self.__struct__(); + try: + for key in current_dic: + new_dic[key.__compute_division__(inner)] = Integer(current_dic[key]/number); + + return SumLIDElement(self.parent(), new_dic); + except TypeError: + raise ValueError("Impossible Exception: can not perform division because integer coefficients"); + + def __inner_is_zero__(self): + self.simplify(); + return (len(self.__struct__()) == _sage_const_0 ); + + def __inner_is_one__(self): + self.simplify(); + return self == self.parent().one(); + + def __inner_add__(self, other): + if(isinstance(other, SumLIDElement)): + return SumLIDElement(self.parent(), self.__struct__(), other.__struct__()); + else: + return SumLIDElement(self.parent(), self.__struct__(), other); + + def __inner_neg__(self): + new_dic = {}; + current_dic = self.__struct__(); + + for key in current_dic: + new_dic[key] = -current_dic[key]; + return SumLIDElement(self.parent(), new_dic); + + def __inner_mul__(self, other): + if(isinstance(other, SumLIDElement)): + current_dic = self.__struct__(); + other_dic = other.__struct__(); + new_dic = {}; + for key_current in current_dic: + for key_other in other_dic: + el = key_current*key_other; + new_dic[el] = new_dic.get(el,_sage_const_0 )+current_dic[key_current]*other_dic[key_other]; + + return SumLIDElement(self.parent(), new_dic); + else: + current_dic = self.__struct__(); + new_dic = {}; + for key in current_dic: + new_dic[key*other] = current_dic[key]; + + return SumLIDElement(self.parent(), new_dic); + + def __inner_pow__(self, other): + aux = self.parent().one(); + for i in range(other): + aux = aux*self; + return aux; + + ############################### + ### Equality methods + ############################### + def __eq__(self, other): + if(self._is_pure_in_base(other)): + other = self.parent()(other); + + current_dic = self.__struct__(); + if(len(current_dic) == _sage_const_0 ): + return other == _sage_const_0 ; + elif(len(current_dic) == _sage_const_1 and (not isinstance(other, SumLIDElement))): + key = current_dic.keys()[_sage_const_0 ]; + return (current_dic[key] == _sage_const_1 ) and (key == other); + elif(isinstance(other, SumLIDElement)): + other_dir = other.__struct__(); + if(len(other_dir) == len(current_dic)): + for key in current_dic: + if(not(current_dic[key] == other_dir.get(key,_sage_const_0 ))): + return False; + return True; + + return False; + + def __hash__(self): + res = _sage_const_0 ; + current_dic = self.__struct__(); + + for key in current_dic: + res += (hash(key)*current_dic[key] % _MAX_INTEGER); + return res; + + ############################### + ### Representation methods + ############################### + def __inner_printing__(self, method): + res = "SumOf("; + current_dic = self.__struct__(); + if(len(current_dic) != _sage_const_0 ): + first = True; + for key in current_dic: + if(not first): + res += " + "; + else: + first = False; + res += "%s*(%s)" %(current_dic[key],method(key)); + res += ")"; + return res; + +##################################################### +### Class for Lazy Integral Domain +##################################################### +class LazyIntegralDomain(UniqueRepresentation, IntegralDomain): + Element = SimpleLIDElement; + + Fraction_Field = None; + + def __init__(self, base): + if base not in IntegralDomains(): + raise ValueError("%s is no integral domain" % base); + IntegralDomain.__init__(self, base, category=IntegralDomains()); + + self._zero_element = SimpleLIDElement(self, base.zero()); + self._one_element = SimpleLIDElement(self, base.one()); + + self.base().register_conversion(LIDSimpleMorphism(self, self.base())); + + + def fraction_field(self): + if(not(LazyIntegralDomain.Fraction_Field is None)): + return LazyIntegralDomain.Fraction_Field(self); + return super(LazyIntegralDomain, self).fraction_field(); + + + ### Coercion methods + def _coerce_map_from_(self, S): + coer = None; + if(isinstance(S, LazyIntegralDomain)): + coer = self.base()._coerce_map_from_(S.base()); + elif(S == self.base()): + coer = True; + else: + coer = self.base()._coerce_map_from_(S); + + if(not(coer is False) and not(coer is None)): + return True; + return None; + + def _has_coerce_map_from(self, S): + coer = self._coerce_map_from_(S); + return (not(coer is False) and not(coer is None)); + + def _element_constructor_(self, *args, **kwds): + if(len(args) < _sage_const_1 ): + print args + raise ValueError("Impossible to build a lazy element without arguments"); + + i = _sage_const_0 ; + if(len(args) >= _sage_const_2 ): + if(not (args[_sage_const_0 ] is self)): + raise ValueError("RIOKO: What the .... are you sending to this method?"); + i = _sage_const_1 ; + X = args[i]; + + try: + if(not isinstance(X, LazyIDElement)): + X_is_int = (X in ZZ) or (type(X) == int); + if((X_is_int) and (not (self.base() is ZZ))): + if(X == _sage_const_0 ): + return self.zero(); + elif(X == _sage_const_1 ): + return self.one(); + else: + return SumLIDElement(self, {self.base().one() : ZZ(X)}); + + return SimpleLIDElement(self, self.base()(X)); + else: + if(X.parent() is self): + return X; + elif(self._has_coerce_map_from(X.parent().base())): + return X.__change_domain__(self); + else: + return self._element_constructor_(X.raw()); + except TypeError: + raise TypeError("This element can not be casted to %s" %repr(self)); + + def construction(self): + return (LazyIntegralDomainFunctor(), self.base()); + + def __contains__(self, X): + try: + return (X.parent() is self) or (self._has_coerce_map_from(X.parent())); + except AttributeError: + try: + self(X) + return True; + except Exception: + return False; + + # Other Integral Domain methods + def _repr_(self): + return "LazyDomain(%s)"%repr(self.base()); + + def base_ring(self): + return self.base().base_ring(); + + def characteristic(self): + return self.base().characteristic(); + + def _an_element_(self): + return self.one(); + + def element(self, X): + return self(X); + + # Ring is_* methods + def is_field(self): + return self.base().is_field(); + + def is_finite(self): + return self.base().is_finite(); + + def is_integrally_closed(self): + return self.base().is_integrally_closed(); + + def is_noetherian(self): + return self.base().is_noetherian(); + +##################################################### +### Construction Functor for LID +##################################################### +class LazyIntegralDomainFunctor (ConstructionFunctor): + def __init__(self): + ID = IntegralDomains(); + self.rank = _sage_const_20 ; + super(LazyIntegralDomainFunctor, self).__init__(ID,ID); + + ### Methods to implement + def _coerce_into_domain(self, x): + if(x not in self.domain()): + raise TypeError("The object [%s] is not an element of [%s]" %(x, self.domain())); + if(isinstance(x, LazyIntegralDomain)): + return x.base(); + return x; + + def _apply_functor(self, x): + return LazyIntegralDomain(x); + +##################################################### +### General Morphism for return to basic rings +##################################################### +class LIDSimpleMorphism (sage.categories.map.Map): + def __init__(self, domain, codomain): + super(LIDSimpleMorphism, self).__init__(domain, codomain); + + def _call_(self, p): + return self.codomain()(p.raw()); + +##################################################### +### Global and static elements +##################################################### +__MAP_TO_LAZY_DOMAINS = {}; +def GetLazyDomain(X): + global __MAP_TO_LAZY_DOMAINS; + if(not __MAP_TO_LAZY_DOMAINS.has_key(X)): + __MAP_TO_LAZY_DOMAINS[X] = LazyIntegralDomain(X); + + return __MAP_TO_LAZY_DOMAINS[X]; + + diff --git a/ajpastor/lazy/lazyRing.py b/ajpastor/lazy/lazyRing.py new file mode 100644 index 0000000..6b7b1e6 --- /dev/null +++ b/ajpastor/lazy/lazyRing.py @@ -0,0 +1,598 @@ + +# This file was *autogenerated* from the file ./lazyRing.sage +from sage.all_cmdline import * # import sage library + +_sage_const_2 = Integer(2); _sage_const_1 = Integer(1); _sage_const_0 = Integer(0); _sage_const_20 = Integer(20) +from sage.rings.ring import IntegralDomain; +from sage.structure.element import IntegralDomainElement; +from sage.categories.integral_domains import IntegralDomains; +from sage.categories.fields import Fields; +from sage.categories.pushout import ConstructionFunctor; + +from sage.rings.polynomial.polynomial_ring import is_PolynomialRing as isUniPolynomial; +from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing as isMPolynomial; + +from ajpastor.lazy.conversion import ConversionSystem; +from ajpastor.misc.ring_w_sequence import Ring_w_Sequence; + +#################################################################################################### +#################################################################################################### +### ELEMENT CLASS +#################################################################################################### +#################################################################################################### +class _LazyElement(IntegralDomainElement): + def __init__(self, parent, el): + if(not (isinstance(parent, LazyRing))): + parent = GetLazyDomain(parent); + + self.__raw = None; + self.__poly = None; + + IntegralDomainElement.__init__(self, parent); + + try: + #if(el in parent.poly_ring()): + self.__poly = parent.poly_ring()(str(el)); + except: + #elif(el in parent.poly_field()): + try: + self.__poly = parent.poly_field()(str(el)); + except: + #else: + self.__raw = parent.base()(el); + self.__poly = self.parent().to_poly(self.__raw); + + self.simplify(); + + ################################################################################################ + ### Methods for a LazyElement + ################################################################################################ + def raw(self): + ''' + Method that computes (if needed) and returns an element of `self.base()` that is equal to `self`. + ''' + if(self.__raw is None): + self.__raw = self.parent().to_real(self.__poly); + + return self.__raw; + + def poly(self): + ''' + Method that computes (if needed) and returns an polynomial such that the conversion using + self.parent() returns self.raw(). + ''' + if(self.__poly is None): + self.__poly = self.parent().to_poly(self.__raw); + + return self.__poly; + + def simplify(self): + self.__poly = self.parent().simplify(self.poly()); + + def variables(self): + ''' + Method that returns a tuple with the variables that appear in self.poly(). + + If such polynomial representation is a quotient of polynomials, it take the union of the variables in the numerator and denominator. + If such polynomial representation has no variables, returns an empty tuple. + ''' + if(self.poly() not in QQ): + if(self.poly() in self.parent().poly_ring()): + return self.parent().poly_ring()(self.poly()).variables(); + else: + var_n = self.parent().poly_ring()(self.poly().numerator()).variables(); + var_d = self.parent().poly_ring()(self.poly().denominator()).variables(); + + return tuple(set(var_n+var_d)); + return tuple(); + + def derivative(self): + ''' + Method that computes the derivative of an element in the laziest way possible. + + This method compute the derivative of each of the variables in 'self.poly()' and then build + a new polynomial using this expressions. + + This method rely on the parent method 'get_derivative()' which saves the derivatives of the + variables as a dictionary + ''' + map_of_derivatives = {var : self.parent().get_derivative(var) for var in self.variables()}; + + return _LazyElement(self.parent(),sum(map_of_derivatives[key]*self.parent().poly_field()(self.poly()).derivative(key) for key in map_of_derivatives)); + + return self.parent().zero(); + + ################################################################################################ + ### Arithmetics methods + ################################################################################################ + def _add_(self, other): + if(isinstance(other, _LazyElement)): + try: + return _LazyElement(self.parent(), self.poly()+self.parent()(other).poly()); + except NotImplementedError as e: + pass; + + return NotImplemented; + + def _sub_(self,other): + return self.__add__(-other); + + def _neg_(self): + try: + return _LazyElement(self.parent(), -self.poly()); + except NotImplementedError: + pass; + + return NotImplemented; + + def _mul_(self,other): + if(isinstance(other, _LazyElement)): + try: + return _LazyElement(self.parent(), self.poly()*self.parent()(other).poly()); + except NotImplementedError as e: + pass; + + return NotImplemented; + + def _div_(self,other): + if(isinstance(other, _LazyElement)): + try: + return _LazyElement(self.parent(), self.parent().poly_field()(self.poly())/self.parent().poly_field()(self.parent()(other).poly())); + except NotImplementedError as e: + pass; + + return NotImplemented; + + def _pow_(self,i): + try: + return _LazyElement(self.parent(), self.poly()**i); + except NotImplementedError: + return NotImplemented; + + def __eq__(self, other): + return (self-other).is_zero(); + + def __call__(self, input): + if(self.__raw is None): + return self.poly()(**{str(var):self.parent().to_real(var)(input) for var in self.variables()}); + else: + return self.raw()(input); + + ################################################################################################ + ### Non-trivial arithmetics methods + ################################################################################################ + def gcd(self,*input): + ''' + Method that a common divisor of 'self' and the input + ''' + if(len(input) > _sage_const_1 ): + return self.gcd(input); + + return _LazyElement(self.parent(), gcd([self.poly()]+[self.parent()(el).poly() for el in input])); + + def lcm(self,*input): + ''' + Method that a common multiple of 'self' and the input + ''' + if(len(input) > _sage_const_1 ): + return self.gcd(input); + + return _LazyElement(self.parent(), lcm([self.poly()]+[self.parent()(el).poly() for el in input])); + + def divides(self, other): + ''' + Method that returns True if 'other = a*self'. + + REMARK: If this methods return False does not mean we can not divide other by self in the level of 'base'. + ''' + return self.poly().divides(self.parent()(other).poly()); + + #def is_zero_real(self): + # if(not self.is_zero()): + # result = False; + # if(not (self.__raw is None)): + # result = self.raw() == 0; + # else: + # pol = None; + # if(self.poly() in self.parent().poly_ring()): + # else: + # pol = self.parent().poly_ring()(self.poly().numerator()); + # + # for factor in pol.factor(): + # result = (self.parent().to_real(factor[0]) == 0); + # if(result): + # self.parent().add_relations(factor[0]); + # break; + # + # return result; + + def is_zero(self): + result = (self.poly() == _sage_const_0 ); + if(not result): + if(not (self.__raw is None)): + result = self.raw() == _sage_const_0 ; + else: + pol = None; + if(self.poly() in self.parent().poly_ring()): + pol = self.parent().poly_ring()(self.poly()); + else: + pol = self.parent().poly_ring()(self.poly().numerator()); + + factors = None; + try: + try: + factors = pol.factor(proof=True); + except NotImplementedError: + factors = pol.factor(proof=False); + except: + factors = [(pol,_sage_const_1 )]; + for factor in factors: + result = (self.parent().to_real(factor[_sage_const_0 ]) == _sage_const_0 ); + if(result): + self.parent().add_relations(factor[_sage_const_0 ]); + break; + + return result; + + def is_one(self): + try: + result = (self.poly() == _sage_const_1 ) or (self.raw() == self.parent().one()); + if(result): + self.parent().add_relations(self.poly()-_sage_const_1 ); + return result; + except TypeError: + return False; + + ################################################################################################ + ### Representation methods + ################################################################################################ + def __repr__(self): + return repr(self.poly()); + + def __str__(self): + if(self.__raw is None): + return "Lazy Element: %s" %(repr(self)); + else: + return "Lazy Element: %s\n%s" %(repr(self), str(self.raw())); + +#################################################################################################### +#################################################################################################### +### RING CLASS +#################################################################################################### +#################################################################################################### +class LazyRing (UniqueRepresentation, ConversionSystem, IntegralDomain): + + Element = _LazyElement; + + def __init__(self, base, constants=QQ, category=None): + ## Checking the arguments + if(not (constants in Fields())): + raise TypeError("The argument 'constants' must be a Field"); + if(not (isinstance(base, Ring_w_Sequence))): + raise TypeError("The argument 'base' must be a Ring with Sequence"); + + ## Initializing the parent structures + ConversionSystem.__init__(self, base); + IntegralDomain.__init__(self, base, category); + + ## Initializing the attributes + self.__constants = constants; + + self.__poly_ring = None; + self._change_poly_ring(constants); + + ## Initializing the map of variables (if there is enough) + self.__map_of_vars = {}; + + ## Initializing the map of derivatives + self.__map_of_derivatives = {}; + + ## Casting and Coercion system + self.base().register_conversion(LRSimpleMorphism(self, self.base())); + + ## Auxiliary data + self.__var_name = "x"; + self.__version = _sage_const_1 ; + + + ################################################################################################ + ### Implementing methods of ConversionSystem + ################################################################################################ + def poly_ring(self): + return self.__poly_ring; + + def poly_field(self): + return self.__poly_field; + + def map_of_vars(self): + return self.__map_of_vars; + + def variables(self): + return tuple(self.poly_ring()(key) for key in self.map_of_vars().keys()); + + def _change_poly_ring(self, new_ring): + super(LazyRing, self)._change_poly_ring(new_ring); + if(not (self.poly_ring() is new_ring)): + self.__poly_ring = new_ring; + self.__create_poly_field(); + + def _to_poly_element(self, element): + if(not (element in self.base())): + raise TypeError("Element is not in the base ring.\n\tExpected: %s\n\tGot: %s" %(element.parent(), self.base())); + + ## We try to cast to a polynomial. If we can do it, it means no further operations has to be done + try: + return self.poly_ring()(element); + except: + pass; + + ## Now we check if the element has a built method + try: + built = element.built(); + if(not (built is None)): + if(built[_sage_const_0 ] == "derivative"): + if(not(element in built[_sage_const_1 ])): + integral = self(built[_sage_const_1 ][_sage_const_0 ]); + if(not (integral.poly().is_monomial() and integral.poly().degree() == _sage_const_1 )): + return self(built[_sage_const_1 ][_sage_const_0 ]).derivative().poly(); + elif(built[_sage_const_0 ] == "polynomial"): + ## We check we do not have infinite recursion + if(not element in built[_sage_const_1 ][_sage_const_1 ].values()): + ## We have some building information + polys = {key : self.to_poly(built[_sage_const_1 ][_sage_const_1 ][key]) for key in built[_sage_const_1 ][_sage_const_1 ]}; + return built[_sage_const_1 ][_sage_const_0 ](**polys); + except AttributeError: + pass; + + ## Otherwise we look for a linear relation between the element and the variables + var_found = None; + rel = None; + for key in self.map_of_vars().keys(): + rel = self.__find_relation(element, self.map_of_vars()[key]); + if(not (rel is None)): + var_found = key; + break; + + ## If we find no relation, we add a new variable + if(rel is None): + new_var = None; + if(self.poly_ring() is self.__constants): + new_var = var("%s0" %self.__var_name); + self._change_poly_ring(PolynomialRing(self.__constants,new_var, order="lex")); + self.__map_of_vars[str(new_var)] = element; + else: + new_var = var("%s%d" %(self.__var_name,self.poly_ring().ngens())); + self._change_poly_ring(PolynomialRing(self.__constants, [new_var]+list(self.poly_ring().gens()), order="lex")); + self.__map_of_vars[str(new_var)] = element; + + return new_var; + + ## We try to keep the real representations small + try: + new_elem = (element-rel[_sage_const_1 ])/rel[_sage_const_0 ]; + if(new_elem.size() < self.map_of_vars()[var_found].size()): + self.__map_of_vars[var_found] = new_elem; + except: + pass; + + ## Otherwise, we return the polynomial computed + return self.poly_ring()(rel[_sage_const_0 ]*self.poly_ring()(var_found) + rel[_sage_const_1 ]); + + ################################################################################################ + ### Other Methods for LazyRing + ################################################################################################ + def sequence(self, el, n): + if(not isinstance(el, _LazyElement)): + return el.parent().sequence(el,n); + + return self.base().sequence(el.raw(),n); + + def clean_ring(self): + ## Clean the relations + self.clean_relations(); + + ## Return to the basic constant field + self._change_poly_ring(self.__constants); + + ## Deleting the variables created + self.__map_of_vars = {}; + + ## Deleting the map of derivatives + self.__map_of_derivatives = {}; + + self.__version += _sage_const_1 ; + + def change_variable_name(self, new_name): + self.__var_name = str(new_name); + + def version(self): + return self.__version; + + def get_derivative(self, el): + if(el in self.__map_of_derivatives): + return self.__map_of_derivatives[el]; + + if(el in self.__constants): + return _sage_const_0 ; + else: + try: + el = self.poly_ring()(el); + if(el in self.poly_ring().gens()): + new_poly = self.to_poly(self.map_of_vars()[str(el)].derivative()); + self.__map_of_derivatives[el] = new_poly; + return new_poly; + except: + pass; + raise ValueError("Method 'get_derivative' can only be summoned with variables.\nGot: %s -- %s" %(el, el.parent())); + + ################################################################################################ + ### Other Integral Domain methods + ################################################################################################ + def base_ring(self): + return self.base().base_ring(); + + def characteristic(self): + return self.base().characteristic(); + + def _an_element_(self): + return self.one(); + + def element(self, X): + return self(X); + + ################################################################################################ + ### Other Ring methods + ################################################################################################ + def is_field(self): + return self.base().is_field(); + + def is_finite(self): + return self.base().is_finite(); + + def is_integrally_closed(self): + return self.base().is_integrally_closed(); + + def is_noetherian(self): + return self.base().is_noetherian(); + + ################################################################################################ + ### Coercion methods + ################################################################################################ + def _coerce_map_from_(self, S): + coer = None; + if(isinstance(S, LazyRing)): + coer = self.base()._coerce_map_from_(S.base()); + elif(S == self.base()): + coer = True; + else: + coer = self.base()._coerce_map_from_(S); + + if(not(coer is False) and not(coer is None)): + return True; + return None; + + def _has_coerce_map_from(self, S): + coer = self._coerce_map_from_(S); + return (not(coer is False) and not(coer is None)); + + def _element_constructor_(self, *args, **kwds): + if(len(args) < _sage_const_1 ): + print args + raise ValueError("Impossible to build an element without arguments"); + + i = _sage_const_0 ; + if(len(args) >= _sage_const_2 ): + if(not (args[_sage_const_0 ] is self)): + raise ValueError("RIOKO: What the .... are you sending to this method?"); + i = _sage_const_1 ; + X = args[i]; + + try: + if(not isinstance(X, _LazyElement)): + ## If the element is not a LazyElement, then we try to create a new element with it + return _LazyElement(self, X); + elif (X.parent() is self): + return X; + else: + ## Otherwise, X.parent() may have different variables + other = X.parent(); + pol = X.poly(); + + ## For each variable in X.poly(), we get the new polynomial + translate = {} + for var in X.variables(): + translate[str(var)] = self.to_poly(other.map_of_vars()[str(var)]); + + ## We now plugin the expressions + return _LazyElement(self, pol(**translate)); + except TypeError: + raise TypeError("This element can not be casted to %s" %repr(self)); + + def construction(self): + return (LazyRingFunctor(), self.base()); + + def __contains__(self, X): + try: + return (X.parent() is self) or (self._has_coerce_map_from(X.parent())); + except AttributeError: + try: + self(X) + return True; + except Exception: + return False; + + ################################################################################################ + ### Representation methods + ################################################################################################ + def __repr__(self): + return "Lazy Ring over (%s)" %(repr(self.base())); + + def __str__(self): + final = "%s with %d variables\n{\n" %(self.__repr__(),len(self.__map_of_vars.keys())); + for k,v in self.__map_of_vars.items(): + final += "\t%s : %s,\n" %(k, repr(v)); + final += "}"; + return final + + ################################################################################################ + ### Private methods + ################################################################################################ + def __create_poly_field(self): + if(isUniPolynomial(self.__poly_ring) or (isMPolynomial(self.__poly_ring))): + self.__poly_field = self.__poly_ring.fraction_field(); + else: + self.__poly_field = self.__poly_ring; + + def __find_relation(self, a, b): + ## TODO We assume a and b are not constants + i = _sage_const_1 ; + while(self.sequence(a,i) == _sage_const_0 and self.sequence(b,i) == _sage_const_0 ): + i = i + _sage_const_1 ; + + ai = self.sequence(a,i); + bi = self.sequence(b,i); + if(bi == _sage_const_0 or ai == _sage_const_0 ): + return None; + + c = ai/bi; + d = self.sequence(a,_sage_const_0 )-c*self.sequence(b,_sage_const_0 ); + + if(a == c*b + d): + return (c,d); + else: + return None; + +#################################################################################################### +#################################################################################################### +### Construction Functor for LazyRing +#################################################################################################### +#################################################################################################### +class LazyRingFunctor (ConstructionFunctor): + def __init__(self): + ID = IntegralDomains(); + self.rank = _sage_const_20 ; + super(LazyRingFunctor, self).__init__(ID,ID); + + ### Methods to implement + def _coerce_into_domain(self, x): + if(x not in self.domain()): + raise TypeError("The object [%s] is not an element of [%s]" %(x, self.domain())); + if(isinstance(x, LazyRing)): + return x.base(); + return x; + + def _apply_functor(self, x): + return LazyRing(x); + +#################################################################################################### +#################################################################################################### +### General Morphism for return to basic rings +#################################################################################################### +#################################################################################################### +class LRSimpleMorphism (sage.categories.map.Map): + def __init__(self, domain, codomain): + super(LRSimpleMorphism, self).__init__(domain, codomain); + + def _call_(self, p): + return self.codomain()(p.raw()); + diff --git a/ajpastor/lazy/lazyToPoly.py b/ajpastor/lazy/lazyToPoly.py new file mode 100644 index 0000000..f737305 --- /dev/null +++ b/ajpastor/lazy/lazyToPoly.py @@ -0,0 +1,220 @@ + +# This file was *autogenerated* from the file ./lazyToPoly.sage +from sage.all_cmdline import * # import sage library + +_sage_const_1 = Integer(1); _sage_const_0 = Integer(0) + +from .lazyIDElements import *; +from .lazyFracField import *; +from .conversion import ConversionSystem; + +class LazyToPoly(ConversionSystem): + def __init__(self, base, *args): + + if(not isinstance(base, LazyIntegralDomain)): + base = LazyIntegralDomain(base); + + super(LazyToPoly,self).__init__(base); + + self.__basic = base.base(); + + self.__map_c_to_v = {}; + self.__map_v_to_c = {}; + + self.__inner_poly_ring = None; + for element in args: + self.__add_components(element); + + if(len(self.__map_c_to_v) > _sage_const_0 ): + self.__inner_poly_ring = PolynomialRing(self.base().base_ring(), self.__map_v_to_c.keys()); + self.__is_polynomial = True; + else: + self.__inner_poly_ring = base.base_ring(); + self.__inner_poly_field = self.poly_ring().fraction_field(); + + self.__map_v_to_c = {self.poly_ring()(key): self.__map_v_to_c[key] for key in self.__map_v_to_c}; + + ## Abstract methods of ConversionSystem + # Public + def poly_ring(self): + return self.__inner_poly_ring; + + def poly_field(self): + return self.__inner_poly_field; + + def map_of_vars(self): + return self.__map_v_to_c; + + # Protected + def _to_poly_element(self, element): + if(not element in self.base()): + raise TypeError("The element is not an element in the base ring"); + + if(element in self.__basic): + element = self.base()(element); + + if(isinstance(element, SimpleLIDElement)): + basic = element.get_basic_elements(); + if(len(basic) == _sage_const_0 ): + return self.poly_ring()(element.raw()); + else: + # Converting the simple element + # Trying to find a variable + el = self.__v(element); + if(el is None): + raise ValueError("Impossible to cast the element to a polynomial with this conversion system"); + return self.poly_ring()(el); + elif(isinstance(element, ProductLIDElement)): + res = self.poly_ring().one(); + current_dic = element.__struct__(); + for key in current_dic: + res *= self.to_poly(key)**current_dic[key]; + return res; + elif(isinstance(element, SumLIDElement)): + res = self.poly_ring().zero(); + current_dic = element.__struct__(); + for key in current_dic: + res += self.to_poly(key)*current_dic[key]; + return res; + else: + raise ValueError("Impossible to cast the element. Not recognized type"); + + ## Mixing conversions systems + def _mix_conversion(self, conversion): + if(isinstance(conversion, LazyToPoly)): + return LazyToPoly(self.base(), self.__map_c_to_v.keys(), conversion.__map_c_to_v.keys()); + if(conversion in self.base().fraction_field()): + if(conversion in self.base()): + return LazyToPoly(self.base(), self.__map_c_to_v.keys(), self.base()(conversion)); + return LazyToPoly(self.base(), self.__map_c_to_v.keys(), self.base()(conversion.numerator()), self.base()(conversion.denominator())); + + + ########################################## + ### PRIVATE METHODS + ########################################## + def __add_components(self, element): + ''' + Auxiliar method for adding new components to the variables (if needed). + + This method is private and should only be called during the initialization. + + This method allows to give as inputs: + - Elements in `self.base()` or `self.base().fraction_field()` + - Lists or Sets + - Matrices with elements in `self.base()` or `self.base().fraction_field()` + ''' + try: + if(isinstance(element, list) or isinstance(element, set)): + for el in element: + self.__add_components(el); + else: + try: + self.__add_components(element.numerator()); + self.__add_components(element.denominator()); + return; + except AttributeError: + pass; + try: + self.base().base_ring()(element); + except Exception: + if(element in self.base().base()): + self.__add_components(self.base()(element)); + elif(isinstance(element, SimpleLIDElement) and len(element.get_basic_elements()) > _sage_const_0 ): + if(not self.base()._coerce_map_from_(element.parent().base())): + raise TypeError("Incompatible lazy element to ring %s" %(self.base())); + if(self.__v(element, check=True) is None): + aux_var = var("x%d" %(len(self.__map_c_to_v))); + self.__map_c_to_v[element] = aux_var; + self.__map_v_to_c[aux_var] = element; + elif(isinstance(element, sage.matrix.matrix.Matrix)): + for row in element: + for el in row: + self.__add_components(el); + else: + self.__add_components(element.get_basic_elements()); + except AttributeError: + raise TypeError("Impossible to polynomize unlazy elements (%s)" %(element)); + + def __v(self, element, check=True): + ''' + Auxiliar method to see if a SimpleLIDElement can be converted into a variable. + + This method check if `element` satisfies a linear equation of the form + ``element == c*var + d`` + for some elements c,d in `self.base().base_ring()`. + + If some error occur during this checking (because the elements does not fit the interfaces usually uses here) we only will check the proper equality (``element == var``) + + RETURN: + - A polynomial in `self.poly_ring()` or `self.poly_field()` if we can cast the element or `None` otherwise. + ''' + try: + if(element == _sage_const_0 ): + return self.poly_ring().zero(); + + ## Getting the unlazy element an the ring self.__basic + R = self.__basic; + el = R(element.raw()); + init_value = lambda p,n : R.sequence(p,n)*factorial(n); + + ## For each lazy element in the conversion system + c = None; d = None; comp = None; k = None; ch_key = None; + for key in self.__map_c_to_v.keys(): + k = R(key.raw()); + ## We compute the ratio c + c = None; i = _sage_const_1 ; + while(c is None): + el_init = init_value(el, i); k_init = init_value(k,i); + if(el_init == _sage_const_0 and k_init == _sage_const_0 ): + i += _sage_const_1 ; + elif(el_init == _sage_const_0 or k_init == _sage_const_0 ): + break; + else: + c = el_init/k_init; + if(not (c is None)): + d = init_value(el, _sage_const_0 ) - c*init_value(k,_sage_const_0 ); + comp = (el-d)/c; + if(comp == k): + ch_key = key; + break; + + ## If we found a conversion (i.e. ch_key variable is set) + if(not (ch_key is None)): + ## If the size is smaller than before, we change the associated function + try: + var = self.__map_c_to_v[key]; + if(check and (self.__size(comp) < self.__size(k))): + #print "Changing representative of a variable:\n\t- Before: %s\n\t- Now: %s" %(repr(k),repr(comp)); + comp = self.base()(comp); + del self.__map_c_to_v[key]; + self.__map_c_to_v[comp] = var; + if(self.__inner_poly_ring is None): + self.__map_v_to_c[var] = comp; + else: + self.__map_v_to_c[self.poly_ring()(var)] = comp; + except Exception as e: + print "Exception caught"; + print e + pass; + ## We return the compute conversion + return c*var + d; + ## Otherwise, we return None + return None; + + #if(el == c*k+d): + # return c*self.__map_c_to_v[key] + d; + ## PREVIOUS VERSION: Just check the equality f == c*g + #el = element.raw().zero_extraction; + #for key in self.__map_c_to_v.keys(): + # k = key.raw().zero_extraction; + # if(el[0] == k[0]): + # l = el[1](0)/k[1](0); + # if(el[1] == l*k[1]): + # return l*self.__map_c_to_v[key]; + #return None; + except Exception: + return self.__map_c_to_v.get(element); + + def __size(self, element): + return element.size(); + diff --git a/ajpastor/misc/__init__.py b/ajpastor/misc/__init__.py new file mode 100644 index 0000000..13bfa9e --- /dev/null +++ b/ajpastor/misc/__init__.py @@ -0,0 +1,3 @@ +from pkgutil import extend_path; +__path__ = extend_path(__path__, __name__); + diff --git a/ajpastor/misc/bareiss.py b/ajpastor/misc/bareiss.py new file mode 100644 index 0000000..d6f521f --- /dev/null +++ b/ajpastor/misc/bareiss.py @@ -0,0 +1,308 @@ + +# This file was *autogenerated* from the file ./bareiss.sage +from sage.all_cmdline import * # import sage library + +_sage_const_2 = Integer(2); _sage_const_1 = Integer(1); _sage_const_0 = Integer(0) +from .matrix import swap_rows; +from .matrix import swap_cols; + +from .cached_property import derived_property; + +from .verbose import *; + +from sage.rings.polynomial.polynomial_ring import is_PolynomialRing as isUniPolynomial; +from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing as isMPolynomial; + +class BareissAlgorithm(object): + ### Initialization method + def __init__(self, parent, M, method=None, relations = []): + ''' + This class represent the application of the Bareiss Algorithm over a matrix with polynomial coefficients. + This class receives several parameters: + - `parent`: the polynomial ring where the operations will be done. A fraction field is allowed if the base domain is a polynomial ring. + - `M`: the matrix we want to treat with the Algorithm + - `method`: a Boolean function + + This algorithm also works in a quotient of the polynomial ring (K[X]/I) and the method provided must be a method to check if some polynomial is in the ideal I. + ''' + ## Checking the parent parameter + if(parent.is_field()): + parent = parent.base(); + if(not (isUniPolynomial(parent) or isMPolynomial(parent))): + raise TypeError("The parent for this algorithm must be a polynomial ring.\n\t Got: %s" %parent); + self.__parent = parent; + + ## Checking the matrix input + self.base_matrix = Matrix(self.parent(), M); + + self.change_of_columns = Permutation(range(_sage_const_1 ,self.base_matrix.ncols())); + + ## Storing the checking method + self.__in_ideal = method; + + ## Cached elements + self.__steps = None; + self.__actions = None; + self.__gb = ideal(self.parent(), relations).groebner_basis(); + self.__echelon = None; + + + ### Getter methods + def parent(self): + ''' + Method to have the same interface that the majority of objects of SAGE. Returns the polynomial ring where the matrix have its coefficients. + ''' + return self.__parent; + + ################################################# + ### Linear algebra methods + ################################################# + #@wverbose + def echelon_form(self): + if(self.__echelon is None): + self.__compute_echelon(); + return self.__echelon; + + @wverbose + def __compute_echelon(self): + sverbose("Computing the echelon form of a matrix of size %dx%d" %(self.base_matrix.nrows(), self.base_matrix.ncols())); + + self.__actions = [("base")]; + self.__steps = [self.base_matrix]; + + ## If we have initial relations, we perform the first reduction + if(self.__have_ideal(self.__gb)): + sverbose("Initial reduction of the matrix"); ## Verbose + self.__steps += [self.__simplify_matrix(self.__steps[-_sage_const_1 ])]; + self.__actions += [("f_reduce", self.__gb)]; + + tr = self.__steps[-_sage_const_1 ].nrows(); tc = self.__steps[-_sage_const_1 ].ncols(); + cr = _sage_const_0 ; cc = _sage_const_0 ; i = -_sage_const_1 ; + + sverbose("Starting the iterations"); + sverbose.start_iteration(min(tr,tc)+_sage_const_1 , True, False); + while(i < min(tr,tc)): + i = i + _sage_const_1 ; + + sverbose.increase_depth(); + + pivot = self.__choose_pivot(self.__steps[-_sage_const_1 ], i,i); + + cr,cc = pivot[_sage_const_0 ]; + new_rels = pivot[_sage_const_2 ]; + + ## If there are new relations, we simplify the matrix + if(len(new_rels) > _sage_const_0 ): + sverbose("New relations found looking for a pivot"); + sverbose("\t%s" %new_rels); + self.__gb = ideal(self.parent(), tuple(new_rels) + tuple(self.__gb)).groebner_basis(); + self.__steps += [self.__simplify_matrix(self.__steps[-_sage_const_1 ])]; + self.__actions += [("reduce", new_rels)]; + sverbose("New reductions applied"); + + ## If no pivot was found, we finish + if(cr == -_sage_const_1 or cc == -_sage_const_1 ): + sverbose("No pivot found."); + sverbose.decrease_depth(); + sverbose.next_iteration(); + break; + + ## If we have a new pivot, we moved everythin so it is in the proper position + swap_actions = pivot[_sage_const_1 ]; + for action in swap_actions: + if(action[_sage_const_0 ] == "sw_r"): + self.__steps += [swap_rows(self.__steps[-_sage_const_1 ], action[_sage_const_1 ],action[_sage_const_2 ])]; + elif(action[_sage_const_0 ] == "sw_c"): + self.__steps += [swap_cols(self.__steps[-_sage_const_1 ], action[_sage_const_1 ],action[_sage_const_2 ])]; + self.change_of_columns = Permutation((action[_sage_const_1 ]+_sage_const_1 ,action[_sage_const_2 ]+_sage_const_1 ))*self.change_of_columns; + + self.__actions += [action]; + + ## One the pivot is in position, we proceed with the Bareiss elimination + M = self.__steps[-_sage_const_1 ]; + + + ## We save the new matrix and go on + self.__steps += [self.__bareiss(self.__steps[-_sage_const_1 ], i)]; + self.__actions += [("bareiss", i)]; + + sverbose.decrease_depth(); + sverbose.next_iteration(); + + sverbose("Performing gcd simplification"); + gcds = [gcd(row) for row in self.__steps[-_sage_const_1 ]]; + for i in range(len(gcds)): + if(gcds[i] == _sage_const_0 ): + gcds[i] = _sage_const_1 ; + self.__steps += [Matrix(self.parent(), [[el/gcds[i] for el in self.__steps[-_sage_const_1 ][i]] for i in range(self.__steps[-_sage_const_1 ].nrows())])]; + self.__actions += [("gcd_simpl")]; + + self.__echelon = self.__steps[-_sage_const_1 ]; + sverbose("Finished computation of echelon form"); + + return; + + @derived_property + def rank(self): + for i in range(self.base_matrix.nrows()): + if(any((el != _sage_const_0 ) for el in self.echelon_form()[-(i+_sage_const_1 )])): + return self.base_matrix.nrows()-i; + + def relations(self): + self.echelon_form(); + return self.__gb; + + @cached_method + def right_kernel_matrix(self): + sol_dimension = self.base_matrix.ncols()-self.rank; + M = self.echelon_form(); + + if(sol_dimension > _sage_const_0 ): + ## Compute the product of the nice diagonal + A = self.__get_lcm([M[i][i] for i in range(self.rank)]); + to_mult = [-A/M[i][i] for i in range(self.rank)]; + + ker_basis = [vector(self.parent(), [to_mult[j]*M[j][i+self.rank] for j in range(self.rank)] + [_sage_const_0 for j in range(i)] + [A] + [_sage_const_0 for j in range(i+_sage_const_1 , sol_dimension)]) for i in range(sol_dimension)]; + + ch = self.change_of_columns; + ## If there were a change of columns (i.e. ch is not the trivial permutation) + ## we swap the result + if(Permutation(range(_sage_const_1 ,self.base_matrix.ncols())) != ch): + ch = ch.inverse(); + rows = [[_sage_const_0 for i in range(M.ncols())] for j in range(len(ker_basis))]; + for j in range(M.ncols()): + new_col = ch(j+_sage_const_1 )-_sage_const_1 ; + for i in range(len(ker_basis)): + rows[i][new_col] = ker_basis[i][j]; + + ker_basis = [vector(self.parent(), row) for row in rows]; + + return ker_basis; + else: + return [vector(self.parent(), [_sage_const_0 for i in range(M.ncols())])]; + + ################################################# + ### Other cached methods + ################################################# + @cached_method + def is_in_ideal(self, p): + p = self.parent()(p); + + try: + return self.__in_ideal(p) is True; + except Exception: + return False; + + @cached_method + def steps(self): + ''' + This method returns a list of pairs with the matrices obtained during the proccess and the steps taken. + ''' + self.echelon_form(); + + return [(self.__steps[i],self.__actions[i]) for i in range(len(self.__steps))]; + + ################################################# + ### Private methods for Bareiss Algorithm + ################################################# + @wverbose + def __choose_pivot(self, M, ir, ic): + ''' + This method computes the next pivot element for the algorithm and returns the information to prepare the matrix for the next step. + The ir and ic are parameters to begin the search from the position (ir,ic) + ''' + + sverbose("Looking for a new pivot from position (%d,%d)" %(ir,ic)); + relations = set(); + actions = []; + end = False; + fc = -_sage_const_1 ; fr = -_sage_const_1 ; + ## Checking rows + for cc in range(ic, M.ncols()): + ## Checking columns + for cr in range(ir, M.nrows()): + sverbose("Checking if position (%d,%d) is zero (%s)" %(cr,cc, M[cr][cc])); + to_check = M[cr][cc]; + if(len(relations) > _sage_const_0 ): + sverbose("Reducing the selected element with the found relations"); + to_check = M[cr][cc].reduce(relations); + sverbose("New element to check: %s" %(to_check)); + + if(not to_check == self.parent().zero()): + if(not self.is_in_ideal(to_check)): + sverbose("Non-zero element found. Pivot found"); + end = True; + fr = cr; + break; + else: + sverbose("Non trivial zero found. New relation: %s" %(to_check)); + relations.add(to_check); + + if(end): + fc = cc; + break; + + if(fc != -_sage_const_1 and fc != ic): + actions += [("sw_c", ic, cc)]; + for i in range(_sage_const_1 ,(min(cc-ic, M.ncols()-cc))): + actions += [("sw_c", ic+i, M.ncols()-i)]; + + if(fr != -_sage_const_1 and fr != ir): + actions += [("sw_r", ir, cr)]; + + sverbose("Finished search of pivot: (%s,%s)" %(fr,fc)); + + return ((fr,fc), tuple(actions), relations); + + def __bareiss(self, M, i): + rows = []; + for j in range(i): + if(M[j][i] != _sage_const_0 ): + rows += [[M[j][k]*M[i][i]-M[j][i]*M[i][k] for k in range(M.ncols())]]; + else: + rows += [[M[j][k] for k in range(M.ncols())]]; + rows += [[M[i][k] for k in range(M.ncols())]]; + for j in range(i+_sage_const_1 , M.nrows()): + if(M[j][i] != _sage_const_0 ): + rows += [[M[j][k]*M[i][i]-M[j][i]*M[i][k] for k in range(M.ncols())]]; + else: + rows += [[M[j][k] for k in range(M.ncols())]]; + + try: + gcds = [gcd(row) for row in rows]; + rows = [[el/gcd[i] for el in rows[i]] for i in range(len(rows))]; + except Exception: + pass; + + return Matrix(self.parent(), rows); + + def __have_ideal(self, basis): + if(len(basis) == _sage_const_1 ): + return basis[_sage_const_0 ] != _sage_const_0 ; + return len(basis) > _sage_const_0 ; + + def __simplify_matrix(self, M): + rows = [[el for el in row] for row in M]; + if(self.__have_ideal(self.__gb)): + rows = [[el.reduce(self.__gb) for el in row] for row in rows]; + + return Matrix(self.parent(), rows); + + def __get_lcm(self, input): + try: + return lcm(input); + except AttributeError: + ## No lcm for this class, implementing a general lcm + try: + ## Relying on gcd + p = self.__parent; + res = p(_sage_const_1 ); + for el in input: + res = p((res*el)/gcd(res,el)); + return res; + except AttributeError: + ## Returning the product of everything + return prod(input); + + + diff --git a/ajpastor/misc/cached_property.py b/ajpastor/misc/cached_property.py new file mode 100644 index 0000000..761586c --- /dev/null +++ b/ajpastor/misc/cached_property.py @@ -0,0 +1,142 @@ +# -*- coding: utf-8 -*- + +__author__ = 'Daniel Greenfeld' +__email__ = 'pydanny@gmail.com' +__version__ = '1.3.0' +__license__ = 'BSD' + +from time import time +import threading + + +class cached_property(object): + """ + A property that is only computed once per instance and then replaces itself + with an ordinary attribute. Deleting the attribute resets the property. + Source: https://github.com/bottlepy/bottle/commit/fa7733e075da0d790d809aa3d2f53071897e6f76 + """ # noqa + + def __init__(self, func): + self.__doc__ = getattr(func, '__doc__') + self.func = func + + def __get__(self, obj, cls): + if obj is None: + return self + value = obj.__dict__[self.func.__name__] = self.func(obj) + return value + +class derived_property(cached_property): + """ + A cached property that is protected from external assigment + + Author: Antonio Jimenez + """ #noqa + def __init__(self, func): + super(derived_property, self).__init__(func); + + def __set__(self, obj, cls): + raise AttributeError("This attribute can not be setted"); + +class threaded_cached_property(object): + """ + A cached_property version for use in environments where multiple threads + might concurrently try to access the property. + """ + + def __init__(self, func): + self.__doc__ = getattr(func, '__doc__') + self.func = func + self.lock = threading.RLock() + + def __get__(self, obj, cls): + if obj is None: + return self + + obj_dict = obj.__dict__ + name = self.func.__name__ + with self.lock: + try: + # check if the value was computed before the lock was acquired + return obj_dict[name] + except KeyError: + # if not, do the calculation and release the lock + return obj_dict.setdefault(name, self.func(obj)) + + +class cached_property_with_ttl(object): + """ + A property that is only computed once per instance and then replaces itself + with an ordinary attribute. Setting the ttl to a number expresses how long + the property will last before being timed out. + """ + + def __init__(self, ttl=None): + if callable(ttl): + func = ttl + ttl = None + else: + func = None + self.ttl = ttl + self._prepare_func(func) + + def __call__(self, func): + self._prepare_func(func) + return self + + def __get__(self, obj, cls): + if obj is None: + return self + + now = time() + obj_dict = obj.__dict__ + name = self.__name__ + try: + value, last_updated = obj_dict[name] + except KeyError: + pass + else: + ttl_expired = self.ttl and self.ttl < now - last_updated + if not ttl_expired: + return value + + value = self.func(obj) + obj_dict[name] = (value, now) + return value + + def __delete__(self, obj): + obj.__dict__.pop(self.__name__, None) + + def __set__(self, obj, value): + obj.__dict__[self.__name__] = (value, time()) + + def _prepare_func(self, func): + self.func = func + if func: + self.__doc__ = func.__doc__ + self.__name__ = func.__name__ + self.__module__ = func.__module__ + +# Aliases to make cached_property_with_ttl easier to use +cached_property_ttl = cached_property_with_ttl +timed_cached_property = cached_property_with_ttl + + +class threaded_cached_property_with_ttl(cached_property_with_ttl): + """ + A cached_property version for use in environments where multiple threads + might concurrently try to access the property. + """ + + def __init__(self, ttl=None): + super(threaded_cached_property_with_ttl, self).__init__(ttl) + self.lock = threading.RLock() + + def __get__(self, obj, cls): + with self.lock: + return super(threaded_cached_property_with_ttl, self).__get__(obj, + cls) + +# Alias to make threaded_cached_property_with_ttl easier to use +threaded_cached_property_ttl = threaded_cached_property_with_ttl +timed_threaded_cached_property = threaded_cached_property_with_ttl diff --git a/ajpastor/misc/dinamic_string.py b/ajpastor/misc/dinamic_string.py new file mode 100644 index 0000000..b9dcab5 --- /dev/null +++ b/ajpastor/misc/dinamic_string.py @@ -0,0 +1,286 @@ + +################################################################################ +################################################################################ +################################################################################ +### Class DinamicString +class DinamicString(object): + + ### Static methods and elements + @staticmethod + def is_string(obj): + '''Method that check if `obj` is str or DinamicString''' + return type(obj) == str or isinstance(obj, DinamicString); + + @staticmethod + def is_list(obj): + '''Method that check if `obj` is list, set or tuple''' + return type(obj) == list or type(obj) == tuple or type(obj) == set; + + ### Initialization methods + def __init__(self, template, arguments, escape="_"): + ''' + Initialization of a DinamicString. This method checks that all the arguments are correct. + + INPUT: + - template: a string (type(template) == str) with the basic text of the DinamicString. + - arguments: the arguments for this DinamicString. They will be replace in the positions of 'template' with 'escape' characters. + 'arguments' can be a single string or DinamicString or a list of them. + - escape: main escape character. This character followed by the number i will be use to replace in 'template' the string given by 'arguments[i]'. + + OUTPUT: + - Can raise a TypeError if 'template' is not a string. + - Can raise a TypeError if 'escape' is not a string or a character. + - Can raise a TypeError if the elements in 'arguments' are not strings. + ''' + ## Assign template and escape + if(not (type(template) == str)): + raise TypeError("The template argument must be of type 'str'. Received: %s" %(type(tempalte))); + self.__template = template; + if(not (type(escape) == str or type(escape) == chr)): + raise TypeError("The escape character must be of type 'str' or 'chr'. Received %s" %(type(escape))); + self.__escape = escape; + + ## Checking the arguments for the string + self.__arguments = None; + if(DinamicString.is_string(arguments)): + self.__arguments = [arguments]; + elif(DinamicString.is_list(arguments)): + self.__arguments = list(arguments); + if(any(not DinamicString.is_string(el) for el in self.__arguments)): + raise TypeError("No valid arguments for a DinamicString: only strings and DinamicStrings are valid."); + + ## Computing the first representation of the DinamicString + self.__real = None; + self.real(); + + ### Getters and setters + def real(self): + ''' + Get the current representation of the DinamicString. This method replace simultaneously the patters "'escape'i" for the string in 'arguments[i]'. + + This is a cached method: calling it several times in row returns the same string directly. To reset the string, call the method 'change_argument'. + ''' + if(self.__real is None): + self.__real = din_m_replace(self.__template, {"%s%d" %(self.__escape, i+1) : str(self.__arguments[i]) for i in range(len(self.__arguments))}); + + return self.__real; + + def change_argument(self, new_argument, index=0): + ''' + Method to change the arguments of the DinamicString. This method has two different syntaxes: + - 'self.change-argument(self, list)' will change completely the arguments of 'self' to the new list. + - 'self.change_argument(self, element, index)' will change only the ith argument to element. + + In any case, this method reset the variable self.__real, so the method self.real() will recompute the final string. + + INPUT: + - new_argument: a list or a string or a DinamicString. Depending of the type of the parameter, this method will perform different actions (see above). + - index: in case 'new_argument' is not a list, this will be the index of the argument to change. + + OUTPUT: + - Can raise a TypeError exception if 'new_argument' is not a list nor a string. + - Can raise a IndexError exception if 'index' is not valid for 'self.__arguments'. + ''' + if(DinamicString.is_list(new_argument)): + self.__arguments = list(new_argument); + elif(DinamicString.is_string(new_argument)): + self.__arguments[index] = new_argument; + else: + raise TypeError("The argument 'new_argument' must be a list or a 'str' o a DinamicString"); + + self.__real = None; + + ### Implementation of 'str' methods + def replace(self, pattern, out, deep=False): + ''' + Method equivalent to the replace method of the 'str' class but using DinamicString + in order to have an improved representation of the strings. + + See documentation 'str.replace?'. + + INPUT: + - pattern: the string we want to change in 'self'. + - out: the new string to put where 'pattern' is found. + - deep: deep=True implies only replacements in the arguments of 'self' will be performed. Otherwise, we will also change the template. + + OUTPUT: + - A new DinamicString such that 'result.real() == self.real().replace(pattern, str(out))'. + ''' + new_arguments = [din_replace(el, pattern,out, deep) for el in self.__arguments]; + new_template = self.__template; + if(not deep): + new_template = new_template.replace(pattern, out); + + return DinamicString(new_template, new_arguments, self.__escape); + + ### Extra methods + def m_replace(self, to_replace, deep=False): + ''' + Method to perform several replacements simultaneously. This method do exactly the same as 'self.replace(key, value) for all (key,value) in to_replace but all at once. + + INPUT: + - to_replace: dictionary with the pairs (pattern, out) to replace in 'self'. + - deep: deep=True implies only replacements in the arguments of 'self' will be performed. Otherwise, we will also change the template. + + OUTPUT: + - A new DinamicString where all the appearances of the keys of 'to_replace' have been changed to their values. + ''' + new_arguments = [din_m_replace(el, to_replace, deep) for el in self.__arguments]; + new_template = self.__template; + if(not deep): + new_template = din_m_replace(new_template, to_replace, False); + + return DinamicString(new_template, new_arguments, self.__escape); + + ### Magic Python methods + def __repr__(self): + '''Magic Python 'repr' implementation''' + return self.real(); + + def __str__(self): + '''Magic Python 'str' implementation''' + return self.real(); + + def __getitem__(self, key): + '''Magic Python implementation for 'self[key]' ''' + return self.real()[key]; + + ## Implementing concatenation + def __add__(self, other): + '''Magic Python implementation for '+' (i.e. concatenation)''' + if(type(other) == str): + return str(self) + other; + elif(isinstance(other, DinamicString)): + ## Changing arguments of other + other_template = din_m_replace(other.__template, {"%s%d" %(other.__escape, i) : "_%d" %(self.__escape, i+len(self.__arguments)) for i in range(len(other.__arguments))}); + + return DinamicString(self.__template + other_template, self.__arguments + other.__arguments); + + return NotImplemented; + + def __radd__(self, other): + '''Magic Python implementation for '+' (i.e. concatenation)''' + if(type(other) == str): + return other + str(self); + elif(isinstance(other, DinamicString)): + return other.__add__(self); + + return NotImplemented; + +################################################################################ +################################################################################ +################################################################################ +### Utility methods combining 'str' and DinamicString + +def din_replace(element, pattern, out, deep=False): + ''' + Enhaced 'replace' method. This function is equivalent to the 'replace' method of + the 'str' class when both element and out are strings. + + This method combines the string functionality with the new DinamicString class. + + INPUT: + - element: 'str' or DinamicString where we want to replace 'pattern'. + - pattern: 'str' with the pattern to replace in 'element'. + - out: 'str' or DinamicString that will be put in the places where 'pattern' appears in 'element'. + - deep: in case 'element' is a DinamicString, deep=True implies the replacement is only performed in the arguments. Otherwise this argument is irrelevant. + + OUTPUT: + If 'element' and 'out' are of type 'str', this method return a new 'str' equivalent + to 'element.replace(pattern, out)'. + + Otherwise, this method returns a new DinamicString where 'pattern' has been replaced by + 'out'. + + - Can raise an error if 'element' is not a 'str' neither a DinamicString. + ''' + if(isinstance(element, DinamicString)): + return element.replace(pattern, out, deep); + elif(type(element) == str): + if(isinsance(out, DinamicString)): + return DinamicString(element.replace(pattern, "_1"), out); + return element.replace(pattern, out); + + raise TypeError("No string given. Impossible to replace string"); + +def din_m_replace(element, to_replace, deep=False): + ''' + Method that replace simultaneously in 'element' the strings in 'to_replace.keys()' with the corresponding string. + + In case element and the values of 'to_replace' are all of type 'str', this method + will return a new 'str'. Otherwise a new DinamicString will be returned. + + In case 'element' is a DinamicString, 'deep' marks wether the replacement must be done only + in the arguments or in the whole string. + + INPUT: + - element: 'str' or DinamicStrign where perform the replacement. + - to_replace: dictionary with the structure 'pattern : out'. This method will wubstitute the patterns in 'to_replace' by their corresponding 'out'. + - deep: in case 'element' is a DinamicString, deep=True implies the replacement is only performed in the arguments. Otherwise this argument is irrelevant. + + OUTPUT: + If 'element' and 'out' are of type 'str', this method return a new 'str' where all the patterns in 'to_replace' have been replaced simultaneously. + Otherwise, this method returns a new DinamicString. + + - Can raise an error if 'element' is not a 'str' neither a DinamicString. + ''' + if(isinstance(element, DinamicString)): + return element.m_replace(to_replace, deep); + elif(type(element) == str): + real_replace = {}; + to_dinam = []; + for k,v in to_replace.items(): + if(isinstance(v, DinamicString)): + to_dinam += [v]; + real_replace[k] = "_%d" %(len(to_dinam)); + else: + real_replace[k] = v; + all_index = _din_m_indices(element, *real_replace.keys()); + res = ""; current_index = 0; + for el in all_index: + res += element[current_index:el[0]] + real_replace[el[1]]; + current_index = el[0]+len(el[1]); + res += element[current_index:]; + + if(len(to_dinam) > 0): + return DinamicString(res, to_dinam); + else: + return res; + + raise TypeError("No string given. Impossible to replace multiple strings"); + +def _din_indices(string, sep): + ''' + Private method for this module. Return a list of indices where the string 'sep' appears on 'string'. + + This method relies in the fact that both 'string' and 'sep' are of type 'str'. + ''' + try: + index = string.index(sep); + return [index] + [el+index+len(sep) for el in _din_indices(string[index+len(sep):],sep)]; + except ValueError: + return []; + +def _din_m_indices(string, *seps): + ''' + Private method for this module. Return a sorted list of pairs of (index, element) such + that the string 'element' starts in 'string[index]'. This method list all the apparitions + of the strings in 'seps' within 'string'. + + + This method relies in the fact that both 'string' and the elements of 'seps' are of type 'str'. + ''' + ## We assume no possible overlapping can occur between elements in seps + all_index = []; + for sep in seps: + all_index += [(el,sep) for el in _din_indices(string,sep)]; + all_index.sort(); + + return all_index; + +################################################################################ +################################################################################ +################################################################################ +### Module variables + +__all__ = ["DinamicString", "din_replace", "din_m_replace"]; diff --git a/ajpastor/misc/euclidean.py b/ajpastor/misc/euclidean.py new file mode 100644 index 0000000..2789dc5 --- /dev/null +++ b/ajpastor/misc/euclidean.py @@ -0,0 +1,262 @@ + +# This file was *autogenerated* from the file ./euclidean.sage +from sage.all_cmdline import * # import sage library + +_sage_const_1 = Integer(1); _sage_const_0 = Integer(0) + +#################################################################################################### +#################################################################################################### +### +### Euclidean module +### +### ------------------------------------------------------------------------------------------------ +### +### This file contains the code for some euclidean operations over for polynomials. +### +### ------------------------------------------------------------------------------------------------ +### +### Version: 0.0 +### Date of begining: 29-03-2017 +#################################################################################################### +#################################################################################################### + +## Basic polynomial -- Private functions +def __coefficients(PolR, p, var): + p = PolR(p); + if(not var in PolR.gens()): + raise ValueError("The variable given (%s) is not in the ring %s"%(var, PolR)); + + var = PolR(var); + + try: + return [p.coefficient({var:i}) for i in range(p.degree(var)+_sage_const_1 )]; + except TypeError: + return p.coefficients(sparse = False); + +def __lc(PolR, p, var): + return __coefficients(PolR, p, var)[-_sage_const_1 ]; + +def __degree(PolR, p, var): + p = PolR(p); + if(not var in PolR.gens()): + raise ValueError("The variable given (%s) is not in the ring %s"%(var, PolR)); + + var = PolR(var); + try: + return p.degree(var); + except TypeError: + return p.degree(); + +## Polynomial Division +def PolyDivide(PolR, A,B): + A = PolR(A); B = PolR(B); + x = PolR.gens()[-_sage_const_1 ]; + + Q = PolR.zero(); R = A; + d = __degree(PolR, R, x) - __degree(PolR, B, x); + while(R != PolR.zero() and d >= _sage_const_0 ): + T = (__lc(PolR, R, x)/__lc(PolR, B, x))*x**d; + Q += T; + R -= B*T; + d = __degree(PolR, R, x) - __degree(PolR, B, x); + + return (Q,R); + +def PolyPseudoDivide(PolR, A, B): + A = PolR(A); B = PolR(B); + x = PolR.gens()[-_sage_const_1 ]; + + b = __lc(PolR, B, x); N = __degree(PolR, A, x) - __degree(PolR, B, x)+_sage_const_1 ; + Q = PolR.zero(); R = A; + d = __degree(PolR, R, x) - __degree(PolR, B, x); + while(R != PolR.zero() and d >= _sage_const_0 ): + T = __lc(PolR, R, x)*x**d; + N -= _sage_const_1 ; + Q = b*Q + T; + R = b*R - T*B; + d = __degree(PolR, R, x) - __degree(PolR, B, x); + + return (b**N*Q, b**N*R); + +## Euclidean Algorithms +def Euclidean(PolR, a, b): + a = PolR(a); b = PolR(b); + while(b != PolR.zero()): + (q,r) = PolyDivide(PolR,a,b); + a = b; + b = r; + return a; + +def ExtendedEuclidean_slow(PolR, a, b): + a = PolR(a); b = PolR(b); + a1 = PolR.one(); a2 = PolR.zero(); b1 = PolR.zero(); b2 = PolR.one(); + + while(b != PolR.zero()): + (q,r) = PolyDivide(PolR,a,b); + a = b; b = r; + r1 = a1-q*b1; r2 = a2-q*b2; + a1 = b1; a2 = b2; b1 = r1; b2 = r2; + + return (a1,a2,a); + +def HalfExtendedEuclidean(PolR, a, b): + a = PolR(a); b = PolR(b); + a1 = PolR.one(); b1 = PolR.zero(); + + while(b != PolR.zero()): + (q,r) = PolyDivide(PolR,a,b); + a = b; b = r; + r1 = a1-q*b1; + a1 = b1; b1 = r1; + + return (a1,a); + +def ExtendedEuclidean(PolR, a, b): + a = PolR(a); b = PolR(b); + (s,g) = HalfExtendedEuclidean(PolR, a,b); + (t,r) = PolyDivide(PolR, g-s*a,b); + + return (s,t,g); + +def ExtendedEuclidean_diophantine_slow(PolR, a,b,c): + a = PolR(a); b = PolR(b); c = PolR(c); + x = PolR.gens()[-_sage_const_1 ]; + + (s,t,g) = ExtendedEuclidean(PolR, a,b); + (q,r) = PolyDivide(PolR, c,g); + if(r != PolR.zero()): + raise ValueError("(%s) is not in the ideal generated by (%s) and (%s)" %(c,a,b)); + s = q*s; t = t*q; + + if(s != PolR.zero() and (__degree(PolR, s, x) >= __degree(PolR, b, x))): + (q,r) = PolyDivide(PolR, s,b); + s = r; + t = t+q*a; + return (s,t); + +def HalfExtendedEuclidean_diophantine(PolR, a,b,c): + a = PolR(a); b = PolR(b); c = PolR(c); + x = PolR.gens()[-_sage_const_1 ]; + + (s,g) = HalfExtendedEuclidean(PolR, a,b); + (q,r) = PolyDivide(PolR, c,g); + if(r != PolR.zero()): + raise ValueError("(%s) is not in the ideal generated by (%s) and (%s)" %(c,a,b)); + s = q*s; + + if(s != PolR.zero() and (__degree(PolR, s, x) >= __degree(PolR, b, x))): + (q,r) = PolyDivide(PolR, s,b); + s = r; + return s; + +def ExtendedEuclidean_diophantine(PolR, a,b,c): + a = PolR(a); b = PolR(b); c = PolR(c); + s = HalfExtendedEuclidean_diophantine(PolR, a,b,c); + (t,r) = PolyDivide(PolR, c-s*a, b); + return (s,t); + +## Partial Fractions +def PartialFraction_simple(PolR, a, d): + a = PolR(a); d = [PolR(el) for el in d]; + if(len(d) == _sage_const_0 ): + return [a]; + + d1 = prod(d); d2 = d1/d[_sage_const_0 ]; + + (q,r) = PolyDivide(PolR, a,d1); + if(len(d) == _sage_const_1 ): + return [q,r]; + + (a1,t) = ExtendedEuclidean_diophantine(PolR, d2, d[_sage_const_0 ],r); + aux = PartialFraction_simple(PolR, t,d[_sage_const_1 :]) + + return [aux[_sage_const_0 ]+q, a1] + aux[_sage_const_1 :]; + +def PartialFraction(PolR, a, d, e = None): + a = PolR(a); d = [PolR(el) for el in d]; + if(e is None): + e = [_sage_const_1 for i in range(len(d))]; + else: + for i in range(len(e)): + e[i] = int(e[i]); + if(e[i] < _sage_const_0 ): + raise ValueError("No negative exponents allowed in the PartialFraction method. The %d-th exponent is negative." %i); + + if(len(d) != len(e)): + raise TypeError("PartialFraction need the same amount of exponents as denominators"); + + dens = [d[i]**e[i] for i in range(len(d))]; + pf = PartialFraction_simple(PolR, a, dens); + + res = [pf[_sage_const_0 ]]; + for i in range(len(e)): + part_res = []; + for j in range(e[i],_sage_const_0 ,-_sage_const_1 ): + (q,aux) = PolyDivide(PolR, pf[i+_sage_const_1 ], d[i]); + pf[i+_sage_const_1 ] = q; + part_res += [aux]; + part_res.reverse(); + res[_sage_const_0 ] = res[_sage_const_0 ] + pf[i+_sage_const_1 ]; + res += part_res; + + return res; + +## Primitive part and content of polynomials +def content(PolR, p): + p = PolR(p); + x = PolR.gens()[-_sage_const_1 ]; + + coeffs = __coefficients(PolR, p, x); + return gcd(coeffs); + +def pp_only(PolR, p): + p = PolR(p); + return p/content(PolR, p); + +def pp(PolR, p): + c = content(PolR, p); + return (c, p/c); + +## Squarefree factorization +def Squarefree_Musser(PolR, A): + A = PolR(A); + x = PolR.gens()[-_sage_const_1 ]; + + (c,S) = pp(PolR, A); + + S_minus = gcd(S, S.derivative(x)); + S_star = PolR(S/S_minus); + res = [] + while(__degree(PolR, S_minus, x) > _sage_const_0 ): + Y = gcd(S_star, S_minus); + res += [PolR(S_star/Y)]; + S_star = Y; + S_minus = PolR(S_minus/Y); + res += [S_star]; + + res[_sage_const_0 ] = res[_sage_const_0 ]*(c*S_minus); + return res; + +def Squarefree_Yun(PolR, A): + A = PolR(A); + x = PolR.gens()[-_sage_const_1 ]; + + (c,S) = pp(PolR, A); + S_prime = S.derivative(x); + S_minus = gcd(S,S_prime); + S_star = PolR(S/S_minus); + Y = PolR(S_prime/S_minus); + res = []; + Z = Y - S_star.derivative(x); + while(Z != _sage_const_0 ): + res += [gcd(S_star, Z)]; + S_star = PolR(S_star/res[-_sage_const_1 ]); + Y = PolR(Z/res[-_sage_const_1 ]); + Z = Y - S_star.derivative(x); + + res += [S_star]; + res[_sage_const_0 ] = res[_sage_const_0 ]*c; + + return res; + + diff --git a/ajpastor/misc/hermite.py b/ajpastor/misc/hermite.py new file mode 100644 index 0000000..d1504b9 --- /dev/null +++ b/ajpastor/misc/hermite.py @@ -0,0 +1,261 @@ + +# This file was *autogenerated* from the file ./hermite.sage +from sage.all_cmdline import * # import sage library + +_sage_const_1 = Integer(1); _sage_const_0 = Integer(0) +from ajpastor.misc.euclidean import *; +from ajpastor.misc.verbose import *; +from ajpastor.misc.matrix import swap_cols as swap; + +#################################################################################### +### +### Two Stages for Hermite Normal Form +### +#################################################################################### +@wverbose +def stage1(A,i): + parent = A.parent().base(); + sverbose("Initiating the loop..."); + sverbose.start_iteration(i,False,False); + sverbose.increase_depth(); + for j in range(i): + (p,q,r) = ExtendedEuclidean(parent, A[j][j],A[j][i]); + + sverbose("Computed the EEA: (%s)(%s) + (%s)(%s) = %s" %(p,A[j][j],q,A[j][i],r)); + + new_rows = [[el for el in row] for row in A]; + p2 = parent(-A[j][i]/r); q2 = parent(A[j][j]/r); + + sverbose("Column operation matrix:\n%s" %(Matrix(parent,[[p,p2],[q,q2]]))); + for k in range(A.nrows()): + new_rows[k][j] = p*A[k][j] + q*A[k][i]; + new_rows[k][i] = p2*A[k][j] + q2*A[k][i]; + coeff = new_rows[j][j].leading_coefficient(); + coeff2 = new_rows[j+_sage_const_1 ][i].leading_coefficient(); + for k in range(A.nrows()): + new_rows[k][j] = new_rows[k][j]/coeff; + new_rows[k][i] = new_rows[k][i]/coeff2; + A = Matrix(parent,new_rows); + sverbose("New matrix computed:\n%s" %A); + sverbose.next_iteration(); + sverbose.decrease_depth(); + sverbose("Finished the loop"); + return A; + +@wverbose +def stage2(Ao, A, i): + parent = Ao.parent().base(); + m = i+_sage_const_1 ; + S = Matrix(parent,[[Ao[i][j] for j in range(m)] for i in range(m)]); + D = Matrix(parent,[([_sage_const_0 for j in range(l)] + [A[l][l]] + [_sage_const_0 for j in range(l+_sage_const_1 ,m)]) for l in range(m)]); + K = S * BackslashOperator() * D; + new_rows = [[el for el in row] + [_sage_const_0 for j in range(m,Ao.ncols())] for row in K]; + for j in range(m,Ao.ncols()): + new_rows += [[_sage_const_0 for k in range(j)] + [_sage_const_1 ] + [_sage_const_0 for k in range(j+_sage_const_1 ,Ao.ncols())]]; + K = Matrix(K.parent().base(), new_rows); + return Ao*K; + +#################################################################################### +### +### Rearrangement methods +### +#################################################################################### +@wverbose_w_data +def index_n_from_m(n,m,subset): + ''' + Let S(n,m) be the subset of the parts of {1,...,m} with n elements. It is well known that |S(n,m)| = binomial(m,n). Let A(n,m) = {1,2,...,binomial(m,m)}. Then there are bijections between S(n,m) and A(n,m). + + Consider f a bijection that orders the elements of S(n,m) in a lexicographic order. This method implements such map. + + INPUT: + - n: size fo the subsets we are considering + - m: number of elements we can take elements + - subset: an element of S(n,m). + ''' + if(not (isinstance(subset,list) or not isinstance(subset,set))): + raise TypeError("The argument subset must be a set or a list of elements"); + + subset = list(set(subset)); subset.sort(); + + if(len(subset) != n): + raise TypeError("The argument subset must have %d elements, but it has %d" %(n,len(subset))); + + for el in subset: + if((not (isinstance(el,Integer) or type(el)==int)) or (el <= _sage_const_0 ) or (el > m)): + raise ValueError("The elements of subset must be integers from 1 to %d" %m); + + if(n == _sage_const_0 ): + return _sage_const_0 ; + elif(n == _sage_const_1 ): + return subset[_sage_const_0 ]; + + return sum(binomial(m-j,n-_sage_const_1 ) for j in range(_sage_const_1 ,subset[_sage_const_0 ])) + index_n_from_m(n-_sage_const_1 ,m-subset[_sage_const_0 ],[subset[i]-subset[_sage_const_0 ] for i in range(_sage_const_1 ,len(subset))]); + + +@wverbose_w_data +def subset_n_from_m(n,m,i): + ''' + Let S(n,m) be the subset of the parts of {1,...,m} with n elements. It is well known that |S(n,m)| = binomial(m,n). Let A(n,m) = {1,2,...,binomial(m,m)}. Then there are bijections between S(n,m) and A(n,m). + + Consider f a bijection that orders the elements of S(n,m) in a lexicographic order (see method index_n_from_m). This method inverts such method. + + INPUT: + - n: size fo the subsets we are considering + - m: number of elements we can take elements + - i: index of the subset we want to rebuild. + ''' + if(n < _sage_const_1 or m < n): + raise TypeError("Imposible compute this: not valid configuration"); + if(n == _sage_const_1 ): + sverbose("Base case (n = 1). Returning [%d]" %i); + return [i]; + bin = binomial(m-_sage_const_1 ,n-_sage_const_1 ) + current_sum = _sage_const_0 ; + index = _sage_const_1 ; + while(current_sum + bin < i): + index += _sage_const_1 ; + current_sum += bin; + bin = binomial(m-index,n-_sage_const_1 ); + + sverbose("Index found: %d" %index); + + output = [index] + [el+index for el in subset_n_from_m(n-_sage_const_1 ,m-index,i-current_sum)]; + sverbose("Output: %s" %output); + + return [index] + [el+index for el in subset_n_from_m(n-_sage_const_1 ,m-index,i-current_sum)]; + +@wverbose_w_data +def permutation_from_m(subset,m): + ''' + Given a subset of {1,2,...,m} on the form [a_1<... M.ncols()): + (o,j) = get_rearrange(M.transpose()); + return ((o[_sage_const_1 ],o[_sage_const_0 ]),j); + + done = False; + j = M.nrows()+_sage_const_1 ; + while((not done) and (j > _sage_const_1 )): + j -= _sage_const_1 ; + i = _sage_const_1 ; + minors = M.minors(j); + for el in minors: + if(el != _sage_const_0 ): + done = True; + break; + i += _sage_const_1 ; + + return (get_rearrange_from_minor(M.nrows(),M.ncols(),j,i), j); + +@wverbose +def rearrange(M): + ''' + Method that returns the same matrix M after changing rows and columns in order to have the rank defining minor in the top left corner of the matrix. + ''' + (orders, size) = get_rearrange(M); + + return Matrix(M.parent().base(), [[M[orders[_sage_const_0 ](i+_sage_const_1 )-_sage_const_1 ][orders[_sage_const_1 ](j+_sage_const_1 )-_sage_const_1 ] for j in range(M.ncols())] for i in range(M.nrows())]), size; + + +@wverbose +def deep_rearrange(M): + ''' + Method that returns the same matrix M after changing rows and columns in order to have all the possible principal minors non zero. + ''' + M,rank = rearrange(M); + + nrows = M.nrows(); ncols = M.ncols(); + for i in range(nrows): + minors = M.minors(i+_sage_const_1 ); + j = i; + for j in range(i,ncols): + if(minors[j-i] != _sage_const_0 ): + break; + if(j != i): + M = swap(M,i+_sage_const_1 ,j+_sage_const_1 ); + + return M; + +#################################################################################### +### +### Hermite Normal Form Method +### +#################################################################################### +@wverbose +def HNF_square(A, check_argument = True): + ''' + Method that computes the Hermite Normal Form of a non-singular square matrix. + + We can disable the checking for non-singularity changing the argument 'check_argument'. + ''' + if(A.ncols() != A.nrows()): + raise TypeError("This method requires a square matrix"); + + if(check_argument and A.determinant() == _sage_const_0 ): + raise ValueError("This method requires a non-singular matrix"); + + sverbose("Matrix before rearrange:\n%s" %A); + A = deep_rearrange(A); + sverbose("Matrix after rearrange:\n%s" %A); + + current = Matrix(A.parent().base(), [[el for el in row] for row in A]); + sverbose.start_iteration(A.nrows()-_sage_const_1 , False, False); + for i in range(_sage_const_1 ,A.nrows()): + current = stage2(A,stage1(current,i),i); + sverbose.next_iteration(); + + return current; + +@wverbose +def HNF(A): + if(A.ncols() < A.nrows()): + sverbose("Case with more rows than columns. Using the transpose case"); + return HNF(A.transpose()).transpose(); + + parent = A.parent().base(); + + A,rank = rearrange(A); + sverbose("Matrix rearrange. The rank computed is %d" %rank); + new_rows = [[el for el in A[i]] for i in range(rank)]; + for i in range(rank,A.ncols()): + new_rows += [[kronecker_delta(j,i) for j in range(A.ncols())]]; + + B = HNF_square(Matrix(parent,new_rows)); + sverbose("Computed the square Hermite Normal Form, we return the first %d rows" %rank); + + return Matrix(parent,[[el for el in B[i]] for i in range(rank)] + [[_sage_const_0 for j in range(B.ncols())] for i in range(rank,A.nrows())]); + +#################################################################################### +### +### Package exports +### +#################################################################################### +#__all__ = ["HNF"]; + diff --git a/ajpastor/misc/matrix.py b/ajpastor/misc/matrix.py new file mode 100644 index 0000000..2c505d3 --- /dev/null +++ b/ajpastor/misc/matrix.py @@ -0,0 +1,371 @@ + +# This file was *autogenerated* from the file ./matrix.sage +from sage.all_cmdline import * # import sage library + +_sage_const_1 = Integer(1); _sage_const_0 = Integer(0) + +from sage.matrix.matrix import Matrix as MatrixClass; + +#################################################################################### +### +### MATRICES BUILDERS +### +### In this section we include some special constructor for matrices in order to +### be able to build matrices with more flexibility than before. +### The main functions here will be the diagonal_matrix and the block_matrix. +### +#################################################################################### +def block_matrix(parent, rows, constant_or_identity = True): + ''' + Method that build a matrix using as blocks the elements of rows. The first step is check that dimensions matches for each row and column assuming that non-matrices elements can be extended to a matrix. + The way to extend that simple elements is marked by argument `constant_or_identity`: if it is False, then we put a constant matrix fullfilled with such element. Otherwise, we build a diagonal matrix with that element in the diagonal (i.e. it is the identity matrix times the element). + ''' + ## we check that rows is a list of lists of the same size + d = len(rows[_sage_const_0 ]); + for i in range(_sage_const_1 , len(rows)): + if(d != len(rows[i])): + raise ValueError("Te rows provided can not be seen as a matrix"); + + ## We check the sizes + rows_hights = [__check_row(row, parent) for row in rows]; + cols_widths = [__check_col([rows[i][j] for i in range(len(rows))], parent) for j in range(len(rows[_sage_const_0 ]))]; + + rows_with_matrix = []; + for i in range(len(rows)): + row_with_matrix = []; + for j in range(len(rows[_sage_const_0 ])): + if(rows[i][j] in parent): + if(constant_or_identity): + row_with_matrix += [Matrix(parent, [[rows[i][j]*kronecker_delta(k,l) for l in range(cols_widths[j])] for k in range(rows_hights[i])])]; + else: + row_with_matrix += [Matrix(parent, [[rows[i][j] for l in range(cols_widths[j])] for k in range(rows_hights[i])])]; + else: + row_with_matrix += [Matrix(parent, rows[i][j])]; + rows_with_matrix += [row_with_matrix]; + final_rows = []; + for i in range(len(rows_with_matrix)): + for j in range(rows_hights[i]): + final_rows += [reduce(lambda p,q : p+q, [list(el[j]) for el in rows_with_matrix[i]])]; + return Matrix(parent, final_rows); + +def diagonal_matrix(parent, *args, **kwds): + ''' + Method that build a diagonal matrix in parent using the elements of args. + + - If args has more than 1 element, we take consider the list as input + - If args has one element and it it a list, we build a diagonal matrix using such elements + - If args has one element and it is not a list, we build a diagonal matrix using such element repeated `size` times. + ''' + if(len(args) > _sage_const_1 ): + return diagonal_matrix(parent, args); + + if(len(args) == _sage_const_1 and (not (type(args[_sage_const_0 ]) == list))): + return diagonal_matrix(parent, [args[_sage_const_0 ] for i in range(kwds.get("size", _sage_const_1 ))]); + + list_of_elements = args[_sage_const_0 ]; + d = len(list_of_elements); + rows = []; + for i in range(d): + rows += [[_sage_const_0 for j in range(i)] + [list_of_elements[i]] + [_sage_const_0 for j in range(i+_sage_const_1 ,d)]]; + + return block_matrix(parent, rows); + +def vandermonde_matrix(parent, *args, **kwds): + if(len(args) > _sage_const_1 ): + return vandermonde_matrix(parent, args, **kwds); + else: + casted = [parent(el) for el in args[_sage_const_0 ]]; + + try: + ncols = kwds["size"]; + except KeyError: + try: + ncols = kwds["ncols"]; + except KeyError: + ncols = len(casted); + + rows = [[pow(casted[i], j) for j in range(ncols)] for i in range(len(casted))]; + return Matrix(parent, rows); + +def random_matrix(parent, *args, **kwds): + if(len(args) > _sage_const_1 ): + return random_matrix(parent, args, **kwds); + else: + casted = [int(el) for el in args[_sage_const_0 ]]; + + try: + ncols = kwds["size"]; + nrows = ncols; + except KeyError: + try: + ncols = kwds["ncols"]; + nrows = kwds["nrows"]; + except KeyError: + if(len(casted) > _sage_const_1 ): + nrows = casted[_sage_const_0 ]; + ncols = casted[_sage_const_1 ]; + else: + nrows = casted[_sage_const_0 ]; + ncols = casted[_sage_const_1 ]; + + try: + min = kwds["min"]; + except KeyError: + min = -sys.maxint-_sage_const_1 ; + try: + max = kwds["max"]; + except KeyError: + max = sys.maxint; + + if(min > max): + raise ValueError("Can not create random numbers between %d and %d.\n\tReason: the minimal value is higher than the maximal value" %(min,max)); + + return Matrix(parent, [[__rand(min, max) for i in range(ncols)] for j in range(nrows)]); + + +### Auxiliary (and private) methods +def __check_input(X, parent): + if(isinstance(X, MatrixClass) and parent.has_coerce_map_from(X.parent().base())): + return True; + elif(isinstance(X, list)): + try: + is_matrix = True; + d = len(X[_sage_const_0 ]); + i = _sage_const_1 ; + while(is_matrix and i < len(X)): + is_matrix = (is_matrix and d == len(X[i])); + i += _sage_const_1 ; + + if(is_matrix): + return True; + except AttributeError: + pass; + + return False; + +def __cast_input(X, parent): + if(__check_input(X,parent)): + return Matrix(parent, X); + + raise TypeError("The input %s can not be seen as a matrix of %s" %(X,parent)); + +def __check_row(row,parent): + hight = None; + for el in row: + if(__check_input(el, parent)): + if(hight is None): + hight = __nrows(el); + else: + if(not (hight == __nrows(el))): + raise ValueError("The row has not the proper format -- different size"); + elif(not (el in parent)): + raise ValueError("The row has not the proper format -- non-castable element"); + if(hight is None): + return _sage_const_1 ; + return hight; + +def __check_col(col,parent): + width = None; + for el in col: + if(__check_input(el, parent)): + if(width is None): + width = __ncols(el); + else: + if(not (width == __ncols(el))): + raise ValueError("The col has not the proper format -- different size"); + elif(not (el in parent)): + raise ValueError("The col has not the proper format -- non-castable element"); + + if(width is None): + return _sage_const_1 ; + return width; + +def __ncols(X): + try: + try: + return X.ncols(); + except AttributeError: + return len(X[_sage_const_0 ]); + except Exception: + raise TypeError("Impossible to compute the number of columns for element %s" %X); + +def __nrows(X): + try: + try: + return X.ncols(); + except AttributeError: + return len(X); + except Exception: + raise TypeError("Impossible to compute the number of rows for element %s" %X); + +def __rand(min, max): + return Integer(floor((random()*(max-min))+min)) + +#################################################################################### +### +### MATRICES OPERATIONS +### +### In this section we include some functionality about matrices as the row/column +### operations and the scalar product of some row/column. +### These operations are essentially those that are used to perform a Gauss-Jordan +### algorithm over a Matrix. Such algorithm may be implemented in further +### versions of this file. +### We also include a simplify method that tries to simplify a matrix and, if some +### element can not be simplified, then returns the original matrix. +### +#################################################################################### +def op_rows(M, s_r, d_r, el): + try: + try: + ## We make a copy of the matrix + new_rows = []; + for i in range(M.nrows()): + new_rows += [[M[i][j] for j in range(M.ncols())]]; + + ## We make the current operations + for i in range(M.ncols()): + new_rows[d_r][i] = M[d_r][i]-el*M[s_r][i]; + + return Matrix(M.parent().base(), new_rows); + except IndexError: + raise IndexError("Impossible access the rows %s or %s at matrix a matrix of dimension %dx%d" %(s_r, d_r, M.nrows(), M.ncols())); + except AttributeError: + raise TypeError("First argument must be a matrix. Given %s" %M); + +def op_cols(M, s_c, d_c, el): + try: + try: + ## We make a copy of the matrix + new_rows = []; + for i in range(M.nrows()): + new_rows += [[M[i][j] for j in range(M.ncols())]]; + + ## We make the current operations + for i in range(M.nrows()): + new_rows[i][d_c] = M[i][d_c]-el*M[i][s_c]; + + return Matrix(M.parent().base(), new_rows); + except IndexError: + raise IndexError("Impossible access to position (%s,%s) at matrix" %(s_c, d_c)); + except AttributeError: + raise TypeError("First argument must be a matrix. Given %s" %M); + +def scal_row(M, d_r, el): + try: + try: + ## We make a copy of the matrix + new_rows = []; + for i in range(M.nrows()): + new_rows += [[M[i][j] for j in range(M.ncols())]]; + + ## We make the current operations + for i in range(M.ncols()): + new_rows[d_r][i] = el*M[d_r][i]; + + return Matrix(M.parent().base(), new_rows); + except IndexError: + raise IndexError("Impossible access to position (%s,%s) at matrix" %(s_c, d_c)); + except AttributeError: + raise TypeError("First argument must be a matrix. Given %s" %M); + +def scal_col(M, d_c, el): + try: + try: + ## We make a copy of the matrix + new_rows = []; + for i in range(M.nrows()): + new_rows += [[M[i][j] for j in range(M.ncols())]]; + + ## We make the current operations + for i in range(M.nrows()): + new_rows[i][d_c] = el*M[i][d_c]; + + return Matrix(M.parent().base(), new_rows); + except IndexError: + raise IndexError("Impossible access to position (%s,%s) at matrix" %(s_c, d_c)); + except AttributeError: + raise TypeError("First argument must be a matrix. Given %s" %M); + +def swap_rows(M, r1,r2): + try: + try: + ## We make a copy of the matrix + new_rows = []; + for i in range(M.nrows()): + new_rows += [[M[i][j] for j in range(M.ncols())]]; + + ## We make the current operations + for i in range(M.ncols()): + new_rows[r1][i] = M[r2][i]; + new_rows[r2][i] = M[r1][i]; + + return Matrix(M.parent().base(), new_rows); + except IndexError: + raise IndexError("Impossible access to rows %s or %s at matrix of dimension %dx%d" %(r1, r2, M.nrows(),M.ncols())); + except AttributeError: + raise TypeError("First argument must be a matrix. Given %s" %M); + +def swap_cols(M, c1,c2): + try: + try: + ## We make a copy of the matrix + new_rows = []; + for i in range(M.nrows()): + new_rows += [[M[i][j] for j in range(M.ncols())]]; + + ## We make the current operation + for i in range(M.nrows()): + new_rows[i][c1] = M[i][c2]; + new_rows[i][c2] = M[i][c1]; + + return Matrix(M.parent().base(), new_rows); + except IndexError: + raise IndexError("Impossible access to rows %s or %s at matrix of dimension %dx%d" %(r1, r2, M.nrows(),M.ncols())); + except AttributeError: + raise TypeError("First argument must be a matrix. Given %s" %M); + +def turn_matrix(M, vertical=False): + if(vertical): + new_rows = [[M[i][j] for j in range(M.ncols())] for i in range(-_sage_const_1 ,-M.nrows()-_sage_const_1 ,-_sage_const_1 )]; + else: + new_rows = [[M[i][j] for j in range(-_sage_const_1 ,-M.ncols()-_sage_const_1 ,-_sage_const_1 )] for i in range(M.nrows())]; + + return Matrix(M.parent().base(), new_rows); + +def simplify_matrix(M): + try: + new_rows = [[M[i][j].simplify() for j in range(M.ncols())] for i in range(M.nrows())]; + return Matrix(M.parent().base(), new_rows); + except AttributeError: + pass; + return M; + +#################################################################################### +### +### MATRICIAL D-MOVE +### +### In this section we include functionality to compute a differential movement +### using a matrix. +### A differential movement of v via M is the vector dm(v,M) = Mv + D(v) where D is +### a derivative over the domain where the coefficients of v lives. +### +#################################################################################### +def derivative_vector(v, D): + parent = v.parent().base(); + + return vector(parent, [D(a) for a in v]); + +def differential_movement(M, v, D): + w = M*v+derivative_vector(v,D); + return w; + +def __dm(M,v,D): + return differential_movement(M,v,D); + +def matrix_of_dMovement(M,v,D, cols): + res = [v]; + for j in range(_sage_const_1 ,cols): + res += [__dm(M,res[-_sage_const_1 ],D)]; + return Matrix(M.parent().base(), res).transpose(); + + diff --git a/ajpastor/misc/nullspace_wrapper.py b/ajpastor/misc/nullspace_wrapper.py new file mode 100644 index 0000000..7e57962 --- /dev/null +++ b/ajpastor/misc/nullspace_wrapper.py @@ -0,0 +1,158 @@ + +# This file was *autogenerated* from the file ./nullspace_wrapper.sage +from sage.all_cmdline import * # import sage library + +_sage_const_1 = Integer(1); _sage_const_0 = Integer(0) +from .matrix import swap_rows; +from .matrix import swap_cols; + +from .cached_property import derived_property; + +from .verbose import *; + +from ore_algebra.nullspace import *; + +from sage.rings.polynomial.polynomial_ring import is_PolynomialRing as isUniPolynomial; +from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing as isMPolynomial; + +class ElementInIdealFound(ValueError): + # Used for returning an exception with an element that is zero but is not the zero polynomial + def __init__(self, element): + self.__element = element; + print "ElementInIdealFound raised" + + def __str__(self): + return "A zero found: %s" %self.__element; + + def element(self): + return self.__element; + +class NullspaceAlgorithm(object): + ### Initialization method + def __init__(self, parent, M, method=None, relations = []): + ''' + This class represent the application of the Bareiss Algorithm over a matrix with polynomial coefficients. + This class receives several parameters: + - `parent`: the polynomial ring where the operations will be done. A fraction field is allowed if the base domain is a polynomial ring. + - `M`: the matrix we want to treat with the Algorithm + - `method`: a Boolean function + + This algorithm also works in a quotient of the polynomial ring (K[X]/I) and the method provided must be a method to check if some polynomial is in the ideal I. + ''' + ## Checking the parent parameter + if(parent.is_field()): + parent = parent.base(); + if(not (isUniPolynomial(parent) or isMPolynomial(parent))): + raise TypeError("The parent for this algorithm must be a polynomial ring.\n\t Got: %s" %parent); + if((not (parent.base() is QQ)) and (not(parent.base() is ZZ))): + raise TypeError("The parent for these algorithms must be based on rational numbers\n\t Got: %s" %parent); + self.__parent = PolynomialRing(ZZ,parent.gens()); + + ## Checking the matrix input + self.base_matrix = Matrix(parent, M); + ## If the parent are the Rational numbers we clean denominators + ## Otherwise, the parent are the Integers, so we can go on. + if(parent.base() is QQ): + ## Cleaning rational denominators + new_rows = []; + for row in M: + aux_list = []; + for el in row: + aux_list += [coeff.denominator() for coeff in el.coefficients()]; + den_lcm = lcm(aux_list); + new_rows += [den_lcm*row]; + M = new_rows; + self.__matrix = Matrix(self.parent(), M); + else: + self.__matrix = self.base_matrix; + + ## Storing the checking method + self.__in_ideal = method; + + ## Cached elements + self.__gb = ideal(self.parent(), relations).groebner_basis(); + + ### Getter methods + def parent(self): + ''' + Method to have the same interface that the majority of objects of SAGE. Returns the polynomial ring where the matrix have its coefficients. + ''' + return self.__parent; + + ################################################# + ### Linear algebra methods + ################################################# + @derived_property + def rank(self): + return self.base_matrix.nrows()-self.right_kernel_matrix().ncols(); + + def relations(self): + self.right_kernel_matrix(); + return self.__gb; + + @cached_method + def right_kernel_matrix(self): + solver = self.__decide_solver(); + + finished = False; + output = None; + while(not finished): + try: + output = solver(self.__matrix); + finished = True; + except ElementInIdealFound as e: + if(e.element().reduce(self.__gb) == _sage_const_0 ): + raise RuntimeError("Same zero detected twice. Probable infinite loop"); + self.__gb = ideal(self.parent(), self.__gb+[e.element()]).groebner_basis(); + self.__matrix = self.__simplify_matrix(self.__matrix); + ## Putting the output in matrix form + rows = [[_sage_const_0 for i in range(len(output))] for j in range(self.__matrix.ncols())]; + for i in range(len(output)): + for j in range(self.__matrix.ncols()): + rows[j][i] = output[i][j]; + return Matrix(self.parent(), rows); + + ################################################# + ### Other cached methods + ################################################# + @cached_method + def is_in_ideal(self, p): + p = self.parent()(p); + + try: + return self.__in_ideal(p) is True; + except Exception: + return False; + + ################################################# + ### Private methods for the Wrapper + ################################################# + @wverbose + def __pivot(self,M, r, n, c, m, zero): + # We delegate to the method _pivot of ore_algebra.nullspace + from ore_algebra.nullspace import _pivot; + (i,j) = _pivot(M,r,n,c,m,zero); + sverbose("A pivot found at position (%d,%d): %s" %(i,j,repr(M[i][j]))); + if(self.is_in_ideal(M[i][j])): + sverbose("The pivot is zero"); + raise ElementInIdealFound(M[i][j]); + + return (i,j); + + def __decide_solver(self): + return kronecker(gauss(lambda M,r,n,c,m,zero : self.__pivot(M,r,n,c,m,zero))); + + def __have_ideal(self, basis): + if(len(basis) == _sage_const_1 ): + return basis[_sage_const_0 ] != _sage_const_0 ; + return len(basis) > _sage_const_0 ; + + def __simplify_matrix(self, M): + rows = [[el for el in row] for row in M]; + if(self.__have_ideal(self.__gb)): + rows = [[el.reduce(self.__gb) for el in row] for row in rows]; + + return Matrix(self.parent(), rows); + + + diff --git a/ajpastor/misc/restore.py b/ajpastor/misc/restore.py new file mode 100644 index 0000000..4cdd5b4 --- /dev/null +++ b/ajpastor/misc/restore.py @@ -0,0 +1,128 @@ +########################################## +### +### RESTORING SESSION METHODS +### +########################################## +from ajpastor.misc.verbose import *; + +## Data structure +class __SystemState(object): + def __init__(self,global_dict, local_dict): + self.__globals = global_dict; + self.__locals = local_dict; + self.__exceptions = []; + + @wverbose + def add_exceptions(self,*input): + if(len(input) == 1 and isinstance(input[0],list)): + sverbose("Only a list gives as argument. Using it as parameter..."); + input = input[0]; + for element in input: + if(isinstance(element, str)): + if(element not in self.__exceptions): + self.__exceptions += [element]; + else: + sverbose("A repeated element given as exception"); + else: + sverbose("A non-string element given as exception. Skipping it..."); + + @wverbose + def remove_exceptions(self,*input): + if(len(input) == 1 and isinstance(input[0],list)): + sverbose("Only a list gives as argument. Using it as parameter..."); + input = input[0]; + for element in input: + if(isinstance(element, str)): + if(element in self.__exceptions): + self.__exceptions.remove(element); + else: + sverbose("A non-existing element given as exception"); + else: + sverbose("A non-string element given as non-exception. Skipping it..."); + + def _restore_from_local(self,other_dict): + if(self.__locals is not None): + self.__restore_dictionary(other_dict, self.__locals); + + def _restore_from_global(self,other_dict): + self.__restore_dictionary(other_dict, self.__globals); + + def __restore_dictionary(self, target, source): + if(target is not None and source is not None): + if(isinstance(target, dict), isinstance(source, dict)): + ## Deleting the new variables (except those that are exceptions) + keys = [el for el in target]; + for key in keys: + if(not key in self.__exceptions): + if(not key in source): + del target[key]; + ## Restoring the old variables (except those that are exceptions) + keys = [el for el in target]; + for key in keys: + if(not key in self.__exceptions): + target[key] = source[key]; + + def __repr__(self): + n = 1; + if(self.__locals is not None): + n = 2; + return "Dictionary state with %d dictionaries" %n; + + def __str__(self): + res = repr(self); + if(len(self.__exceptions) > 0): + res += "\n - Exceptions: %s" %self.__exceptions; + + return res; + + +## Main functions +def get_state(global_dict, local_dict = None): + ''' + Method that save the current state of the program. It gets two dictionaries that are + considered the global and the local variables. + + There are two recommended usages of this function: + - get_state(globals(),locals()): save the state of the global and the local variables + - get_state(globals()): only consider the global variables. + + Also other dictionaries can be given, and then they state will be saved. + ''' + ## Checking arguments + if(not isinstance(global_dict, dict)): + raise TypeError("The global argument must be a dictionary"); + if((not (local_dict is None)) and (not isinstance(local_dict, dict))): + raise TypeError("The local argument must be None or a dictionary"); + + ## Returning the saved state + local_copy = None; + if(local_dict is not None): + local_copy = local_dict.copy(); + return __SystemState(global_dict.copy(), local_copy); + +def restore_state(state, global_dict, local_dict=None): + ''' + Restore a saved state to the dictionaries given as arguments. It considers the first + (required) as global variables and the second (optional) as the local variables. + + There are two recommended usages of this function: + - restore_state(state,globals(),locals()): restore the state of the global and the local variables + - restore_state(state,globals()): only restore the global variables. + + Also other dictionaries can be given, and they will be restore to the state saved. + ''' + ## Checking arguments + if(not isinstance(global_dict, dict)): + raise TypeError("The global argument must be a dictionary"); + if((not (local_dict is None)) and (not isinstance(local_dict, dict))): + raise TypeError("The local argument must be None or a dictionary"); + if(not isinstance(state, __SystemState)): + raise TypeError("The state argument must be a value obtained by 'get_state' method"); + + if(local_dict is not None): + state._restore_from_local(local_dict); + state._restore_from_global(global_dict); + + +## Package variables +__all__ = ["get_state", "restore_state"]; diff --git a/ajpastor/misc/ring_w_sequence.py b/ajpastor/misc/ring_w_sequence.py new file mode 100644 index 0000000..ebcdba4 --- /dev/null +++ b/ajpastor/misc/ring_w_sequence.py @@ -0,0 +1,104 @@ + +# This file was *autogenerated* from the file ./ring_w_sequence.sage +from sage.all_cmdline import * # import sage library + +_sage_const_1 = Integer(1); _sage_const_0 = Integer(0) + +######################################################## +### + +from sage.rings.ring import Ring; + +class Ring_w_Sequence (UniqueRepresentation, Ring): + def __init__(self, base, method = None, category = None): + Ring.__init__(self, base, category=category); + self.__method = method; + + def sequence(self, el, n): + return self.getSequenceElement(el,n); + + def getSequenceElement(self, el, n): + if(el in self): + return self.__method(el,n); + raise TypeError("Can not compute a sequence for an element which is not in the ring"); + +class Wrap_w_Sequence_Ring (Ring_w_Sequence): + def __init__(self, base, method = None): + super(Wrap_w_Sequence_Ring, self).__init__(base, method=method, category=base.category()); + + def _coerce_map_from_(self, S): + return self.base()._coerce_map_from_(S); + + def _has_coerce_map_from(self, S): + return self.base()._has_coerce_map_from(S); + + def __call__(self, *args, **kwds): + return self.base()(*args, **kwds); + + def __getattribute__(self, name): + ## Avoiding infinite recursion + if(name == "base"): + return super(Ring_w_Sequence,self).__getattribute__(name); + + ## Now that base is not the attribute + try: + ## Trying to have the attribute f the base ring + attr = self.base().__getattribute__(name); + if(attr is None): + raise AttributeError; + return attr; + except AttributeError: + ## No attribute for the ring, searching for this ring + attr = super(Ring_w_Sequence, self).__getattribute__(name); + if(attr is None): + raise AttributeError; + return attr; + + raise AttributeError("'%s' object has no attribute '%'" %(self.__class__, name)); + + def __repr__(self): + return "(SR)" + repr(self.base()); + + def __str__(self): + return "(SR)" + str(self.base()); + + def getSequenceElement(self, el, n): + self_gen = 'x'; + try: + self_gen = str(self.base().gens()[-_sage_const_1 ]); + except: + pass; + + if(self_gen == '1'): + if(n > _sage_const_0 ): + return _sage_const_0 ; + elif(n==_sage_const_0 ): + return el; + else: + raise ValueError("Impossible get negative element of the sequence"); + if(el in self): + res = el; + for i in range(n): + try: + res = derivative(res,self(var(self_gen))); + except AttributeError: + ## Not derivative possible. Considering a constant + if(n == _sage_const_0 ): + return res; + return _sage_const_0 ; + try: + return res(**{self_gen:_sage_const_0 })/factorial(n); + except TypeError: + ## Not callable element. Returning the element without evaluation + return res/factorial(n); + else: + raise TypeError("Element not in `self` to compute the sequence."); +##################################################################### + +def sequence(el, n): + R = el.parent(); + if(not isinstance(R, Ring_w_Sequence)): + R = Wrap_w_Sequence_Ring(R); + + return R.getSequenceElement(el,n); + diff --git a/ajpastor/misc/storj_villard.py b/ajpastor/misc/storj_villard.py new file mode 100644 index 0000000..1e6dd09 --- /dev/null +++ b/ajpastor/misc/storj_villard.py @@ -0,0 +1,250 @@ + +# This file was *autogenerated* from the file ./storj_villard.sage +from sage.all_cmdline import * # import sage library + +_sage_const_100 = Integer(100); _sage_const_2 = Integer(2); _sage_const_1 = Integer(1); _sage_const_0 = Integer(0) +from .cached_property import derived_property; + +from sage.rings.polynomial.polynomial_ring import is_PolynomialRing as isUniPolynomial; + +class StorjVillardAlgorithm(object): + ### Initialization method + def __init__(self, parent, M): + ''' + This class represent the application of the Storjohann-Villard algorithm (ISSAC 2005 paper) over a matrix with polynomial coefficients. + This class receives several parameters: + - `parent`: the polynomial ring where the operations will be done. A fraction field is allowed if the base domain is a polynomial ring. + - `M`: the matrix we want to treat with the Algorithm + ''' + ## Checking the parent parameter + if(parent.is_field()): + parent = parent.base(); + if(not (isUniPolynomial(parent))): + raise TypeError("The parent for this algorithm must be a univariate polynomial ring.\n\t Got: %s" %parent); + self.__parent = parent; + + self.__c_rank = None; + self.__c_nulls = None; + + ## Checking the matrix input + self.base_matrix = Matrix(self.parent(), M); + + ### Getter methods + def parent(self): + ''' + Method to have the same interface that the majority of objects of SAGE. Returns the polynomial ring where the matrix have its coefficients. + ''' + return self.__parent; + + ################################################# + ### Linear algebra methods + ################################################# + @derived_property + def rank(self): + if(self.__c_rank is None): + return self.__nullspace()[_sage_const_0 ]; + return self.__c_rank; + + @cached_method + def right_kernel_matrix(self): + return self.symmetric_problem.left_kernel_matrix(); + + @cached_method + def left_kernel_matrix(self): + if(self.__c_nulls is None): + return self.__nullspace()[_sage_const_1 ]; + return self.__c_nulls; + + ################################################# + ### Other cached methods + ################################################# + @derived_property + def symmetric_problem(self): + return StorjVillardAlgorithm(self.parent(), M.transpose()); + + ################################################# + ### Private methods for Storjohann-Villard Algorithm + ################################################# + def __null_min_vector(self, M, thres): + ''' + This method receives a matrix M and a degree threshold and computes some vectors in the + left null-space of M. + + This requires that M has more rows than columns and a maximal rank. + + This operation use some random matrices. + ''' + if(M.nrows() <= M.ncols()): + raise TypeError("Invalid matrix: more columns than rows"); + n = M.ncols(); + p = M.nrows()-n; + d = max([max([el.degree() for el in row]) for row in M]); + + ## M = QM for a random matrix Q + Q = random_matrix(self.parent(), n+p, min=-_sage_const_100 ,max=_sage_const_100 ); + M = Q*M; + ## M = M(x+x0) for a random x0 + x0 = self.__rand_pol(_sage_const_0 ); + M = Matrix(self.parent(), [[el(x=x+x0) for el in row] for row in M]); + + ## Splitting the matrix into a square matrix and remaining rows + A = Matrix(self.parent(), [[M[i][j] for j in range(n)] for i in range(n)]); + B = Matrix(self.parent(), [[M[i][j] for j in range(n)] for i in range(n, n+p)]); + + ## Checking rank of M + if(A.determinant() == _sage_const_0 ): + raise ValueError("Failure in the algorithm. Probably rank(M) lower than require"); + + ## Computing some degree bound + nu = thres+d+int(ceil(n*d/p)); + + ## Computing a expansion of BA^-1 mod x^nu + H = raise NotImplementedError("Do not know how to implement this"); + + ## Compressing the matrix H + Hp = H*Matrix(self.parent(), [[__rand_pol(d) for i in range(p)] for j in range(n)]); + t = [d-_sage_const_1 for i in range(p)] + [_sage_const_0 for i in range(p)]; + + ## Compute a sigma-basis with respect to t for [-Ip Hp^T]^T of order nu + L = raise NotImplementedError("Do not know how to implement this"); + + ## Computing kappa = #{rows of L such t-degree is at most thres} + ordered_rows = {}; + kappa = _sage_const_0 + for row in L: + t_degree = max([row[i] - t[i] for i in range(len(t))]); + if(t_degree <= thres): + kappa += _sage_const_1 ; + + if(ordered_rows.get(t_degree) is None): + ordered_rows[t_degree] = []; + ordered_rows[t_degree] = ordered_rows[t_degree] + [row]; + S = []; + for i in range(thres+_sage_const_1 ): + S += ordered_rows[i]; + + ## Computing the partial reconstructions Ni + N = [el*block_matrix(self.parent(),H,_sage_const_1 ) for i in el]; + N = [Matrix(self.parent(), [[el%((self.parent().gens()[_sage_const_0 ])**(thres+_sage_const_1 )) for el in row] for row in Ni]) for Ni in N]; + N = [Matrix(self.parent(), [[el(x=x-x0) for el in row] for row in Ni]) for Ni in N]; + N = [Ni*Q for Ni in N]; + l = [Ni*M].count(_sage_const_0 ); + + if(l != kappa): + raise ValueError("Failure in the algorithm. Error certifiating kappa"); + + N = Matrix(self.parent(), N); + + lc_N = Matrix(self.parent().base(), [[el.leading_coefficient() for el in row] for row in N]); + if(lc_N.rank() != max(lc_N.nrows(), lc_N.ncols())): + raise ValueError("Failure in the algorithm. No minimality got"); + + return kappa, N; + + def __nullspace2n(self, M): + ''' + This method compute some `small` linearly independent polynomial vectors in the nullspace of a matrix M. + + Such matrix M must have more rows than columns, but no more than twice. + + Moreover, M must have maximal rank. + ''' + if(M.nrows() <= M.ncols() or M.nrows() > _sage_const_2 *M.ncols()): + raise TypeError("Invalid matrix: invalid amount of rows with respect to columns"); + + n = M.ncols(); + q = M.nrows()-n; + d = max([max([el.degree() for el in row]) for row in M]); + + ## M = QM for a random matrix Q + Q = random_matrix(self.parent(), n+q, min=-_sage_const_100 ,max=_sage_const_100 ); + M = Q*M; + + ## Splitting the matrix into a square matrix and remaining rows + A = Matrix(self.parent(), [[M[i][j](x=self.__rand_pol(_sage_const_0 )) for j in range(n)] for i in range(n)]); + + if(A.determinant() == _sage_const_0 ): + raise ValueError("Failure in the algorithm. Non singular upper part of matrix"); + + I = set(); + p = q; + + res = []; + + while(len(I) < q): + ## Getting indices left + index_to_check = set([n+i for i in range(_sage_const_1 ,q+_sage_const_1 )])-I; + + ## Computing a degree bound + delta = _sage_const_2 *n*d/p; + + ## Building a matrix C + rows = [[_sage_const_0 for j in range(n+q)] for i in range(n+p)]; + for i in range(n): + rows[i][i] = _sage_const_1 ; + for j in range(p): + rows[n+j][index_to_check[j]] = _sage_const_1 ; + C = Matrix(self.parent().base(), rows); + + ## Using C and M to use the null_min_vector method + M1 = C*M; + kappa, D = self.__null_min_vector(M1, delta); + N = Matrix(self.parent(),[Di*C for Di in D]); + + ## Getting new indices + J = raise NotImplementedError("Do not know how to implement this"); + + ## Updating loop variables + I = I + set(J); + p = p - kappa; + + ## Nullspace update + res = block_matrix(self.parent(), res.transpose(), N.transpose()).transpose(); + + return res*Q; + + def __nullspace(self): + ''' + This method privately compute the left kernel of the matrix and its rank and stores the result in cached variables of the structure. + + This method returns both the rank and the minimal basis. + ''' + m = self.base_matrix.nrows(); + n = self.base_matrix.ncols(); + d = max([max([el.degree() for el in row]) for row in M]); + + ## Computing a guess r0 for rank + r0 = raise NotImplementedError("Do not know how to implement this"); + M1 = raise NotImplementedError("Do not know how to implement this"); + + ## If the guess is too big, we can return an empty nullspace + if(m == r0): + self.__c_rank = m; + self.__c_nulls = []; + + return self.__c_rank, self.__c_nulls; + + ## Randomly ensure that the top r0xr0 submatrix of M1 is non-singular + raise NotImplementedError("Do not know how to implement this"); + + s = int(ceil((m-_sage_const_2 *r0)/r0)); + + ## Getting the + N = []; + for k in range(s): + N += [row for row in self.__null_min_vector(raise NotImplementedError("Do not know how to implement this"), d)[_sage_const_1 ]]; + N += [row for row in self.__nullspace2n(raise NotImplementedError("Do not know how to implement this"))]; + + N = Matrix(self.parent(), N); + if(N*M != _sage_const_0 ): + raise ValueError("Failure in the algorithm. No kernel computed"); + + self.__c_rank = r0; + self.__c_nulls = N; + + return self.__c_rank, self.__c_nulls; + + + def __rand_pol(self, degree): + return self.parent()([randint(-_sage_const_100 ,_sage_const_100 ) for i in range(randint(_sage_const_0 ,degree)+_sage_const_1 )]); + diff --git a/ajpastor/misc/timing.py b/ajpastor/misc/timing.py new file mode 100644 index 0000000..f08df0b --- /dev/null +++ b/ajpastor/misc/timing.py @@ -0,0 +1,118 @@ + +# This file was *autogenerated* from the file ./timing.sage +from sage.all_cmdline import * # import sage library + +_sage_const_3 = Integer(3); _sage_const_2 = Integer(2); _sage_const_1 = Integer(1); _sage_const_0 = Integer(0); _sage_const_60 = Integer(60); _sage_const_24 = Integer(24) + + +########################################## +### +### TIME OPERATIONS +### +########################################## +from timeit import default_timer as timer; + +__GLOBAL_TIMER = None; +__GENERAL_TIMER = None; +def start_timer(reset = False): + global __GLOBAL_TIMER; + global __GENERAL_TIMER; + + if(reset): + __GENERAL_TIMER = timer(); + + __GLOBAL_TIMER = timer(); + +def finish_timer(text = "an action"): + global __GLOBAL_TIMER; + t = timer() - __GLOBAL_TIMER; + __print_timer(t, "Perfomed %s: it took" %(text)); + +def get_general_timing(): + global __GENERAL_TIMER; + t = timer() - __GENERAL_TIMER; + __print_timer(t, "General timing:"); + +def __print_timer(T, text): + END = "secs."; + if(T > _sage_const_60 ): + END = "mins."; + T = float(T/_sage_const_60 ); + + if(T > _sage_const_60 ): + END = "h."; + T = float(T/_sage_const_60 ); + + if(T > _sage_const_24 ): + END = "days" + T = float(T/_sage_const_24 ); + print "%s %s %s" %(text, T, END); + +########################################## +### +### COMPARISON BETWEEN METHODS +### +########################################## +def __compare_methods(arguments, *args): + if(len(args) == _sage_const_1 and (type(args[_sage_const_0 ]) == list)): + args = args[_sage_const_0 ]; + + res = [arguments]; + for method in args: + try: + init = timer(); + method(*arguments); + time = timer()-init; + res += [time]; + except Exception as e: + print e + res += [-_sage_const_1 ]; + return res; + +def __compare_for_entries(entries, *args): + if(len(args) == _sage_const_0 and type(args[_sage_const_0 ]) == list): + args = args[_sage_const_0 ]; + try: + res = [["Entry\Alg"] + [el.__name__ for el in args]]; + for arguments in entries: + res += [__compare_methods(arguments, *args)]; + + return res; + except AttributeError: + raise ValueError("The args must be a list of methods"); + +def __compute_global_stats(results): + try: + res = [["Stat\Alg"] + [el for el in results[_sage_const_0 ][_sage_const_1 :]]]; + reverse = [[results[i][j] for i in range(len(results))] for j in range(len(results[_sage_const_0 ]))]; + stats = []; + for i in range(_sage_const_1 , len(results[_sage_const_0 ])): + stats += [__compute_stats(reverse[i][_sage_const_1 :])]; + + res += [["Mean"] + [stats[j][_sage_const_0 ] for j in range(len(stats))]]; + res += [["Min"] + [stats[j][_sage_const_1 ] for j in range(len(stats))]]; + res += [["Max"] + [stats[j][_sage_const_2 ] for j in range(len(stats))]]; + res += [["N. Err"] + [stats[j][_sage_const_3 ] for j in range(len(stats))]]; + + return res; + except (TypeError, IndexError): + raise TypeError("The input for compute stats must be a list of lists with at least one row"); + +def __compute_stats(results): + cMin = float('inf'); + cMax = float('-inf'); + cSum = _sage_const_0 ; + + for el in results: + cSum += el; + cMin = min(cMin, el); + cMax = max(cMax, el); + + return [cSum/len(results), cMin, cMax, results.count(-_sage_const_1 )]; + +def full_comparison(entries, *args): + if(len(args) == _sage_const_1 and (type(args[_sage_const_0 ]) == list)): + args = args[_sage_const_0 ]; + + return __compute_global_stats(__compare_for_entries(entries, *args)); + diff --git a/ajpastor/misc/verbose.py b/ajpastor/misc/verbose.py new file mode 100644 index 0000000..35b214c --- /dev/null +++ b/ajpastor/misc/verbose.py @@ -0,0 +1,416 @@ + +# This file was *autogenerated* from the file ./verbose.sage +from sage.all_cmdline import * # import sage library + +_sage_const_100 = Integer(100); _sage_const_1 = Integer(1); _sage_const_0 = Integer(0) +#################################################################### +### +### verbose.sage +### +### Code for perform an easy conditional printing. +### +### Version 0.0 (12-07-2017) +### +### Version 1.0 (06-10-2017) +### - Changed the main system of verbose +### - Private class __verbose_session created +### - Visible instance sverbose created +### - To print with the sverbose session, use the call method ``sverbose(text)`` +### - To change the configuration of the sverbose, call the public methods of the class +### - Added a decorator to easily set up a function to verbose +### +#################################################################### + +import sys; +from functools import wraps; + +class __verbose_session(object): + def __init__(self): + self.__time = True; + self.__current_depth = _sage_const_0 ; + self.__level = _sage_const_1 ; + self.__default_beg = ""; + self.__begs = ["** ","## ","-- ","%% ","&& ","@@ "]; + self.__end = "\n"; + self.__deep_wrapper = _sage_const_0 ; + + self.__n_iterations = _sage_const_0 ; + self.__perc = False; + self.__inline = True; + self.__current_iteration = _sage_const_0 ; + + ## Basic call method + def __call__(self,string, end=None): + if(self.__level+self.__current_depth <= get_verbose()): + if(self.__time): + from datetime import datetime as time; + print "|",str(time.now()), "|", + for i in range(self.__current_depth): + print "\t", + if(self.__current_depth > _sage_const_0 ): + print self.__begs[(self.__current_depth-_sage_const_1 )%len(self.__begs)], + else: + print self.__default_beg, + + print string, + if(not(end is None)): + print end, + else: + print self.__end, + + sys.stdout.flush(); + return; + + ## Changing level of verbose for the session + def get_level(self): + return self.__level; + + def set_level(self, level): + self.__level = level; + + ## Changing the time printing + def set_time(self, time): + self.__time = time; + + def get_time(self, time): + return self.__time; + + ## Changing depth methods + def reset_depth(self): + self.__current_depth = _sage_const_0 ; + + def change_depth(self,amount): + self.__current_depth = max(_sage_const_0 , self.__current_depth+amount); + + def increase_depth(self,amount = _sage_const_1 ): + if(amount < _sage_const_0 ): + raise ValueError("To increase the tab depth it is needed a positive number, not %d" %amount); + self.change_depth(amount); + + def decrease_depth(self,amount = _sage_const_1 ): + if(amount < _sage_const_0 ): + raise ValueError("To decrease the tab depth it is needed a positive number, not %d" %amount); + self.change_depth(-amount); + + ## Changing the wrapper behavior + def get_deep_wrapper(self): + return self.__deep_wrapper; + + def set_deep_wrapper(self, new_deep): + self.__deep_wrapper = new_deep; + + ## Changing default beginning + def get_default_beginning(self): + return self.__default_beginning; + + def set_default_beginning(self, beg): + self.__default_beginning = beg; + + ## Iteration helping system variables + def get_n_iterations(self): + return self.__n_iterations; + def set_n_iterations(self, n_iterations): + self.__n_iterations = n_iterations; + + def get_perc(self): + return self.__perc; + def set_perc(self, perc): + self.__perc = perc; + + def get_inline(self): + return self.__inline; + def set_inline(self, inline): + self.__inline = inline; + + def get_current_iteration(self): + return self.__current_iteration; + def set_current_iteration(self, current_iteration): + self.__current_iteration = current_iteration; + + def get_iteration_state(self): + return {'n': self.__n_iterations, 'perc' : self.__perc, 'inline' : self.__inline, 'current' : self.__current_iteration}; + + def set_iteration_state(self, state): + try: + self.__n_iterations = state['n']; + self.__perc = state['perc']; + self.__inline = state['inline']; + self.__current_iteration = state['current']; + except KeyError: + pass; + + ## Iteration helping system + def start_iteration(self,iterations, percentage = False, in_line = True): + self.__n_iterations = iterations; + self.__perc = percentage; + self.__inline = in_line; + self.__current_iteration = _sage_const_1 ; + + def next_iteration(self): + msg = "Performed %d-th iteration " %self.__current_iteration; + + if(self.__perc and self.__n_iterations > _sage_const_0 ): + msg += "(%.2f %%)" %(self.__current_iteration*_sage_const_100 /self.__n_iterations); + else: + msg += "(%d/%d)" %(self.__current_iteration, self.__n_iterations); + + if(self.__inline and self.__current_iteration < self.__n_iterations): + end = "\r"; + else: + end = None; + + self(msg, end); + self.__current_iteration += _sage_const_1 ; + +## Conditional definition of the verbose variables +sverbose = __verbose_session(); + +class VerboseError(RuntimeError): + def __init__(self): + pass; + +#@wraps +def wverbose(func): + ## Creating the wrapped function + def wrapped_function(*args, **kwds): + try: + sverbose.increase_depth(sverbose.get_deep_wrapper()); + sverbose("Calling method %s" %(func.__name__)); + state = sverbose.get_iteration_state(); + sverbose.increase_depth(); + except Exception: + raise VerboseError(); + + e = None; + try: + output = func(*args,**kwds); + except Exception as e: + pass; + except KeyboardInterrupt as e: + pass; + sverbose.decrease_depth(); + sverbose.set_iteration_state(state); + + if(not e is None): + sverbose("Exception raised in method %s" %(func.__name__)); + sverbose.decrease_depth(sverbose.get_deep_wrapper()); + raise e; + else: + sverbose("Finished method %s" %(func.__name__)); + sverbose.decrease_depth(sverbose.get_deep_wrapper()); + return output; + + ## Setting up the other attributes for the wrapped function + wrapped_function.__doc__ = getattr(func, '__doc__'); + + return wrapped_function; + +#@wraps +def wverbose_w_data(func): + ## Creating the wrapped function + def wrapped_function(*args, **kwds): + try: + sverbose.increase_depth(sverbose.get_deep_wrapper()); + sverbose("Calling method %s" %(func.__name__)); + sverbose("Arguments:"); + if(len(args) > _sage_const_0 ): + sverbose("%s" %(str(args))); + if(len(kwds)> _sage_const_0 ): + sverbose("%s" %(str(kwds))); + state = sverbose.get_iteration_state(); + sverbose.increase_depth(); + except Exception: + raise VerboseError(); + + e = None; + try: + output = func(*args,**kwds); + except Exception as e: + pass; + except KeyboardInterrupt as e: + pass; + sverbose.decrease_depth(); + sverbose.set_iteration_state(state); + + if(not e is None): + sverbose("Exception raised in method %s" %(func.__name__)); + sverbose.decrease_depth(sverbose.get_deep_wrapper()); + raise e; + else: + sverbose("Finished method %s" %(func.__name__)); + sverbose("Output: %s" %(str(output))); + sverbose.decrease_depth(sverbose.get_deep_wrapper()); + return output; + + ## Setting up the other attributes for the wrapped function + wrapped_function.__doc__ = getattr(func, '__doc__'); + + return wrapped_function; + +__all__ = ["sverbose", "wverbose", "wverbose_w_data"]; + +__VERBOSE_LEVEL = None; +try: + set_verbose(get_verbose()); +except Exception: + __VERBOSE_LEVEL = _sage_const_0 ; + def set_verbose(level): + global __VERBOSE_LEVEL; + __VERBOSE_LEVEL = max(_sage_const_0 , level); + if(__VERBOSE_LEVEL >= _sage_const_1 ): + print "-- Changed verbose level to %d" %(__VERBOSE_LEVEL); + + def get_verbose(): + global __VERBOSE_LEVEL; + return __VERBOSE_LEVEL; + + __all__ += ["set_verbose", "get_verbose"]; + + +## Getters and setters of the verbose level +#def set_verbose(level): +# global __VERBOSE_LEVEL; +# +# __VERBOSE_LEVEL = max(0, level); +# verbose("-- Changed verbose level to %d" %(__VERBOSE_LEVEL), 1); +# +#def get_verbose(): +# global __VERBOSE_LEVEL; +# return __VERBOSE_LEVEL; + +## Main printing function +#def verbose(text, level = 0, end = '\n', depth = 0, beg = '', time = False): +# global __VERBOSE_LEVEL; +# +# if(level <= __VERBOSE_LEVEL): +# if(time): +# from datetime import datetime as time; +# print str(time.now()), " --", +# for i in range(depth): +# print "\t", +# if(len(beg) > 0): +# print beg, " ", +# print text, +# print end, +# sys.stdout.flush(); + +## Other useful methods +#__sv_active = False; +#__sv_time = False; +#__sv_current_depth = 0; +#__sv_current_level = 0; +#__sv_beg = ""; +#__sv_end = "\n"; + +## Making smart verbose +#def sverbose(text, end=None): +# global __sv_active; +# global __sv_current_depth; +# global __sv_current_level; +# global __sv_beg; +# global __sv_time; +# global __sv_end; +# if(end is None): +# tend = __sv_end; +# else: +# tend = end; +# +# if(__sv_active): +# verbose(text, __sv_current_level, end=tend, depth=__sv_current_depth, beg=__sv_beg, time=__sv_time); + +## Starting and finishing sessions +#def start_smart_verbose(level, time=True): +# global __sv_active; +# global __sv_current_depth; +# global __sv_current_level; +# global __sv_time; +# +# if(__sv_active): +# verbose("A smart verbose session was already initiated. Reseting session...", 1, beg = "--"); + +# __sv_active = True; +# __sv_time = time; +# __sv_current_depth = 0; +# __sv_current_level = level; +# +#def end_smart_verbose(): +# global __sv_active; +# +# __sv_active = False; + +## Changing depth in smart session +#def change_depth(amount): +# global __sv_active; +# global __sv_current_depth; +# +# if(__sv_active): +# __sv_current_depth = max(0, __sv_current_depth+amount); +# +#def increase_depth(amount = 1): +# if(amount <= 0): +# raise ValueError("To increase the tab depth it is needed a positive number, not %d" %amount); +# change_depth(amount); +# +#def decrease_depth(amount = 1): +# if(amount <= 0): +# raise ValueError("To decrease the tab depth it is needed a positive number, not %d" %amount); +# change_depth(-amount); +# +#def change_beginning(beg = ""): +# global __sv_beg; +# __sv_beg = beg; + +## Printing iterations in smart session +#__sv_iteration = False; +#__sv_n_iterations = 0; +#__sv_perc = False; +#__sv_inline = True; +#__sv_current_iteration = 0; +#def start_iteration(iterations, percentage = False, in_line = True): +# global __sv_iteration; +# global __sv_n_iterations; +# global __sv_perc; +# global __sv_inline; +# global __sv_current_iteration; +# +# __sv_iteration = True; +# __sv_n_iterations = iterations; +# __sv_perc = percentage; +# __sv_inline = in_line; +# __sv_current_iteration = 1; +# +#def next_iteration(): +# global __sv_iteration; +# global __sv_n_iterations; +# global __sv_perc; +# global __sv_inline; +# global __sv_current_iteration; +# +# if(__sv_iteration): +# msg = "Performed %d-th iteration " %__sv_current_iteration; +# +# if(__sv_perc and __sv_n_iterations > 0): +# msg += "(%.2f %%)" %(__sv_current_iteration*100/__sv_n_iterations); +# else: +# msg += "(%d/%d)" %(__sv_current_iteration, __sv_n_iterations); +# +# if(__sv_inline): +# end = "\r"; +# else: +# end = None; +# +# sverbose(msg, end); +# __sv_current_iteration += 1; +# +#def end_iteration(): +# global __sv_iteration; +# global __sv_inline; +# global __sv_current_level; +# global __VERBOSE_LEVEL; +# +# if(__sv_iteration): +# __sv_iteration = False; +# if(__sv_inline and __sv_current_level <= __VERBOSE_LEVEL): +# print ""; + + diff --git a/ajpastor/operator/__init__.py b/ajpastor/operator/__init__.py new file mode 100644 index 0000000..00cbfa0 --- /dev/null +++ b/ajpastor/operator/__init__.py @@ -0,0 +1,7 @@ +from .operator import Operator; +from .oreOperator import w_OreOperator as OreOperator; +from .directStepOperator import DirectStepOperator; +from .polynomialLazyOperator import PolynomialLazyOperator; + +from pkgutil import extend_path; +__path__ = extend_path(__path__, __name__); diff --git a/ajpastor/operator/directStepOperator.py b/ajpastor/operator/directStepOperator.py new file mode 100644 index 0000000..c99f50b --- /dev/null +++ b/ajpastor/operator/directStepOperator.py @@ -0,0 +1,90 @@ + +# This file was *autogenerated* from the file ./directStepOperator.sage +from sage.all_cmdline import * # import sage library + +_sage_const_1 = Integer(1); _sage_const_0 = Integer(0) + +#################################################################################################### +#################################################################################################### +### +### DirectStepOperator module +### +### ------------------------------------------------------------------------------------------------ +### +### This file contains an extension of a TwoStepsOperator that computes the kernell of the appropiate matrix using standard SAGE algorithms. +### ------------------------------------------------------------------------------------------------ +### +### Version: 0.0 +### Date of begining: 05-05-2017 +### +### Updated (21-08-2017) +### - Changed name parent to base +### +### +### ------------------------------------------------------------------------------------------------ +### Dependencies: +### - TwoStepsOperator class +#################################################################################################### +#################################################################################################### + +# Local imports +from .twoStepsOperator import TwoStepsOperator; +from .operator import foo_derivative; + +class DirectStepOperator(TwoStepsOperator): + + ####################################################### + ### INIT METHOD AND GETTERS + ####################################################### + def __init__(self, base, input, derivate = foo_derivative): + super(DirectStepOperator, self).__init__(base, input, derivate); + + ####################################################### + + ####################################################### + ### SOLVING MATRICES METHOD + ####################################################### + def _get_element_nullspace(self, M): + from ajpastor.misc.bareiss import BareissAlgorithm; + ## We take the domain where our elements will lie + parent = M.parent().base().base(); + + ## Computing the kernell of the matrix + try: + lcms = [lcm([el.denominator() for el in row]) for row in M]; + N = Matrix(parent, [[el*lcms[i] for el in M[i]] for i in range(M.nrows())]); + ba = BareissAlgorithm(parent, N, lambda p : False); + + ker = ba.right_kernel_matrix(); + except Exception as e: + print e + ker = M.right_kernel_matrix(); + #ker = M.right_kernel_matrix(); + ## If the nullspace has hight dimension, we try to reduce the final vector computing zeros at the end + aux = [row for row in ker]; + i = _sage_const_1 ; + + while(len(aux) > _sage_const_1 ): + new = []; + current = None; + for j in range(len(aux)): + if(aux[j][-(i)] == _sage_const_0 ): + new += [aux[j]]; + elif(current is None): + current = j; + else: + new += [aux[current]*aux[j][-(i)] - aux[j]*aux[current][-(i)]]; + current = j; + aux = [el/gcd(el) for el in new]; + i = i+_sage_const_1 ; + + + ## When exiting the loop, aux has just one vector + sol = aux[_sage_const_0 ]; + + ## Our solution has denominators. We clean them all + p = prod([el.denominator() for el in sol]); + return vector(parent, [el*p for el in sol]); + ####################################################### + + diff --git a/ajpastor/operator/fullLazyOperator.py b/ajpastor/operator/fullLazyOperator.py new file mode 100644 index 0000000..63c15ab --- /dev/null +++ b/ajpastor/operator/fullLazyOperator.py @@ -0,0 +1,232 @@ + +# This file was *autogenerated* from the file ./fullLazyOperator.sage +from sage.all_cmdline import * # import sage library + +_sage_const_1 = Integer(1); _sage_const_0 = Integer(0); _sage_const_5 = Integer(5) + +#################################################################################################### +#################################################################################################### +### +### FullLazyOperator module +### +### ------------------------------------------------------------------------------------------------ +### +### This file contains an extension of a TwoStepsOperator that computes the kernell of the appropiate matrix trnaslating the elements into polynomials and reversing the change once obtained the final vector. +### +### If some relations between the variables are known, Groebner basis reduction will be used. +### ------------------------------------------------------------------------------------------------ +### +### Version: 0.0 +### Date of begining: 07-02-2018 +### +### ------------------------------------------------------------------------------------------------ +### Dependencies: +### - TwoStepsOperator class +#################################################################################################### +#################################################################################################### + +# Local imports +from .twoStepsOperator import TwoStepsOperator; +from .operator import foo_derivative; + +from ajpastor.lazy.lazyRing import LazyRing; + +from ajpastor.misc.bareiss import BareissAlgorithm; + +class FullLazyOperator(TwoStepsOperator): + ### Static parameters + _op_preference = _sage_const_5 ; + + ####################################################### + ### INIT METHOD AND GETTERS + ####################################################### + def __init__(self, base, input, derivate = foo_derivative): + super(FullLazyOperator, self).__init__(base, input, derivate); + + self.__conversion = LazyRing(self.base(), self.base().base_ring()); + self.__version = self.__conversion.version(); + self.__companion = {}; + + ####################################################### + def companion(self, element=None): + R = self.__conversion; + if((not (element in self.__companion)) or (self.__version != R.version())): + self.__version = R.version(); + if(element is None): + coefficients = [R(el) for el in self.getCoefficients()]; + else: + try: + if(not element in self.base().original_ring()): + raise TypeError("Composition with no polynomials is not allowed"); + except AttributeError: + if(not element in self.base().base_ring()): + raise TypeError("Composition with no polynomials is not allowed"); + ## TODO Fix this step to work for compositions + coefficients = [R(el(x=element)) for el in self.getCoefficients()]; + + ## We divide by the leading coefficient + coefficients = [-(coefficients[i]/coefficients[-_sage_const_1 ]) for i in range(len(coefficients)-_sage_const_1 )]; + ## Trying to reduce the elements + try: + for i in range(len(coefficients)): + coefficients[i].reduce(); + except AttributeError: + pass; + d = len(coefficients); + + ## Building the rows of our matrix + rows = [[_sage_const_0 for i in range(d-_sage_const_1 )] + [coefficients[_sage_const_0 ]]]; + for i in range(d-_sage_const_1 ): + rows += [[kronecker_delta(i,j) for j in range(d-_sage_const_1 )] + [coefficients[i+_sage_const_1 ]]]; + + ## Returning the matrix + self.__companion[element] = Matrix(R, rows); + + return self.__companion[element]; + + ####################################################### + ### GETTING MATRICES METHODS + ####################################################### + def _get_matrix_add(self, other): + ''' + Method to obtain a matrix such any element of the nullspace can be interpreted as a new operator that annihilates the sum (f+g) where f is solution to 'self=0' and g is solution to 'other=0' + ''' + from ajpastor.misc.matrix import diagonal_matrix as diagonal; + from ajpastor.misc.matrix import matrix_of_dMovement as move; + + f = self; + g = other; + + R = self.__conversion; + + ## Computing the companion matrices + Mf = f.companion(); + Mg = g.companion(); + + full_companion = diagonal(R, [Mf,Mg]); + init_vector = vector(R,([_sage_const_1 ] + [_sage_const_0 for i in range(self.getOrder()-_sage_const_1 )])+([_sage_const_1 ] + [_sage_const_0 for i in range(other.getOrder()-_sage_const_1 )])); + + return move(full_companion, init_vector, lambda p : p.derivative(), full_companion.ncols()+_sage_const_1 ); + + def _get_matrix_mult(self, other): + ''' + Method to obtain a matrix such any element of the nullspace can be interpreted as a new operator that annihilates the product (fg) where f is solution to 'self=0' and g is solution to 'other=0' + ''' + from ajpastor.misc.matrix import matrix_of_dMovement as move; + + f = self; + g = other; + + R = self.__conversion; + + ## Computing the companion matrices + Mf = f.companion(); + Mg = g.companion(); + + ## Computing Kroenecker sum of the matrices + full_companion = Mf.tensor_product(Matrix.identity(R, Mg.nrows())) + Matrix.identity(R, Mf.nrows()).tensor_product(Mg); + + init_vector = vector(R,([_sage_const_1 ] + [_sage_const_0 for i in range(f.getOrder()*g.getOrder()-_sage_const_1 )])); + + return move(full_companion, init_vector, lambda p : p.derivative(), full_companion.ncols()+_sage_const_1 ); + + def _get_matrix_composition(self, other): + #raise TypeError("Composition not allowed *'FullLazyOperator level'*"); + from ajpastor.misc.matrix import matrix_of_dMovement as move; + + g = other; + + Mf = self.companion(g); + + Mf,dg, parent = self._compose_companion(Mf,g); + + full_companion = dg*Mf; + init_vector = vector(parent, [_sage_const_1 ] + [_sage_const_0 for i in range(_sage_const_1 ,self.getOrder())]); + + return move(full_companion, init_vector, lambda p : p.derivative(), full_companion.ncols()+_sage_const_1 ); + ####################################################### + + ####################################################### + ### SOLVING MATRICES METHOD + ####################################################### + def _get_element_nullspace(self, M): + ## We take the Conversion System + R = self.__conversion; + + ## We assume the matrix is in the correct space + M = Matrix(R.poly_field(), [[R.simplify(el.poly()) for el in row] for row in M]); + + ## We clean denominators to lie in the polynomial ring + lcms = [self.__get_lcm([el.denominator() for el in row]) for row in M]; + M = Matrix(R.poly_ring(), [[el*lcms[i] for el in M[i]] for i in range(M.nrows())]); + f = lambda p: R(p).is_zero(); + try: + assert(f(_sage_const_1 ) == False); + except Exception: + raise ValueError("The method to check membership is not correct"); + + ## Computing the kernell of the matrix + if(len(R.map_of_vars()) > _sage_const_0 ): + bareiss_algorithm = BareissAlgorithm(R.poly_ring(),M,f,R._ConversionSystem__relations); + ker = bareiss_algorithm.right_kernel_matrix(); + ## If some relations are found during this process, we add it to the conversion system + R.add_relations(bareiss_algorithm.relations()); + else: + ker = [v for v in M.right_kernel_matrix()]; + + ## If the nullspace has high dimension, we try to reduce the final vector computing zeros at the end + aux = [row for row in ker]; + i = _sage_const_1 ; + + while(len(aux) > _sage_const_1 ): + new = []; + current = None; + for j in range(len(aux)): + if(aux[j][-(i)] == _sage_const_0 ): + new += [aux[j]]; + elif(current is None): + current = j; + else: + new += [aux[current]*aux[j][-(i)] - aux[j]*aux[current][-(i)]]; + current = j; + aux = [el/gcd(el) for el in new]; + i = i+_sage_const_1 ; + + + ## When exiting the loop, aux has just one vector + sol = aux[_sage_const_0 ]; + sol = [R.simplify(a) for a in sol]; + + ## Just to be sure everything is as simple as possible, we divide again by the gcd of all our + ## coefficients and simplify them using the relations known. + fin_gcd = gcd(sol); + finalSolution = [a/fin_gcd for a in sol]; + finalSolution = [R.simplify(a) for a in finalSolution]; + + ## We transform our polynomial into elements of our destiny domain + realSolution = [R.to_real(a) for a in finalSolution]; + for i in range(len(realSolution)): + try: + realSolution[i].change_built("polynomial", (finalSolution[i], {str(key): R.map_of_vars()[str(key)] for key in finalSolution[i].variables()})); + except AttributeError: + pass; + + return vector(R.base(), realSolution); + + def __get_lcm(self, input): + try: + return lcm(input); + except AttributeError: + ## No lcm for this class, implementing a general lcm + try: + ## Relying on gcd + p = self.__conversion.poly_ring(); + res = p(_sage_const_1 ); + for el in input: + res = p((res*el)/gcd(res,el)); + return res; + except AttributeError: + ## Returning the product of everything + return prod(input); + ####################################################### + diff --git a/ajpastor/operator/lazyStepOperator.py b/ajpastor/operator/lazyStepOperator.py new file mode 100644 index 0000000..02c923a --- /dev/null +++ b/ajpastor/operator/lazyStepOperator.py @@ -0,0 +1,74 @@ + +# This file was *autogenerated* from the file ./lazyStepOperator.sage +from sage.all_cmdline import * # import sage library + +_sage_const_3 = Integer(3); _sage_const_1 = Integer(1); _sage_const_0 = Integer(0) + +#################################################################################################### +#################################################################################################### +### +### LazyStepOperator module +### +### ------------------------------------------------------------------------------------------------ +### +### This file contains an extension of a TwoStepsOperator that computes the companion matrix in a lazy field, so the computations of the matrix are not directly done. +### ------------------------------------------------------------------------------------------------ +### +### Version: 0.0 +### Date of begining: 05-05-2017 +### +### Updated (21-08-2017) +### - Changed name parent to base +### +### +### ------------------------------------------------------------------------------------------------ +### Dependencies: +### - TwoStepsOperator class +#################################################################################################### +#################################################################################################### + +# Imports +from .twoStepsOperator import TwoStepsOperator; +from .operator import foo_derivative; + +from ajpastor.lazy.lazyIDElements import *; + +class LazyStepOperator(TwoStepsOperator): + ### Static parameters + _op_preference = _sage_const_3 ; + + ####################################################### + ### INIT METHOD AND GETTERS + ####################################################### + def __init__(self, base, input, derivate = foo_derivative): + super(LazyStepOperator, self).__init__(base, input, derivate); + + ####################################################### + + @cached_method + def companion(self): + field = LazyIntegralDomain(self._original_base).fraction_field(); + + coefficients = [field(el) for el in self.getCoefficients()]; + + ## We divide by the leading coefficient + coefficients = [-(coefficients[i]/coefficients[-_sage_const_1 ]) for i in range(len(coefficients)-_sage_const_1 )]; + ## Trying to reduce the elements + try: + for i in range(len(coefficients)): + coefficients[i].reduce(); + except AttributeError: + pass; + except ArithmeticError: + pass; + d = len(coefficients); + + ## Building the rows of our matrix + rows = [[_sage_const_0 for i in range(d-_sage_const_1 )] + [coefficients[_sage_const_0 ]]]; + for i in range(d-_sage_const_1 ): + rows += [[kronecker_delta(i,j) for j in range(d-_sage_const_1 )] + [coefficients[i+_sage_const_1 ]]]; + + ## Returning the matrix + return Matrix(field, rows); + + diff --git a/ajpastor/operator/listOperator.py b/ajpastor/operator/listOperator.py new file mode 100644 index 0000000..78b0cb4 --- /dev/null +++ b/ajpastor/operator/listOperator.py @@ -0,0 +1,235 @@ + +# This file was *autogenerated* from the file ./listOperator.sage +from sage.all_cmdline import * # import sage library + +_sage_const_3 = Integer(3); _sage_const_2 = Integer(2); _sage_const_1 = Integer(1); _sage_const_0 = Integer(0) + +#################################################################################################### +#################################################################################################### +### +### ListOperator module +### +### ------------------------------------------------------------------------------------------------ +### +### This file contains a simple implementation of the abstract class Operator needed to the some +### of the calculations for DD-Rings and DD-Functions +### ------------------------------------------------------------------------------------------------ +### +### Version: 2.0 +### Date of begining: 21-11-2016 +### +### Update (05-05-2017): +### - Deleted method getCoefficientParent (now the Operator method parent() do the same) +### - Deleted attributes "parent" and "__derivate" (the Operator methods parent() and derivate() do the same) +### - Added the option of initialize a FooOperator from another operator. +### - Maded the imports more precise +### - Rearrange file: more readable structure +### - Added the methods __compute_add_solution, __compute_mult_solution, __compute_derivative_solution and __compute_integral_solution. +### - Changed name: method "isZero" to "is_zero" +### - Changed name of class FooOperator to ListOperator +### +### Updated (21-08-2017) +### - Changed name parent to base +### +### ------------------------------------------------------------------------------------------------ +### Dependencies: +### - Operator class +#################################################################################################### +#################################################################################################### + +# Local imports +from .operator import Operator; +from .operator import foo_derivative; +from ajpastor.misc.ring_w_sequence import Wrap_w_Sequence_Ring; + +from sage.rings.polynomial.polynomial_ring import is_PolynomialRing as isPolynomial; + +class ListOperator(Operator): + ### Static parameters + _op_preference = _sage_const_2 ; + + ####################################################### + ### 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. + ''' + ## Initializing the minimal structure + super(ListOperator, self).__init__(base, input, derivate); + + if (isinstance(input, list)): + if(len(input) == _sage_const_0 ): + self.__coefficients = [_sage_const_1 ]; + else: + # Getting the las non-zero position + i = len(input); + while(input[i-_sage_const_1 ] == _sage_const_0 and i > _sage_const_0 ): + i -= _sage_const_1 ; + try: + if(i == _sage_const_0 ): + self.__coefficients = [self.base().one()]; + else: + self.__coefficients = [self.base()(input[j]) for j in range(i)]; + except Exception as e: + raise TypeError('The input (%s) is not valid for a ListOperator with coefficients in (%s)' %(input,format(base))); + elif(isinstance(input, Operator)): + self.__coefficients = [self.base()(coeff) for coeff in input.getCoefficients()]; + else: + try: + self.__coefficients = [self.base()(input)]; + except Exception: + raise TypeError('The input is not valid for an operator with coefficients in (%s)' %(format(base))); + #coeff_gcd = gcd(self.__coefficients); + #if(coeff_gcd != 0): + # self.__coefficients = [self.base()(el/coeff_gcd) for el in self.__coefficients]; + #if(isinstance(self.base(), Wrap_w_Sequence_Ring) and isPolynomial(self.base().base())): + # l = [] + # for el in self.__coefficients: + # l += el.coefficients(x); + # + # coeff_gcd = gcd(l); + # if(coeff_gcd != 0): + # self.__coefficients = [el/coeff_gcd for el in self.__coefficients]; + + ### Getter methods + def getOrder(self): + return len(self.__coefficients)-_sage_const_1 ; + + def getCoefficients(self): + return self.__coefficients; + + def getCoefficient(self, i): + if (i < _sage_const_0 ): + raise IndexError('The argument must be a number greater or equal to zero'); + elif (i < len(self.__coefficients)): + return self.__coefficients[i]; + + return _sage_const_0 ; + ####################################################### + + ####################################################### + ### OPERATOR ARITHMETIC METHODS (ABSTRACT) + ####################################################### + ### Arithmetic + def add(self, other): + if(isinstance(other, ListOperator)): + if(self.is_zero()): + return other; + if(other.is_zero()): + return self; + return self.__class__(self.base(), + [self.getCoefficient(i)+self.base()(other.getCoefficient(i)) for i in range(max(self.getOrder(), other.getOrder())+_sage_const_1 )], + self.derivate()); + elif(isinstance(other, Operator)): + return other.__radd__(self); + else: + return self.add(self.__class__(self.base(),other, self.derivate())); + + def scalar(self, other): + try: + r = self.base()(other); + if(r == _sage_const_0 ): + return self.__class__(self.base(), _sage_const_0 , self.derivate()); + return self.__class__(self.base(), [r*coeff for coeff in self.getCoefficients()], self.derivate()); + except TypeError: + raise TypeError("The argument for this method must be an element of the current domain"); + + def mult(self, other): + try: + if(isinstance(other, ListOperator)): + if(self.is_zero() or other.is_zero()): + self.__class__(self.base(), _sage_const_0 , self.derivate()); + res = self.__class__(self.base(), _sage_const_0 , self.derivate()); + aux = None; + for coeff in self.getCoefficients(): + if(aux is None): + aux = other; + else: + aux = aux.derivative(); + res = res.add(aux.scalar(coeff)); + return res; + elif(isinstance(other, Operator)): + return other.__rmul__(self); + else: + return self.mult(self.__class__(self.base(),other, self.derivate())); + except Exception as e: + print "Got an exception: %s"%(e); + raise e; + + ### Equality + def is_zero(self): + for coeff in self.getCoefficients(): + if not (coeff == _sage_const_0 ): + return False; + return True; + + def __eq__(self, other): + if(isinstance(other, ListOperator)): + selfCoeffs = self.getCoefficients(); + otherCoeffs = other.getCoefficients(); + + if(len(selfCoeffs) == len(otherCoeffs)): + for i in range(len(selfCoeffs)): + if(not (selfCoeffs[i] == otherCoeffs[i])): + return False; + return True; + return False; + + ### Differential + def derivative(self): + newCoeffs = [self.derivate()(self.getCoefficient(_sage_const_0 ))]; + for j in range(_sage_const_1 ,self.getOrder()+_sage_const_1 ): + newCoeffs += [self.derivate()(self.getCoefficient(j)) + self.getCoefficient(j-_sage_const_1 )]; + newCoeffs += [self.getCoefficient(self.getOrder())]; + + return self.__class__(self.base(), newCoeffs, self.derivate()); + ####################################################### + + ####################################################### + ### SOLUTION ARITHMETHIC METHODS (ABSTRACT) + ####################################################### + def _compute_derivative_solution(self): + r = self.getCoefficients(); + ### The operation depends on the first coefficient of the equation + if(r[_sage_const_0 ] == _sage_const_0 ): + ### If is zero, then the next derivative has the same coefficients shifted 1 position to the left. + newCoeffs = [r[i] for i in range(_sage_const_1 ,len(r))]; + else: + ### Otherwise, we compute another operator + der0 = self.derivate()(r[_sage_const_0 ]); + newCoeffs = [r[i]*r[_sage_const_0 ] + self.derivate()(r[i+_sage_const_1 ])*r[_sage_const_0 ]-der0*r[i+_sage_const_1 ] for i in range(self.getOrder())]; + newCoeffs += [r[_sage_const_0 ]*r[-_sage_const_1 ]]; + + return self.__class__(self.base(), newCoeffs, self.derivate()); + + def _compute_integral_solution(self): + return self.__class__(self.base(), [_sage_const_0 ] + self.getCoefficients(), self.derivate()); + ####################################################### + + ####################################################### + ### MAGIC PYTHON METHODS + ####################################################### + def __call__(self, obj): + try: + obj = self.base()(obj); + except Exception: + verbose("The object %s can not be casted into an element of the coefficient ring" %(obj), level=_sage_const_3 ); + res = _sage_const_0 ; + for coeff in self.getCoefficients(): + res += coeff*obj; + obj = self.derivate()(obj); + return res; + + def __repr__(self): + return ("%s(%s)"%(self.__class__.__name__,self.__coefficients.__repr__())); + + ####################################################### + + diff --git a/ajpastor/operator/operator.py b/ajpastor/operator/operator.py new file mode 100644 index 0000000..73ac3b6 --- /dev/null +++ b/ajpastor/operator/operator.py @@ -0,0 +1,516 @@ + +# This file was *autogenerated* from the file ./operator.sage +from sage.all_cmdline import * # import sage library + +_sage_const_1 = Integer(1); _sage_const_0 = Integer(0) + +#################################################################################################### +#################################################################################################### +### +### Operator module +### +### ------------------------------------------------------------------------------------------------ +### +### This file contains the code for the ABSTRACT class operator, that will be a helpful tool to the +### implementation of DD-Rings and DD-Functions. +### +### ------------------------------------------------------------------------------------------------ +### +### Version: 2.0 +### Date of begining: 21-11-2016 +### +### Updated (28-04-2017) +### - Added forward and backward polynomial +### - Added the computation of the jp_value and jp_matrix +### - Added a cache functionality to high cost elements +### +### Updated (05-04-2017) +### - Added the Ring_w_Sequence to this file +### - Deleted the method _get_sequence_element +### - Added cached methods "parent" and "derivate" +### - Deleted the method getCoefficientsParent (now self.parent() do the same) +### - Rearrange file: more readable structure +### - Added the methods add_solution, mult_solution, derivative_solution and integral_solution. +### - Added the methods __compute_add_solution, __compute_mult_solution, __compute_derivative_solution and __compute_integral_solution. +### - Added a preference system for casting operators (see methods *_solution and get_preference +### - Changed name: method "isZero" to "is_zero" +### +### Updated (21-08-2017) +### - Changed name parent to base +### +#################################################################################################### +#################################################################################################### + +from ajpastor.misc.cached_property import derived_property; +from ajpastor.misc.ring_w_sequence import Ring_w_Sequence; +from ajpastor.misc.ring_w_sequence import Wrap_w_Sequence_Ring; + +## GENERIC UTILITIES METHODS +def foo_derivative(p): + try: + return p.derivative(x); + except AttributeError: + return _sage_const_0 ; + +## Operator class +class Operator(object): + ### Static parameters + _op_preference = _sage_const_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 = -_sage_const_1 ; + names = [str(gen) for gen in base.base_ring().gens()]; + while(self.__pol_var in names): + i += _sage_const_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 getOrder(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 order(self): + return self.getOrder(); + + def getCoefficients(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 coefficients(self): + return self.getCoefficients(); + + def getCoefficient(self, i): + ''' + This method allows the user to get a coefficient 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): + return self.getCoefficient(i); + + @cached_method + def companion(self, element=None): + field = self.base().fraction_field(); + + if(element is None): + coefficients = [field(el) for el in self.getCoefficients()]; + else: + coefficients = [field(el(x=element)) for el in self.getCoefficients()]; + + ## We divide by the leading coefficient + coefficients = [-(coefficients[i]/coefficients[-_sage_const_1 ]) for i in range(len(coefficients)-_sage_const_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 = [[_sage_const_0 for i in range(d-_sage_const_1 )] + [coefficients[_sage_const_0 ]]]; + for i in range(d-_sage_const_1 ): + rows += [[kronecker_delta(i,j) for j in range(d-_sage_const_1 )] + [coefficients[i+_sage_const_1 ]]]; + + ## Returning the matrix + return Matrix(field, rows); + ####################################################### + + ####################################################### + ### 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 neccessary. + + 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 >= _sage_const_0 ): + try: + return self.forward(n); + except IndexError: + return _sage_const_0 ; + else: + return self.backward(-n); + + @cached_method + def forward(self, n): + if(n < _sage_const_0 ): + raise IndexError("Forward polynomials have only positive index"); + elif(n > self.getOrder()): + return _sage_const_0 ; + elif(self.is_zero()): + return _sage_const_0 ; + else: + var = self.__polynomialRing.gens()[-_sage_const_1 ]; + + return sum([self.base().sequence(self.getCoefficient(l),l-n)*falling_factorial(var+n,l) for l in range(n, self.getOrder()+_sage_const_1 )]); + + @cached_method + def backward(self, n): + if(n < _sage_const_0 ): + raise IndexError("Backward polynomials have only positive index"); + elif(self.is_zero()): + return _sage_const_0 ; + + var = self.__polynomialRing.gens()[-_sage_const_1 ]; + return sum([self.base().sequence(self.getCoefficient(l), l+n)*falling_factorial(var-n, l) for l in range(_sage_const_0 ,self.getOrder()+_sage_const_1 )]); + + def get_recursion_row(self,i): + r = self.getCoefficients(); + d = self.getOrder(); + 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 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): + ''' + This method computes a new operator that annihilates any solution of 'self' compose with 'other' (an element that must be in the base ring). + ''' + ## 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): + ''' + This method computes a new operator such any solution of 'self == 0' plus 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_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__); + ####################################################### + + ####################################################### + ### MAGIC PYTHON METHODS + ####################################################### + def __getitem__(self, key): + if(key > self.getOrder() or key < _sage_const_0 ): + raise IndexError("No coefficient can be get bigger than the order or with negative index"); + return self.getCoefficient(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.getOrder()+_sage_const_1 ): + res += self.getCoefficient(i)*to_mult; + to_mult = to_mult.derivative(); + return res; + except Exception: + raise NotImplementedError('Method not implemented -- Abstract class asked'); + + ### 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(-_sage_const_1 ).add(other).scalar(-_sage_const_1 ); + except Exception: + return NotImplemented; + + ### Multiplication + def __mul__(self, other): + try: + return self.mult(other); + except Exception: + return NotImplemented; + + ### Reverse addition + def __radd__(self, other): + return self.__add__(other); + + ### Reverse substraction + def __rsub__(self, other): + return self.scalar(-_sage_const_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(-_sage_const_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))); + diff --git a/ajpastor/operator/oreOperator.py b/ajpastor/operator/oreOperator.py new file mode 100644 index 0000000..f65051d --- /dev/null +++ b/ajpastor/operator/oreOperator.py @@ -0,0 +1,210 @@ + +# This file was *autogenerated* from the file ./oreOperator.sage +from sage.all_cmdline import * # import sage library + +_sage_const_1 = Integer(1); _sage_const_0 = Integer(0) + +#################################################################################################### +#################################################################################################### +### +### Ore-Operator module +### +### ------------------------------------------------------------------------------------------------ +### +### This file contains a wrapper that adapts the OreOperator class from Manuel Kauer's package (see +### ore_algebra.OreOperator) so we can use them together with other classes of Operators +### +### ------------------------------------------------------------------------------------------------ +### +### Version: 2.0 +### Date of begining: 21-11-2016 +### +### Update (05-05-2017) +### - Deleted the method getCoefficientParent (now the Operator method parent() do the same) +### - Deleted the field "parent" (it is stored in the Operator class) +### - Made the imports more precise +### - Created a new attribute "__oa" where the OreAlgebra of the element is stored +### - Rearrange file: more readable structure +### - Added the methods __compute_add_solution, __compute_mult_solution, __compute_derivative_solution and __compute_integral_solution. +### - Changed name: method "isZero" to "is_zero" +### +### Update (10-05-2017) +### - Updated the code for the new version of ore_algebra package ( +### +### Updated (21-08-2017) +### - Changed name parent to base +### +### ------------------------------------------------------------------------------------------------ +### Dependencies: +### - Operator class +### - ore_algebra package +#################################################################################################### +#################################################################################################### + +# General imports +from ore_algebra import OreAlgebra; +from ore_algebra.ore_operator import OreOperator; + +# Local imports +from .operator import Operator; +from .directStepOperator import DirectStepOperator; +from .operator import foo_derivative; + +class w_OreOperator(Operator): + ### Static parameters + map_to_algebras = {}; + _op_preference = _sage_const_1 ; + + ####################################################### + ### 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. It can be an element of the associated + OreAlgebra for `base` or a list of elements of `base` + ''' + ## Initializing the minimal structure + super(w_OreOperator, self).__init__(base, input, derivate); + + ## Computing the Ore_Algebra associated with base + if(not w_OreOperator.map_to_algebras.has_key(base)): + newOreAlgebra = OreAlgebra(base, ('D', lambda p : p, derivate)); + w_OreOperator.map_to_algebras[self.base()] = newOreAlgebra; + + self.__oa = w_OreOperator.map_to_algebras[self.base()]; + d = self.__oa.gen(); + + ## Computing the operator + try: + if isinstance(input, w_OreOperator): + self.operator = input.operator; + else: + if isinstance(input, Operator): + input = input.getCoefficients(); + if isinstance(input, list): + res = self.__oa(_sage_const_0 ); + for i in range(len(input)): + res += self.base()(input[i])*d**i; + self.operator = res; + else: + self.operator = self.__oa(input); + + if(self.operator == _sage_const_0 ): + self.operator = d; + + except TypeError as e: + raise e; + except Exception as e: + raise TypeError('The input (%s) is not valid for an oreOperator with coefficients in (%s)\nCaused by: %s - %s' %(input,format(base),type(e),e)); + + + ### Getter methods + def getOrder(self): + return self.operator.order(); + + def getCoefficients(self): + return self.operator.coefficients(sparse=False); + + def getCoefficient(self, i): + if (i < _sage_const_0 ): + raise IndexError('The argument must be a number greater or equal to zero'); + elif (i < self.getOrder()+_sage_const_1 ): + return self.getCoefficients()[i]; + + return _sage_const_0 ; + ####################################################### + + ####################################################### + ### OPERATOR ARITHMETIC METHODS (ABSTRACT) + ####################################################### + ### Arithmetic + def add(self, other): + if(isinstance(other, w_OreOperator)): + return w_OreOperator(self.base(), self.operator+self.__oa(other.operator)); + elif(isinstance(other, Operator)): + return self+w_OreOperator(self.base(),other.getCoefficients()); + else: + return w_OreOperator(self.base(), self.operator+self.__oa(other)); + + def scalar(self, other): + return w_OreOperator(self.base(), self.__oa(other)*self.operator); + + def mult(self, other): + if(isinstance(other, w_OreOperator)): + return w_OreOperator(self.base(), self.operator*self.__oa(other.operator)); + elif(isinstance(other, Operator)): + try: + return self*w_OreOperator(self.base(),other.getCoefficients()); + except Exception: + return other.__class__(self.base(),self).mult(other); + else: + return w_OreOperator(self.base(), self.operator*self.__oa(other)); + + ### Equality + def is_zero(self): + return self.operator == _sage_const_0 ; + + def __eq__(self, other): + try: + if(not isinstance(other, OreOperator)): + other = self.__oa(other); + return self.operator == other.operator; + except Exception: + pass; + return False; + + ### Differential + def derivative(self): + d = self.__oa.gen(); + return w_OreOperator(self.base(), d*self.operator); + ####################################################### + + ####################################################### + ### SOLUTION ARITHMETHIC METHODS (ABSTRACT) + ####################################################### + def _compute_add_solution(self, other): + try: + return w_OreOperator(self.base(),self.operator.lclm(other.operator, algorithm="linalg")); + except TypeError: + return w_OreOperator(self.base(),self.operator.lclm(other.operator, algorithm="euclid")); + + def _compute_mult_solution(self, other): + return w_OreOperator(self.base(),self.operator.symmetric_product(other.operator)); + + def _compute_derivative_solution(self): + d = self.__oa.gen(); + try: + return w_OreOperator(self.base(), self.operator/d); + except ValueError: + c = self.getCoefficient(_sage_const_0 ); + dc = self.derivate()(c); + + return w_OreOperator(self.base(), ((c*d - dc)*self.operator)/d); + + def _compute_integral_solution(self): + return w_OreOperator(self.base(),self.operator.annihilator_of_integral()); + + def _compute_compose_solution(self, other): + op1 = DirectStepOperator(self.base(), self, self.derivate()); + + return w_OreOperator(self.base(), op1._compute_compose_solution(other), self.derivate()); + ####################################################### + + ####################################################### + ### MAGIC PYTHON METHODS + ####################################################### + def __call__(self, obj): + return self.operator(obj); + + def __repr__(self): + return ("Wrapped_OreOperator(%s)"%(self.operator.__repr__())); + + ####################################################### + + diff --git a/ajpastor/operator/polynomialLazyOperator.py b/ajpastor/operator/polynomialLazyOperator.py new file mode 100644 index 0000000..f0c5560 --- /dev/null +++ b/ajpastor/operator/polynomialLazyOperator.py @@ -0,0 +1,201 @@ + +# This file was *autogenerated* from the file ./polynomialLazyOperator.sage +from sage.all_cmdline import * # import sage library + +_sage_const_1 = Integer(1); _sage_const_0 = Integer(0); _sage_const_4 = Integer(4) + +#################################################################################################### +#################################################################################################### +### +### PolynomialLazyOperator module +### +### ------------------------------------------------------------------------------------------------ +### +### This file contains an extension of a LazyStepOperator that computes the kernell of the appropiate matrix trnaslating the elements into polynomials and reversing the change once obtained the final vector. +### +### If some relations between the variables are known, Groebner basis reduction will be used. +### ------------------------------------------------------------------------------------------------ +### +### Version: 0.0 +### Date of begining: 05-05-2017 +### +### Updated (21-08-2017) +### - Changed name parent to base +### +### +### ------------------------------------------------------------------------------------------------ +### Dependencies: +### - TwoStepsOperator class +#################################################################################################### +#################################################################################################### + +# Local imports +from .twoStepsOperator import TwoStepsOperator; +from .operator import foo_derivative; + +from ajpastor.lazy.lazyIDElements import LazyIntegralDomain; +from ajpastor.lazy.lazyToPoly import LazyToPoly; + +from ajpastor.misc.bareiss import BareissAlgorithm; + +class PolynomialLazyOperator(TwoStepsOperator): + ### Static parameters + _op_preference = _sage_const_4 ; + + ####################################################### + ### INIT METHOD AND GETTERS + ####################################################### + def __init__(self, base, input, derivate = foo_derivative): + super(PolynomialLazyOperator, self).__init__(base, input, derivate); + + self.__conversion = None; + + ####################################################### + + @cached_method + def companion(self, element=None): + field = LazyIntegralDomain(self._original_base).fraction_field(); + + if(element is None): + coefficients = [field(el) for el in self.getCoefficients()]; + else: + coefficients = [field(el(x=element)) for el in self.getCoefficients()]; + + if(self.__conversion is None): + self.__conversion = LazyToPoly(self.base(), coefficients); + + coefficients = self.__conversion.to_poly(coefficients); + + ## We divide by the leading coefficient + coefficients = [-(coefficients[i]/coefficients[-_sage_const_1 ]) for i in range(len(coefficients)-_sage_const_1 )]; + ## Trying to reduce the elements + try: + for i in range(len(coefficients)): + coefficients[i].reduce(); + except AttributeError: + pass; + d = len(coefficients); + + ## Building the rows of our matrix + rows = [[_sage_const_0 for i in range(d-_sage_const_1 )] + [coefficients[_sage_const_0 ]]]; + for i in range(d-_sage_const_1 ): + rows += [[kronecker_delta(i,j) for j in range(d-_sage_const_1 )] + [coefficients[i+_sage_const_1 ]]]; + + ## Returning the matrix + return Matrix(self.__conversion.poly_field(), rows); + + def _mix_matrices(self, other, Mf, Mg): + new_conversion = self.__conversion.mix_conversion(other.__conversion); + self.__conversion = new_conversion; + self.__dict__['companion'].cache.clear(); + other.__conversion = new_conversion; + other.__dict__['companion'].cache.clear(); + + + return self.companion(), other.companion(), self.companion().parent().base(); + + def _compose_companion(self, Mf, g): + ## Creating the new conversion system adding `dg` + dg = self.derivate()(self.base()(g)); + new_conversion = self.__conversion.mix_conversion(dg); + + ## Changing the conversion system of `self` + self.__conversion = new_conversion; + self.__dict__['companion'].cache.clear(); + + ## Returning the new companion matrix and the conversed version of `dg` + return self.companion(g), new_conversion.to_poly(dg), self.companion(g).parent().base(); + + def _pre_proc(self, M): + return self.__conversion.to_real(M); + + def _post_proc(self, M): + try: + return self.__conversion.to_poly(M); + except Exception: + new_conversion = self.__conversion.mix_conversion(LazyToPoly(self.base(), M)); + self.__conversion = new_conversion; + self.__dict__['companion'].cache.clear(); + + return self.__conversion.to_poly(M); + + + ####################################################### + ### SOLVING MATRICES METHOD + ####################################################### + def _get_element_nullspace(self, M): + ## We take the domain where our elements will lie + ## conversion->lazy domain->domain + parent = self.__conversion.base().base(); + + ## We assume the matrix is in the correct space + M = self.__conversion.simplify(M); + + ## We clean denominators to lie in the polynomial ring + lcms = [lcm([el.denominator() for el in row]) for row in M]; + R = M.parent().base().base(); + M = Matrix(R, [[el*lcms[i] for el in M[i]] for i in range(M.nrows())]); + f = lambda p: self.__smart_is_null(p); + try: + assert(f(_sage_const_1 ) == False); + except Exception: + raise ValueError("The method to check membership is not correct"); + + from sage.rings.polynomial.polynomial_ring import is_PolynomialRing as isUniPolynomial; + from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing as isMPolynomial; + ## Computing the kernell of the matrix + if(isUniPolynomial(R) or isMPolynomial(R)): + bareiss_algorithm = BareissAlgorithm(R,M,f); + ker = bareiss_algorithm.right_kernel_matrix(); + ## If some relations are found during this process, we add it to the conversion system + self.__conversion.add_relations(bareiss_algorithm.relations()); + else: + ker = [v for v in M.right_kernel_matrix()]; + + ## If the nullspace has high dimension, we try to reduce the final vector computing zeros at the end + aux = [row for row in ker]; + i = _sage_const_1 ; + + while(len(aux) > _sage_const_1 ): + new = []; + current = None; + for j in range(len(aux)): + if(aux[j][-(i)] == _sage_const_0 ): + new += [aux[j]]; + elif(current is None): + current = j; + else: + new += [aux[current]*aux[j][-(i)] - aux[j]*aux[current][-(i)]]; + current = j; + aux = [el/gcd(el) for el in new]; + i = i+_sage_const_1 ; + + + ## When exiting the loop, aux has just one vector + sol = aux[_sage_const_0 ]; + sol = [self.__conversion.simplify(a) for a in sol]; + + ## This steps for clearing denominator are no longer needed + #lazyLcm = lcm([a.denominator() for a in sol]); + #finalLazySolution = [a.numerator()*(lazyLcm/(a.denominator())) for a in sol]; + + ## Just to be sure everything is as simple as possible, we divide again by the gcd of all our + ## coefficients and simplify them using the relations known. + fin_gcd = gcd(sol); + finalSolution = [a/fin_gcd for a in sol]; + finalSolution = [self.__conversion.simplify(a) for a in finalSolution]; + + ## We transform our polynomial into elements of our destiny domain + finalSolution = [self.__conversion.to_real(a).raw() for a in finalSolution]; + + return vector(parent, finalSolution); + ####################################################### + + def __smart_is_null(self, p): + try: + return any(self.__conversion.to_real(factor[_sage_const_0 ]).raw().is_null for factor in p.factor()); + except AttributeError: + return self.__conversion.to_real(p).raw().is_null; + + + diff --git a/ajpastor/operator/twoStepsOperator.py b/ajpastor/operator/twoStepsOperator.py new file mode 100644 index 0000000..487ba95 --- /dev/null +++ b/ajpastor/operator/twoStepsOperator.py @@ -0,0 +1,148 @@ + +# This file was *autogenerated* from the file ./twoStepsOperator.sage +from sage.all_cmdline import * # import sage library + +_sage_const_1 = Integer(1); _sage_const_0 = Integer(0) + +#################################################################################################### +#################################################################################################### +### +### TwoStepsOperator module +### +### ------------------------------------------------------------------------------------------------ +### +### This file contains an abstract extension of a ListOperator that add the structure for different algorithms fro computing the solution methods of operators +### +### ------------------------------------------------------------------------------------------------ +### +### Version: 0.0 +### Date of begining: 05-05-2017 +### +### Updated (21-08-2017) +### - Changed name parent to base +### +### +### ------------------------------------------------------------------------------------------------ +### Dependencies: +### - ListOperator class +#################################################################################################### +#################################################################################################### + +# Local imports +from .listOperator import ListOperator; +from .operator import foo_derivative; + +class TwoStepsOperator(ListOperator): + + ####################################################### + ### INIT METHOD AND GETTERS + ####################################################### + def __init__(self, base, input, derivate = foo_derivative): + super(TwoStepsOperator, self).__init__(base, input, derivate); + + ####################################################### + + ####################################################### + ### SOLUTION ARITHMETHIC METHODS (ABSTRACT) + ####################################################### + def _compute_add_solution(self, other): + M = self._get_matrix_add(other); + v = self._get_element_nullspace(M); + + return self.__class__(self.base(), [el for el in v], self.derivate()); + + def _compute_mult_solution(self, other): + M = self._get_matrix_mult(other); + v = self._get_element_nullspace(M); + + return self.__class__(self.base(), [el for el in v], self.derivate()); + + def _compute_compose_solution(self, other): + M = self._get_matrix_composition(other); + v = self._get_element_nullspace(M); + + return self.__class__(self.base(), [el for el in v], self.derivate()); + ####################################################### + + ####################################################### + ### GETTING MATRICES METHODS + ####################################################### + def _get_matrix_add(self, other): + ''' + Method to obtain a matrix such any element of the nullspace can be interpreted as a new operator that annihilates the sum (f+g) where f is solution to 'self=0' and g is solution to 'other=0' + ''' + from ajpastor.misc.matrix import diagonal_matrix as diagonal; + from ajpastor.misc.matrix import matrix_of_dMovement as move; + + Mf = self.companion(); + Mg = other.companion(); + + Mf, Mg, parent = self._mix_matrices(other, Mf, Mg); + + full_companion = diagonal(parent, [Mf,Mg]); + init_vector = vector(parent,([_sage_const_1 ] + [_sage_const_0 for i in range(self.getOrder()-_sage_const_1 )])+([_sage_const_1 ] + [_sage_const_0 for i in range(other.getOrder()-_sage_const_1 )])); + + return self._post_proc(move(self._pre_proc(full_companion), self._pre_proc(init_vector), self.derivate(), full_companion.ncols()+_sage_const_1 )); + + def _get_matrix_mult(self, other): + ''' + Method to obtain a matrix such any element of the nullspace can be interpreted as a new operator that annihilates the product (fg) where f is solution to 'self=0' and g is solution to 'other=0' + ''' + from ajpastor.misc.matrix import block_matrix as block; + from ajpastor.misc.matrix import diagonal_matrix as diagonal; + from ajpastor.misc.matrix import matrix_of_dMovement as move; + + f = self; + g = other; + + Mf = f.companion(); + Mg = g.companion(); + + Mf, Mg, parent = self._mix_matrices(other, Mf, Mg); + + ## Using tensor product + full_companion = Mf.tensor_product(Matrix.identity(parent.base(), Mg.nrows())) + Matrix.identity(parent.base(), Mf.nrows()).tensor_product(Mg); + + init_vector = vector(parent,([_sage_const_1 ] + [_sage_const_0 for i in range(f.getOrder()*g.getOrder()-_sage_const_1 )])); + + return self._post_proc(move(self._pre_proc(full_companion), self._pre_proc(init_vector), self.derivate(), full_companion.ncols()+_sage_const_1 )); + + def _get_matrix_composition(self, other): + + from ajpastor.misc.matrix import matrix_of_dMovement as move; + + g = other; + + Mf = self.companion(g); + + Mf,dg, parent = self._compose_companion(Mf,g); + + full_companion = dg*Mf; + init_vector = vector(parent, [_sage_const_1 ] + [_sage_const_0 for i in range(_sage_const_1 ,self.getOrder())]); + + return self._post_proc(move(self._pre_proc(full_companion), self._pre_proc(init_vector), self.derivate(), full_companion.ncols()+_sage_const_1 )); + + def _mix_matrices(self, other, Mf, Mg): + return Mf, Mg, Mf.parent().base(); + + def _compose_companion(self,Mf,g): + return Mf, self.derivate()(self.base()(g)), Mf.parent().base(); + + def _pre_proc(self, M): + return M; + + def _post_proc(self, M): + return M; + ####################################################### + + ####################################################### + ### SOLVING MATRICES METHOD + ####################################################### + def _get_element_nullspace(self, M): + ''' + Method that computes an element in the nullspace of M. + ''' + raise NotImplementedError('Method not implemented. Class: %s' %self.__class__); + ####################################################### + + diff --git a/ajpastor/tests/__init__.py b/ajpastor/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ajpastor/tests/dd_functions/__init__.py b/ajpastor/tests/dd_functions/__init__.py new file mode 100644 index 0000000..2daee45 --- /dev/null +++ b/ajpastor/tests/dd_functions/__init__.py @@ -0,0 +1,10 @@ +def run(): + __LIST_OF_FILES = ["ddFunction", "ddFunction2", "examples", "identities", "hypergeometric", "chebyshev", "bessel"]; + + import importlib; + for file_name in __LIST_OF_FILES: + module = importlib.import_module(__package__+"."+file_name); + print '###############################################################################' + print "### Testing file %s" %file_name; + module.run(); + print '###############################################################################' diff --git a/ajpastor/tests/dd_functions/bessel.py b/ajpastor/tests/dd_functions/bessel.py new file mode 100644 index 0000000..d1113fb --- /dev/null +++ b/ajpastor/tests/dd_functions/bessel.py @@ -0,0 +1,92 @@ + +# This file was *autogenerated* from the file ./dd_functions/bessel.sage +from sage.all_cmdline import * # import sage library + +_sage_const_2 = Integer(2); _sage_const_1 = Integer(1); _sage_const_20 = Integer(20); _sage_const_4 = Integer(4)######################################################################## +### +### File for testing the definition of Chebyshev polynomials +### +### Here we will check several identities of the Chebyshev polynomials +### for different values of the parameter $n$. +### +######################################################################## + +from timeit import default_timer as timer; + +from ajpastor.dd_functions import *; +from ajpastor.misc.verbose import *; + + +def run(): + __MIN_N = _sage_const_1 ; + __MAX_N = _sage_const_20 ; + + __LEVEL = -_sage_const_2 ; ## Verbose level (-2 print loops, -1 print test, 0 print global data, greater print nothing + __PREV_LEVEL = sverbose.get_level(); + + sverbose.set_level(__LEVEL); + __deep_wrap = sverbose.get_deep_wrapper(); + sverbose.set_deep_wrapper(-__LEVEL); + sverbose("Running tests over Bessel Functions using the package dd_functions"); + sverbose(""); + sverbose("Author: Antonio Jimenez"); + sverbose("Date: 12-02-2018"); + sverbose(""); + sverbose("The values of the parameters will run from %d to %d" %(__MIN_N, __MAX_N)); + sverbose.increase_depth(); + + ## Starting execution + t = timer(); + + try: + sverbose("Checking 2nxJ_n(x) = J_{n-1}(x)+J_{n+1}(x)"); + sverbose.increase_depth(); + sverbose.start_iteration(__MAX_N-__MIN_N, True, True); + for n in range(__MIN_N, __MAX_N): + f = BesselD(n); fp = BesselD(n+_sage_const_1 ); fm = BesselD(n-_sage_const_1 ); + if(not _sage_const_2 *n*f == (fm+fp)*x): + raise ValueError("Error with the equality of 0F0(;;z) with initial value %d" %n); + sverbose.next_iteration(); + + sverbose.decrease_depth(); + sverbose("Finished test"); + + sverbose("Checking 2xJ_n'(x) = J_{n-1}(x)-J_{n+1}(x)"); + sverbose.increase_depth(); + sverbose.start_iteration(__MAX_N-__MIN_N, True, True); + for n in range(__MIN_N, __MAX_N): + f = BesselD(n); fp = BesselD(n+_sage_const_1 ); fm = BesselD(n-_sage_const_1 ); + if(not _sage_const_2 *f.derivative() == (fm-fp)): + raise ValueError("Error with the equality of 0F0(;;z) with initial value %d" %n); + sverbose.next_iteration(); + + sverbose.decrease_depth(); + sverbose("Finished test"); + + sverbose("Checking J_n(x) = ((x/2)^n/(n!))*0F1(;n+1;-x^2/4)"); + sverbose.increase_depth(); + sverbose.start_iteration(__MAX_N-__MIN_N, True, True); + for n in range(__MIN_N, __MAX_N): + sverbose.increase_depth(); + f = BesselD(n); + g = GenericHypergeometricFunction((),(n+_sage_const_1 ),_sage_const_1 )(-x**_sage_const_2 /_sage_const_4 ); + if(not f == ((x/_sage_const_2 )**n/factorial(n))*g): + raise ValueError("Error with the equality of 0F0(;;z) with initial value %d" %n); + sverbose.decrease_depth(); + sverbose.next_iteration(); + + sverbose.decrease_depth(); + sverbose("Finished test"); + except (Exception, KeyboardInterrupt) as e: + sverbose.reset_depth(); + sverbose.set_deep_wrapper(__deep_wrap); + sverbose.set_level(__PREV_LEVEL); + raise e; + + sverbose.decrease_depth(); + sverbose("Finished all tests over the Generic Hypergeometric functions"); + t = timer() - t; + sverbose("Parametric tests finished successfully --> %s" %(t)); + sverbose.set_deep_wrapper(__deep_wrap); + sverbose.set_level(__PREV_LEVEL); + diff --git a/ajpastor/tests/dd_functions/chebyshev.py b/ajpastor/tests/dd_functions/chebyshev.py new file mode 100644 index 0000000..db2c435 --- /dev/null +++ b/ajpastor/tests/dd_functions/chebyshev.py @@ -0,0 +1,99 @@ + +# This file was *autogenerated* from the file ./dd_functions/chebyshev.sage +from sage.all_cmdline import * # import sage library + +_sage_const_2 = Integer(2); _sage_const_20 = Integer(20); _sage_const_0 = Integer(0); _sage_const_1 = Integer(1)######################################################################## +### +### File for testing the definition of Chebyshev polynomials +### +### Here we will check several identities of the Chebyshev polynomials +### for different values of the parameter $n$. +### +######################################################################## + +from timeit import default_timer as timer; + +from ajpastor.dd_functions import *; +from ajpastor.misc.verbose import *; + +def run(): + __MIN_N = _sage_const_2 ; + __MAX_N = _sage_const_20 ; + + __LEVEL = -_sage_const_2 ; ## Verbose level (-2 print loops, -1 print test, 0 print global data, greater print nothing + + __PREV_LEVEL = sverbose.get_level(); + + sverbose.set_level(__LEVEL); + __deep_wrap = sverbose.get_deep_wrapper(); + sverbose.set_deep_wrapper(-__LEVEL); + sverbose("Running test over the Chebyshev polynomials using dd_functions package"); + sverbose(""); + sverbose("Author: Antonio Jimenez"); + sverbose("Date: 12-07-2017"); + sverbose(""); + sverbose("All values of the parameter will run from %d to %d" %(__MIN_N, __MAX_N)); + sverbose.increase_depth(); + + ## Starting execution + t = timer(); + + try: + ### Checking the recursive equality + sverbose("Recursion formula: p_{n+1} = 2xp_n - p_{n-1}"); + sverbose.increase_depth(); + sverbose.start_iteration(__MAX_N-__MIN_N, True, True); + for n in range(__MIN_N, __MAX_N): + ch1 = [ChebyshevD(n+_sage_const_1 ),ChebyshevD(n),ChebyshevD(n-_sage_const_1 )]; + ch2 = [ChebyshevD(n+_sage_const_1 ,_sage_const_2 ),ChebyshevD(n,_sage_const_2 ),ChebyshevD(n-_sage_const_1 ,_sage_const_2 )]; + is_true = [(ch1[_sage_const_0 ] == _sage_const_2 *x*ch1[_sage_const_1 ] - ch1[_sage_const_2 ]),(ch2[_sage_const_0 ] == _sage_const_2 *x*ch2[_sage_const_1 ] - ch2[_sage_const_2 ])]; + if(not is_true[_sage_const_0 ]): + raise ValueError("Error with the recurrence equality for Chebyshev of first kind (%d)" %n); + if(not is_true[_sage_const_1 ]): + raise ValueError("Error with the recurrence equality for Chebyshev of second kind (%d)" %n); + sverbose.next_iteration(); + + sverbose.decrease_depth(); + + sverbose("Finished test"); + + ### Checking Pell's equation + sverbose("Pell's equation: T_n^2 - (x^2-1)U_{n-1}^2 = 1"); + sverbose.increase_depth(); + sverbose.start_iteration(__MAX_N-__MIN_N, True, True); + for n in range(__MIN_N, __MAX_N): + T = ChebyshevD(n,_sage_const_1 ); U = ChebyshevD(n-_sage_const_1 ,_sage_const_2 ); + if(not (T**_sage_const_2 -(x**_sage_const_2 -_sage_const_1 )*U**_sage_const_2 == _sage_const_1 )): + raise ValueError("Error with the Pell's equation for n=%d" %n); + sverbose.next_iteration(); + + sverbose.decrease_depth(); + + ### Derivation properties + sverbose("Derivative equations"); + sverbose.increase_depth(); + sverbose.start_iteration(__MAX_N-__MIN_N, True, True); + for n in range(__MIN_N, __MAX_N): + T = [ChebyshevD(n,_sage_const_1 ),ChebyshevD(n+_sage_const_1 ,_sage_const_1 )]; U = [ChebyshevD(n,_sage_const_2 ),ChebyshevD(n-_sage_const_1 ,_sage_const_2 )]; + if(not (T[_sage_const_0 ].derivative() == n*U[-_sage_const_1 ])): + raise ValueError("Error with the derivate of $T_%d$" %n); + if(not (U[_sage_const_0 ].derivative() == ((n+_sage_const_1 )*T[_sage_const_1 ] - x*U[_sage_const_0 ]).divide(DFinite(x**_sage_const_2 -_sage_const_1 )))): + raise ValueError("Error with the derivate of $U_%d$" %n); + sverbose.next_iteration(); + + sverbose.decrease_depth(); + + sverbose("Finished test"); + except (Exception, KeyboardInterrupt) as e: + sverbose.reset_depth(); + sverbose.set_deep_wrapper(__deep_wrap); + sverbose.set_level(__PREV_LEVEL); + raise e; + + sverbose.decrease_depth(); + sverbose("Finished all tests over the Chebyshev polynomials"); + t = timer() - t; + sverbose("Chebyshev polynomials tests finished successfully --> %s" %(t)); + sverbose.set_deep_wrapper(__deep_wrap); + sverbose.set_level(__PREV_LEVEL); + diff --git a/ajpastor/tests/dd_functions/ddFunction.py b/ajpastor/tests/dd_functions/ddFunction.py new file mode 100644 index 0000000..129d5c8 --- /dev/null +++ b/ajpastor/tests/dd_functions/ddFunction.py @@ -0,0 +1,218 @@ + +# This file was *autogenerated* from the file ./dd_functions/ddFunction.sage +from sage.all_cmdline import * # import sage library + +_sage_const_3 = Integer(3); _sage_const_2 = Integer(2); _sage_const_1 = Integer(1); _sage_const_0 = Integer(0); _sage_const_6 = Integer(6); _sage_const_4 = Integer(4); _sage_const_8 = Integer(8); _sage_const_10 = Integer(10); _sage_const_16 = Integer(16); _sage_const_32 = Integer(32) +import sys, traceback; +from timeit import default_timer as timer; + +from ajpastor.misc.verbose import *; +from ajpastor.dd_functions import *; + + +def run(): + __LEVEL = -_sage_const_2 ; ## Verbose level (-2 print loops, -1 print test, 0 print global data, greater print nothing + __PREV_LEVEL = sverbose.get_level(); + + ########################################## + ### AUXILIARY FUNCTIONS + ########################################## + def assert_initialValues(func, values, name): + for i in range(len(values)): + aux = func.getInitialValue(i); + assert (aux == values[i]), "Error in the %d initial value of the function %s: expected %s but got %s"%(i,name,values[i],aux); + + def assert_coefficients(func, values, name): + aux = func.getOperator().getCoefficients() + assert (aux == values), "Error in the coefficients of the function %s: expected %s but got %s"%(name,values,aux); + + #################################################################################### + + sverbose.set_level(__LEVEL); + __deep_wrap = sverbose.get_deep_wrapper(); + sverbose.set_deep_wrapper(-__LEVEL); + sverbose("Running basic tests over the implementation of DDRings and their functions"); + sverbose(""); + sverbose("Author: Antonio Jimenez"); + sverbose("Date: 12-02-2018"); + sverbose(""); + sverbose.increase_depth(); + + + ##Starting execution + t = timer(); + ######################################### + ### TESTS FOR STRUCTURES DDRing and DDFunction + ######################################### + try: + + _aux = None; # Auxiliar variable for useless assigments + + ## Creation and Initial Values tests + sverbose("Test for creation and initial values computation"); + sin = DFinite_example('sin'); + assert_coefficients(sin, [_sage_const_1 ,_sage_const_0 ,_sage_const_1 ], 'sin'); + assert_initialValues(sin, [_sage_const_0 , _sage_const_1 , _sage_const_0 , -_sage_const_1 , _sage_const_0 , _sage_const_1 ], 'sin'); + + # Inhomogeneous tests + sverbose("Test for inhomogeneous creation of elements"); + b = DFinite.element([_sage_const_1 ], inhomogeneous = x**_sage_const_3 +_sage_const_1 ) + assert_coefficients(b, [-_sage_const_3 *x**_sage_const_2 ,(x**_sage_const_3 +_sage_const_1 )], '3x^2 + 1'); + + # Differential tests + sverbose("Test for constant checking"); + a=DFinite.element([_sage_const_0 ,_sage_const_1 ], [_sage_const_1 ]); + assert_initialValues(a, [_sage_const_1 ,_sage_const_0 ,_sage_const_0 ,_sage_const_0 ,_sage_const_0 ,_sage_const_0 ], 'constant=1'); + + #Derivation + sverbose("Test for derivation of elements"); + _aux = a.derivative(); + assert_coefficients(_aux, [_sage_const_0 ,_sage_const_1 ], 'constant=0'); + assert_initialValues(_aux, [_sage_const_0 ,_sage_const_0 ,_sage_const_0 ,_sage_const_0 ,_sage_const_0 ,_sage_const_0 ], 'constant=0'); + + cos = sin.derivative(); + assert_coefficients(cos, [_sage_const_1 ,_sage_const_0 ,_sage_const_1 ], 'cos'); + assert_initialValues(cos, [_sage_const_1 , _sage_const_0 , -_sage_const_1 , _sage_const_0 , _sage_const_1 , _sage_const_0 ], 'cos'); + + #Integration + sverbose("Test for integration of elements"); + _aux = a.integrate(); + assert_coefficients(_aux, [_sage_const_0 ,_sage_const_0 ,_sage_const_1 ], 'x'); + assert_initialValues(_aux, [_sage_const_0 ,_sage_const_1 ,_sage_const_0 ,_sage_const_0 ,_sage_const_0 ,_sage_const_0 ], 'x'); + + _aux = sin.integrate(-_sage_const_1 ); + assert_coefficients(_aux, [_sage_const_0 ,_sage_const_1 ,_sage_const_0 ,_sage_const_1 ], '-cos'); + assert_initialValues(_aux, [-_sage_const_1 , _sage_const_0 , _sage_const_1 , _sage_const_0 , -_sage_const_1 , _sage_const_0 ], '-cos'); + + # Summation, multiplication and scalar tests + # Summation + sverbose("Test for addition of elements (exp(x) + exp(x))"); + e = DFinite_example('e'); + _aux = e.add(e) + assert_coefficients(_aux, [-_sage_const_1 ,_sage_const_1 ], '2exp(x)'); + assert_initialValues(_aux, [_sage_const_2 ,_sage_const_2 ,_sage_const_2 ,_sage_const_2 ,_sage_const_2 ,_sage_const_2 ], '2exp(x)'); + + #Multiplication + sverbose("Test for product of elements (exp(x) * exp(x))"); + _aux = e.mult(e) + assert_coefficients(_aux, [-_sage_const_2 ,_sage_const_1 ], 'exp(2x)'); + assert_initialValues(_aux, [_sage_const_1 ,_sage_const_2 ,_sage_const_4 ,_sage_const_8 ,_sage_const_16 ,_sage_const_32 ], 'exp(2x)'); + + #Scalar + sverbose("Test for scalar product of elements (6*exp(x))"); + _aux = e.scalar(_sage_const_6 ) + assert_coefficients(_aux, [-_sage_const_1 ,_sage_const_1 ], '6exp(x)'); + assert_initialValues(_aux, [_sage_const_6 ,_sage_const_6 ,_sage_const_6 ,_sage_const_6 ,_sage_const_6 ,_sage_const_6 ], '6exp(x)'); + + # Zero and Equality methods + # Simple equalities (f == f) + # f == f + sverbose("Test for equality 1 (reflexiveness)"); + assert e.equals(e), "Error with self-equality"; + + # 2*f == f+f + sverbose("Test for equality 2 (exp(x)+exp(x) == 2*exp(x))"); + assert e.add(e).equals(e.scalar(_sage_const_2 )), "Error with the equality 2e == e+e"; + + # Different representations + sverbose("Test for equality 3 (same function with different representations)"); + _aux = DFinite.element([_sage_const_0 ,_sage_const_0 ,_sage_const_1 ,-_sage_const_1 ], [_sage_const_1 ,_sage_const_1 ,_sage_const_1 ]); + assert e.equals(_aux), 'Error with the equality from different representations for same function'; + + #sin^2+cos^2 = 1 + sverbose("Test for equality 4 (cos^2(x)+sin^2(x) == 1)"); + unity = DFinite(_sage_const_1 ); + assert unity.equals(sin.mult(sin).add(cos.mult(cos))), 'Error with sin^2 + cos^2 == 1 using basic operations'; + + #sin(2x) = 2sin(x)cos(x) + sverbose("Test for equality 5 (sin(2x) == 2sin(x)cos(x))"); + sin2 = DFinite.element([_sage_const_4 ,_sage_const_0 ,_sage_const_1 ], [_sage_const_0 ,_sage_const_2 ]); + assert sin2.equals(sin.mult(cos).scalar(_sage_const_2 )), 'Error with sin(2x) == 2sin(x)cos(x) using basic operations'; + + #cos(2x) = cos^2(x)-sin^2(x) + sverbose("Test for equality 6 (cos(2x) = cos^2(x)-sin^2(x))"); + cos2 = DFinite.element([_sage_const_4 ,_sage_const_0 ,_sage_const_1 ], [_sage_const_1 ,_sage_const_0 ]); + assert cos2.equals(cos.mult(cos).sub(sin.mult(sin))), 'Error with cos(2x) == cos^2(x)-sin^2(x) using basic operations'; + + # Python user friendly methods tests + sverbose("Test for magic Python methods"); + sverbose.increase_depth(); + # Object methods + try: + b[-_sage_const_1 ]; + assert False, "Error getting position (-1). Previously, this meant inhomogeneous term. Now is nothing." + except IndexError: + pass; + # Python user friendly methods tests + sverbose("Direct arithmetic"); + _aux = DFinite.element([x,-_sage_const_1 ],[_sage_const_1 ]); + assert (-sin).equals(sin.scalar(-_sage_const_1 )), 'Error checking unary -'; + assert (sin+_aux).equals(sin.add(_aux)), 'Error checking + for DFinite'; + assert (cos-e).equals(cos.sub(e)), 'Error checking - for DFinite'; + assert (sin*sin).equals(sin.mult(sin)), 'Error checking * for DFinite'; + assert (sin**_sage_const_2 ).equals(sin.mult(sin)), 'Error checking ** with value 2 for DFinite'; + assert DFinite(_sage_const_1 ).equals(sin**_sage_const_0 ), 'Error checking ** with value 0 for DFinite'; + assert (cos**_sage_const_3 ).equals(cos.mult(cos.mult(cos))), 'Error checking ** with value 3 for DFinite'; + try: + aux = sin**cos; + assert False, "Error catching a NotImplementedError for method __pow__"; + except (NotImplementedError): + pass; + try: + aux = pow(sin, -_sage_const_3 ); + assert False, "Error catching a ZeroDivisionError for method __pow__"; + except ZeroDivisionError: + pass; + + sverbose("In-place arithmetic"); + inPlace = sin; + inPlace += e; + assert inPlace.equals(sin+e), 'Error checking inplace + for DFinite'; + inPlace = sin; + inPlace -= cos; + assert inPlace.equals(sin-cos), 'Error checking inplace - for DFinite'; + inPlace = cos; + inPlace *= sin; + assert inPlace.equals(sin.mult(cos)), 'Error checking inplace * for DFinite'; + inPlace = sin; + inPlace **= _sage_const_2 ; + inPlace += cos**_sage_const_2 ; + assert inPlace.equals(DFinite(_sage_const_1 )), 'Error checking implace ** for DFinite'; + + sverbose("Python equality (==)"); + assert (sin**_sage_const_2 +cos**_sage_const_2 == _sage_const_1 ), 'Error checking equality sin^2+cos^2 == 1'; + assert (not (cos2 == (sin*cos).scalar(_sage_const_2 ))), 'Error checking the inequality cos(2x) != 2sin(x)cos(x)'; + assert (sin2 == (sin*cos).scalar(_sage_const_2 )), 'Error checking equality sin(2x) = 2sin(x)cos(x)'; + assert (cos2 == cos**_sage_const_2 -sin**_sage_const_2 ), 'Error checking equality cos(2x) == cos^2(x)+sin^2(x)'; + _aux=DFinite(_sage_const_3 *x**_sage_const_2 +x+_sage_const_1 ); + assert (_aux.derivative() == _sage_const_6 *x+_sage_const_1 ), 'Error checking equality D(3x^2+x+1) == 6x+1'; + sverbose.decrease_depth(); + + sverbose("Test over initial values required"); + # Impossible get initial values -- Giving more than expected + try: + f1 = DFinite.element([-_sage_const_4 *x**_sage_const_3 ,_sage_const_1 ,-x], [_sage_const_0 ,_sage_const_0 ,_sage_const_2 ]); + f2 = DFinite.element([-_sage_const_1 -x,x], [_sage_const_0 ,_sage_const_1 ]); + f1*f2; + except Exception: + assert False, "Error with DD-Function with more initial values than needed"; + + # Impossible get initial values -- Giving less than expected + _aux = DFinite.element([x,-_sage_const_1 ]); + _aux = e*_aux; + assert_coefficients(_aux, [-(x+_sage_const_1 ),_sage_const_1 ], 'e^x*e^(-x^2/2)'); + assert (_aux.getInitialValueList(_sage_const_10 ) == []), "Error getting initial values: expected [] but got %s"%(_aux.getInitialValueList(_sage_const_10 )); + except (Exception, KeyboardInterrupt) as e: + sverbose.reset_depth(); + sverbose.set_deep_wrapper(__deep_wrap); + sverbose.set_level(__PREV_LEVEL); + print "Error found during tests: %s" %(e); + raise e; + + sverbose.decrease_depth(); + sverbose("Finished all the basic tests"); + t = timer() - t; + sverbose("Basic tests finished successfully --> %s" %(t)); + sverbose.set_deep_wrapper(__deep_wrap); + sverbose.set_level(__PREV_LEVEL); + diff --git a/ajpastor/tests/dd_functions/ddFunction2.py b/ajpastor/tests/dd_functions/ddFunction2.py new file mode 100644 index 0000000..ad24b27 --- /dev/null +++ b/ajpastor/tests/dd_functions/ddFunction2.py @@ -0,0 +1,123 @@ + +# This file was *autogenerated* from the file ./dd_functions/ddFunction2.sage +from sage.all_cmdline import * # import sage library + +_sage_const_3 = Integer(3); _sage_const_2 = Integer(2); _sage_const_9 = Integer(9); _sage_const_0 = Integer(0) +import sys, traceback, warnings; +from timeit import default_timer as timer; + +from ajpastor.misc.verbose import *; +from ajpastor.dd_functions import *; +from ajpastor.dd_functions.ddFunction import DDFunctionWarning; + +def run(): + __LEVEL = -_sage_const_2 ; ## Verbose level (-2 print loops, -1 print test, 0 print global data, greater print nothing + __PREV_LEVEL = sverbose.get_level(); + + ########################################## + ### AUXILIARY FUNCTIONS + ########################################## + def assert_initial(func, symbolic, size, name): + for i in range(size): + func_val = func.getInitialValue(i); + real_val = symbolic.derivative(i)(x=_sage_const_0 ); + assert (func_val == real_val), "Error in the %d initial value of the function %s: expected %s but got %s"%(i,name,real_val,func_val); + + #################################################################################### + + sverbose.set_level(__LEVEL); + __deep_wrap = sverbose.get_deep_wrapper(); + sverbose.set_deep_wrapper(-__LEVEL); + sverbose("Running advanced tests over the implementation of DDRings and their functions"); + sverbose(""); + sverbose("Author: Antonio Jimenez"); + sverbose("Date: 14-02-2018"); + sverbose(""); + sverbose.increase_depth(); + + + ##Starting execution + warnings.simplefilter('ignore',DDFunctionWarning); + t = timer(); + ######################################### + ### TESTS FOR STRUCTURES DDRing and DDFunction + ######################################### + try: + + ## Creation for the DDRing get the same object + sverbose("Test for checking the uniqueness of DDRings"); + sverbose.increase_depth(); + sverbose("Iteration vs. Direct"); + assert DDRing(DDFinite) is DDRing(PolynomialRing(QQ,x), depth=_sage_const_3 ), "Error building DDRing. Not uniqueness detected from iteration against the direct step"; + assert DDRing(DFinite, depth=_sage_const_2 ) is DDRing(PolynomialRing(QQ,x), depth=_sage_const_3 ), "Error building DDRing. Not uniqueness detected from a semi-direct step against the direct step"; + + sverbose("P. DDRings with Symbols vs. P. DDRings with strings"); + assert ParametrizedDDRing(DFinite, 'a') is ParametrizedDDRing(DFinite, var('a')), "Error building parameters. Not uniqueness from string or symbol"; + + sverbose("Order on the parameters"); + assert ParametrizedDDRing(DFinite, ['a','q']) is ParametrizedDDRing(DFinite, ['q', 'a']), "Error building parameters. Not uniqueness with the order of parameters"; + + sverbose("Adding parameters vs. Create parameters"); + assert ParametrizedDDRing(DFinite, ['a','q']) is ParametrizedDDRing(ParametrizedDDRing(DFinite, 'a'), 'q'), "Error building parameters. Not uniqueness adding variables instead of creating them"; + + sverbose("Checking repeated parameters"); + assert ParametrizedDDRing(ParametrizedDDRing(DFinite, 'a'), 'a') is ParametrizedDDRing(DFinite, 'a'), "Error building parameters. Not uniqueness when adding the same parameter"; + assert ParametrizedDDRing(DFinite, ['a', 'q']) is ParametrizedDDRing(ParametrizedDDRing(DFinite, 'a'), ['a', 'q']), "Error building parameters. Not uniqueness when adding some repeated parameters"; + + sverbose("Checking depth with parameters"); + assert ParametrizedDDRing(DDFinite, ['a','q']).base() is ParametrizedDDRing(DFinite, ['a','q']), "Error building parameters. Not uniqueness in P(DD,var).base() and P(D,var)"; + assert ParametrizedDDRing(DDFinite, 'P').base() is DFiniteP, "Error building parameters. Not uniqueness in P(DD,var).base() and P(D,var)"; + sverbose.decrease_depth(); + + sverbose("Testing pushouts"); + sverbose.increase_depth(); + sverbose.start_iteration(_sage_const_9 , True, True); + + from sage.categories.pushout import pushout; + DDFiniteP = ParametrizedDDRing(DDFinite, 'P'); + DDFiniteA = ParametrizedDDRing(DDFinite, 'a'); + DFiniteA = DDFiniteA.base(); + DDFiniteQ = ParametrizedDDRing(DDFinite, 'q'); + DFiniteQ = DDFiniteQ.base(); + DDFiniteAQ = ParametrizedDDRing(DDFinite, ['a','q']); + DFiniteAQ = DDFiniteAQ.base(); + DDFiniteAP = ParametrizedDDRing(DDFinite, ['a','P']); + DFiniteAP = DDFiniteAP.base(); + DDFiniteAQP = ParametrizedDDRing(DDFinite, ['a','q','P']); + DFiniteAQP = DDFiniteAQP.base(); + + assert DFiniteP is pushout(DFinite, DFiniteP), "Error in pushouts: D - D(P) -- D(P)"; + sverbose.next_iteration(); + assert DDFiniteP is pushout(DFinite, DDFiniteP), "Error in pushouts: D - DD(P) -- DD(P)"; + sverbose.next_iteration(); + assert DDFiniteP is pushout(DDFinite, DFiniteP), "Error in pushouts: DD - D(P) -- DD(P)"; + sverbose.next_iteration(); + assert DDFiniteAQ is pushout(DDFiniteA, DDFiniteQ), "Error in pushouts: DD(a) - DD(q) -- DD(a,q)"; + sverbose.next_iteration(); + assert DDFiniteAQ is pushout(DFiniteA, DDFiniteQ), "Error in pushouts: D(a) - DD(q) -- DD(a,q)"; + sverbose.next_iteration(); + assert DDFiniteAQ is pushout(DFiniteA, DDFiniteAQ), "Error in pushouts: D(a) - DD(a,q) -- DD(a,q)"; + sverbose.next_iteration(); + assert DFiniteAQ is pushout(DFiniteA, DFiniteQ), "Error in pushouts: D(a) - D(q) -- D(a,q)"; + sverbose.next_iteration(); + assert DDFiniteAQP is pushout(DFiniteP, DDFiniteAQ), "Error in pushouts: D(p) - DD(a,q) -- DD(a,q,P)"; + sverbose.next_iteration(); + assert DDFiniteAQP is pushout(DFiniteAP, DDFiniteAQ), "Error in pushouts: D(a,P) - D(a,q) -- D(a,q,P)"; + sverbose.next_iteration(); + sverbose.decrease_depth(); + + except (Exception, KeyboardInterrupt) as e: + sverbose.reset_depth(); + sverbose.set_deep_wrapper(__deep_wrap); + sverbose.set_level(__PREV_LEVEL); + print "\nError found during tests: %s" %(e); + raise e; + + sverbose.decrease_depth(); + sverbose("Finished all the advanced tests"); + t = timer() - t; + sverbose("Advanced tests finished successfully --> %s" %(t)); + warnings.simplefilter('always',DDFunctionWarning); + sverbose.set_deep_wrapper(__deep_wrap); + sverbose.set_level(__PREV_LEVEL); + diff --git a/ajpastor/tests/dd_functions/examples.py b/ajpastor/tests/dd_functions/examples.py new file mode 100644 index 0000000..a7b65c7 --- /dev/null +++ b/ajpastor/tests/dd_functions/examples.py @@ -0,0 +1,216 @@ + +# This file was *autogenerated* from the file ./dd_functions/examples.sage +from sage.all_cmdline import * # import sage library + +_sage_const_3 = Integer(3); _sage_const_2 = Integer(2); _sage_const_1 = Integer(1); _sage_const_0 = Integer(0); _sage_const_4 = Integer(4); _sage_const_8 = Integer(8); _sage_const_10 = Integer(10) +import sys, traceback; +from timeit import default_timer as timer; + +from ajpastor.misc.verbose import *; +from ajpastor.dd_functions import *; + + +def run(): + __MIN_N = _sage_const_2 ; + __MAX_N = _sage_const_10 ; + __DIFF = __MAX_N - __MIN_N; + + __LEVEL = -_sage_const_2 ; ## Verbose level (-2 print loops, -1 print test, 0 print global data, greater print nothing + __PREV_LEVEL = sverbose.get_level(); + + def assert_initial(func, symbolic, size, name): + for i in range(size): + func_val = func.getInitialValue(i); + real_val = symbolic.derivative(i)(x=_sage_const_0 ); + assert (func_val == real_val), "Error in the %d initial value of the function %s: expected %s but got %s"%(i,name,real_val,func_val); + + def random_polynomial(min=-_sage_const_10 ,max=_sage_const_10 ,degree=_sage_const_3 ): + R = PolynomialRing(QQ,x); + p = R(_sage_const_1 ); + while(p.degree() <= _sage_const_0 ): + p = R([randint(min,max) for i in range(degree+_sage_const_1 )]) + + return p; + + + #################################################################################### + + sverbose.set_level(__LEVEL); + __deep_wrap = sverbose.get_deep_wrapper(); + sverbose.set_deep_wrapper(-__LEVEL); + sverbose("Running tests over the example functions of DDRings"); + sverbose(""); + sverbose("Author: Antonio Jimenez"); + sverbose("Date: 13-02-2018"); + sverbose(""); + sverbose.increase_depth(); + + + ##Starting execution + t = timer(); + ######################################### + ### TESTS FOR STRUCTURES DDRing and DDFunction + ######################################### + try: + + sverbose("Checking built-in functions"); + sverbose.increase_depth(); + sverbose.start_iteration(_sage_const_8 +_sage_const_4 *__DIFF, True, True); + sverbose.increase_depth(); + assert_initial(Exp(x), exp(x), _sage_const_10 , "exp(x)"); + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + assert_initial(Sin(x), sin(x), _sage_const_10 , "sin(x)"); + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + assert_initial(Cos(x), cos(x), _sage_const_10 , "cos(x)"); + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + assert_initial(Sinh(x), sinh(x), _sage_const_10 , "sinh(x)"); + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + assert_initial(Cosh(x), cosh(x), _sage_const_10 , "cosh(x)"); + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + assert_initial(Tan(x), tan(x), _sage_const_10 , "Tan(x)"); + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + assert_initial(Log(x+_sage_const_1 ), log(x+_sage_const_1 ), _sage_const_10 , "log(x+1)"); + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + assert_initial(Log1(x), log(x+_sage_const_1 ), _sage_const_10 , "log(x+1)(v2)"); + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + for n in range(__MIN_N, __MAX_N): + assert_initial(BesselD(n), bessel_J(n,x), _sage_const_10 , "bessel_J(%d,x)" %n); + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + for n in range(__MIN_N, __MAX_N): + assert_initial(LegendreD(n), legendre_P(n,x), _sage_const_10 , "legendre_P(%d,x)" %n); + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + for n in range(__MIN_N, __MAX_N): + assert_initial(ChebyshevD(n,_sage_const_1 ), chebyshev_T(n,x), _sage_const_10 , "chebyshev_T(%d,x)" %n); + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + for n in range(__MIN_N, __MAX_N): + assert_initial(ChebyshevD(n,_sage_const_2 ), chebyshev_U(n,x), _sage_const_10 , "chebyshev_U(%d,x)" %n); + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + + sverbose.decrease_depth(); + sverbose.decrease_depth(); + sverbose("Finished tests"); + + sverbose("Checking built-in functions with polynomial input"); + sverbose.increase_depth(); + sverbose.start_iteration(_sage_const_8 *__DIFF, True, True); + sverbose.increase_depth(); + for i in range(__MIN_N, __MAX_N): + p = x*random_polynomial(); + assert_initial(Exp(p), exp(p), _sage_const_10 , "exp(%s)" %p); + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + assert_initial(Sin(p), sin(p), _sage_const_10 , "sin(%s)" %p); + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + assert_initial(Cos(p), cos(p), _sage_const_10 , "cos(%s)" %p); + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + assert_initial(Sinh(p), sinh(p), _sage_const_10 , "sinh(%s)" %p); + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + assert_initial(Cosh(p), cosh(p), _sage_const_10 , "cosh(%s)" %p); + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + assert_initial(Tan(p), tan(p), _sage_const_10 , "Tan(%s)" %p); + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + assert_initial(Log(p+_sage_const_1 ), log(p+_sage_const_1 ), _sage_const_10 , "log(%s)" %(p+_sage_const_1 )); + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + assert_initial(Log1(p), log(p+_sage_const_1 ), _sage_const_10 , "log(%s)(v2)" %(p+_sage_const_1 )); + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + + sverbose.decrease_depth(); + sverbose.decrease_depth(); + sverbose("Finished tests"); + + sverbose("Checking composition of functions with polynomials"); + sverbose.increase_depth(); + sverbose.start_iteration(_sage_const_8 *__DIFF, True, True); + sverbose.increase_depth(); + for i in range(__MIN_N, __MAX_N): + p = x*random_polynomial(); + assert Exp(p) == Exp(x)(p), "Error checking the composition of 'Exp'"; + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + assert Sin(p) == Sin(x)(p), "Error checking the composition of 'Sin'"; + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + assert Cos(p) == Cos(x)(p), "Error checking the composition of 'Cos'"; + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + assert Sinh(p) == Sinh(x)(p), "Error checking the composition of 'Sinh'"; + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + assert Cosh(p) == Cosh(x)(p), "Error checking the composition of 'Cosh'"; + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + assert Tan(p) == Tan(x)(p), "Error checking the composition of 'Tan'"; + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + assert Log(p+_sage_const_1 ) == Log(x+_sage_const_1 )(p), "Error checking the composition of 'Log'"; + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + assert Log1(p) == Log1(x)(p), "Error checking the composition of 'Log'(v2)"; + sverbose.decrease_depth(); + sverbose.next_iteration(); + sverbose.increase_depth(); + + sverbose.decrease_depth(); + sverbose.decrease_depth(); + sverbose("Finished tests"); + + except (Exception, KeyboardInterrupt) as e: + sverbose.reset_depth(); + sverbose.set_deep_wrapper(__deep_wrap); + sverbose.set_level(__PREV_LEVEL); + print "\nError found during tests: %s" %(e); + raise e; + + sverbose.decrease_depth(); + sverbose("Finished all the tests on the examples"); + t = timer() - t; + sverbose("Example tests finished successfully --> %s" %(t)); + sverbose.set_deep_wrapper(__deep_wrap); + sverbose.set_level(__PREV_LEVEL); + diff --git a/ajpastor/tests/dd_functions/hypergeometric.py b/ajpastor/tests/dd_functions/hypergeometric.py new file mode 100644 index 0000000..840138f --- /dev/null +++ b/ajpastor/tests/dd_functions/hypergeometric.py @@ -0,0 +1,147 @@ + +# This file was *autogenerated* from the file ./dd_functions/hypergeometric.sage +from sage.all_cmdline import * # import sage library + +_sage_const_3 = Integer(3); _sage_const_2 = Integer(2); _sage_const_1 = Integer(1); _sage_const_10 = Integer(10); _sage_const_4 = Integer(4)######################################################################## +### +### File for testing the definition of Chebyshev polynomials +### +### Here we will check several identities of the Chebyshev polynomials +### for different values of the parameter $n$. +### +######################################################################## + +from timeit import default_timer as timer; + +from ajpastor.dd_functions import *; +from ajpastor.misc.verbose import *; + + +def run(): + __MIN_N = _sage_const_2 ; + __MAX_N = _sage_const_10 ; + + __LEVEL = -_sage_const_2 ; ## Verbose level (-2 print loops, -1 print test, 0 print global data, greater print nothing + __PREV_LEVEL = sverbose.get_level(); + + sverbose.set_level(__LEVEL); + __deep_wrap = sverbose.get_deep_wrapper(); + sverbose.set_deep_wrapper(-__LEVEL); + sverbose("Running test over the Generic Hypergeometric Functions using dd_functions package"); + sverbose(""); + sverbose("Author: Antonio Jimenez"); + sverbose("Date: 01-08-2017"); + sverbose(""); + sverbose("All values of the parameters will run from %d to %d" %(__MIN_N, __MAX_N)); + sverbose.increase_depth(); + + ## Starting execution + t = timer(); + + try: + sverbose("Identity for 0F0"); + sverbose.increase_depth(); + sverbose.start_iteration(__MAX_N-__MIN_N, True, True); + for n in range(__MIN_N, __MAX_N): + f = GenericHypergeometricFunction((),(),n); + g = n*Exp(x); + if(not f == g): + raise ValueError("Error with the equality of 0F0(;;z) with initial value %d" %n); + sverbose.next_iteration(); + + sverbose.decrease_depth(); + + sverbose("Finished test"); + + sverbose("Identity for 1F0"); + sverbose.increase_depth(); + + sverbose.start_iteration((__MAX_N-__MIN_N)**_sage_const_2 , True, True); + for a in range(__MIN_N, __MAX_N): + for n in range(__MIN_N, __MAX_N): + f = GenericHypergeometricFunction((a),(),n); + g = DFinite.element([-a,_sage_const_1 -x],[n]); + if(not f == g): + raise ValueError("Error with the equality of 1F0(%d;;z) with initial value %d" %(a,n)); + sverbose.next_iteration(); + + sverbose.decrease_depth(); + + sverbose("Finished test"); + + sverbose("Identity for 0F1"); + sverbose.increase_depth(); + sverbose.start_iteration((__MAX_N-__MIN_N)**_sage_const_2 , True, True); + for a in range(__MIN_N, __MAX_N): + for n in range(__MIN_N, __MAX_N): + f = GenericHypergeometricFunction((),(a),n); + g = DFinite.element([-_sage_const_1 ,a,x],[n]); + if(not f == g): + raise ValueError("Error with the equality of 1F0(;%d;z) with initial value %d" %(a,n)); + sverbose.next_iteration(); + + sverbose.decrease_depth(); + + sverbose("Finished test"); + + sverbose("Identity for 1F1"); + sverbose.increase_depth(); + sverbose.start_iteration((__MAX_N-__MIN_N)**_sage_const_3 , True, True); + for a in range(__MIN_N, __MAX_N): + for b in range(__MIN_N, __MAX_N): + for n in range(__MIN_N, __MAX_N): + f = GenericHypergeometricFunction((a),(b),n); + g = DFinite.element([-a,(b-x),x],[n]); + if(not f == g): + raise ValueError("Error with the equality of 1F1(%d;%d;z) with initial value %d" %(a,b,n)); + sverbose.next_iteration(); + + sverbose.decrease_depth(); + + sverbose("Finished test"); + + sverbose("Identity for 2F1 (Usual Hypergeometric function)"); + sverbose.increase_depth(); + sverbose.start_iteration((__MAX_N-__MIN_N)**_sage_const_4 , True, True); + for a in range(__MIN_N, __MAX_N): + for b in range(__MIN_N, __MAX_N): + for c in range(__MIN_N, __MAX_N): + for n in range(__MIN_N, __MAX_N): + f = HypergeometricFunction(a,b,c,n); + g = DFinite.element([-a*b,c-(a+b+_sage_const_1 )*x, x*(_sage_const_1 -x)],[n]); + if(not f == g): + raise ValueError("Error with the equality of 2F1(%d,%d;%d;z) with initial value %d" %(a,b,c,n)); + sverbose.next_iteration(); + + sverbose.decrease_depth(); + + sverbose("Finished test"); + + ### Clausen's formula + sverbose("Clausen's formula"); + sverbose.increase_depth(); + sverbose.start_iteration((__MAX_N-__MIN_N)**_sage_const_2 , True, True); + for c in range(__MIN_N, __MAX_N): + for s in range(__MIN_N, __MAX_N): + f = GenericHypergeometricFunction((_sage_const_2 *c-_sage_const_2 *s-_sage_const_1 ,_sage_const_2 *s,c-_sage_const_1 /_sage_const_2 ),(_sage_const_2 *c-_sage_const_1 ,c)); + g = GenericHypergeometricFunction((c-s-_sage_const_1 /_sage_const_2 ,s),(c)) + if(not f == g**_sage_const_2 ): + raise ValueError("Error with the Clausen's formula for parameters c=%d and s=%d" %(c,s)); + sverbose.next_iteration(); + + sverbose.decrease_depth(); + + sverbose("Finished test"); + except (Exception, KeyboardInterrupt) as e: + sverbose.reset_depth(); + sverbose.set_level(__PREV_LEVEL); + raise e; + + sverbose.decrease_depth(); + sverbose("Finished all tests over the Generic Hypergeometric functions"); + t = timer() - t; + sverbose("Hypergeometric function tests finished successfully --> %s" %(t)); + sverbose.set_deep_wrapper(__deep_wrap); + sverbose.set_level(__PREV_LEVEL); + + diff --git a/ajpastor/tests/dd_functions/identities.py b/ajpastor/tests/dd_functions/identities.py new file mode 100644 index 0000000..527557f --- /dev/null +++ b/ajpastor/tests/dd_functions/identities.py @@ -0,0 +1,100 @@ + +# This file was *autogenerated* from the file ./dd_functions/identities.sage +from sage.all_cmdline import * # import sage library + +_sage_const_3 = Integer(3); _sage_const_2 = Integer(2); _sage_const_1 = Integer(1); _sage_const_10 = Integer(10); _sage_const_0 = Integer(0) +import sys, traceback; +from timeit import default_timer as timer; + +from ajpastor.misc.verbose import *; +from ajpastor.dd_functions import *; + + +def run(): + __MIN_N = _sage_const_2 ; + __MAX_N = _sage_const_10 ; + __DIFF = __MAX_N - __MIN_N; + + __LEVEL = -_sage_const_2 ; ## Verbose level (-2 print loops, -1 print test, 0 print global data, greater print nothing + __PREV_LEVEL = sverbose.get_level(); + + def assert_initial(func, symbolic, size, name): + for i in range(size): + func_val = func.getInitialValue(i); + real_val = symbolic.derivative(i)(x=_sage_const_0 ); + assert (func_val == real_val), "Error in the %d initial value of the function %s: expected %s but got %s"%(i,name,real_val,func_val); + + def random_polynomial(min=-_sage_const_10 ,max=_sage_const_10 ,degree=_sage_const_3 ): + R = PolynomialRing(QQ,x); + p = R(_sage_const_1 ); + while(p.degree() == _sage_const_0 ): + p = R([randint(min,max) for i in range(degree+_sage_const_1 )]) + + return p; + + + #################################################################################### + + sverbose.set_level(__LEVEL); + __deep_wrap = sverbose.get_deep_wrapper(); + sverbose.set_deep_wrapper(-__LEVEL); + sverbose("Running tests over identities of DDFunctions"); + sverbose(""); + sverbose("Author: Antonio Jimenez"); + sverbose("Date: 13-02-2018"); + sverbose(""); + sverbose.increase_depth(); + + + ##Starting execution + t = timer(); + ######################################### + ### TESTS FOR STRUCTURES DDRing and DDFunction + ######################################### + try: + + sverbose("Checking trigonometric equalities"); + sverbose.increase_depth(); + sverbose("sin^2(x)+cos^2(x) = 1"); + assert Sin(x)**_sage_const_2 +Cos(x)**_sage_const_2 == _sage_const_1 , "Error with sin^2(x)+cos^2(x) = 1"; + sverbose("sin(2x) = 2sin(x)cos(x)"); + assert Sin(_sage_const_2 *x) == _sage_const_2 *Sin(x)*Cos(x) , "Error with sin(2x) = 2sin(x)cos(x)"; + sverbose("cos(2x) = cos^2(x) - sin^2(x)"); + assert Cos(_sage_const_2 *x) == Cos(x)**_sage_const_2 - Sin(x)**_sage_const_2 , "Error with cos(2x) = cos^2(x) - sin^2(x)"; + sverbose("tan(x) = sin(x)/cos(x)"); + sverbose.increase_depth(); + assert Tan(x) == Sin(x)/Cos(x), "Error with tan(x) = sin(x)/cos(x)"; + sverbose.decrease_depth(); + sverbose("tan'(x) = 1 + tan^2(x)"); + ## Including the sine and cosine in the system of DFinite + R = Tan(x).equation._FullLazyOperator__conversion; + #assert R(Sin(x))^2+R(Cos(x))^2 == 1, "Error with sin^2(x)+cos^2(x) = 1 in the Lazy Ring"; + sverbose.increase_depth(); + assert Tan(x).derivative() == Tan(x)**_sage_const_2 + _sage_const_1 , "Error with tan'(x) = 1 + tan^2(x)"; + sverbose.decrease_depth(); + + sverbose.decrease_depth(); + sverbose("Checking some parametric equalities"); + sverbose.increase_depth(); + sverbose("Matheiu Wronskian"); + f = MathieuCos(); g = MathieuSin(); + sverbose.increase_depth(); + assert f*g.derivative()-g*f.derivative() == _sage_const_1 , "Error with the Mathieu's Wronskian"; + sverbose.decrease_depth(); + sverbose.decrease_depth(); + sverbose("Finished tests"); + + except (Exception, KeyboardInterrupt) as e: + sverbose.reset_depth(); + sverbose.set_level(__PREV_LEVEL); + print "Error found during tests: %s" %(e); + raise e; + + sverbose.decrease_depth(); + sverbose("Finished all the tests on the examples"); + t = timer() - t; + sverbose("Example tests finished successfully --> %s" %(t)); + sverbose.set_deep_wrapper(__deep_wrap); + sverbose.set_level(__PREV_LEVEL); + + diff --git a/dependencies b/dependencies new file mode 100644 index 0000000..e1acdc8 --- /dev/null +++ b/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. \ No newline at end of file diff --git a/package-version.txt b/package-version.txt new file mode 100644 index 0000000..ba66466 --- /dev/null +++ b/package-version.txt @@ -0,0 +1 @@ +0.0 diff --git a/releases/diff_defined_functions__0.0.zip b/releases/diff_defined_functions__0.0.zip new file mode 100644 index 0000000..5b00e0f Binary files /dev/null and b/releases/diff_defined_functions__0.0.zip differ diff --git a/releases/old/diff_defined_functions__0.0__18.03.08_11:36:53.zip b/releases/old/diff_defined_functions__0.0__18.03.08_11:36:53.zip new file mode 100644 index 0000000..5b00e0f Binary files /dev/null and b/releases/old/diff_defined_functions__0.0__18.03.08_11:36:53.zip differ diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..148a4ef --- /dev/null +++ b/setup.py @@ -0,0 +1,12 @@ + +from distutils.core import setup + +setup( + name = "ajpastor", + version = "0.0", + author = "Antonio Jimenez-Pastor", + author_email = "antonio.jimenez-pastor@dk-compmath.jku.at", + licence = "GPL", + packages = ["ajpastor", "ajpastor.dd_functions", "ajpastor.operator", "ajpastor.lazy", "ajpastor.misc", "ajpastor.tests"] + ) + diff --git a/type b/type new file mode 100644 index 0000000..134d9bc --- /dev/null +++ b/type @@ -0,0 +1 @@ +optional