This paper gives a detailed overview and a number of worked out examples illustrating
the Kovacic [1] algorithm for solving second order linear differential equation
Kovacic [1] gave an algorithm for finding a closed form
Liouvillian2
solution to any linear second order differential equation
The current implementation is based on the original paper by Kovacic and uses the
new object oriented features in Maple. The accompanied software package have been
tested on
The Kovacic algorithm finds one (basis) solution of
The algorithm starts by writing the input ode
Where
is then applied to (1) which transforms it to a second order ode in the new dependent
variable
It is ode (3) which is solved by the algorithm and not (1). Equation (3) will be called the DE from now on.
If a solution
The second solution is found using reduction of order.
These are the four possible cases to consider.
Before describing how the algorithm works, there are necessary (but not sufficient) conditions that determine which case the DE satisfies. Only those cases that meet the necessary conditions will be attempted.
The following are the necessary conditions for each case. To check each case, let
Knowing the order of the poles of
If the conditions of a case are not satisfied then the case will be attempted as the algorithm
guarantees that there will be no Liouvillian solution. However if the conditions are
satisfied, this does not necessarily mean a solution exists. As an example
The following table summarizes the above conditions for each case.
Case |
Allowed pole order for |
Allowed value for |
1 |
||
2 |
Need to have at least one pole
of order |
no conditions |
3 |
||
Some observations: In case one, no odd order pole is allowed except for order 1. Case one is
the only case that could have no pole in
The above table also shows that when
These are the only two possibilities where all three cases have the same necessary conditions.
Assuming that the necessary conditions for case one are satisfied and
How this is done depends on the order of the pole as described below. If the set
If the pole
Where
If the pole is of order
Where in the above
The coefficients in the Laurent series can be obtained as follows. Given
To obtain
For the special case of the last term
The above is implemented in the function laurent_coeff() in the Kovacic class.
This completes finding all the quantities
The next step calculates the following three quantities for
If
The coefficients
The corresponding
Where
If
Here
Using quantities calculated in step
If non-negative
If no non-negative integer
In this step the algorithm finds polynomial
Where
For an example, if
If the degree
The first basis solution to the original ode is now be found from
And the second basis solution using reduction of order formula is
Hence the general solution to the original ode is
This completes the full algorithm for case 1. The part that needs most care is in finding
Assuming that the necessary conditions for case two are satisfied and
The next step is to determine
Using quantities calculated in step
Where in the above
If no non-negative integer
In this step the algorithm determines a polynomial
Where
If solution
This completes the full algorithm for case two. The general solution to the original ode is now determined as outlined at the end of case one above.
Assuming the necessary conditions for case three are satisfied and
If the pole
Where
The next step determines
Using quantities calculated in step
Where in the above
The sum above is over all families of
The product above is over families of
In this step the algorithm determines a polynomial
The last polynomial
In Maple this is done using the solve command with the identity option. If it
is possible to find coefficients
This completes the full algorithm for case three. The general solution to the original ode can now be determined as outlined at the end of case one above.
This gives summary of results obtained using testsuite of
The ode’s used in the testsuite were collected by the author and stored in sql database. These were collected from a number of standard textbooks and other references such as “Differential Equations. E. Kamke. 3th edition. Chelsea.” and “Ordinary Differential Equations And Their Solutions. Murphy, George Moseley. Dover. 2011”.
All the ode’s were successfully solved using the Kovacic algorithm as implemented here and each solution was verified using Maple odetest.
The following diagram shows the percentage of ode’s solved using each case.
Case
This result shows that case
When forcing the algorithm to use case
In comparison, the same ode was solved using case one in less than one second giving the same solution on the same computer.
The testsuite also calculates the distribution of cases which has its necessary conditions
satisfied for each ode. Recall that having the necessary conditions for a case satisfied does not
mean a solution would be found using that case. The following bar chart shows the
percentages of the
Given the ode
Converting it
Where
There is one pole at
Where
Since
The coefficient of
The above completes step 1 for case one. Step
Where the above is carried over all possible combinations resulting in the following 4
possibilities (in this example, there is only one pole, hence the sum contains only one term) and
the number of possible combinations is therefore
The above shows there are two possible
But
Since there is one pole, then the candidate
Substituting the known values found in step 1 into the above gives
So there are two possible
Starting with
Since the left side is not identically zero, then this candidate
Substituting
Therefore the solution to
Given this solution for
The second basis solution is found using reduction of order
Therefore the general solution to the original ode
This completes the solution.
Given the ode
Converting it
Where
There is one pole at
Where
The above shows that
For the second pole at
Therefore
What remains is to determine
The above completes finding
Since the order of
This completes step 1 for case one. Step 2 searches for non-negative integer
Where the above is carried over all possible combinations resulting in the following 8
possibilities (in this example, there are two poles, hence the sum contains two term) and the
number of possible combinations is therefore
There is only one possible
Which gives
Substituting values found in step 1 into the above gives
Which shows there are only two different
Because the equation is satisfied, the polynomial
Given this solution for
Which simplifies to
The second solution
This ode is a standard second order representing the oscillating harmonics ode with constant
coefficients and does not require Kovacic algorithm to solve it as it can be readily solved using
standard method by finding the roots of the characteristic equation. It is included here in order
to illustrate the Kovacic algorithm.
Converting it to
Hence
Therefore
And since
This completes step 1 for case one. Step 2 searches for non-negative integer
Since there are no poles then
Since
The above reduces to
Now that
Since
The equation is satisfied. Therefore the first solution to the ode
The first solution to the original ode in
The second solution
Therefore the general solution is
Using Euler’s formula the above can be simplified to the standard looking solution
Given the ode
Converting it
Where
There is one pole at
Therefore
Since
There is only one pole, so the sum contains only one term. There are 3 possible
combinations to try, using either
The above shows that only the family
This completes step
Substituting
Since
Next,
Substituting the values for
Solving for
Therefore the first solution to the ode
The first solution to the original ode in
The second solution
This is an ode in which the necessary conditions for all three cases are satisfied, but solved using
case two to illustrate the algorithm.
Converting it
Where
There is a pole at
The pole of order
The above shows that
The above completes step 1 for case two. Step 2 searches for a non-negative integer
Where in the above
The above shows that
The following rational function
The algorithm now searches for a monic polynomial
Since
Hence
And
Substituting the values for
Solving for
Therefore to
The first solution to the original ode in
The second solution
This is the same ode used in second example above for case two as the necessary conditions for
case three are also satisfied.
As shown earlier, this ode is transformed to
There is a pole at
Where
The above shows that
Which simplifies to
This simplifies to
The next step is to determine a non negative integer
Where in the above
This results in the following values for
Therefore only the first case using
And
This completes the step 2 of the algorithm.
Since the degree
These generate the following set
There is nothing to solve for from the last equation
Next
Solving the above and using any one of the roots results in
The above
Therefore one solution to the original ode in
The second solution to the original ode is found using reduction of order. This completes the
solution using case
The ode is
Converting it
Where
There is a pole at
Starting with
This simplifies to
The above shows that
For the pole at
The next step is to determine a non negative integer
Where in the above
This results in the following values for
From the above, the following families all produce non-negative
Starting with the smallest
And
This completes the step 2 of the algorithm.
Polynomial
The
The above results in the following set
There is coefficient for
Solving the above and using any one of the roots gives
This
The first solution to the original ode in
Which simplifies to
The second solution to the original ode is found using reduction of order.
Detailed description of the Kovacic algorithm with worked out examples were given. All three
cases of the Kovacic algorithm were implemented using object oriented design in Maple. The
software was then used to analyze over
One restriction found on the use of the algorithm is that it requires an ode with its
coefficients being numerical and not symbolic. This is because the algorithm has to decide in
step
The Kovacic class is included in the file KOV.mpl and the Kovacic testsuite module is in the file kovacic_tester.mpl. These two files accompany the arXiv version of this paper.
To use these, download these two files to some directory at your computer. For example, on windows, assuming the files were downloaded to c:/my_folder/, then now start Maple and type
read "c:/my_folder/KOV.mpl" read "c:/my_folder/kovacic_tester.mpl"
The above will load the kovacic_class and the testsuite module. Once the above is successfully completed, then to solve an ode the command is
ode := diff(y(x),x$2)+diff(y(x),x)+y(x)=0; o := Object(kovacic_class,ode,y(x)); #create the object sol := o:-dsolve();
The above command will automatically try all the cases that have been detected one by one until a solution is found. If no solution is found, it returns FAIL. To verify the solution, the command is
if sol<>FAIL then odetest(sol,ode); fi;
Which returns
A note on the type of ode’s supported: it is recommenced to use only ode’s with numeric
coefficients and not symbolic coefficients. This is because the Kovacic algorithm needs to decide
if the degree
ode :=diff(diff(y(x),x),x)=((4*n^2-1)/(4*x^2)-1)*y(x); n :=-3/2; o := Object(kovacic_class,ode,y(x)); sol := o:-dsolve();
To solve an ode using specific case number, say case
ode := ...; o := Object(kovacic_class,ode,y(x)); sol := o:-dsolve_case(2);
If the ode happened to satisfy cases
The object created above, named as “o”, has additional public methods that can be invoked. The following is description of all public methods available.
o:-get_poles() This returns list of the poles of
[ [pole location,pole order],[pole location,pole order], ...]
If there are no poles, then the empty list [] is returned.
To run the full testsuite of
kovacic_tester:-unit_test_main_api(); "Test ", 6735, " PASSED " "Test ", 6736, " PASSED " "Test ", 6737, " PASSED " . . "Test ", 7579, " PASSED " "Test ", 7580, " PASSED " "Test ", 7581, " PASSED "
To run testsuite using specific cases only, the commands are
kovacic_tester:-unit_test_case_1(); kovacic_tester:-unit_test_case_2(); kovacic_tester:-unit_test_case_3();
#-------------------------------------------------------------------- #FILE NAME : KOV.mpl # # Copyright (C) 2022, Nasser M. Abbasi. All rights reserved # email: nma@12000.org # # Free software to use and modify in anyway as long as the above # copyright notice remains attached in the file # # Change history #----------------- # Oct 27, 2022. Initial version # # Note that the latest version and any updates can alaways be obtained # from the author web site at # http://12000.org/my_notes/my_paper_on_kovacic/paper.htm # # Any problems found in the software please report so I can correct. # This implementation was done using Maple 2022.2 on windows 10. # #-------------------------------------------------------------------- #-------------------------------------------------------------------- # An Object oriented implementation of the Kovacic algortithm # using Maple 2022. # # based on original Kovacic paper description of the algorithm. # # This file contains two modules. The first is called kovacic_class # used to create kovacic object. The second is kovacic_tester module # used for unit testing the kovacic_class module # # To use this file just do # # read "KOV.mpl" # ode := diff(y(x),x$2)=2/x^2*y(x) # o := Object(kovacic_class,ode,y(x)); # sol := o:-dsolve(); # # Make sure to set the currentdir() in Maple correctly in order to find # where you downloaded the file "KOV.mpl" to. #-------------------------------------------------------------------- #-------------------------------------------------------------------- # This class is used to solve an ode using Kovacic algorithm. # # Please see documantion section in the paper for additional # information on using this class. #-------------------------------------------------------------------- kovacic_class :=module() option object; #class to hold one entry in the gamma set for case 1 local case_one_gamma_entry := module() option object; export pole_location := 0; export pole_order := 0; export sqrt_r := 0; export alpha_plus := 0; export alpha_minus := 0; export b:=0; end module; #class to hold O_infinity information for case 1 local case_one_O_inf := module() option object; export sqrt_r_inf := 0; export alpha_plus_inf := 0; export alpha_minus_inf := 0; export a := 0; export b := 0; end module; #class to hold one entry in the gamma set for case 2,3 local case_2_and_3_gamma_entry:=module() option object; export pole_location := 0; export pole_order := 0; export Ec::set := {}; export b := 0; end module; #-------------------------------------------------------------------- # PRIVATE variables for the kovacic class # only methods inside this class can access these #-------------------------------------------------------------------- local original_ode; #coefficients of original linear ode A y''+ B y' + C y =0 local A,B,C; #original ode dependent and independent variables local y::symbol,x::symbol; local modified_ode; #this is the z''=r*z ode; local z::symbol; #dependent variable for the r_ode z''(x)=r*z(x) local r,s,t; #where r=s/t; local O_inf; #order of r at infinity. degree(s)-degree(t) # poles or r. format is [ [pole,order],[pole,order],...] # if no poles, for example r=x, then list is empty [] # this means pole order zero is not in the list, since no pole. local poles_list::list := []; #contains all possible cases detected. Hence [1] or [1,2] or [1,2,3] #or remains empty [] for case 4. local list_of_possible_cases::list := []; local case_used_to_solve::integer:=-1; #this will have case used: 1,2 or 3 #this will have n=4,6 or 12 used for case 3 only local n_used_for_case_3::integer:=-1; #-------------------------------------------------------------------- # CONSTRUCTOR # # the input is the ode itself as first argument, and the # dependent variable, y(x) for example, as second argument. #-------------------------------------------------------------------- export ModuleCopy::static:= proc( _self, proto::kovacic_class, ode::`=`,func::function(name) ,$) local A,B,C; local x,y,r,z::nothing,s,t; x,y,A,B,C := parse_and_validate_ode(ode,func); _self:-original_ode := ode; #force r to be relatively prime ratio of 2 polynomials r := normal( (2*diff(B,x)*A-2*B*diff(A,x)+B^2-4*A*C)/(4*A^2)); if not type(r,'ratpoly'(anything,x)) then ERROR("r= ", r ," is not polynomial or rational function in ",x); fi; s := numer(r); t := denom(r); #save all findings into the object private data #so it can be used later _self:-modified_ode := diff(z(x),x$2) = r*z(x); _self:-O_inf := degree(t,x)-degree(s,x); _self:-r := r; _self:-x := x; _self:-y := y; _self:-z := z; _self:-s := s; _self:-t := t; _self:-C := C; _self:-B := B; _self:-A := A; #determine all poles and order _self:-generate_poles_and_order_list(); #determine all possible kovacic cases _self:-find_possible_cases(); return NULL; end proc; #-------------------------------------------------------------------- # This module private function is called by constructor to parse and # validate the ode #-------------------------------------------------------------------- local parse_and_validate_ode:=proc(ode::`=`,func::function(name),$) local x,y,A,B,C,L::list; local item,dep_variables_found; if nops(func)<>1 then ERROR("dependent variable ",func," has more than one argument"); fi; y := op(0,func); x := op(1,func); #basic verification if not has(ode,y) then ERROR("Supplied ",ode," has no ",y); fi; if not has(ode,x) then ERROR("Supplied ", ode," has no ",x); fi; if not has(ode,func) then ERROR("Supplied ",ode," has no ",func); fi; #check it is second order if PDEtools:-difforder(ode)<>2 then ERROR("Only second order ode's can be used in Kocacic algorithm. "); fi; #check all dependent variables in ode which is y, match given func y(x) try dep_variables_found := PDEtools:-Library:-GetDepVars([y],ode); catch: ERROR(lastexception); end try; #o over dep_variables_found and check the #independent variable is same as x i.e. ode can be y'(z)+y(z)=0 but #function is y(x). for item in dep_variables_found do if not type(item,function) then ERROR("Parse error. Expected ",func," found ",item," in", ode); else if op(1,item) <> x then ERROR("Parse error. Expected ",func," found ",item," in",ode); fi; if nops(item)<>1 then ERROR("Parse error. One argument allowed in ", func," found ", item," in " , ode); fi; fi; od; #now go over all indents in ode and check that y shows as y(x) #and not as just y as the PDEtools:-Library:-GetDepVars([_self:-y],ode) #code above does not detect this. i.e. it does not check y'(x)+y=0 if numelems(indets(ode,identical(y))) > 0 then ERROR("Parsing error, Can not have ",y," with no argument inside ",ode); fi; #check ode is linear in y(x) if not has(DEtools:-odeadvisor(ode,func,['linear']),'_linear') then ERROR("Only linear ode's can be used in Kocacic algorithm. "); fi; #extract coefficients of the ode L:=DEtools:-convertAlg(ode,func); #this only works on linear ode's if L[2]<>0 then ERROR("Not homogeneous ode"); fi; #Finished parsing the ode. A y''+B y' + C y = 0 C := L[1,1]; B := L[1,2]; A := L[1,3]; if not type(A,'ratpoly'(anything,x)) or not type(B,'ratpoly'(anything,x)) or not type(C,'ratpoly'(anything,x)) then error "ode coefficients are not rational functions of ",x; fi; return x,y,A,B,C; end proc; #-------------------------------------------------------------------- # main API. Called to solve the ode using Kovacic algorithm. # returns the solution in the form of y(x)=.... or if no solution # is found returns FAIL. # see user guide how to use. #-------------------------------------------------------------------- export dsolve::static:=proc(_self,$) local sol:=FAIL, current_case_number::posint; if nops(_self:-list_of_possible_cases) = 0 then return FAIL; fi; #keep trying all possible cases, starting from case 1 to case 3. #until one case succeed or all are tried for current_case_number in _self:-list_of_possible_cases do sol := _self:-dsolve_case(current_case_number); if sol<>FAIL then _self:-case_used_to_solve:=current_case_number; return sol; fi; od; return FAIL; end proc; #-------------------------------------------------------------------- # Called to solve the ode using a specific case number # made public to allow user to solve using specific case #-------------------------------------------------------------------- export dsolve_case::static:=proc(_self,case_number::posint,$) local sol; if case_number>3 then ERROR("Only case number 1,2,3 are allowed"); fi; if nops(_self:-list_of_possible_cases)=0 then ERROR("No possible cases detected for this ode"); fi; if not member(case_number,_self:-list_of_possible_cases) then ERROR("Case ", case_number, " not one of possible cases ", _self:-list_of_possible_cases); fi; if case_number = 1 then sol:= _self:-solve_case_1(); elif case_number = 2 then sol:= _self:-solve_case_2(); else sol:= _self:-solve_case_3(); fi; _self:-case_used_to_solve := case_number; return sol; end proc; #-------------------------------------------------------------------- # returns back the z''(x) = r(x) z(x) ode, which is the one # actually solved by Kovacic algorithm #-------------------------------------------------------------------- export get_z_ode::static:=proc(_self,$) local x := _self:-x; local r := _self:-r; local z := _self:-z; if _self:-s = 0 then return diff(z(x),x$2) = 0; else return diff(z(x),x$2) = numer(r)%/denom(r) * z(x); fi; end proc; #-------------------------------------------------------------------- # returns back the r term in the z''(x) = r(x) z(x) ode #-------------------------------------------------------------------- export get_r::static:=proc(_self,$) if _self:-s = 0 then return 0; else return numer(_self:-r)%/denom(_self:-r); fi; end proc; #-------------------------------------------------------------------- # returns s, where r = s/t and z'' = r z(t) #-------------------------------------------------------------------- export get_s::static:=proc(_self,$) return _self:-s; end proc; #-------------------------------------------------------------------- # returns t, where r = s/t and z'' = r z(t) #-------------------------------------------------------------------- export get_t::static:=proc(_self,$) return _self:-t; end proc; #-------------------------------------------------------------------- # Returns back the original ode ( A y''+ B y' + C y = 0 ) #-------------------------------------------------------------------- export get_y_ode::static:=proc(_self,$) return _self:-original_ode; end proc; #-------------------------------------------------------------------- # returns list of poles of r, where z''(x) = r z(x) # The list has format [ [pole location,order], ...] #-------------------------------------------------------------------- export get_poles::static:=proc(_self,$) return _self:-poles_list; end proc; #-------------------------------------------------------------------- # returns O_infinity of r, where z''(x) = r z(x) #-------------------------------------------------------------------- export get_order_at_infinity::static:=proc(_self,$) return _self:-O_inf; end proc; #-------------------------------------------------------------------- # returns list of possible kovacic cases possible. #-------------------------------------------------------------------- export get_possible_cases::static:=proc(_self,$)::list; return _self:-list_of_possible_cases; end proc; #-------------------------------------------------------------------- # returns actual case number used when solving ode. # can be 1,2 or 3 # if no cases applicable, then -1 is returned #-------------------------------------------------------------------- export get_case_used::static:=proc(_self,$)::integer; return _self:-case_used_to_solve; end proc; #-------------------------------------------------------------------- # returns n used for case 3. Either 4,6, or 12. # if not case 3 used, then -1 is returned. #-------------------------------------------------------------------- export get_n_case_3::static:=proc(_self,$)::integer; return _self:-n_used_for_case_3; end proc; #-------------------------------------------------------------------- # All functions below are private #-------------------------------------------------------------------- #-------------------------------------------------------------------- #This proc find all possible kovacic cases. These can be 1,2 or 3. #if none of these found, then empty list is returned, which is #case 4 in the paper. For example, if case 1 is only possible, #then [1] is returned. if case 1 and 2 are possible, then [1,2] #is returned, if all three cases possible, then [1,2,3] is returned. #If no cases possible then [] returned. #-------------------------------------------------------------------- local find_possible_cases::static:=proc(_self,$) local L::list := []; local poles_order := convert(_self:-poles_list[..,2],set); local T::set; #check for case 1 T := select(Z-> Z>2,poles_order); if nops( select(Z->type(Z,odd),T) )=0 and (_self:-O_inf<0 and type(_self:-O_inf,even)) or _self:-O_inf=0 or _self:-O_inf>1 then L:= [1]; fi; if nops(poles_order)>0 then #must have at least one pole for 2,3 cases if nops(poles_order)<>0 then #can not have pole order 0, i.e. no poles T:=select(Z-> Z>2,poles_order); # r must have at least one pole that is either odd order greater #than 2 or else has order 2 if nops( select(Z->type(Z,odd),T) )<>0 or member(2,poles_order) then L:= [ op(L),2 ]; fi; #check for case 3 if not member(0,poles_order) and nops(select(Z-> Z>2,poles_order))=0 and _self:-O_inf>=2 then L:= [ op(L),3 ]; fi; fi; fi; _self:-list_of_possible_cases := L; return NULL; end proc; #-------------------------------------------------------------------- #called to find all poles of r and the order #of each pole. Uses Maple's sqrfree. #-------------------------------------------------------------------- local generate_poles_and_order_list::static:=proc(_self,$) local L::list := []; local sol::list; local current_sol; local poles_list::list; local current_pole; local r := _self:-r, x := _self:-x; poles_list := sqrfree(denom(r),x); poles_list := poles_list[2,..]; #we do not need the overall factor if nops(poles_list) = 0 then _self:-poles_list:=[]; else for current_pole in poles_list do sol := solve(current_pole[1]=0,[x]); sol := ListTools:-Flatten(sol); for current_sol in sol do L := [op(L), [rhs(current_sol) ,current_pole[2] ] ]; od; od; _self:-poles_list := L; fi; return NULL; end proc: #-------------------------------------------------------------------- # # C A S E O N E I M P L E M E N T A T I O N # # returns ode solution using case 1, or FAIL is no solution exist #-------------------------------------------------------------------- local solve_case_1::static:=proc(_self,$) local O_infinity_set::kovacic_class:-case_one_O_inf; local gamma_set::set(kovacic_class:-case_one_gamma_entry):={}; gamma_set, O_infinity_set := _self:-case_1_step_1(); return _self:-case_1_step_2(gamma_set, O_infinity_set); end proc; #-------------------------------------------------------------------- # called from _self:-solve_case_1() #-------------------------------------------------------------------- local case_1_step_1::static:=proc(_self,$):: set(kovacic_class:-case_one_gamma_entry), kovacic_class:-case_one_O_inf; local current_pole; local e::kovacic_class:-case_one_gamma_entry; local o::kovacic_class:-case_one_O_inf; local b,a,v,i::integer; local b_coeff_in_r,b_coeff_in_r_inf_square; local x := _self:-x, r := _self:-r; local N::integer; local laurent_c; local current_term; local b_from_r,b_from_laurent_series; #this contains all information generated for each pole of r #this is what is called the set GAMMA in the paper and #in the diagram of algorithm above. local gamma_set::set(kovacic_class:-case_one_gamma_entry) := {}; if nops(_self:-poles_list) = 0 then gamma_set := {}; else for current_pole in _self:-poles_list do e := Object(kovacic_class:-case_one_gamma_entry); e:-pole_location := current_pole[1]; e:-pole_order := current_pole[2]; if e:-pole_order =1 then e:-sqrt_r := 0; e:-alpha_plus := 1; e:-alpha_minus := 1; gamma_set := { op(gamma_set), e }; elif e:-pole_order = 2 then e:-sqrt_r := 0; e:-b := _self:-b_partial_fraction(r,x,e:-pole_location,2); e:-alpha_plus := 1/2+1/2*sqrt(1+4*e:-b); e:-alpha_minus := 1/2-1/2*sqrt(1+4*e:-b);; gamma_set := { op(gamma_set), e }; else v := e:-pole_order/2; e:-sqrt_r := 0; for N from 2 to v do laurent_c := _self:-laurent_coeff(sqrt(r),x, e:-pole_location,v,N); current_term := laurent_c/(x-e:-pole_location)^N; e:-sqrt_r := e:-sqrt_r + current_term; if N = v then a := laurent_c; fi; od; b_from_r :=_self:-b_partial_fraction(r,x,e:-pole_location,v+1); b_from_laurent_series :=_self:-laurent_coeff(sqrt(r),x, e:-pole_location,v,v+1); e:-b := b_from_r - b_from_laurent_series; e:-alpha_plus := 1/2*((e:-b)/a + v); e:-alpha_minus := 1/2*(-(e:-b)/a + v); gamma_set := { op(gamma_set), e }; fi; od; fi; o := Object(case_one_O_inf); if _self:-O_inf > 2 then o:-sqrt_r_inf := 0; o:-alpha_plus_inf := 0; o:-alpha_minus_inf := 1; elif _self:-O_inf=2 then o:-sqrt_r_inf :=0; o:-b := lcoeff(_self:-s) / lcoeff(_self:-t); b := radsimp((1+4*o:-b)^(1/2)); o:-alpha_plus_inf := 1/2 + 1/2*b; o:-alpha_minus_inf := 1/2 - 1/2*b; else #order at infinity -2*v<= 0 which must be even v := (-_self:-O_inf) / 2; o:-sqrt_r_inf :=0; for i from 0 to v do laurent_c := _self:-laurent_coeff(sqrt(r),x,infinity,v,i); o:-sqrt_r_inf := o:-sqrt_r_inf + laurent_c*x^i; if i = v then o:-a := laurent_c; fi; od; b_coeff_in_r_inf_square := _self:-laurent_coeff( o:-sqrt_r_inf^2,x,0,v,v-1); b_coeff_in_r := _self:-get_coefficient_of_r(v-1); o:-b := b_coeff_in_r - b_coeff_in_r_inf_square; o:-alpha_plus_inf := 1/2*( (o:-b)/(o:-a) - v); o:-alpha_minus_inf := 1/2*( -(o:-b)/(o:-a) - v); fi; return gamma_set,o; end proc; #-------------------------------------------------------------------- # Finds b coefficient in r for the case one only when v<=0 # using long division #-------------------------------------------------------------------- local get_coefficient_of_r::static:=proc(_self,the_degree,$) local r := _self:-r; local x := _self:-x; local c,R,t; try c := coeff(r,x,the_degree); catch: R := rem(numer(r),denom(r),x); t := denom(r); c := lcoeff(R)/lcoeff(t); end try; return c; end proc; #-------------------------------------------------------------------- # called from _self:-solve_case_1() # This determines the set of d non-negative integers and # corresponding w for each d. #-------------------------------------------------------------------- local case_1_step_2::static:=proc(_self, gamma_set::set(kovacic_class:-case_one_gamma_entry), O_infinity::kovacic_class:-case_one_O_inf,$) local d,N,K,number_of_poles::integer,current_alpha_infinity; local item::list; local the_sign::integer,sign_list::list; local w; local r_solution,y_solution; local x := _self:-x; #this will contains all data found for any nonnegative d. Each #entry will be a list of this form # [d, w] local good_d_found := Array(1..0); local B::Matrix; #this will contain good_d_found but as matrix number_of_poles := nops(gamma_set); sign_list := combinat:-permute([1$number_of_poles,-1$number_of_poles], number_of_poles); for K,current_alpha_infinity in [O_infinity:-alpha_plus_inf,O_infinity:-alpha_minus_inf] do for item in sign_list do d := 0; if number_of_poles>0 then for N,the_sign in item do if the_sign = -1 then d := d-gamma_set[N]:-alpha_minus; else d := d-gamma_set[N]:-alpha_plus; fi; od; fi; d := simplify(d + current_alpha_infinity); if type(d,'integer') and d >= 0 then w := 0; for N,the_sign in item do if number_of_poles>0 then if the_sign = -1 then w := w +(-1)*gamma_set[N]:-sqrt_r + (gamma_set[N]:-alpha_minus)/ (x - gamma_set[N]:-pole_location); else w := w + gamma_set[N]:-sqrt_r + ( gamma_set[N]:-alpha_plus)/ (x - gamma_set[N]:-pole_location); fi; fi; od; #this to get the sign in according to paper. First + then - if K = 1 then w := w + O_infinity:-sqrt_r_inf; else w := w - O_infinity:-sqrt_r_inf; fi; good_d_found ,= [ d, w]; fi; od; od; if numelems(good_d_found) = 0 then return FAIL; fi; #now convert the array to Matrix, and sort on d, so we #start will the smallest #degree d, which is the first column, as that will be most efficient. #convert to set first, to remove any possible duplicat entries #then convert to Matrix convert(good_d_found,set); B := convert(convert(%,list),Matrix); B := B[sort(B[.., 1], 'output'= 'permutation')]; for N from 1 to LinearAlgebra:-RowDimension(B) do r_solution := _self:-case_1_step_3(B[N,1], B[N,2]); #(d,w) if r_solution <> FAIL then y_solution := _self:-build_y_solution_from_r_solution(r_solution); return y_solution; fi; od; return FAIL; end proc; #-------------------------------------------------------------------- # called from _self:-case_1_step_2() #-------------------------------------------------------------------- local case_1_step_3::static:=proc(_self,d::nonnegative,w,$) local x := _self:-x; local r := _self:-r; local p; local i::integer; local a::nothing; local eq; local coeff_sol; local tmp,W; local final_result; p := x^d; for i from d-1 by -1 to 0 do p := p + a[i] * x^i; od; #using original kovacis method. Not Smith. eq := simplify( diff(p,x$2)+2*w*diff(p,x)+(diff(w,x)+w^2-r)*p) = 0; if d = 0 then if not evalb(eq) then return FAIL; fi; else #solve for coefficients try coeff_sol := timelimit(30,solve( identity(eq,x), [seq(a[i],i=0..d-1)])); catch: return FAIL; end try; if nops(coeff_sol) = 0 then return FAIL; fi; tmp := map(evalb,coeff_sol[1]); if has(tmp,true) then return FAIL; fi; p := eval(p,coeff_sol[1]); #to force a[i] solutions to update fi; W := diff(p,x)/p + w; W := radsimp(W); tmp := diff(W,x)+W^2; if evalb( tmp = r) or is(tmp = r) or simplify(tmp-r)=0 then #can be used try tmp := int(w, x); catch: return FAIL; end try; if has(tmp,int) then return FAIL; fi; final_result := simplify(p*exp(tmp)); if has(final_result,signum) or has(final_result,csgn) then final_result := p*exp(tmp); fi; return final_result; else return FAIL; fi; end proc; #-------------------------------------------------------------------- # # C A S E T W O I M P L E M E N T A T I O N # # returns ode solution using case 2, or FAIL is no solution exist #-------------------------------------------------------------------- local solve_case_2::static:=proc(_self,$) local E_inf::set; local gamma_set::set(kovacic_class:-case_2_and_3_gamma_entry):={}; gamma_set, E_inf := _self:-case_2_step_1(); return _self:-case_2_step_2(gamma_set, E_inf ); end proc; #-------------------------------------------------------------------- # #-------------------------------------------------------------------- local case_2_step_1::static:=proc(_self,$):: set(kovacic_class:-case_2_and_3_gamma_entry),set; local current_pole; local e::kovacic_class:-case_2_and_3_gamma_entry; local E_inf::set; local b; local x := _self:-x; local r := _self:-r; #this contains all information generated for each pole of r #this is what is called the set GAMMA in the paper and in the diagram of #algorithm above. local gamma_set::set(kovacic_class:-case_2_and_3_gamma_entry) := {}; for current_pole in _self:-poles_list do e := Object(kovacic_class:-case_2_and_3_gamma_entry); e:-pole_location := current_pole[1]; e:-pole_order := current_pole[2]; if e:-pole_order = 1 then e:-Ec := {4}; gamma_set := { op(gamma_set), e }; elif e:-pole_order = 2 then e:-b := _self:-b_partial_fraction(r,x,e:-pole_location,2); e:-Ec := {2,2+2*sqrt(1+4* e:-b),2-2*sqrt(1+4* e:-b)}; e:-Ec := select(z->type(z,integer),e:-Ec); gamma_set := { op(gamma_set), e }; else e:-Ec := {e:-pole_order}; gamma_set := { op(gamma_set), e }; fi; od; if _self:-O_inf>2 then E_inf := {0,2,4}; elif _self:-O_inf=2 then b := lcoeff(_self:-s) / lcoeff(_self:-t); E_inf := {2,2+2*sqrt(1+4*b),2-2*sqrt(1+4*b)}; E_inf := select(z->type(z,integer),E_inf); else #order at infinity v< 2 E_inf := {_self:-O_inf}; fi; return gamma_set,E_inf; end proc; #-------------------------------------------------------------------- # called from _self:-solve_case_2() # This determines the set of d non-negative integers and # corresponding w for each d. #-------------------------------------------------------------------- local case_2_step_2::static:=proc(_self, gamma_set::set(kovacic_class:-case_2_and_3_gamma_entry), E_inf::set, $) local L::list := []; local item,current_E_inf; local d,N,ee,theta; local x := _self:-x; local r_solution,y_solution; #this will contains all data found for any nonnegative d. Each #entry will be a list of this form # [d, theta] #so if we obtain say 3 values of d that are nonnegative, #there will be 3 such lists in this array local good_d_found := Array(1..0); local B::Matrix; #this will contain good_d_found as matrix for item in gamma_set do L := [ op(L), convert(item:-Ec,list) ]; od; #now find all possible tuples if nops(L)>1 then L := kovacic_class:-cartProdSeq(op(L)); else L := L[1]; fi; #DEBUG(); for current_E_inf in E_inf do for item in L do d := 1/2*( current_E_inf - add(item)); if type(d,'integer') and d >= 0 then theta :=0; for N,ee in item do theta := theta + ee/(x-gamma_set[N]:-pole_location); od; theta := 1/2*theta; good_d_found ,= [ d, theta]; fi; od; od; if numelems(good_d_found) = 0 then return FAIL; fi; #now convert the array to Matrix, and sort on d, so #we start will the smallest degree d, which is the first column, as #that will be most efficient. #convert to set first, to remove any possible duplicat entries #then convert to Matrix convert(good_d_found,set); B := convert(convert(%,list),Matrix); B := B[sort(B[.., 1], 'output'= 'permutation')]; for N from 1 to LinearAlgebra:-RowDimension(B) do r_solution := _self:-case_2_step_3(B[N,1], B[N,2]); #(d,theta) if r_solution <> FAIL then y_solution := _self:-build_y_solution_from_r_solution(r_solution); return y_solution; fi; od; return FAIL; end proc; #-------------------------------------------------------------------- # called from _self:-case_2_step_2() #-------------------------------------------------------------------- local case_2_step_3::static:=proc(_self,d::nonnegative,theta,$) local p, i; local a::nothing; local tmp; local coeff_sol := []; local eq; local phi; local sol_w; local current_w; local r:=_self:-r; local w::nothing; local x := _self:-x; local sol; p := x^d; for i from d-1 by -1 to 0 do p := p + a[i] * x^i; od; eq:= simplify(diff(p,x$3) + 3*theta*diff(p,x$2) + (3*theta^2+3*diff(theta,x) -4*r) * diff(p,x) + ( diff(theta,x$2)+3*theta*diff(theta,x)+theta^3 - 4*r*theta - 2*diff(r,x))*p) = 0; if d=0 then if not evalb(eq) then return FAIL; fi; else #solve for polynomial coefficients try coeff_sol:= timelimit(30,solve( identity(eq,x), [seq(a[i],i=0..d-1)])); catch: return FAIL; end try; if nops(coeff_sol) = 0 then return FAIL; fi; tmp := map(evalb,coeff_sol[1]); if has(tmp,true) then return FAIL; fi; p := eval(p,coeff_sol[1]); #to force a[i] solutions to update fi; phi := theta + diff(p,x)/p; eq := w^2 - phi*w + simplify(1/2*diff(phi,x)+1/2*phi^2-r) = 0; try sol_w := timelimit(30,solve(eq,[w])); #changed to [] sol_w := ListTools:-Flatten(sol_w); catch: return FAIL; end try; if nops(sol_w) = 0 then return FAIL; fi; for current_w in sol_w do current_w := radsimp(rhs(current_w)); #verify w before using it. Added 1/12/2022 4 PM tmp := diff(current_w,x)+current_w^2; if evalb( tmp= r) or is(tmp = r) or simplify(tmp-r)=0 then try sol := timelimit(30,int(current_w, x)); catch: return FAIL; end try; if has(sol,int) then return FAIL; fi; return simplify(exp(sol)); fi; od; end proc; #-------------------------------------------------------------------- # # C A S E T H R E E I M P L E M E N T A T I O N # # returns ode solution using case 3, or FAIL is no solution exist #-------------------------------------------------------------------- local solve_case_3::static:=proc(_self,$) local E_inf::set; local gamma_set::set(kovacic_class:-case_2_and_3_gamma_entry):={}; local sol; #these are possible degress of w to try until one works or none works #local w_degree::list := [4,6,12]; local w_degree::list := [4,6,12]; local n::posint; for n in w_degree do gamma_set, E_inf := _self:-case_3_step_1(n); sol := _self:-case_3_step_2(gamma_set, E_inf, n ); if sol<>FAIL then return sol; fi; od; return FAIL; end proc; #-------------------------------------------------------------------- # First step in case 3 #-------------------------------------------------------------------- local case_3_step_1::static:=proc(_self, n::posint, $)::set(kovacic_class:-case_2_and_3_gamma_entry),set; local current_pole; local e::kovacic_class:-case_2_and_3_gamma_entry; local E_inf::set; local b,k; local x := _self:-x; local r := _self:-r; #this contains all information generated for each pole of r #this is what is called the set GAMMA in the paper and in the diagram of #algorithm above. local gamma_set::set(kovacic_class:-case_2_and_3_gamma_entry):={}; for current_pole in _self:-poles_list do e := Object(kovacic_class:-case_2_and_3_gamma_entry); e:-pole_location := current_pole[1]; e:-pole_order := current_pole[2]; if e:-pole_order = 1 then e:-Ec := {12}; gamma_set := { op(gamma_set), e }; elif e:-pole_order = 2 then e:-b := _self:-b_partial_fraction(r,x,e:-pole_location,2); e:-Ec := {seq( 6+12*k/n*sqrt(1+4*(e:-b)),k=-n/2..n/2,1)}; e:-Ec := select(z->type(z,integer),e:-Ec); gamma_set := { op(gamma_set), e }; else ERROR("Internal error. Case 3 can only have poles of order 1 or 2"); fi; od; #same formula, but different b b := lcoeff(_self:-s) / lcoeff(_self:-t); E_inf := {seq( 6+12*k/n*sqrt(1+4*b),k=-n/2..n/2,1)}; E_inf := select(z->type(z,integer),E_inf); return gamma_set,E_inf; end proc; #-------------------------------------------------------------------- # Second step in case 3 # # called from _self:-solve_case_3() # This determines the set of d non-negative integers and # corresponding w for each d. #-------------------------------------------------------------------- local case_3_step_2::static:=proc( _self, gamma_set::set(kovacic_class:-case_2_and_3_gamma_entry), E_inf::set, n::posint,$) local L::list := []; local item,current_E_inf; local d,ee,theta,N; local x := _self:-x; local r_solution,y_solution; local S; local current_iteration::integer; local tmp; #this will contains all data found for any nonnegative d. Each #entry will be a list of this form # [d, theta, S] #so if we obtain say 3 values of d that are nonnegative local good_d_found := Array(1..0); local B::Matrix; #this will contain good_d_found as matrix #DEBUG(); for item in gamma_set do L := [ op(L), convert(item:-Ec,list) ]; od; #now find all possible tuples if nops(L)>1 then L := kovacic_class:-cartProdSeq(op(L)); else L := L[1]; fi; current_iteration:=0; for current_E_inf in E_inf do for item in L do current_iteration := current_iteration+1; d := n/12*( current_E_inf - add(item)); if type(d,'integer') and d >= 0 then theta :=0; for N,ee in item do theta := theta + ee/(x-gamma_set[N]:-pole_location); od; theta := n/12*theta; theta := simplify(theta); S := mul( (x-gamma_set[N]:-pole_location), N=1..nops(item)); tmp := simplify(S) assuming real; if not has(tmp,csgn) and not has(tmp,signum) then S:= tmp; fi; good_d_found ,= [ d, theta, S]; fi; od; od; #now convert the array to Matrix, and sort on d, so we start # with the smallest #degree d, which is the first column, as that will be most efficient. if numelems(good_d_found) = 0 then return FAIL; fi; #convert to set first, to remove any possible duplicat entries #then convert to Matrix convert(good_d_found,set); B := convert(convert(%,list),Matrix); B := B[sort(B[.., 1], 'output'= 'permutation')]; for N from 1 to LinearAlgebra:-RowDimension(B) do #(d,theta,S,n) r_solution := _self:-case_3_step_3(B[N,1], B[N,2], B[N,3],n); if r_solution <> FAIL then _self:-n_used_for_case_3 := n; y_solution := _self:-build_y_solution_from_r_solution(r_solution); return y_solution; fi; od; return FAIL; end proc; #-------------------------------------------------------------------- # Third and final step in case 3. # called from _self:-case_3_step_2(). Returns solution or FAIL is no # solution found. #-------------------------------------------------------------------- local case_3_step_3::static:=proc( _self, d::nonnegative, theta, S, n::posint, $) local p, i,P_minus_1; local a::nothing; local final_result,tmp; local coeff_sol := []; local sol_w; local current_w; local r := _self:-r; local x := _self:-x; local omega::symbol; local P := Array(-1..n); local result_of_simplify; local result_of_is_check; local omega_equation; #this makes p(x). For example if d=3, then the result will be # p(x) = x^3 + a(2) x^2 + a(1) x + a(0) #where the number of unknowns to determine is always the same #as the degree, in this case a(0),a(1),a(2) p := x^d; #this will be 1 if degree is zero for i from d-1 by -1 to 0 do p := p + a[i] * x^i; od; #build the P_n polynomials based on p(x) above P[n] := -p; for i from n by -1 to 0 do if i=n then P[i-1] := -S * diff(P[i], x) - S*theta*P[i]; P[i-1] := simplify( P[i-1]); else P[i-1] := -S * diff(P[i], x) + ( (n-i)*diff(S,x) - S*theta)*P[i] - (n-i)*(i+1)*S^2*r*P[i+1]; P[i-1] := simplify( P[i-1]); fi; od; #solve P[-1] for p(x) if needed. P_minus_1 := expand(numer(radsimp(P[-1]))); if P_minus_1 <>0 and evalb(p<>1) then if not hastype(P_minus_1,'indexed') then #it must be indexed now ERROR("Internal error. Please report. This should not happen"); fi; try coeff_sol := timelimit(30,solve(identity(P_minus_1,x), [seq(a[i],i=0..d-1)])); catch: return FAIL; end try; if nops(coeff_sol)=0 then #unable to solve return FAIL; fi; map(evalb,coeff_sol[1]); #check all solved for if has(%,true) then return FAIL; fi; fi; #build the equation for omega omega_equation := 0; for i from 0 to n do omega_equation := omega_equation + S^i*P[i]/(n-i)! * omega^i ; od; omega_equation := simplify(omega_equation); if nops(coeff_sol)<>0 then #to force a[i] solutions to update to solved coefficients omega_equation := eval(omega_equation,coeff_sol[1]); fi; try sol_w := timelimit(30, solve(omega_equation=0, [omega])); sol_w := ListTools:-Flatten(sol_w); catch: return FAIL; end try; if nops(sol_w) = 0 then return FAIL; fi; #go over each w solution and use one that works. verify before using for current_w in sol_w do current_w := rhs(current_w); if not has(current_w, RootOf) then try current_w := timelimit(30,simplify(current_w)); catch: NULL; end try; if is_w_verified(current_w,x,r) then final_result := _self:-simplify_final_result(current_w,x); if final_result<>FAIL then return final_result; fi; fi; else #no Rootof, try to resolve try current_w := timelimit(30,[allvalues(current_w)]); catch: return FAIL; end try; current_w := current_w[1]; #just use any root. Pick first if not has(current_w, RootOf) then try current_w := timelimit(30,simplify(current_w)); catch: NULL; end try; if is_w_verified(current_w,x,r) then final_result := _self:-simplify_final_result(current_w,x); if final_result<>FAIL then return final_result; fi; fi; fi; fi; od; return FAIL; end proc; #-------------------------------------------------------------------- # Called from case 3, step 3 to verify w #-------------------------------------------------------------------- local is_w_verified:=proc(current_w,x,r)::truefalse; local tmp; local result_of_is_check::truefalse; local result_of_simplify; tmp := diff(current_w,x)+current_w^2; if evalb( tmp=r) then return true; fi; try result_of_simplify := timelimit(30,simplify(tmp-r)); if evalb(result_of_simplify=0) then return true; fi; catch: NULL; end try; try result_of_is_check := timelimit(30,is(tmp = r)); if result_of_is_check then return true; fi; catch: NULL; end try; return false; end proc; #-------------------------------------------------------------------- # Called from case 3, step 3 to simplify final result #-------------------------------------------------------------------- local simplify_final_result::static:=proc(_self,omega,x,$) local final_result; local integral_result; try integral_result := timelimit(30,int(omega, x)); catch: return FAIL; end try; if has(integral_result,int) or has(integral_result,Int) then return exp(Int(omega, x)); fi; try final_result := timelimit(30,simplify(exp(integral_result))) assuming real; if has(final_result,signum) or has(final_result,csgn) or has(final_result,abs) then final_result := exp(integral_result); fi; catch: final_result:= exp(integral_result); end try; return final_result; end proc; #-------------------------------------------------------------------- # External proc helper # provided thanks to Joseph Riel #-------------------------------------------------------------------- local cartProdSeq:= proc(L::seq(list)) local Seq::nothing,i::nothing,j; option `Copyright (C) 2007, Joseph Riel. All rights reserved.`; eval([subs(Seq= seq, foldl(Seq, [cat(i, 1..nargs)], seq(cat(i,j)= L[j], j= nargs..1, -1)))]) end proc: #-------------------------------------------------------------------- # Checks for special math function. # This function was provided thanks to Carl Love. # modified to allow some special functions. #-------------------------------------------------------------------- local has_special_math_function:= subs( _F= {(op@FunctionAdvisor)~(FunctionAdvisor("class_members", "quiet"), "quiet")[]} minus ( {FunctionAdvisor("elementary", "quiet")[]} union {erf,erfc, erfi, Im,Re,signum,max,argument} ), proc(e::algebraic, x::{name, set(name)}:= {}) hastype(e, And(specfunc(_F), dependent(x))) end proc ): #-------------------------------------------------------------------- # called if step 3 is successful. Build y solution from r solution #-------------------------------------------------------------------- local build_y_solution_from_r_solution::static:=proc(_self,r_solution,$) local y1,y2; local int_B_over_A; local A :=_self:-A, B:=_self:-B; local x :=_self:-x, y:= _self:-y; local tmp; if B = 0 then y1 := r_solution; int_B_over_A := 0; else try int_B_over_A := timelimit(60,int(-B/A,x)); if has(int_B_over_A,int) or has(int_B_over_A,RootOf) then int_B_over_A := Int(-B/A, x); y1 :=r_solution*exp(1/2*int_B_over_A); else y1 := simplify(r_solution*exp(1/2*int_B_over_A)); if has(y1,signum) or has(y1,csgn) then y1 := r_solution*exp(1/2*int_B_over_A); fi; fi; catch: int_B_over_A := Int(-B/A,x); y1 := r_solution*exp(1/2*int_B_over_A); end try; fi; try y2 := timelimit(30, int( simplify(exp(int_B_over_A))/y1^2,x)); if kovacic_class:-has_special_math_function(y2,x) then y2 := y1 * Int( exp(int_B_over_A)/y1^2,x) else y2 := y1 * y2; fi; tmp := simplify(y2); if has(tmp,signum) or has(tmp,csgn) then y2 := y1 * y2; else y2 := tmp; fi; catch: y2 := y1 * Int( exp(int_B_over_A)/y1^2,x) end try; return y(x) = _C1*y1 + _C2*y2; end proc; #-------------------------------------------------------------------- # Helper private function called to obtain b from the partial fraction # decomposition of r needed by the differenent case implementations #-------------------------------------------------------------------- local b_partial_fraction::static:=proc(_self, r, x::symbol, pole_location, the_power::posint,$) local r_partial_fraction,T::nothing; r_partial_fraction := allvalues(convert(r,'fullparfrac',x)); #adding dummy T so that select below always works r_partial_fraction := T+r_partial_fraction; select(z->hastype(z, anything/(anything*identical(x - pole_location)^the_power)), r_partial_fraction); return coeff(%,1/(x-pole_location)^the_power); end proc; #-------------------------------------------------------------------- # Helper private function called to return Laurent series coefficient # of the 1/(x-c)^n term. where n=1,2,3,.... for the function f(x) # expandid about pole of order m # if c=infinity, then x is replaced by 1/y and expansion is around 0 # for infinity, n=0,1,2,... #-------------------------------------------------------------------- local laurent_coeff::static:=proc(_self, f, x::symbol, c, m::integer, n::integer,$) local the_coeff,y::nothing,fy; if c = infinity then fy := eval(f,x=1/y); if m-n>0 then the_coeff := limit( diff( y^m * fy,y$(m-n)),y=0,right)/(m-n)!; elif m-n=0 then the_coeff := limit( y^m * fy,y=0,right)/(m-n)!; else the_coeff := 0; fi; else if m-n>0 then the_coeff := limit( diff( (x-c)^m * f,x$(m-n)),x=c,right)/(m-n)! ; elif m-n=0 then the_coeff := limit( (x-c)^m * f,x=c,right)/(m-n)!; else the_coeff := 0; fi; fi; the_coeff := eval(the_coeff,[csgn=1,signum=1]); return the_coeff; end proc; end module;
[1] Jerald J. Kovacic. An Algorithm for Solving Second Order Linear Homogeneous Differential Equations. J. Symb. Comput., 2(1):3–43, 1986.
[2] Carolyn J. Smith. A DISCUSSION AND IMPLEMENTATION OF KOVACICS ALGORITHM FOR ORDINARY DIFFERENTIAL EQUATIONS. Research Report CS-84-35, October 1984.
[3] B. David Saunders. An Implementation of Kovacic’s Algorithm for Solving Second Order Linear Homogeneous Differential Equations. Proceedings of the 1981 ACM Symposium on Symbolic and Algebraic Computation, 1981.
1The complete Kovacic package in one mpl file accompany the arXiv version of this paper
2Wikipedia defines Liouvillian function as function of one variable which is the composition of a finite
number of arithmetic operations