From: Antonio Jimenez Pastor Date: Thu, 6 Dec 2018 16:29:29 +0000 (+0100) Subject: Added the hyperbolic inverses. X-Git-Url: http://git.risc.jku.at/gitweb/?a=commitdiff_plain;h=8ae0e05b2b330371411ce7723bee3ac7a59c34c4;p=ajpastor%2Fdiff_defined_functions.git Added the hyperbolic inverses. New features added to the DDRing structure: - The method extend_base_field allows to extend the field of constants from a DDRing. Useful to add algebraic elements. New examples functions: - Tanh - Arcsinh - Arccosh - Arctanh --- diff --git a/ajpastor/dd_functions/ddExamples.py b/ajpastor/dd_functions/ddExamples.py index 04b64c1..1ea4f9b 100644 --- a/ajpastor/dd_functions/ddExamples.py +++ b/ajpastor/dd_functions/ddExamples.py @@ -32,9 +32,13 @@ def ddExamples(functions = False, names=False): - Tan - Sinh - Cosh + - Tanh - Arcsin - Arccos - Arctan + - Arcsinh + - Arccosh + - Arctanh ** EXPONENTIAL FUNCTIONS - Exp - Log @@ -195,7 +199,7 @@ def Tan(input, ddR = None): 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)]; + init_input = [factorial(i)*dR.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 @@ -275,6 +279,57 @@ def Cosh(input, ddR = None): 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)); @cached_function +def Tanh(input, ddR = None): + ''' + DD-finite implementation of the Tangent hyperbolic function (tanh(x)). + + References: + - http://mathworld.wolfram.com/HyperbolicTangent.html + - https://en.wikipedia.org/wiki/Hyperbolic_function + + This functions allows the user to fix the argument. The argument can be: + - A symbolic expression: all variables but "x" will be considered as parameters. Must be a polynomial expression with x as a factor. + - A polynomial: the first generator of the polynomial ring will be considered the variable to compute derivatives and the rest will be considered as parameters. The polynomial must be divisible by the main variable. + - A DDFunction: the composition will be computed. The DDFunction must have initial value 0. + + This function can be converted into symbolic expressions. + ''' + if(is_DDFunction(input)): + return Tanh(x)(input); + 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 = Cosh(input)**_sage_const_2 ; + + ### First we compute the new linear differential operator + newOperator = dR.element([2*dg**3, -ddg*a, dg*a]).equation; + + ### Now, we compute the initial values required + if(input == x): + newInit = [0,1]; + else: + required = newOperator.get_jp_fo()+_sage_const_1 ; + + init_tanh = Tanh(x).getInitialValueList(required); + init_input = [factorial(i)*dR.getSequenceElement(g,i) for i in range(required)]; + + newInit = [init_tanh[_sage_const_0 ]]+[sum([init_tanh[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("tanh(_1)",newName); + return result; + +@cached_function def Arcsin(input, ddR = None): ''' DD-finite implementation of the Arcsine function (arcsin(x)). @@ -311,7 +366,7 @@ def Arcsin(input, ddR = None): required = newOperator.get_jp_fo()+_sage_const_1 ; init_arcsin = Arcsin(x).getInitialValueList(required); - init_input = [factorial(i)*dR.base().getSequenceElement(g,i) for i in range(required)]; + init_input = [factorial(i)*dR.getSequenceElement(g,i) for i in range(required)]; newInit = [init_arcsin[_sage_const_0 ]]+[sum([init_arcsin[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 @@ -330,7 +385,7 @@ def Arccos(input, ddR = None): DD-finite implementation of the Arccosine function (arccos(x)). References: - - http://mathworld.wolfram.com/InverseSine.html + - http://mathworld.wolfram.com/InverseCosine.html - https://en.wikipedia.org/wiki/Inverse_trigonometric_functions This functions allows the user to fix the argument. The argument can be: @@ -362,7 +417,7 @@ def Arccos(input, ddR = None): required = newOperator.get_jp_fo()+_sage_const_1 ; init_arccos = Arccos(x).getInitialValueList(required); - init_input = [factorial(i)*dR.base().getSequenceElement(g,i) for i in range(required)]; + init_input = [factorial(i)*dR.getSequenceElement(g,i) for i in range(required)]; newInit = [init_arccos[_sage_const_0 ]]+[sum([init_arccos[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 @@ -412,7 +467,7 @@ def Arctan(input, ddR = None): required = newOperator.get_jp_fo()+_sage_const_1 ; init_arctan = Arctan(x).getInitialValueList(required); - init_input = [factorial(i)*dR.base().getSequenceElement(g,i) for i in range(required)]; + init_input = [factorial(i)*dR.getSequenceElement(g,i) for i in range(required)]; newInit = [init_arctan[_sage_const_0 ]]+[sum([init_arctan[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 @@ -425,6 +480,155 @@ def Arctan(input, ddR = None): result._DDFunction__name = DinamicString("arctan(_1)",newName); return result; +@cached_function +def Arcsinh(input, ddR = None): + ''' + DD-finite implementation of the hyperbolic Arcsine function (arcsinh(x)). + + References: + - http://mathworld.wolfram.com/InverseHyperbolicSine.html + - https://en.wikipedia.org/wiki/Inverse_hyperbolic_functions + + This functions allows the user to fix the argument. The argument can be: + - A symbolic expression: all variables but "x" will be considered as parameters. Must be a polynomial expression with x as a factor. + - A polynomial: the first generator of the polynomial ring will be considered the variable to compute derivatives and the rest will be considered as parameters. The polynomial must be divisible by the main variable. + - A DDFunction: the composition will be computed. The DDFunction must have initial value 0. + + This function can be converted into symbolic expressions. + ''' + if(is_DDFunction(input)): + return Arcsinh(x)(input); + g, dR = __decide_parent(input, ddR); + + evaluate = lambda p : dR.getSequenceElement(p,_sage_const_0 ); + if(evaluate(g) != _sage_const_0 ): + raise ValueError("Impossible to compute arcsinh(f) with f(0) != 0"); + + dg = dR.base_derivation(g); ddg = dR.base_derivation(dg); a = g**2+1 + + ### First we compute the new linear differential operator + newOperator = dR.element([dR.base().zero(),(g*dg**2 - ddg*a),dg*a]).equation; + + ### Now, we compute the initial values required + if(input == x): + newInit = [_sage_const_0,_sage_const_1]; + else: + required = newOperator.get_jp_fo()+_sage_const_1 ; + + init_arcsinh = Arcsinh(x).getInitialValueList(required); + init_input = [factorial(i)*dR.getSequenceElement(g,i) for i in range(required)]; + + newInit = [init_arcsinh[_sage_const_0 ]]+[sum([init_arcsinh[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("arcsinh(_1)",newName); + + return result; + +@cached_function +def Arccosh(input, ddR = None): + ''' + DD-finite implementation of the hyperbolic Arccosine function (arccosh(x)). + + References: + - http://mathworld.wolfram.com/InverseHyperbolicCosine.html + - https://en.wikipedia.org/wiki/Inverse_hyperbolic_functions + + This functions allows the user to fix the argument. The argument can be: + - A symbolic expression: all variables but "x" will be considered as parameters. Must be a polynomial expression with x as a factor. + - A polynomial: the first generator of the polynomial ring will be considered the variable to compute derivatives and the rest will be considered as parameters. The polynomial must be divisible by the main variable. + - A DDFunction: the composition will be computed. The DDFunction must have initial value 0. + + This function can be converted into symbolic expressions. + ''' + if(is_DDFunction(input)): + return Arccosh(x)(input); + g, dR = __decide_parent(input, ddR); + dR = dR.extend_base_field(NumberField(x**2+1, name='I')); I = dR.base_field.gens()[0]; + dR = ParametrizedDDRing(dR, 'pi'); pi = dR.parameter('pi'); + + evaluate = lambda p : dR.getSequenceElement(p,_sage_const_0 ); + if(evaluate(g) != _sage_const_0 ): + raise ValueError("Impossible to compute arccosh(f) with f(0) != 0"); + + dg = dR.base_derivation(g); ddg = dR.base_derivation(dg); a = g**2-1 + + ### First we compute the new linear differential operator + newOperator = dR.element([dR.base().zero(),(g*dg**2 - ddg*a),dg*a]).equation; + + ### Now, we compute the initial values required + if(input == x): + newInit = [I*pi/2,-I]; + else: + required = newOperator.get_jp_fo()+_sage_const_1 ; + + init_arccosh = Arccosh(x).getInitialValueList(required); + init_input = [factorial(i)*dR.getSequenceElement(g,i) for i in range(required)]; + + newInit = [init_arccosh[_sage_const_0 ]]+[sum([init_arccosh[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("arccosh(_1)",newName); + + return result; + +@cached_function +def Arctanh(input, ddR = None): + ''' + DD-finite implementation of the hyperbolic Arctangent function (arctanh(x)). + + References: + - http://mathworld.wolfram.com/InverseHyperbolicTangent.html + - https://en.wikipedia.org/wiki/Inverse_hyperbolic_functions + + This functions allows the user to fix the argument. The argument can be: + - A symbolic expression: all variables but "x" will be considered as parameters. Must be a polynomial expression with x as a factor. + - A polynomial: the first generator of the polynomial ring will be considered the variable to compute derivatives and the rest will be considered as parameters. The polynomial must be divisible by the main variable. + - A DDFunction: the composition will be computed. The DDFunction must have initial value 0. + + This function can be converted into symbolic expressions. + ''' + if(is_DDFunction(input)): + return Arctanh(x)(input); + g, dR = __decide_parent(input, ddR); + + evaluate = lambda p : dR.getSequenceElement(p,_sage_const_0 ); + if(evaluate(g) != _sage_const_0 ): + raise ValueError("Impossible to compute arctanh(f) with f(0) != 0"); + + dg = dR.base_derivation(g); ddg = dR.base_derivation(dg); a = 1-g**2; + + ### First we compute the new linear differential operator + newOperator = dR.element([dR.base().zero(), -(ddg*a + g*dg**2*2), dg*a]).equation; + + ### Now, we compute the initial values required + if(input == x): + newInit = [_sage_const_0,_sage_const_1]; + else: + required = newOperator.get_jp_fo()+_sage_const_1 ; + + init_arctanh = Arctanh(x).getInitialValueList(required); + init_input = [factorial(i)*dR.getSequenceElement(g,i) for i in range(required)]; + + newInit = [init_arctanh[_sage_const_0 ]]+[sum([init_arctanh[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("arctanh(_1)",newName); + return result; + ################################################################################## ################################################################################## ### diff --git a/ajpastor/dd_functions/ddFunction.py b/ajpastor/dd_functions/ddFunction.py index 86109db..0b0a66d 100644 --- a/ajpastor/dd_functions/ddFunction.py +++ b/ajpastor/dd_functions/ddFunction.py @@ -569,42 +569,40 @@ class DDRing (Ring_w_Sequence, IntegralDomain): Method to compute a random element in this ring. This method relies in a random generator of the self.base() ring and a generator of elements of the ring self.base_ring(). This method accepts different named arguments: - - "min_order": minimal order for the equation we would get (default to 1) - - "max_order": maximal order for the equation we would get (default to 3) - - "simple": if True, the leading coefficient will always be one (default True) - ''' - ## Getting the arguments values - min_order = kwds.get("min_order", 1); - max_order = kwds.get("max_order", 3); - simple = kwds.get("simple", True); + - "min_order": minimal order for the equation we would get (default to 1) + - "max_order": maximal order for the equation we would get (default to 3) + - "simple": if True, the leading coefficient will always be one (default True) + ''' + ## Getting the arguments values + min_order = kwds.get("min_order", 1); + max_order = kwds.get("max_order", 3); + simple = kwds.get("simple", True); - ## Checking the argument values - min_order = max(min_order,0); ## Need at least order 1 - max_order = max(min_order, max_order); ## At least the maximal order must be equal to the minimal - if(simple != True and simple != False): + ## Checking the argument values + min_order = max(min_order,0); ## Need at least order 1 + max_order = max(min_order, max_order); ## At least the maximal order must be equal to the minimal + if(simple != True and simple != False): simple = False; ## Computing the list of coefficients - R = self.base(); S = self.base_field; + R = self.base(); S = self.base_field; coeffs = [R.random_element() for i in range(randint(min_order,max_order)+1)]; - - init_values = [0]; - while(init_values[0] == 0): + + init_values = [0]; + while(init_values[0] == 0): init_values[0] = S.random_element(); - ## If we want simple elements, we can compute the initial values directly - if(simple): - coeffs[-1] = R.one(); - init_values += [S.random_element() for i in range(len(coeffs)-2)]; - return self.element(coeffs,init_values); - ## Otherwise, we need to know the initial value condition + ## If we want simple elements, we can compute the initial values directly + if(simple): + coeffs[-1] = R.one(); + init_values += [S.random_element() for i in range(len(coeffs)-2)]; + return self.element(coeffs,init_values); + ## Otherwise, we need to know the initial value condition equation = self.element(coeffs).equation; warnings.warn("Not-simple random element not implemented. Returning zero", DDFunctionWarning, stacklevel=2); - return self.zero(); + return self.zero(); - - def characteristic(self): ''' Method inherited from the Ring SAGE class. It returns the characteristic of the coefficient ring. @@ -622,6 +620,9 @@ class DDRing (Ring_w_Sequence, IntegralDomain): 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 extend_base_field(self, new_field): + return DDRing(pushout(self.original_ring(), new_field), depth = self.depth(), base_field = pushout(self.base_field, new_field), invertibility = self.base_inversionCriteria, derivation = self.base_derivation, default_operator = self.default_operator); def is_field(self, **kwds): return False; @@ -854,6 +855,10 @@ class ParametrizedDDRing(DDRing): # Override to_depth method from DDRing def to_depth(self, dest): return ParametrizedDDRing(self.base_ddRing().to_depth(dest), self.parameters(True)); + + # Override extend_base_field method from DDRing + def extend_base_field(self, new_field): + return ParametrizedDDRing(self.base_ddRing().extend_base_field(new_field), self.parameters(True)); # Override eval method from DDRing def eval(self, element, X=None, **input): diff --git a/releases/diff_defined_functions__0.6.zip b/releases/diff_defined_functions__0.6.zip index 7ab871f..91790ed 100644 Binary files a/releases/diff_defined_functions__0.6.zip and b/releases/diff_defined_functions__0.6.zip differ diff --git a/releases/old/diff_defined_functions__0.6__18.12.06_17:29:29.zip b/releases/old/diff_defined_functions__0.6__18.12.06_17:29:29.zip new file mode 100644 index 0000000..91790ed Binary files /dev/null and b/releases/old/diff_defined_functions__0.6__18.12.06_17:29:29.zip differ