(* CAPO - Computational Analysis Platform for Oberon - by Alan Freed and Felix Friedrich. *) (* Version 1, Update 2 *) MODULE MathMitLef; (** AUTHOR "adf"; PURPOSE "Mittag-Leffler function"; *) (** The Mittag-Leffler function is the characteristic solution to fractional-order differential equations, like the exponential function is the characteristic solution to ordinary differential equations. *) IMPORT NbrInt, NbrRe, NbrCplx, DataErrors, MathRe, MathGamma, MathCplx, MathCplxSeries, CalcGauss; VAR maxIterations: NbrInt.Integer; storeAlpha, storeBeta, tolerance: NbrRe.Real; yk: ARRAY 2 OF NbrRe.Real; yp: ARRAY 3 OF NbrRe.Real; TYPE MitLef = OBJECT (MathCplxSeries.Coefficient) (* Series solution for the Mittag-Leffler function. Only apply this as a solution around 0. *) PROCEDURE Evaluate*; VAR x: NbrRe.Real; BEGIN x := storeBeta + n * storeAlpha; IF GammaIsSingularAt( x ) THEN coef := 0 ELSE coef := 1 / MathGamma.Fn( x ) END; IF n = maxIterations THEN eos := TRUE; DataErrors.ReWarning( x, "Did not converge - timed out." ) END END Evaluate; END MitLef; AsympMitLef = OBJECT (MathCplxSeries.Coefficient) (* Series solution for the Mittag-Leffler function. Only apply this as a solution for large x. *) PROCEDURE Evaluate*; VAR x: NbrRe.Real; BEGIN x := storeBeta - n * storeAlpha; IF GammaIsSingularAt( x ) THEN coef := 0 ELSE coef := 1 / MathGamma.Fn( x ) END; IF n = maxIterations THEN eos := TRUE; DataErrors.ReWarning( x, "Did not converge - timed out." ) END END Evaluate; END AsympMitLef; DMitLef = OBJECT (MathCplxSeries.Coefficient) (* Series solution for the derivative of the Mittag-Leffler function. Only apply this as a solution around 0. *) PROCEDURE Evaluate*; VAR x: NbrRe.Real; BEGIN x := (1 + n) * storeAlpha + storeBeta; IF GammaIsSingularAt( x ) THEN coef := 0 ELSE coef := (1 + n) / MathGamma.Fn( x ) END; IF n = maxIterations THEN eos := TRUE; DataErrors.ReWarning( x, "Did not converge - timed out." ) END END Evaluate; END DMitLef; PROCEDURE K( x: NbrRe.Real; z: NbrCplx.Complex ): NbrCplx.Complex; (* Use the mappings: x , c, yk[0] , a, yk[1] , b, z , z. *) VAR coef1, coef2, r1: NbrRe.Real; answer, denom, numer: NbrCplx.Complex; BEGIN coef1 := MathRe.Power( x, (1 - yk[1]) / yk[0] ) / (NbrRe.Pi * yk[0]); coef2 := MathRe.Exp( -MathRe.Power( x, 1 / yk[0] ) ); r1 := NbrRe.Pi * (1 - yk[1]); numer := x * MathRe.Sin( r1 ) - z * MathRe.Sin( r1 + NbrRe.Pi * yk[0] ); denom := x * x - 2 * x * z * MathRe.Cos( NbrRe.Pi * yk[0] ) + z * z; answer := coef1 * coef2 * numer / denom; RETURN answer END K; PROCEDURE P( x: NbrRe.Real; z: NbrCplx.Complex ): NbrCplx.Complex; (* Use the mappings: x , f, yp[0] , a, yp[1] , b, yp[2] , e, z , z. *) VAR coef1, coef2, r1: NbrRe.Real; answer, denom, numer: NbrCplx.Complex; BEGIN coef1 := MathRe.Power( yp[2], 1 + (1 - yp[1]) / yp[0] ) / (2 * NbrRe.Pi * yp[0]); coef2 := MathRe.Exp( MathRe.Power( yp[2], 1 / yp[0] ) * MathRe.Cos( x / yp[0] ) ); r1 := x * (1 + (1 - yp[1]) / yp[0]) + MathRe.Power( yp[2], 1 / yp[0] ) * MathRe.Sin( x / yp[0] ); numer := MathRe.Cos( r1 ) + NbrCplx.I * MathRe.Sin( r1 ); denom := yp[2] * MathCplx.Exp( NbrCplx.I * x ) - z; answer := coef1 * coef2 * numer / denom; RETURN answer END P; PROCEDURE GammaIsSingularAt( x: NbrRe.Real ): BOOLEAN; BEGIN IF x > 0 THEN RETURN FALSE ELSIF x < 0 THEN IF x = NbrRe.Int( x ) + 1 THEN RETURN TRUE ELSE RETURN FALSE END ELSE RETURN TRUE END END GammaIsSingularAt; (** The Mittag-Leffler function, Ea,b(z) = ek=0% zk/G(ak+b), a, b N R, a > 0, z N C. *) PROCEDURE Fn*( alpha, beta, x: NbrRe.Real ): NbrRe.Real; VAR abs, answer, arg: NbrRe.Real; ml, z: NbrCplx.Complex; BEGIN z := x; ml := CplxFn( alpha, beta, z ); abs := NbrCplx.Abs( ml ); arg := NbrCplx.Arg( ml ); IF (arg < tolerance) & (-tolerance < arg) THEN answer := abs ELSIF (arg > NbrRe.Pi - tolerance) OR (arg < -NbrRe.Pi + tolerance) THEN answer := -abs ELSE DataErrors.Error( "The result is complex. Use CplxFn instead." ) END; RETURN answer END Fn; PROCEDURE CplxFn*( alpha, beta: NbrRe.Real; z: NbrCplx.Complex ): NbrCplx.Complex; VAR i1, i2, k, k0, res: NbrInt.Integer; a, absZ, argZ, b, r1, r2, x0: NbrRe.Real; answer, c1, c2: NbrCplx.Complex; ml: MitLef; aml: AsympMitLef; BEGIN (* Implements the algorithm of R. Gorenflo, I. Loutchko and Yu. Luchko, "Computation of the Mittag-Leffler function Ea,b(z) and its derivatives," Fractional Calculus and Applied Analysis, 5 (2002), 491-518.. *) alpha := NbrRe.Abs( alpha ); absZ := NbrCplx.Abs( z ); argZ := NbrCplx.Arg( z ); IF absZ < 1 / NbrRe.MaxNbr THEN IF GammaIsSingularAt( beta ) THEN answer := 0 ELSE answer := 1 / MathGamma.Fn( beta ) END ELSIF (alpha = 1) & (beta = 1) THEN answer := MathCplx.Exp( z ) ELSIF 1 < alpha THEN k0 := NbrRe.Floor( alpha ) + 1; c1 := (2 * NbrRe.Pi / k0) * NbrCplx.I; r1 := 1 / k0; r2 := alpha / k0; answer := 0; FOR k := 0 TO k0 - 1 DO c2 := MathCplx.RealPower( z, r1 ) * MathCplx.Exp( c1 * k ); answer := answer + CplxFn( r2, beta, c2 ) END; answer := answer / k0 ELSE IF absZ < 0.9 THEN BEGIN {EXCLUSIVE} storeAlpha := alpha; storeBeta := beta; i1 := NbrRe.Ceiling( (1 - beta) / alpha ); i2 := NbrRe.Ceiling( MathRe.Ln( NbrRe.Epsilon * (1 - absZ) ) / MathRe.Ln( absZ ) ); maxIterations := NbrInt.Max( i1, i2 ); NEW( ml ); answer := MathCplxSeries.PowerSeries( ml, z ) END ELSIF absZ < NbrRe.Floor( 10 + 5 * alpha ) THEN IF beta >= 0 THEN r1 := 1; x0 := NbrRe.Max( r1, 2 * absZ ); x0 := NbrRe.Max( x0, MathRe.Power( -MathRe.Ln( NbrRe.Epsilon * NbrRe.Pi / 6 ), alpha ) ) ELSE r1 := NbrRe.Abs( beta ); r2 := MathRe.Power( 1 + r1, alpha ); x0 := NbrRe.Max( r2, 2 * absZ ); r2 := 6 * (2 + r1) * MathRe.Power( 2 * r1, r1 ); x0 := NbrRe.Max( x0, MathRe.Power( -2 * MathRe.Ln( NbrRe.Epsilon * NbrRe.Pi / r2 ), alpha ) ) END; IF (NbrRe.Abs( argZ ) > alpha * NbrRe.Pi) & (NbrRe.Abs(NbrRe.Abs( argZ ) - alpha * NbrRe.Pi) > NbrRe.Epsilon) THEN IF beta < 1 + alpha THEN BEGIN {EXCLUSIVE} a := 0; b := x0; yk[0] := alpha; yk[1] := beta; answer := CalcGauss.SolveCplx( K, a, b, z, CalcGauss.Medium, tolerance, res ) END; IF res # CalcGauss.OKay THEN IF res = CalcGauss.MaxSubDivReached THEN DataErrors.Warning( "Maximum subdivisions reached when integrating K." ) ELSIF res = CalcGauss.RoundoffError THEN DataErrors.Warning( "Excessive roundoff error occurred when integrating K." ) ELSIF res = CalcGauss.RoughIntegrand THEN DataErrors.Warning( "A rough integrand encountered when integrating K." ) ELSE DataErrors.Error( "Unknown error originating from CalcGauss.SolveCplx." ) END END ELSE BEGIN {EXCLUSIVE} a := 1; b := x0; yk[0] := alpha; yk[1] := beta; answer := CalcGauss.SolveCplx( K, a, b, z, CalcGauss.Medium, tolerance, res ) END; IF res # CalcGauss.OKay THEN IF res = CalcGauss.MaxSubDivReached THEN DataErrors.Warning( "Maximum subdivisions reached when integrating K." ) ELSIF res = CalcGauss.RoundoffError THEN DataErrors.Warning( "Excessive roundoff error occurred when integrating K." ) ELSIF res = CalcGauss.RoughIntegrand THEN DataErrors.Warning( "A rough integrand encountered when integrating K." ) ELSE DataErrors.Error( "Unknown error originating from CalcGauss.SolveCplx." ) END END; BEGIN {EXCLUSIVE} a := -alpha * NbrRe.Pi; b := -a; yp[0] := alpha; yp[1] := beta; yp[2] := 1; answer := answer + CalcGauss.SolveCplx( P, a, b, z, CalcGauss.Fine, tolerance, res ) END; IF res # CalcGauss.OKay THEN IF res = CalcGauss.MaxSubDivReached THEN DataErrors.Warning( "Maximum subdivisions reached when integrating P." ) ELSIF res = CalcGauss.RoundoffError THEN DataErrors.Warning( "Excessive roundoff error occurred when integrating P." ) ELSIF res = CalcGauss.RoughIntegrand THEN DataErrors.Warning( "A rough integrand encountered when integrating P." ) ELSE DataErrors.Error( "Unknown error originating from CalcGauss.SolveCplx." ) END END END ELSIF (NbrRe.Abs( argZ ) < alpha * NbrRe.Pi) & (NbrRe.Abs(NbrRe.Abs( argZ ) - alpha * NbrRe.Pi) > NbrRe.Epsilon) THEN IF beta < 1 + alpha THEN BEGIN {EXCLUSIVE} a := 0; b := x0; yk[0] := alpha; yk[1] := beta; answer := CalcGauss.SolveCplx( K, a, b, z, CalcGauss.Medium, tolerance, res ) END; IF res # CalcGauss.OKay THEN IF res = CalcGauss.MaxSubDivReached THEN DataErrors.Warning( "Maximum subdivisions reached when integrating K." ) ELSIF res = CalcGauss.RoundoffError THEN DataErrors.Warning( "Excessive roundoff error occurred when integrating K." ) ELSIF res = CalcGauss.RoughIntegrand THEN DataErrors.Warning( "A rough integrand encountered when integrating K." ) ELSE DataErrors.Error( "Unknown error originating from CalcGauss.SolveCplx." ) END END ELSE BEGIN {EXCLUSIVE} a := absZ / 2; b := x0; yk[0] := alpha; yk[1] := beta; answer := CalcGauss.SolveCplx( K, a, b, z, CalcGauss.Medium, tolerance, res ) END; IF res # CalcGauss.OKay THEN IF res = CalcGauss.MaxSubDivReached THEN DataErrors.Warning( "Maximum subdivisions reached when integrating K." ) ELSIF res = CalcGauss.RoundoffError THEN DataErrors.Warning( "Excessive roundoff error occurred when integrating K." ) ELSIF res = CalcGauss.RoughIntegrand THEN DataErrors.Warning( "A rough integrand encountered when integrating K." ) ELSE DataErrors.Error( "Unknown error originating from CalcGauss.SolveCplx." ) END END; BEGIN {EXCLUSIVE} a := -alpha * NbrRe.Pi; b := -a; yp[0] := alpha; yp[1] := beta; yp[2] := absZ / 2; answer := answer + CalcGauss.SolveCplx( P, a, b, z, CalcGauss.Fine, tolerance, res ) END; IF res # CalcGauss.OKay THEN IF res = CalcGauss.MaxSubDivReached THEN DataErrors.Warning( "Maximum subdivisions reached when integrating P." ) ELSIF res = CalcGauss.RoundoffError THEN DataErrors.Warning( "Excessive roundoff error occurred when integrating P." ) ELSIF res = CalcGauss.RoughIntegrand THEN DataErrors.Warning( "A rough integrand encountered when integrating P." ) ELSE DataErrors.Error( "Unknown error originating from CalcGauss.SolveCplx." ) END END END; answer := answer + MathCplx.RealPower( z, (1 - beta) / alpha ) * MathCplx.Exp( MathCplx.RealPower( z, 1 / alpha ) ) / alpha ELSE BEGIN {EXCLUSIVE} a := (absZ + 1) / 2; b := x0; yk[0] := alpha; yk[1] := beta; answer := CalcGauss.SolveCplx( K, a, b, z, CalcGauss.Medium, tolerance, res ) END; IF res # CalcGauss.OKay THEN IF res = CalcGauss.MaxSubDivReached THEN DataErrors.Warning( "Maximum subdivisions reached when integrating K." ) ELSIF res = CalcGauss.RoundoffError THEN DataErrors.Warning( "Excessive roundoff error occurred when integrating K." ) ELSIF res = CalcGauss.RoughIntegrand THEN DataErrors.Warning( "A rough integrand encountered when integrating K." ) ELSE DataErrors.Error( "Unknown error originating from CalcGauss.SolveCplx." ) END END; BEGIN {EXCLUSIVE} a := -alpha * NbrRe.Pi; b := -a; yp[0] := alpha; yp[1] := beta; yp[2] := (absZ + 1) / 2; answer := answer + CalcGauss.SolveCplx( P, a, b, z, CalcGauss.Fine, tolerance, res ) END; IF res # CalcGauss.OKay THEN IF res = CalcGauss.MaxSubDivReached THEN DataErrors.Warning( "Maximum subdivisions reached when integrating P." ) ELSIF res = CalcGauss.RoundoffError THEN DataErrors.Warning( "Excessive roundoff error occurred when integrating P." ) ELSIF res = CalcGauss.RoughIntegrand THEN DataErrors.Warning( "A rough integrand encountered when integrating P." ) ELSE DataErrors.Error( "Unknown error originating from CalcGauss.SolveCplx." ) END END END ELSE (* absZ > NbrRe.Floor( 10 + 5*alpha ) *) IF argZ < 3 * alpha * NbrRe.Pi / 4 THEN answer := MathCplx.RealPower( z, (1 - beta) / alpha ) * MathCplx.Exp( MathCplx.RealPower( z, 1 / alpha ) ) / alpha ELSE answer := 0 END; BEGIN {EXCLUSIVE} storeAlpha := alpha; storeBeta := beta; (* Factor of 2 put in for 32-bit precision - needed to assure convergence. *) maxIterations := 2 * NbrRe.Floor( -MathRe.Ln( NbrRe.Epsilon ) / MathRe.Ln( absZ ) ); NEW( aml ); answer := answer - MathCplxSeries.PowerSeries( aml, 1 / z ); (* Remove the zeroth term from the sum, because the actual starts at one. *) IF ~GammaIsSingularAt( beta ) THEN answer := answer + 1 / MathGamma.Fn( beta ) END END END END; RETURN answer END CplxFn; (** The derivative of the Mittag-Leffler function, dEa,b(z)/dz = ek=0% (1+k)zk/G(a(1+k)+b). *) PROCEDURE DFn*( alpha, beta, x: NbrRe.Real ): NbrRe.Real; VAR abs, answer, arg: NbrRe.Real; dml, z: NbrCplx.Complex; BEGIN z := x; dml := DCplxFn( alpha, beta, z ); abs := NbrCplx.Abs( dml ); arg := NbrCplx.Arg( dml ); IF (arg < tolerance) & (-tolerance < arg) THEN answer := abs ELSIF (arg > NbrRe.Pi - tolerance) OR (arg < -NbrRe.Pi + tolerance) THEN answer := -abs ELSE DataErrors.Error( "The result is complex. Use DCplxFn instead." ) END; RETURN answer END DFn; PROCEDURE DCplxFn*( alpha, beta: NbrRe.Real; z: NbrCplx.Complex ): NbrCplx.Complex; VAR answer: NbrCplx.Complex; absZ, d, k, k1, omega: NbrRe.Real; k0: NbrInt.Integer; dml: DMitLef; BEGIN alpha := NbrRe.Abs( alpha ); absZ := NbrCplx.Abs( z ); IF absZ < 1 / NbrRe.MaxNbr THEN IF GammaIsSingularAt( alpha + beta ) THEN answer := 0 ELSE answer := 1 / MathGamma.Fn( alpha + beta ) END ELSIF absZ < 0.9 THEN IF alpha > 1 THEN k1 := 1 + (2 - alpha - beta) / (alpha - 1) ELSE d := 1 + alpha * (alpha - 4 * beta + 6); k := 1 + (3 - alpha - beta) / alpha; IF d <= 1 THEN k1 := k ELSE omega := alpha + beta - 1.5; k1 := NbrRe.Max( k, 1 + (1 - 2 * omega * alpha + MathRe.Sqrt( d )) / (2 * alpha * alpha) ) END END; BEGIN {EXCLUSIVE} k0 := NbrRe.Ceiling( NbrRe.Max( k1, MathRe.Ln( NbrRe.Epsilon * (1 - absZ) ) ) / MathRe.Ln( absZ ) ); maxIterations := k0; storeAlpha := alpha; storeBeta := beta; NEW( dml ); answer := MathCplxSeries.PowerSeries( dml, z ) END ELSE answer := CplxFn( alpha, beta - 1, z ); IF beta # 1 THEN answer := answer - (beta - 1) * CplxFn( alpha, beta, z ) END; answer := answer / (alpha * z) END; RETURN answer END DCplxFn; BEGIN tolerance := MathRe.Sqrt( NbrRe.Epsilon ) END MathMitLef.