public void OdeAiry() { // This is the airy differential equation Func <double, double, double> f = (double x, double y) => x * y; // Solutions should be of the form f(x) = a Ai(x) + b Bi(x). // Given initial value of f and f', this equation plus the Wronskian can be solved to give // a = \pi ( f Bi' - f' Bi ) b = \pi ( f' Ai - f Ai' ) // Start with some initial conditions double x0 = 0.0; double y0 = 0.0; double yp0 = 1.0; // Find the a and b coefficients consistent with those values SolutionPair s0 = AdvancedMath.Airy(x0); double a = Math.PI * (y0 * s0.SecondSolutionDerivative - yp0 * s0.SecondSolutionValue); double b = Math.PI * (yp0 * s0.FirstSolutionValue - y0 * s0.FirstSolutionDerivative); Assert.IsTrue(TestUtilities.IsNearlyEqual(y0, a * s0.FirstSolutionValue + b * s0.SecondSolutionValue)); Assert.IsTrue(TestUtilities.IsNearlyEqual(yp0, a * s0.FirstSolutionDerivative + b * s0.SecondSolutionDerivative)); // Integrate to a new point (pick a negative one so we test left integration) double x1 = -5.0; OdeResult result = FunctionMath.IntegrateConservativeOde(f, x0, y0, yp0, x1); Assert.IsTrue(TestUtilities.IsNearlyEqual(result.X, x1)); Console.WriteLine(result.EvaluationCount); // The solution should still hold SolutionPair s1 = AdvancedMath.Airy(x1); Assert.IsTrue(TestUtilities.IsNearlyEqual(result.Y, a * s1.FirstSolutionValue + b * s1.SecondSolutionValue, result.Settings)); Assert.IsTrue(TestUtilities.IsNearlyEqual(result.YPrime, a * s1.FirstSolutionDerivative + b * s1.SecondSolutionDerivative, result.Settings)); }
public void OdeSine() { // The sine and cosine functions satisfy // y'' = - y // This is perhaps the simplest conservative differential equation. // (i.e. right hand side depends only on y, not y') Func <double, double, double> f = (double x, double y) => - y; int count = 0; OdeSettings settings = new OdeSettings() { Listener = (OdeResult r) => { Assert.IsTrue(TestUtilities.IsNearlyEqual( MoreMath.Sqr(r.Y) + MoreMath.Sqr(r.YPrime), 1.0, r.Settings )); Assert.IsTrue(TestUtilities.IsNearlyEqual( r.Y, MoreMath.Sin(r.X), r.Settings )); count++; } }; OdeResult result = FunctionMath.IntegrateConservativeOde(f, 0.0, 0.0, 1.0, 5.0, settings); Assert.IsTrue(TestUtilities.IsNearlyEqual(result.Y, MoreMath.Sin(5.0))); Assert.IsTrue(count > 0); }
public static void IntegrateOde() { Func <double, double, double> rhs = (x, y) => - x * y; OdeResult sln = FunctionMath.IntegrateOde(rhs, 0.0, 1.0, 2.0); Console.WriteLine($"Numeric solution y({sln.X}) = {sln.Y}."); Console.WriteLine($"Required {sln.EvaluationCount} evaluations."); Console.WriteLine($"Analytic solution y({sln.X}) = {Math.Exp(-MoreMath.Sqr(sln.X) / 2.0)}"); // Lotka-Volterra equations double A = 0.1; double B = 0.02; double C = 0.4; double D = 0.02; Func <double, IReadOnlyList <double>, IReadOnlyList <double> > lkRhs = (t, y) => { return(new double[] { A *y[0] - B * y[0] * y[1], D *y[0] * y[1] - C * y[1] }); }; MultiOdeSettings lkSettings = new MultiOdeSettings() { Listener = r => { Console.WriteLine($"t={r.X} rabbits={r.Y[0]}, foxes={r.Y[1]}"); } }; MultiFunctionMath.IntegrateOde(lkRhs, 0.0, new double[] { 20.0, 10.0 }, 50.0, lkSettings); Func <double, IReadOnlyList <double>, IReadOnlyList <double> > rhs1 = (x, u) => { return(new double[] { u[1], -u[0] }); }; MultiOdeSettings settings1 = new MultiOdeSettings() { EvaluationBudget = 100000 }; MultiOdeResult result1 = MultiFunctionMath.IntegrateOde( rhs1, 0.0, new double[] { 0.0, 1.0 }, 500.0, settings1 ); double s1 = MoreMath.Sqr(result1.Y[0]) + MoreMath.Sqr(result1.Y[1]); Console.WriteLine($"y({result1.X}) = {result1.Y[0]}, (y)^2 + (y')^2 = {s1}"); Console.WriteLine($"Required {result1.EvaluationCount} evaluations."); Func <double, double, double> rhs2 = (x, y) => - y; OdeSettings settings2 = new OdeSettings() { EvaluationBudget = 100000 }; OdeResult result2 = FunctionMath.IntegrateConservativeOde( rhs2, 0.0, 0.0, 1.0, 500.0, settings2 ); double s2 = MoreMath.Sqr(result2.Y) + MoreMath.Sqr(result2.YPrime); Console.WriteLine($"y({result2.X}) = {result2.Y}, (y)^2 + (y')^2 = {s2}"); Console.WriteLine($"Required {result2.EvaluationCount} evaluations"); Console.WriteLine(MoreMath.Sin(500.0)); }
private static double CoulombF_Integrate(int L, double eta, double rho) { // start at the series limit double rho1 = Math.Min( 4.0 + 2.0 * Math.Sqrt(L), (8.0 + 4.0 * L) / Math.Abs(eta) ); double F, FP; CoulombF_Series(L, eta, rho1, out F, out FP); // TODO: switch so we integrate w/o the C factor, then apply it afterward if ((F == 0.0) && (FP == 0.0)) { return(0.0); } OdeResult r = FunctionMath.IntegrateConservativeOde( (double x, double y) => ((L * (L + 1) / x + 2.0 * eta) / x - 1.0) * y, rho1, F, FP, rho, new OdeEvaluationSettings() { RelativePrecision = 2.5E-13, AbsolutePrecision = 0.0, EvaluationBudget = 8192 * 2 } ); /* * F = r.Y; * FP = r.YPrime; * * // integrate out to rho * BulrischStoerStoermerStepper s = new BulrischStoerStoermerStepper(); * s.RightHandSide = delegate(double x, double U) { * return ((L * (L + 1) / x / x + 2.0 * eta / x - 1.0) * U); * }; * s.X = rho0; * s.Y = F; * s.YPrime = FP; * s.DeltaX = 0.25; * s.Accuracy = 2.5E-13; * s.Integrate(rho); */ // return the result return(r.Y); }
public void OdePendulum() { // Without the small-angle approximation, the period of a pendulum is not // independent of angle. Instead, it is given by // P = 4 K(\sin(\phi_0) / 2) // where \phi_0 is the release angle and K is the complete elliptic function // of the first kind. // See https://en.wikipedia.org/wiki/Pendulum_(mathematics) // We compute for an initial angle of 45 degrees, far beyond a small angle. Func <double, double, double> rhs = (double t, double u) => - MoreMath.Sin(u); double u0 = Math.PI / 4.0; double p = 4.0 * AdvancedMath.EllipticK(MoreMath.Sin(u0 / 2.0)); OdeResult r = FunctionMath.IntegrateConservativeOde(rhs, 0.0, u0, 0.0, p); Assert.IsTrue(TestUtilities.IsNearlyEqual(r.Y, u0)); Console.WriteLine(r.EvaluationCount); }
public double Evaluate() { CoulombF_Series(L, eta, rho0, out double F, out double FP); if ((F == 0.0) && (FP == 0.0)) { return(0.0); } OdeResult r = FunctionMath.IntegrateConservativeOde( (double x, double y) => ((L * (L + 1) / x + 2.0 * eta) / x - 1.0) * y, rho0, F, FP, rho, new OdeSettings() { RelativePrecision = 2.5E-13, AbsolutePrecision = 0.0, EvaluationBudget = 25000 } ); double C = CoulombFactor(L, eta); return(C * r.Y); }
/// <summary> /// Computes the irregular Coulomb wave function. /// </summary> /// <param name="L">The angular momentum number, which must be non-negative.</param> /// <param name="eta">The charge parameter, which can be postive or negative.</param> /// <param name="rho">The radial distance parameter, which must be non-negative.</param> /// <returns>The value of G<sub>L</sub>(η,ρ).</returns> /// <remarks> /// <para>For information on the Coulomb wave functions, see the remarks on <see cref="Coulomb" />.</para> /// </remarks> /// <exception cref="ArgumentOutOfRangeException"><paramref name="L"/> or <paramref name="rho"/> is negative.</exception> /// <seealso cref="Coulomb"/> /// <seealso cref="CoulombF"/> /// <seealso href="http://en.wikipedia.org/wiki/Coulomb_wave_function" /> /// <seealso href="http://mathworld.wolfram.com/CoulombWaveFunction.html" /> public static double CoulombG(int L, double eta, double rho) { if (L < 0) { throw new ArgumentOutOfRangeException(nameof(L)); } if (rho < 0) { throw new ArgumentOutOfRangeException(nameof(rho)); } if ((rho < 4.0) && Math.Abs(rho * eta) < 8.0) { // For small enough rho, use the power series for L=0, then recurse upward to desired L. double F, FP, G, GP; Coulomb_Zero_Series(eta, rho, out F, out FP, out G, out GP); Coulomb_Recurse_Upward(0, L, eta, rho, ref G, ref GP); return(G); } else if (rho > 32.0 + (L * L + eta * eta) / 2.0) { // For large enough rho, use the asymptotic series. SolutionPair s = Coulomb_Asymptotic(L, eta, rho); return(s.SecondSolutionValue); } else { // Transition region if (rho >= CoulombTurningPoint(L, eta)) { // Beyond the turning point, use Steed's method. SolutionPair result = Coulomb_Steed(L, eta, rho); return(result.SecondSolutionValue); } else { // we will start at L=0 (which has a smaller turning point radius) and recurse up to the desired L // this is okay because G increasees with increasing L double G, GP; double rho0 = 2.0 * eta; if (rho < rho0) { // if inside the turning point even for L=0, start at the turning point and integrate in // this is okay becaue G increases with decraseing rho // use Steed's method at the turning point // for large enough eta, we could use the turning point expansion at L=0, but it contributes // a lot of code for little overall performance increase so we have chosen not to SolutionPair result = Coulomb_Steed(0, eta, 2.0 * eta); G = result.SecondSolutionValue; GP = result.SecondSolutionDerivative; OdeResult r = FunctionMath.IntegrateConservativeOde( (double x, double y) => (2.0 * eta / x - 1.0) * y, rho0, G, GP, rho, new OdeEvaluationSettings() { RelativePrecision = 2.5E-13, AbsolutePrecision = 0.0, EvaluationBudget = 8192 * 2 } ); G = r.Y; GP = r.YPrime; /* * BulrischStoerStoermerStepper s = new BulrischStoerStoermerStepper(); * s.RightHandSide = delegate(double x, double U) { * return ((2.0 * eta / x - 1.0) * U); * }; * s.X = 2.0 * eta; * s.Y = G; * s.YPrime = GP; * s.DeltaX = 0.25; * s.Accuracy = 2.5E-13; * s.Integrate(rho); * * G = s.Y; * GP = s.YPrime; */ } else { // if beyond the turning point for L=0, just use Steeds method SolutionPair result = Coulomb_Steed(0, eta, rho); G = result.SecondSolutionValue; GP = result.SecondSolutionDerivative; } // Recurse up to the desired L. Coulomb_Recurse_Upward(0, L, eta, rho, ref G, ref GP); return(G); } } }
/// <summary> /// Computes the regular and irregular Coulomb wave functions and their derivatives. /// </summary> /// <param name="L">The angular momentum number, which must be non-negative.</param> /// <param name="eta">The charge parameter, which can be postive or negative.</param> /// <param name="rho">The radial distance parameter, which must be non-negative.</param> /// <returns>The values of F, F', G, and G' for the given parameters.</returns> /// <remarks> /// <para>The Coulomb wave functions are the radial wave functions of a non-relativistic particle in a Coulomb /// potential.</para> /// <para>They satisfy the differential equation:</para> /// <img src="../images/CoulombODE.png" /> /// <para>A repulsive potential is represented by η > 0, an attractive potential by η < 0.</para> /// <para>F is oscilatory in the region beyond the classical turning point. In the quantum tunneling region inside /// the classical turning point, F is exponentially supressed and vanishes at the origin, while G grows exponentially and /// diverges at the origin.</para> /// <para>Many numerical libraries compute Coulomb wave functions in the quantum tunneling region using a WKB approximation, /// which accurately determine only the first handfull of digits; our library computes Coulomb wave functions even in this /// computationaly difficult region to nearly full precision -- all but the last 3-4 digits can be trusted.</para> /// <para>The irregular Coulomb wave functions G<sub>L</sub>(η,ρ) are the complementary independent solutions /// of the same differential equation.</para> /// </remarks> /// <exception cref="ArgumentOutOfRangeException"><paramref name="L"/> or <paramref name="rho"/> is negative.</exception> /// <seealso cref="CoulombF"/> /// <seealso cref="CoulombG"/> /// <seealso href="http://en.wikipedia.org/wiki/Coulomb_wave_function" /> /// <seealso href="http://mathworld.wolfram.com/CoulombWaveFunction.html" /> public static SolutionPair Coulomb(int L, double eta, double rho) { if (L < 0) { throw new ArgumentOutOfRangeException(nameof(L)); } if (rho < 0) { throw new ArgumentOutOfRangeException(nameof(rho)); } if (rho == 0.0) { if (L == 0) { double C = CoulombFactor(L, eta); return(new SolutionPair(0.0, C, 1.0 / C, Double.NegativeInfinity)); } else { return(new SolutionPair(0.0, 0.0, Double.PositiveInfinity, Double.NegativeInfinity)); } } else if ((rho < 4.0) && Math.Abs(rho * eta) < 8.0) { // Below the safe series radius for L=0, compute using the series double F, FP, G, GP; Coulomb_Zero_Series(eta, rho, out F, out FP, out G, out GP); // For higher L, recurse G upward, but compute F via the direct series. // G is safe to compute via recursion and F is not because G is increasing // rapidly and F is decreasing rapidly with increasing L. if (L > 0) { CoulombF_Series(L, eta, rho, out F, out FP); Coulomb_Recurse_Upward(0, L, eta, rho, ref G, ref GP); } return(new SolutionPair(F, FP, G, GP)); } else if (rho > 32.0 + (L * L + eta * eta) / 2.0) { return(Coulomb_Asymptotic(L, eta, rho)); } else { double rho0 = CoulombTurningPoint(L, eta); if (rho > rho0) { return(Coulomb_Steed(L, eta, rho)); } else { // First F double F, FP; double rho1 = Math.Min( 4.0 + 2.0 * Math.Sqrt(L), (8.0 + 4.0 * L) / Math.Abs(eta) ); if (rho < rho1) { CoulombF_Series(L, eta, rho, out F, out FP); } else { CoulombF_Series(L, eta, rho1, out F, out FP); OdeResult r = FunctionMath.IntegrateConservativeOde( (double x, double y) => ((L * (L + 1) / x + 2.0 * eta) / x - 1.0) * y, rho1, F, FP, rho, new OdeEvaluationSettings() { RelativePrecision = 2.5E-13, AbsolutePrecision = 0.0, EvaluationBudget = 8192 * 2 } ); F = r.Y; FP = r.YPrime; } // Then G double G, GP; // For L = 0 the transition region is smaller, so we will determine G // at L = 0, where non-integration methods apply over a larger region, // and then recurse upward. double rho2 = CoulombTurningPoint(0, eta); if (rho > 32.0 + eta * eta / 2.0) { SolutionPair s = Coulomb_Asymptotic(0, eta, rho); G = s.SecondSolutionValue; GP = s.SecondSolutionDerivative; } else if (rho > rho2) { SolutionPair s = Coulomb_Steed(0, eta, rho); G = s.SecondSolutionValue; GP = s.SecondSolutionDerivative; } else { SolutionPair s = Coulomb_Steed(0, eta, rho2); G = s.SecondSolutionValue; GP = s.SecondSolutionDerivative; // Integrate inward from turning point. // G increases and F decreases in this direction, so this is stable. OdeResult r = FunctionMath.IntegrateConservativeOde( (double x, double y) => (2.0 * eta / x - 1.0) * y, rho2, G, GP, rho, new OdeEvaluationSettings() { RelativePrecision = 2.5E-13, AbsolutePrecision = 0.0, EvaluationBudget = 8192 * 2 } ); G = r.Y; GP = r.YPrime; } Coulomb_Recurse_Upward(0, L, eta, rho, ref G, ref GP); return(new SolutionPair(F, FP, G, GP)); } } }
/// <summary> /// Computes the irregular Coulomb wave function. /// </summary> /// <param name="L">The angular momentum number, which must be non-negative.</param> /// <param name="eta">The charge parameter, which can be positive or negative.</param> /// <param name="rho">The radial distance parameter, which must be non-negative.</param> /// <returns>The value of G<sub>L</sub>(η,ρ).</returns> /// <remarks> /// <para>For information on the Coulomb wave functions, see the remarks on <see cref="Coulomb" />.</para> /// </remarks> /// <exception cref="ArgumentOutOfRangeException"><paramref name="L"/> or <paramref name="rho"/> is negative.</exception> /// <seealso cref="Coulomb"/> /// <seealso cref="CoulombF"/> /// <seealso href="http://en.wikipedia.org/wiki/Coulomb_wave_function" /> /// <seealso href="http://mathworld.wolfram.com/CoulombWaveFunction.html" /> public static double CoulombG(int L, double eta, double rho) { if (L < 0) { throw new ArgumentOutOfRangeException(nameof(L)); } if (rho < 0) { throw new ArgumentOutOfRangeException(nameof(rho)); } if (rho <= Coulomb_Series_Limit(0, eta)) { // For small enough rho, use the power series for L=0, then recurse upward to desired L. Coulomb_Zero_Series(eta, rho, out double F, out double FP, out double G, out double GP); Coulomb_Recurse_Upward(0, L, eta, rho, ref G, ref GP); return(G); } else if (rho >= Coulomb_Asymptotic_Limit(L, eta)) { // For large enough rho, use the asymptotic series. SolutionPair s = Coulomb_Asymptotic(L, eta, rho); return(s.SecondSolutionValue); } else if (rho >= CoulombTurningPoint(L, eta)) { // Beyond the turning point, use Steed's method. SolutionPair result = Coulomb_Steed(L, eta, rho); return(result.SecondSolutionValue); } else { // We will start at L=0 (which has a smaller turning point radius) and recurse up to the desired L. // This is okay because G increases with increasing L. We already know that we are beyond the L=0 // series limit; otherwise we would have taken the branch for it above. We might still be // in the asymptotic region, in the Steed region, below the L=0 turning point (if \eta > 0). double G, GP; if (rho >= Coulomb_Asymptotic_Limit(0, eta)) { SolutionPair s = Coulomb_Asymptotic(0, eta, rho); G = s.SecondSolutionValue; GP = s.SecondSolutionDerivative; } else { double rho1 = CoulombTurningPoint(0, eta); if (rho >= rho1) { SolutionPair s = Coulomb_Steed(0, eta, rho); G = s.SecondSolutionValue; GP = s.SecondSolutionDerivative; } else { SolutionPair s = Coulomb_Steed(0, eta, rho1); G = s.SecondSolutionValue; GP = s.SecondSolutionDerivative; OdeResult r = FunctionMath.IntegrateConservativeOde( (double x, double y) => (2.0 * eta / x - 1.0) * y, rho1, G, GP, rho, new OdeSettings() { RelativePrecision = 2.5E-13, AbsolutePrecision = 0.0, EvaluationBudget = 25000 } ); G = r.Y; GP = r.YPrime; } } Coulomb_Recurse_Upward(0, L, eta, rho, ref G, ref GP); return(G); } }
/// <summary> /// Computes the regular and irregular Coulomb wave functions and their derivatives. /// </summary> /// <param name="L">The angular momentum number, which must be non-negative.</param> /// <param name="eta">The charge parameter, which can be positive or negative.</param> /// <param name="rho">The radial distance parameter, which must be non-negative.</param> /// <returns>The values of F, F', G, and G' for the given parameters.</returns> /// <remarks> /// <para>The Coulomb wave functions are the radial wave functions of a non-relativistic particle in a Coulomb /// potential.</para> /// <para>They satisfy the differential equation:</para> /// <img src="../images/CoulombODE.png" /> /// <para>A repulsive potential is represented by η > 0, an attractive potential by η < 0.</para> /// <para>F is oscillatory in the region beyond the classical turning point. In the quantum tunneling region inside /// the classical turning point, F is exponentially suppressed and vanishes at the origin, while G grows exponentially and /// diverges at the origin.</para> /// <para>Many numerical libraries compute Coulomb wave functions in the quantum tunneling region using a WKB approximation, /// which accurately determine only the first few decimal digits; our library computes Coulomb wave functions even in this /// computationally difficult region to nearly full precision -- all but the last 4-5 decimal digits can be trusted.</para> /// <para>The irregular Coulomb wave functions G<sub>L</sub>(η,ρ) are the complementary independent solutions /// of the same differential equation.</para> /// </remarks> /// <exception cref="ArgumentOutOfRangeException"><paramref name="L"/> or <paramref name="rho"/> is negative.</exception> /// <seealso cref="CoulombF"/> /// <seealso cref="CoulombG"/> /// <seealso href="http://en.wikipedia.org/wiki/Coulomb_wave_function" /> /// <seealso href="http://mathworld.wolfram.com/CoulombWaveFunction.html" /> public static SolutionPair Coulomb(int L, double eta, double rho) { if (L < 0) { throw new ArgumentOutOfRangeException(nameof(L)); } if (rho < 0) { throw new ArgumentOutOfRangeException(nameof(rho)); } if (rho == 0.0) { if (L == 0) { double C = CoulombFactor(L, eta); return(new SolutionPair(0.0, C, 1.0 / C, Double.NegativeInfinity)); } else { return(new SolutionPair(0.0, 0.0, Double.PositiveInfinity, Double.NegativeInfinity)); } } else if (rho <= Coulomb_Series_Limit(0, eta)) { // Below the safe series radius for L=0, compute using the series. Coulomb_Zero_Series(eta, rho, out double F, out double FP, out double G, out double GP); // For higher L, recurse G upward, but compute F via the direct series. // G is safe to compute via recursion and F is not because G is increasing // rapidly and F is decreasing rapidly with increasing L. Since the series // for F was good for L = 0, it's certainly good for L > 0. if (L > 0) { CoulombF_Series(L, eta, rho, out F, out FP); double C = CoulombFactor(L, eta); F *= C; FP *= C; Coulomb_Recurse_Upward(0, L, eta, rho, ref G, ref GP); } return(new SolutionPair(F, FP, G, GP)); } else if (rho >= Coulomb_Asymptotic_Limit(L, eta)) { return(Coulomb_Asymptotic(L, eta, rho)); } else if (rho >= CoulombTurningPoint(L, eta)) { return(Coulomb_Steed(L, eta, rho)); } else { // This code is copied from CoulombG; factor it into a seperate method. double G, GP; if (rho >= Coulomb_Asymptotic_Limit(0, eta)) { SolutionPair s = Coulomb_Asymptotic(0, eta, rho); G = s.SecondSolutionValue; GP = s.SecondSolutionDerivative; } else { double rho1 = CoulombTurningPoint(0, eta); if (rho >= rho1) { SolutionPair s = Coulomb_Steed(0, eta, rho); G = s.SecondSolutionValue; GP = s.SecondSolutionDerivative; } else { SolutionPair s = Coulomb_Steed(0, eta, rho1); G = s.SecondSolutionValue; GP = s.SecondSolutionDerivative; OdeResult r = FunctionMath.IntegrateConservativeOde( (double x, double y) => (2.0 * eta / x - 1.0) * y, rho1, G, GP, rho, new OdeSettings() { RelativePrecision = 2.5E-13, AbsolutePrecision = 0.0, EvaluationBudget = 25000 } ); G = r.Y; GP = r.YPrime; } } Coulomb_Recurse_Upward(0, L, eta, rho, ref G, ref GP); // We can determine F and FP via CF1 and Wronskian, but if we are within // series limit, it's likely a little faster and more accurate. double F, FP; if (rho < Coulomb_Series_Limit(L, eta)) { CoulombF_Series(L, eta, rho, out F, out FP); double C = CoulombFactor(L, eta); F *= C; FP *= C; } else { double f = Coulomb_CF1(L, eta, rho, out int _); F = 1.0 / (f * G - GP); FP = f * F; } return(new SolutionPair(F, FP, G, GP)); } }