/// <summary> /// Returns Iv(x) for small <paramref name="x"/> /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <returns></returns> public static double I_SmallArg(double v, double x) { double prefix; if (v <= DoubleLimits.MaxGamma - 1) { prefix = Math.Pow(x / 2, v) / Math2.Tgamma(v + 1); } else { prefix = StirlingGamma.PowDivGamma(x / 2, v) / v; } if (prefix == 0) return prefix; double series = HypergeometricSeries.Sum0F1(v + 1, x * x / 4); return prefix * series; }
/// <summary> /// Compute (z^a)(e^-z)/Γ(a) used in the regularized incomplete gammas /// </summary> /// <param name="a"></param> /// <param name="z"></param> /// <returns></returns> /// <remarks>Note: most of the error occurs in this function</remarks> public static double PrefixRegularized(double a, double z) { Debug.Assert(a > 0); Debug.Assert(z >= 0); // if we can't compute Tgamma directly, use Stirling if (a > DoubleLimits.MaxGamma || (a >= StirlingGamma.LowerLimit && z > -DoubleLimits.MinLogValue)) { return(StirlingGamma.PowExpDivGamma(a, z)); } // avoid overflows from dividing small Gamma(a) // for z > 1, e^(eps*ln(z)-z) ~= e^-z, so underflow is ok if (a < DoubleLimits.MachineEpsilon / Constants.EulerMascheroni) { return(a * Math.Pow(z, a) * Math.Exp(-z)); } double alz = a * Math.Log(z); double g = Math2.Tgamma(a); double prefix; if ((alz > DoubleLimits.MinLogValue && alz < DoubleLimits.MaxLogValue) && (z < -DoubleLimits.MinLogValue)) { prefix = Math.Pow(z, a) * Math.Exp(-z) / g; } else if ((alz > 2 * DoubleLimits.MinLogValue && alz < 2 * DoubleLimits.MaxLogValue) && (z < -2 * DoubleLimits.MinLogValue)) { double rp = Math.Pow(z, a / 2) * Math.Exp(-z / 2); prefix = (rp / g) * rp; } else if ((alz > 4 * DoubleLimits.MinLogValue && alz < 4 * DoubleLimits.MaxLogValue) && (z < -4 * DoubleLimits.MinLogValue)) { double rp = Math.Pow(z, a / 4) * Math.Exp(-z / 4); prefix = (rp / g) * rp * rp * rp; } else { prefix = Math.Exp(alz - z - Math.Log(g)); } return(prefix); }
/// <summary> /// Series evaluation for Jv(x), for small x /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <returns></returns> public static double J_SmallArg(double v, double x) { Debug.Assert(v >= 0); // See http://functions.wolfram.com/Bessel-TypeFunctions/BesselJ/06/01/04/01/01/0003/ // Converges rapidly for all x << v. // Most of the error occurs calculating the prefix double prefix; if (v <= DoubleLimits.MaxGamma - 1) prefix = Math.Pow(x / 2, v) / Math2.Tgamma(v + 1); else prefix = StirlingGamma.PowDivGamma(x / 2, v)/v; if (prefix == 0) return prefix; double series = HypergeometricSeries.Sum0F1(v + 1, -x * x / 4); return prefix * series; }
// /// <summary> /// Calculates Ix(a,b) for large a and b (i.e >= 15) using an asymptotic expansion /// </summary> /// <param name="a"></param> /// <param name="b"></param> /// <param name="lambda"></param> /// <returns></returns> static double LargeABAsymptotic(double a, double b, double lambda) { const double eps = 10 * DoubleLimits.MachineEpsilon; // relative tolerance Debug.Assert(a >= 15 && b >= 15, "Requires a >= 15 && b >= 15"); Debug.Assert(lambda >= 0); // lambda = (a + b)*(1-x) - b // This is BASYM from TOMS708. // See: Armido Didonato, Alfred Morris, Algorithm 708: // Significant Digit Computation of the Incomplete Beta Function Ratios, // ACM Transactions on Mathematical Software, // Volume 18, Number 3, 1992, pages 360-373. // // num is the maximum value that n can take in the do loop // ending at statement 50. it is required that num be even. // the arrays a0, b0, c, d have dimension num + 2. // const double e0 = 2.0 * Constants.RecipSqrtPI; // 2/Sqrt(PI) const double e1 = 0.5 * Constants.RecipSqrt2; // 0.5/Sqrt(2) const int num = 20; double[] a0 = new double[num + 2]; double[] b0 = new double[num + 2]; double[] c = new double[num + 2]; double[] d = new double[num + 2]; double h, r0, r1, w0; if (a < b) { h = a / b; r0 = 1.0 / (1.0 + h); r1 = (b - a) / b; w0 = 1.0 / Math.Sqrt(a * (1.0 + h)); } else { h = b / a; r0 = 1.0 / (1.0 + h); r1 = (b - a) / a; w0 = 1.0 / Math.Sqrt(b * (1.0 + h)); } //double f = -(a * Math2.Log1pmx( -lambda/a ) + b * Math2.Log1pmx(lambda/b)); double f = -(a * Math2.Log1p(-lambda / a) + b * Math2.Log1p(lambda / b)); double t = Math.Exp(-f); if (t == 0) { return(0); } double z0 = Math.Sqrt(f); double z = 0.5 * (z0 / e1); double z2 = f + f; a0[1] = (2.0 / 3.0) * r1; c[1] = -0.5 * a0[1]; d[1] = -c[1]; // double j0 = ( 0.5 / e0 ) * Math.Exp(z0*z0)*Math2.Erfc(z0); double j0 = (0.5 / e0) * Math2.Erfcx(z0); double j1 = e1; double sum2 = j0 + d[1] * w0 * j1; double s = 1.0; double h2 = h * h; double hn = 1.0; double w = w0; double znm1 = z; double zn = z2; for (int n = 2; n <= num; n += 2) { hn = h2 * hn; a0[n] = 2.0 * r0 * (1.0 + h * hn) / (n + 2.0); int np1 = n + 1; s = s + hn; a0[np1] = 2.0 * r1 * s / (n + 3.0); for (int i = n; i <= np1; i++) { double r = -0.5 * (i + 1.0); b0[1] = r * a0[1]; for (int m = 2; m <= i; m++) { double bsum = 0.0; int mm1 = m - 1; for (int j = 1; j <= mm1; j++) { int mmj = m - j; bsum += (j * r - mmj) * a0[j] * b0[mmj]; } b0[m] = r * a0[m] + bsum / m; } c[i] = b0[i] / (i + 1.0); double dsum = 0.0; int im1 = i - 1; for (int j = 1; j <= im1; j++) { dsum += d[i - j] * c[j]; } d[i] = -(dsum + c[i]); } j0 = e1 * znm1 + (n - 1.0) * j0; j1 = e1 * zn + n * j1; znm1 = z2 * znm1; zn = z2 * zn; w = w0 * w; double t0 = d[n] * w * j0; w = w0 * w; double t1 = d[np1] * w * j1; sum2 += (t0 + t1); if ((Math.Abs(t0) + Math.Abs(t1)) <= eps * sum2) { break; } } double u = StirlingGamma.GammaSeries(a + b) / (StirlingGamma.GammaSeries(a) * StirlingGamma.GammaSeries(b)); //Math.Exp(-Bcorr(a, b)); // below is the equivalent: // double p = a/(a+b); // double q = b/(a+b); // return Math.Sqrt(2*Math.PI*(a+b)/(a*b))*_Beta.PowerTerms(a, b, p, q, true); return(e0 * t * u * sum2); }
// sc = 1-s public static double Imp(double s, double sc) { Debug.Assert(s != 1); // Use integer routines if we can. if (Math.Floor(s) == s && s > int.MinValue && s <= int.MaxValue) { return(Imp((int)s)); } // Zeta(x) approx = -0.5 - 1/2 * Log(2*PI) * x // Expected error should be approx 3 ulps const double LowerLimit = DoubleLimits.RootMachineEpsilon._2; if (Math.Abs(s) < LowerLimit) { return(-0.5 - (Constants.Ln2PI / 2) * s); } if (s < 0) { // The zeta function = 0 for -2, -4, -6...-2n if (Math.Floor(s / 2) == s / 2) { return(0); } // http://functions.wolfram.com/ZetaFunctionsandPolylogarithms/Zeta/17/01/01/0001/ // Use reflection: ζ(s) = Γ(1-s) * (2^s) * π^(s-1) * Sin(sπ/2) * ζ(1-s) if (sc <= DoubleLimits.MaxGamma) { return(2 * Math.Pow(2 * Math.PI, -sc) * Math2.Tgamma(sc) * Math2.SinPI(0.5 * s) * Imp(sc, s)); } // use Stirling double sin = Math2.SinPI(0.5 * s); double sign = Math.Sign(sin); sin = Math.Abs(sin); double value; var factor = StirlingGamma.GammaSeries(sc) * Imp(sc, s); var log = (sc - 0.5) * Math.Log(sc / (2 * Math.PI)); if (log < DoubleLimits.MaxLogValue) { value = factor * Math.Pow(sc / (2 * Math.PI), sc - 0.5) * Math.Exp(-sc) * (2 * sin); } else if (log < 2 * DoubleLimits.MaxLogValue) { double e = Math.Pow(sc / (2 * Math.PI), sc / 2 - 0.25) * Math.Exp(-sc / 2); value = factor * e * (sin * 2) * e; } else { value = Math.Exp(log - sc + Math.Log(2 * sin * factor)); } return(sign * value); } double result; if (s < 1) { // Rational Approximation // Maximum Deviation Found: 2.020e-18 // Expected Error Term: -2.020e-18 // Max error found at double precision: 3.994987e-17 const double Y = -1.2433929443359375; const double p0 = 0.24339294433593750202; const double p1 = -0.49092470516353571651; const double p2 = 0.0557616214776046784287; const double p3 = -0.00320912498879085894856; const double p4 = 0.000451534528645796438704; const double p5 = -0.933241270357061460782e-5; const double q0 = 1; const double q1 = -0.279960334310344432495; const double q2 = 0.0419676223309986037706; const double q3 = -0.00413421406552171059003; const double q4 = 0.00024978985622317935355; const double q5 = -0.101855788418564031874e-4; double z = sc; double P = p0 + z * (p1 + z * (p2 + z * (p3 + z * (p4 + z * p5)))); double Q = q0 + z * (q1 + z * (q2 + z * (q3 + z * (q4 + z * q5)))); result = ((P / Q) + Y + sc) / sc; } else if (s <= 2) { // Maximum Deviation Found: 9.007e-20 // Expected Error Term: 9.007e-20 const double p0 = 0.577215664901532860516; const double p1 = 0.243210646940107164097; const double p2 = 0.0417364673988216497593; const double p3 = 0.00390252087072843288378; const double p4 = 0.000249606367151877175456; const double p5 = 0.110108440976732897969e-4; const double q0 = 1; const double q1 = 0.295201277126631761737; const double q2 = 0.043460910607305495864; const double q3 = 0.00434930582085826330659; const double q4 = 0.000255784226140488490982; const double q5 = 0.10991819782396112081e-4; double z = -sc; double P = p0 + z * (p1 + z * (p2 + z * (p3 + z * (p4 + z * p5)))); double Q = q0 + z * (q1 + z * (q2 + z * (q3 + z * (q4 + z * q5)))); result = P / Q + 1 / (-sc); } else if (s <= 4) { // Maximum Deviation Found: 5.946e-22 // Expected Error Term: -5.946e-22 const double _Y3 = 0.6986598968505859375; const double p0 = -0.0537258300023595030676; const double p1 = 0.0445163473292365591906; const double p2 = 0.0128677673534519952905; const double p3 = 0.00097541770457391752726; const double p4 = 0.769875101573654070925e-4; const double p5 = 0.328032510000383084155e-5; const double q0 = 1; const double q1 = 0.33383194553034051422; const double q2 = 0.0487798431291407621462; const double q3 = 0.00479039708573558490716; const double q4 = 0.000270776703956336357707; const double q5 = 0.106951867532057341359e-4; const double q6 = 0.236276623974978646399e-7; double z = s - 2; double P = p0 + z * (p1 + z * (p2 + z * (p3 + z * (p4 + z * p5)))); double Q = q0 + z * (q1 + z * (q2 + z * (q3 + z * (q4 + z * (q5 + z * q6))))); result = P / Q + _Y3 + 1 / (-sc); } else if (s <= 7) { // Maximum Deviation Found: 2.955e-17 // Expected Error Term: 2.955e-17 // Max error found at double precision: 2.009135e-16 const double p0 = -2.49710190602259410021; const double p1 = -2.60013301809475665334; const double p2 = -0.939260435377109939261; const double p3 = -0.138448617995741530935; const double p4 = -0.00701721240549802377623; const double p5 = -0.229257310594893932383e-4; const double q0 = 1; const double q1 = 0.706039025937745133628; const double q2 = 0.15739599649558626358; const double q3 = 0.0106117950976845084417; const double q4 = -0.36910273311764618902e-4; const double q5 = 0.493409563927590008943e-5; const double q6 = -0.234055487025287216506e-6; const double q7 = 0.718833729365459760664e-8; const double q8 = -0.1129200113474947419e-9; double z = s - 4; double P = p0 + z * (p1 + z * (p2 + z * (p3 + z * (p4 + z * p5)))); double Q = q0 + z * (q1 + z * (q2 + z * (q3 + z * (q4 + z * (q5 + z * (q6 + z * (q7 + z * q8))))))); result = 1 + Math.Exp(P / Q); } else if (s < 15) { // Maximum Deviation Found: 7.117e-16 // Expected Error Term: 7.117e-16 // Max error found at double precision: 9.387771e-16 const double p0 = -4.78558028495135619286; const double p1 = -1.89197364881972536382; const double p2 = -0.211407134874412820099; const double p3 = -0.000189204758260076688518; const double p4 = 0.00115140923889178742086; const double p5 = 0.639949204213164496988e-4; const double p6 = 0.139348932445324888343e-5; const double q0 = 1; const double q1 = 0.244345337378188557777; const double q2 = 0.00873370754492288653669; const double q3 = -0.00117592765334434471562; const double q4 = -0.743743682899933180415e-4; const double q5 = -0.21750464515767984778e-5; const double q6 = 0.471001264003076486547e-8; const double q7 = -0.833378440625385520576e-10; const double q8 = 0.699841545204845636531e-12; double z = s - 7; double P = p0 + z * (p1 + z * (p2 + z * (p3 + z * (p4 + z * (p5 + z * p6))))); double Q = q0 + z * (q1 + z * (q2 + z * (q3 + z * (q4 + z * (q5 + z * (q6 + z * (q7 + z * q8))))))); result = 1 + Math.Exp(P / Q); } else if (s < 36) { // Max error in interpolated form: 1.668e-17 // Max error found at long double precision: 1.669714e-17 const double p0 = -10.3948950573308896825; const double p1 = -2.85827219671106697179; const double p2 = -0.347728266539245787271; const double p3 = -0.0251156064655346341766; const double p4 = -0.00119459173416968685689; const double p5 = -0.382529323507967522614e-4; const double p6 = -0.785523633796723466968e-6; const double p7 = -0.821465709095465524192e-8; const double q0 = 1; const double q1 = 0.208196333572671890965; const double q2 = 0.0195687657317205033485; const double q3 = 0.00111079638102485921877; const double q4 = 0.408507746266039256231e-4; const double q5 = 0.955561123065693483991e-6; const double q6 = 0.118507153474022900583e-7; const double q7 = 0.222609483627352615142e-14; double z = s - 15; double P = p0 + z * (p1 + z * (p2 + z * (p3 + z * (p4 + z * (p5 + z * (p6 + z * p7)))))); double Q = q0 + z * (q1 + z * (q2 + z * (q3 + z * (q4 + z * (q5 + z * (q6 + z * q7)))))); result = 1 + Math.Exp(P / Q); } else if (s < 53) { result = 1 + Math.Pow(2.0, -s); } else { result = 1; } return(result); }
/// <summary> /// Returns Log(Beta(a,b)) /// </summary> /// <param name="a">Requires finite a > 0</param> /// <param name="b">Requires finite b > 0</param> public static double LogBeta(double a, double b) { double c = a + b; if ((!(a > 0) || double.IsInfinity(a)) || (!(b > 0) || double.IsInfinity(b))) { Policies.ReportDomainError("Beta(a: {0}, b: {1}): Requires finite a,b > 0", a, b); return double.NaN; } if (double.IsInfinity(c)) { Policies.ReportDomainError("Beta(a: {0}, b: {1}): Requires finite c == a+b: c = {2}", a, b, c); return double.NaN; } // Special cases: if ((c == a) && (b < DoubleLimits.MachineEpsilon)) return Math2.Lgamma(b); if ((c == b) && (a < DoubleLimits.MachineEpsilon)) return Math2.Lgamma(a); if (b == 1) return -Math.Log(a); if (a == 1) return -Math.Log(b); // B(a,b) == B(b, a) if (a < b) Utility.Swap(ref a, ref b); // from this point a >= b if (a < 1) { // When x < TinyX, Γ(x) ~ 1/x const double SmallX = DoubleLimits.MachineEpsilon / Constants.EulerMascheroni; if (a < SmallX) { if (c < SmallX) { // In this range, Beta(a, b) ~= 1/a + 1/b // so, we won't have an argument overflow as long as // the min(a, b) >= 2*DoubleLimits.MinNormalValue if (b < 2 * DoubleLimits.MinNormalValue) return Math.Log(c / a) - Math.Log(b); return Math.Log((c / a) / b); } Debug.Assert(c <= GammaSmall.UpperLimit); return -Math.Log((GammaSmall.Tgamma(c) * a * b)); } if (b < SmallX) return Math.Log(Tgamma(a) / (b * Tgamma(c))); return Math.Log((Tgamma(a) / Tgamma(c)) * Tgamma(b)); } else if (a >= StirlingGamma.LowerLimit) { if ( b >= StirlingGamma.LowerLimit ) return StirlingGamma.LogBeta(a, b); return StirlingGamma.LgammaDelta(a, b) + Lgamma(b); } return Lanczos.LogBeta(a, b); }
/// <summary> /// Returns the Beta function /// <para>B(a,b) = Γ(a)*Γ(b)/Γ(a+b)</para> /// </summary> /// <param name="a">Requires finite a > 0</param> /// <param name="b">Requires finite b > 0</param> public static double Beta(double a, double b) { double c = a + b; if ((!(a > 0) || double.IsInfinity(a)) || (!(b > 0) || double.IsInfinity(b))) { Policies.ReportDomainError("Beta(a: {0}, b: {1}): Requires finite a,b > 0", a, b); return double.NaN; } if (double.IsInfinity(c)) { Policies.ReportDomainError("Beta(a: {0}, b: {1}): Requires finite c == a+b: c = {2}", a, b, c); return double.NaN; } // Special cases: if ((c == a) && (b < DoubleLimits.MachineEpsilon)) return Math2.Tgamma(b); if ((c == b) && (a < DoubleLimits.MachineEpsilon)) return Math2.Tgamma(a); if (b == 1) return 1 / a; if (a == 1) return 1 / b; // B(a,b) == B(b, a) if (a < b) Utility.Swap(ref a, ref b); // from this point a >= b if (a < 1) { // When x < TinyX, Γ(x) ~ 1/x const double SmallX = DoubleLimits.MachineEpsilon / Constants.EulerMascheroni; if ( a < SmallX ) { if (c < SmallX) return (c / a) / b; Debug.Assert(c <= GammaSmall.UpperLimit); return 1/(GammaSmall.Tgamma(c) * a * b); } if ( b < SmallX ) return Tgamma(a) / (b * Tgamma(c)); return Tgamma(a) * ( Tgamma(b)/Tgamma(c) ); } // Our Stirling series is more accurate than Lanczos if (a >= StirlingGamma.LowerLimit ) { if (b >= StirlingGamma.LowerLimit) return StirlingGamma.Beta(a, b); // for large a, Γ(a)/Γ(a + b) ~ a^-b, so don't underflow too soon if (b < 1 || b * Math.Log(a) <= -DoubleLimits.MinLogValue) return Tgamma(b) * StirlingGamma.TgammaDeltaRatio(a, b); // fall through for very large a small 1 < b < LowerLimit } return Lanczos.Beta(a, b); }
/// <summary> /// Returns the natural log of the Gamma Function = ln(|Γ(x)|). /// Sets the sign = Sign(Γ(x)); 0 for poles or indeterminate. /// </summary> /// <param name="x">Lgamma function argument</param> /// <param name="sign">Lgamma output value = Sign(Γ(x))</param> public static double Lgamma(double x, out int sign) { sign = 0; if (double.IsNaN(x)) { Policies.ReportDomainError("Lgamma(x: {0}): NaN not allowed", x); return(double.NaN); } if (double.IsInfinity(x)) { if (x < 0) { Policies.ReportDomainError("Lgamma(x: {0}): Requires x != -Infinity", x); return(double.PositiveInfinity); } sign = 1; return(double.PositiveInfinity); } double result = 0; sign = 1; if (x <= 0) { if (IsInteger(x)) { sign = 0; Policies.ReportPoleError("Lgamma(x: {0}): Evaluation at zero, or a negative integer", x); return(double.PositiveInfinity); } if (x > -GammaSmall.UpperLimit) { return(GammaSmall.Lgamma(x, out sign)); } if (x <= -StirlingGamma.LowerLimit) { // Our Stirling routine does the reflection // So no need to reflect here result = StirlingGamma.Lgamma(x, out sign); return(result); } else { double product = 1; double xm2 = x - 2; double xm1 = x - 1; while (x < 1) { product *= x; xm2 = xm1; xm1 = x; x += 1; } if (product < 0) { sign = -1; product = -product; } result = -Math.Log(product) + _Lgamma.Rational_1_3(x, xm1, xm2); return(result); } } else if (x < 1) { if (x < GammaSmall.UpperLimit) { return(GammaSmall.Lgamma(x, out sign)); } // Log(Γ(x)) = Log(Γ(x+1)/x) result = -Math.Log(x) + _Lgamma.Rational_1_3(x + 1, x, x - 1); } else if (x < 3) { result = _Lgamma.Rational_1_3(x, x - 1, x - 2); } else if (x < 8) { // use recurrence to shift x to [2, 3) double product = 1; while (x >= 3) { product *= --x; } result = Math.Log(product) + _Lgamma.Rational_1_3(x, x - 1, x - 2); } else { // regular evaluation: result = StirlingGamma.Lgamma(x); } return(result); }
/// <summary> /// Returns the Gamma Function = Γ(x) /// </summary> /// <param name="x">Tgamma function argument</param> public static double Tgamma(double x) { if (double.IsNaN(x)) { Policies.ReportDomainError("Tgamma(x: {0}): NaN not allowed", x); return(double.NaN); } if (double.IsInfinity(x)) { if (x < 0) { Policies.ReportDomainError("Tgamma(x: {0}): Requires x is not negative infinity", x); return(double.NaN); } return(double.PositiveInfinity); } double result = 1; if (x <= 0) { if (IsInteger(x)) { Policies.ReportPoleError("Tgamma(x: {0}): Requires x != 0 and x be a non-negative integer", x); return(double.NaN); } if (x >= -GammaSmall.UpperLimit) { return(GammaSmall.Tgamma(x)); } if (x <= -StirlingGamma.LowerLimit) { const double MinX = -185; if (x < MinX) { return(0); } // If we use the reflection formula directly for x < -NegMaxGamma, Tgamma will overflow; // so, use recurrence to get x > -NegMaxGamma. // The result is likely to be subnormal or zero. double shiftedX = x; double recurrenceMult = 1.0; double NegMaxGamma = -(Math2.MaxFactorialIndex + 1); while (shiftedX < NegMaxGamma) { recurrenceMult *= shiftedX; shiftedX += 1; } result = -(Math.PI / StirlingGamma.Tgamma(-shiftedX)) / (shiftedX * recurrenceMult * Math2.SinPI(shiftedX)); return(result); } // shift x to > 0: double product = 1; while (x < 0) { product *= x++; } result /= product; // fall through } if (x < 1) { if (x <= GammaSmall.UpperLimit) { result *= GammaSmall.Tgamma(x); } else { // Γ(x) = Exp(LogΓ(x+1))/x result *= Math.Exp(_Lgamma.Rational_1_3(x + 1, x, x - 1)) / x; } } else if (IsInteger(x) && (x <= Math2.FactorialTable.Length)) { // Γ(n) = (n-1)! result *= Math2.FactorialTable[(int)x - 1]; } else if (x < StirlingGamma.LowerLimit) { // ensure x is in [1, 3) // if x >= 3, use recurrence to shift x to [2, 3) while (x >= 3) { result *= --x; } result *= Math.Exp(_Lgamma.Rational_1_3(x, x - 1, x - 2)); } else { result *= StirlingGamma.Tgamma(x); } return(result); }