/// <summary> /// Computes the complex Faddeeva function. /// </summary> /// <param name="z">The complex argument.</param> /// <returns>The complex value of w(z).</returns> /// <remarks> /// <para>The Faddeeva function w(z) is related to the error function with a complex argument.</para> /// <img src="../images/FaddeevaErfcRelation.png" /> /// <para>It also has an integral representation.</para> /// <img src="../images/FaddeevaIntegral.png" /> /// <para>For purely imaginary values, it reduces to the complementary error function (<see cref="AdvancedMath.Erfc"/>). /// For purely real values, it reduces to Dawson's integral (<see cref="AdvancedMath.Dawson"/>).</para> /// <para>It appears in the computation of the Voigt line profile function V(x;σ,γ).</para> /// <img src="../images/Voigt.png" /> /// <para>Near the origin, w(z) ≈ 1. To accurately determine w(z) - 1 in this region, use the <see cref="Erf"/> /// function. Away from the origin near the large negative imaginary axis, the magnitude w(z) increases rapidly and /// may overflow.</para> /// <para>The image below shows the complex Faddeeva function near the origin, using domain coloring.</para> /// <img src="../images/ComplexFaddeevaPlot.png" /> /// </remarks> /// <seealso cref="AdvancedComplexMath.Erf"/> /// <seealso cref="AdvancedMath.Erf" /> /// <seealso cref="AdvancedMath.Erfc" /> /// <seealso cref="AdvancedMath.Dawson"/> /// <seealso href="http://en.wikipedia.org/wiki/Voigt_profile" /> public static Complex Faddeeva(Complex z) { // use reflection formulae to ensure that we are in the first quadrant if (z.Im < 0.0) { return(2.0 * ComplexMath.Exp(-ComplexMath.Sqr(z)) - Faddeeva(-z)); } if (z.Re < 0.0) { return(Faddeeva(-z.Conjugate).Conjugate); } double r = ComplexMath.Abs(z); if (r < 2.0) { // use series for small z return(ComplexMath.Exp(-ComplexMath.Sqr(z)) * (1.0 - Erf_Series(-ComplexMath.I * z))); //return (Faddeeva_Series(z)); } else if ((z.Im < 0.1) && (z.Re < 30.0)) { // this is a special, awkward region // along the real axis, Re{w(x)} ~ e^{-x^2}; the Weideman algorithm doesn't compute this small number // well and the Laplace continued fraction misses it entirely; therefore very close to the real axis // we will use an analytic result on the real axis and Taylor expand to where we need to go. // unfortunately the Taylor expansion converges poorly for large x, so we drop this work-around near x~30, // when this real part becomes too small to represent as a double anyway double x = z.Re; double y = z.Im; return(Faddeeva_Taylor(new Complex(x, 0.0), Math.Exp(-x * x) + 2.0 * AdvancedMath.Dawson(x) / Global.SqrtPI * ComplexMath.I, new Complex(0.0, y))); } else if (r > 7.0) { // use Laplace continued fraction for large z return(Faddeeva_ContinuedFraction(z)); } else { // use Weideman algorithm for intermediate region return(Faddeeva_Weideman(z)); } }
/* * private static double Fadeeva_Weideman_L = Math.Sqrt(20.0 / Math.Sqrt(2.0)); * private static double[] Faddeeva_Weideman_Coefficients = { * 0.145204167876254758882, // 0 * 0.136392115479649636711, * 0.112850364752776356308, * 0.081814663814142472504, * 0.051454522270816839073, * 0.027588397192576112169, // 5 * 0.012224615682792673110, * 0.004202799708104555100, * 0.00094232196590738341762, * 0.000024250951611669019436, * -0.000075616164647923403908, // 10 * -0.000025048115578198088771, * 1.35444636058911259445e-6, * 3.1103803514939499680e-6, * 4.3812443485181690079e-7, * -3.1958626141517655223e-7, // 15 * -9.9512459304812101824e-8, * 3.38225585699344840075e-8, * 1.68263253379413070440e-8, * -4.206345505308078461584e-9, * -2.7027917878529318399463e-9 // 20 * }; */ // The power series for the error function (DLMF 7.6.1) // erf(z) = \frac{2}{\sqrt{\pi}} \sum_{k=0}^{\infty} \frac{ (-1)^k z^{2k + 1} }{(2k+1) k!} // = \frac{2}{\sqrt{\pi}} \left[ z - z^3 / 3 + z^5 / 10 - \cdots \right] // requires about 15 terms at |z| ~ 1, 30 terms at |z| ~ 2, 45 terms at |z| ~ 3 private static Complex Erf_Series(Complex z) { Complex zp = 2.0 / Global.SqrtPI * z; Complex zz = -ComplexMath.Sqr(z); Complex f = zp; for (int k = 1; k < Global.SeriesMax; k++) { Complex f_old = f; zp *= zz / k; f += zp / (2 * k + 1); if (f == f_old) { return(f); } } throw new NonconvergenceException(); }
private static Complex Sum(Complex z) { Complex rzPower = 1.0 / z; Complex rzSquared = ComplexMath.Sqr(rzPower); Complex f = 0.5 * AdvancedIntegerMath.Bernoulli[1] * rzPower; for (int k = 2; k < AdvancedIntegerMath.Bernoulli.Length; k++) { Complex f_old = f; rzPower *= rzSquared; f += AdvancedIntegerMath.Bernoulli[k] / ((2 * k) * (2 * k - 1)) * rzPower; if (f == f_old) { return(f); } } throw new NonconvergenceException(); }
public static Complex AiryAi_Series(Complex z) { Complex p = AdvancedMath.Ai0; Complex q = AdvancedMath.AiPrime0 * z; Complex f = p + q; Complex z3 = ComplexMath.Sqr(z) * z; for (int k = 0; k < Global.SeriesMax; k += 3) { Complex f_old = f; p *= z3 / ((k + 2) * (k + 3)); q *= z3 / ((k + 3) * (k + 4)); f += p + q; if (f == f_old) { return(f); } } throw new NonconvergenceException(); }
/// <summary> /// Computes the complex error function. /// </summary> /// <param name="z">The complex argument.</param> /// <returns>The value of erf(z).</returns> /// <remarks> /// <para>This function is the analytic continuation of the error function (<see cref="AdvancedMath.Erf"/>) to the complex plane.</para> /// <para>The image below shows the complex error function near the origin, using domain coloring.</para> /// <img src="../images/ComplexErfPlot.png" /> /// <para>The complex error function is entire: it has no poles, cuts, or discontinuities anywhere in the complex plane.</para> /// <para>For pure imaginary arguments, erf(z) reduces to the Dawson integral (<see cref="AdvancedMath.Dawson"/>).</para> /// <para>Away from the origin near the real axis, the real part of erf(z) quickly approaches ±1. To accurately determine /// the small difference erf(z) ∓ 1 in this region, use the <see cref="Faddeeva"/> function. Away from the origin near /// the imaginary axis, the magnitude of erf(z) increases very quickly. Although erf(z) may overflow in this region, you /// can still accurately determine the value of the product erf(z) exp(z<sup>2</sup>) using the <see cref="Faddeeva"/> /// function.</para> /// </remarks> /// <seealso cref="AdvancedMath.Erf"/> /// <seealso cref="AdvancedMath.Dawson"/> /// <seealso cref="AdvancedComplexMath.Faddeeva"/> public static Complex Erf(Complex z) { double r = ComplexMath.Abs(z); if (r < 4.0) { // near the origin, use the series return(Erf_Series(z)); } else { // otherwise, just compute from Faddeva if (z.Re < 0.0) { // since Fadddeeva blows up for negative z.Re, use erf(z) = -erf(-z) return(ComplexMath.Exp(-ComplexMath.Sqr(z)) * Faddeeva(-ComplexMath.I * z) - 1.0); } else { return(1.0 - ComplexMath.Exp(-ComplexMath.Sqr(z)) * Faddeeva(ComplexMath.I * z)); } // we don't do this near the origin beause we would loose accuracy in the very small real parts there by subtracting from 1 } }