public void SymmetricRandomMatrixCholeskyDecomposition() { int d = 100; Random rng = new Random(d); ColumnVector[] V = new ColumnVector[d]; for (int i = 0; i < d; i++) { V[i] = new ColumnVector(d); for (int j = 0; j < d; j++) { V[i][j] = rng.NextDouble(); } } SymmetricMatrix A = new SymmetricMatrix(d); for (int i = 0; i < d; i++) { for (int j = 0; j <= i; j++) { A[i, j] = V[i].Transpose * V[j]; } } Stopwatch s = Stopwatch.StartNew(); CholeskyDecomposition CD = A.CholeskyDecomposition(); s.Stop(); Console.WriteLine("{0} {1}", d, s.ElapsedMilliseconds); Assert.IsTrue(CD != null); }
public MultivariateSample CreateMultivariateNormalSample(ColumnVector M, SymmetricMatrix C, int n) { int d = M.Dimension; MultivariateSample S = new MultivariateSample(d); SquareMatrix A = C.CholeskyDecomposition().SquareRootMatrix(); Random rng = new Random(1); ContinuousDistribution normal = new NormalDistribution(); for (int i = 0; i < n; i++) { // create a vector of normal deviates ColumnVector V = new ColumnVector(d); for (int j = 0; j < d; j++) { double y = rng.NextDouble(); double z = normal.InverseLeftProbability(y); V[j] = z; } // form the multivariate distributed vector ColumnVector X = M + A * V; // add it to the sample S.Add(X); } return(S); }
private void TestAutocovariance(IList <double> acv) { // Verify that variance is positive Assert.IsTrue(acv[0] >= 0.0); // Verify that correlations -1 <= r <= 1 for (int i = 0; i < acv.Count; i++) { Assert.IsTrue(Math.Abs(acv[i]) <= acv[0]); } // Verify that autocovariance is positive definite SymmetricMatrix C = new SymmetricMatrix(acv.Count); for (int r = 0; r < acv.Count; r++) { for (int c = 0; c <= r; c++) { C[r, c] = acv[r - c]; } } CholeskyDecomposition CD = C.CholeskyDecomposition(); Assert.IsTrue(CD != null); }
public static void EigenvaluesAndEigenvectors() { SymmetricMatrix H = new SymmetricMatrix(3); for (int r = 0; r < H.Dimension; r++) { for (int c = 0; c <= r; c++) { H[r, c] = 1.0 / (r + c + 1); } } RealEigendecomposition ed = H.Eigendecomposition(); SquareMatrix V = ed.TransformMatrix; PrintMatrix("V^T V", V.Transpose * V); PrintMatrix("V D V^T", V * ed.DiagonalizedMatrix * V.Transpose); foreach (RealEigenpair pair in ed.Eigenpairs) { Console.WriteLine($"Eigenvalue {pair.Eigenvalue}"); PrintMatrix("Hv", H * pair.Eigenvector); PrintMatrix("ev", pair.Eigenvalue * pair.Eigenvector); } ed.Eigenpairs.Sort(OrderBy.MagnitudeDescending); Console.WriteLine($"Largest eigenvalue {ed.Eigenpairs[0].Eigenvalue}"); ed.Eigenpairs.Sort(OrderBy.ValueAscending); Console.WriteLine($"Least eigenvalue {ed.Eigenpairs[0].Eigenvalue}"); double[] eigenvalues = H.Eigenvalues(); double sum = 0.0; double product = 1.0; foreach (double eigenvalue in eigenvalues) { sum += eigenvalue; product *= eigenvalue; } Console.WriteLine($"sum(e) = {sum}, tr(H) = {H.Trace()}"); Console.WriteLine($"prod(e) = {product}, det(H) = {H.CholeskyDecomposition().Determinant()}"); SquareMatrix G1 = new SquareMatrix(4); G1[0, 3] = 1.0; G1[1, 2] = 1.0; G1[2, 1] = -1.0; G1[3, 0] = -1.0; ComplexEigendecomposition ced = G1.Eigendecomposition(); foreach (ComplexEigenpair pair in ced.Eigenpairs) { Console.WriteLine(pair.Eigenvalue); } }
public void GaussianIntegrals() { Random rng = new Random(1); for (int d = 2; d < 4; d++) { if (d == 4 || d == 5 || d == 6) { continue; } Console.WriteLine(d); // Create a symmetric matrix SymmetricMatrix A = new SymmetricMatrix(d); for (int r = 0; r < d; r++) { for (int c = 0; c < r; c++) { A[r, c] = rng.NextDouble(); } // Ensure it is positive definite by diagonal dominance A[r, r] = r + 1.0; } // Compute its determinant, which appears in the analytic value of the integral CholeskyDecomposition CD = A.CholeskyDecomposition(); double detA = CD.Determinant(); // Compute the integral Func <IList <double>, double> f = (IList <double> x) => { ColumnVector v = new ColumnVector(x); double s = v.Transpose() * (A * v); return(Math.Exp(-s)); }; Interval[] volume = new Interval[d]; for (int i = 0; i < d; i++) { volume[i] = Interval.FromEndpoints(Double.NegativeInfinity, Double.PositiveInfinity); } IntegrationResult I = MultiFunctionMath.Integrate(f, volume); // Compare to the analytic result Console.WriteLine("{0} ({1}) {2}", I.Value, I.Precision, Math.Sqrt(MoreMath.Pow(Math.PI, d) / detA)); Assert.IsTrue(TestUtilities.IsNearlyEqual(I.Value, Math.Sqrt(MoreMath.Pow(Math.PI, d) / detA), new EvaluationSettings() { AbsolutePrecision = 2.0 * I.Precision })); } }
public void SymmetricMatrixDecomposition() { for (int d = 1; d <= 4; d++) { SymmetricMatrix H = TestUtilities.CreateSymmetricHilbertMatrix(d); CholeskyDecomposition CD = H.CholeskyDecomposition(); Assert.IsTrue(CD != null, String.Format("d={0} not positive definite", d)); Assert.IsTrue(CD.Dimension == d); SymmetricMatrix HI = CD.Inverse(); SquareMatrix I = TestUtilities.CreateSquareUnitMatrix(d); Assert.IsTrue(TestUtilities.IsNearlyEqual(H * HI, I)); } }
public void GaussianIntegrals() { Random rng = new Random(1); for (int d = 2; d < 8; d++) { Console.WriteLine(d); // Create a symmetric matrix SymmetricMatrix A = new SymmetricMatrix(d); for (int r = 0; r < d; r++) { for (int c = 0; c < r; c++) { A[r, c] = rng.NextDouble(); } // Ensure it is positive definite by diagonal dominance A[r, r] = r + 1.0; } // Compute its determinant, which appears in the analytic value of the integral CholeskyDecomposition CD = A.CholeskyDecomposition(); double detA = CD.Determinant(); // Compute the integral Func <IReadOnlyList <double>, double> f = (IReadOnlyList <double> x) => { ColumnVector v = new ColumnVector(x); double s = v.Transpose * (A * v); return(Math.Exp(-s)); }; Interval[] volume = new Interval[d]; for (int i = 0; i < d; i++) { volume[i] = Interval.FromEndpoints(Double.NegativeInfinity, Double.PositiveInfinity); } // These are difficult integrals; demand reduced precision. IntegrationSettings settings = new IntegrationSettings() { RelativePrecision = Math.Pow(10.0, -(4.0 - d / 2.0)) }; IntegrationResult I = MultiFunctionMath.Integrate(f, volume, settings); // Compare to the analytic result Assert.IsTrue(I.Estimate.ConfidenceInterval(0.95).ClosedContains(Math.Sqrt(MoreMath.Pow(Math.PI, d) / detA))); } }
public void CholeskySolveExample() { // This is a very simple 3 X 3 problem I just wrote down to test the Cholesky solver SymmetricMatrix S = new SymmetricMatrix(3); S[0, 0] = 4.0; S[1, 0] = -2.0; S[1, 1] = 5.0; S[2, 0] = 1.0; S[2, 1] = 3.0; S[2, 2] = 6.0; CholeskyDecomposition CD = S.CholeskyDecomposition(); ColumnVector b = new ColumnVector(9.0, 7.0, 15.0); ColumnVector x = CD.Solve(b); Assert.IsTrue(TestUtilities.IsNearlyEqual(S * x, b)); }
/// <summary> /// Fits the data to an arbitrary parameterized function. /// </summary> /// <param name="function">The fit function.</param> /// <param name="start">An initial guess at the parameters.</param> /// <returns>A fit result containing the best-fitting function parameters /// and a χ<sup>2</sup> test of the quality of the fit.</returns> /// <exception cref="ArgumentNullException"><paramref name="function"/> or <paramref name="start"/> are <see langword="null"/>.</exception> /// <exception cref="InsufficientDataException">There are fewer data points than fit parameters.</exception> /// <exception cref="DivideByZeroException">The curvature matrix is singular, indicating that the data is independent of /// one or more parameters, or that two or more parameters are linearly dependent.</exception> public FitResult FitToFunction(Func <double[], T, double> function, double[] start) { if (function == null) { throw new ArgumentNullException(nameof(function)); } if (start == null) { throw new ArgumentNullException(nameof(start)); } // you can't do a fit with less data than parameters if (this.Count < start.Length) { throw new InsufficientDataException(); } /* * Func<IList<double>, double> function0 = (IList<double> x0) => { * double[] x = new double[x0.Count]; * x0.CopyTo(x, 0); * return(function(x)); * }; * MultiExtremum minimum0 = MultiFunctionMath.FindMinimum(function0, start); */ // create a chi^2 fit metric and minimize it FitMetric <T> metric = new FitMetric <T>(this, function); SpaceExtremum minimum = FunctionMath.FindMinimum(new Func <double[], double>(metric.Evaluate), start); // compute the covariance (Hessian) matrix by inverting the curvature matrix SymmetricMatrix A = 0.5 * minimum.Curvature(); CholeskyDecomposition CD = A.CholeskyDecomposition(); // should not return null if we were at a minimum if (CD == null) { throw new DivideByZeroException(); } SymmetricMatrix C = CD.Inverse(); // package up the results and return them TestResult test = new TestResult("ChiSquare", minimum.Value, TestType.RightTailed, new ChiSquaredDistribution(this.Count - minimum.Dimension)); FitResult fit = new FitResult(minimum.Location(), C, test); return(fit); }
public void CatalanHankelMatrixDeterminant() { for (int d = 1; d <= 8; d++) { SymmetricMatrix S = new SymmetricMatrix(d); for (int r = 0; r < d; r++) { for (int c = 0; c <= r; c++) { int n = r + c; S[r, c] = AdvancedIntegerMath.BinomialCoefficient(2 * n, n) / (n + 1); } } CholeskyDecomposition CD = S.CholeskyDecomposition(); Assert.IsTrue(TestUtilities.IsNearlyEqual(CD.Determinant(), 1.0)); } }
public void HilbertMatrixCholeskyDecomposition() { for (int d = 1; d <= 4; d++) { SymmetricMatrix H = TestUtilities.CreateSymmetricHilbertMatrix(d); // Decomposition succeeds CholeskyDecomposition CD = H.CholeskyDecomposition(); Assert.IsTrue(CD != null); Assert.IsTrue(CD.Dimension == d); // Decomposition works SquareMatrix S = CD.SquareRootMatrix(); Assert.IsTrue(TestUtilities.IsNearlyEqual(S * S.Transpose, H)); // Inverse works SymmetricMatrix HI = CD.Inverse(); Assert.IsTrue(TestUtilities.IsNearlyEqual(H * HI, UnitMatrix.OfDimension(d))); } }
internal static DistributionFitResult <ContinuousDistribution> MaximumLikelihoodFit(IReadOnlyList <double> sample, Func <IReadOnlyList <double>, ContinuousDistribution> factory, IReadOnlyList <double> start, IReadOnlyList <string> names) { Debug.Assert(sample != null); Debug.Assert(factory != null); Debug.Assert(start != null); Debug.Assert(names != null); Debug.Assert(start.Count == names.Count); // Define a log likelihood function Func <IReadOnlyList <double>, double> logL = (IReadOnlyList <double> a) => { ContinuousDistribution d = factory(a); double lnP = 0.0; foreach (double value in sample) { double P = d.ProbabilityDensity(value); if (P == 0.0) { throw new InvalidOperationException(); } lnP += Math.Log(P); } return(lnP); }; // Maximize it MultiExtremum maximum = MultiFunctionMath.FindLocalMaximum(logL, start); ColumnVector b = maximum.Location; SymmetricMatrix C = maximum.HessianMatrix; CholeskyDecomposition CD = C.CholeskyDecomposition(); if (CD == null) { throw new DivideByZeroException(); } C = CD.Inverse(); ContinuousDistribution distribution = factory(maximum.Location); TestResult test = sample.KolmogorovSmirnovTest(distribution); return(new ContinuousDistributionFitResult(names, b, C, distribution, test)); }
/// <summary> /// Fits the data to an arbitrary parameterized function. /// </summary> /// <param name="function">The fit function.</param> /// <param name="start">An initial guess at the parameters.</param> /// <returns>A fit result containing the best-fitting function parameters /// and a χ<sup>2</sup> test of the quality of the fit.</returns> /// <exception cref="ArgumentNullException"><paramref name="function"/> or <paramref name="start"/> are <see langword="null"/>.</exception> /// <exception cref="InsufficientDataException">There are fewer data points than fit parameters.</exception> /// <exception cref="DivideByZeroException">The curvature matrix is singular, indicating that the data is independent of /// one or more parameters, or that two or more parameters are linearly dependent.</exception> public UncertainMeasurementFitResult FitToFunction(Func <double[], T, double> function, double[] start) { if (function == null) { throw new ArgumentNullException(nameof(function)); } if (start == null) { throw new ArgumentNullException(nameof(start)); } // you can't do a fit with less data than parameters if (this.Count < start.Length) { throw new InsufficientDataException(); } // create a chi^2 fit metric and minimize it FitMetric <T> metric = new FitMetric <T>(this, function); SpaceExtremum minimum = FunctionMath.FindMinimum(new Func <double[], double>(metric.Evaluate), start); // compute the covariance (Hessian) matrix by inverting the curvature matrix SymmetricMatrix A = 0.5 * minimum.Curvature(); CholeskyDecomposition CD = A.CholeskyDecomposition(); // should not return null if we were at a minimum if (CD == null) { throw new DivideByZeroException(); } SymmetricMatrix C = CD.Inverse(); // package up the results and return them TestResult test = new TestResult("χ²", minimum.Value, new ChiSquaredDistribution(this.Count - minimum.Dimension), TestType.RightTailed); ParameterCollection parameters = new ParameterCollection(NumberNames(start.Length), new ColumnVector(minimum.Location(), 0, 1, start.Length, true), C); return(new UncertainMeasurementFitResult(parameters, test)); }
/// <summary> /// Find the Gumbel distribution that best fit the given sample. /// </summary> /// <param name="sample">The sample to fit.</param> /// <returns>The fit result.</returns> /// <exception cref="ArgumentNullException"><paramref name="sample"/> is <see langword="null"/>.</exception> /// <exception cref="InsufficientDataException"><paramref name="sample"/> contains fewer than three values.</exception> public static GumbelFitResult FitToGumbel(this IReadOnlyList <double> sample) { if (sample == null) { throw new ArgumentNullException(nameof(sample)); } if (sample.Count < 3) { throw new InsufficientDataException(); } // To do a maximum likelihood fit, start from the log probability of each data point and aggregate to // obtain the log likelihood of the sample // z_i = \frac{x_i - m}{s} // -\ln p_i = \ln s + ( z_i + e^{-z_i}) // \ln L = \sum_i \ln p_i // Take derivatives wrt m and s. // \frac{\partial \ln L}{\partial m} = \frac{1}{s} \sum_i ( 1 - e^{-z_i} ) // \frac{\partial \ln L}{\partial s} = \frac{1}{s} \sum_i ( -1 + z_i - z_i e^{-z_i} ) // Set derivatives to zero to get a system of equations for the maximum. // n = \sum_i e^{-z_i} // n = \sum_i ( z_i - z_i e^{-z_i} ) // that is, <e^z> = 1 and <z> - <z e^z> = 1. // To solve this system, pull e^{m/s} out of the sum in the first equation and solve for m // n = e^{m / s} \sum_i e^{-x_i / s} // m = -s \ln \left( \frac{1}{n} \sum_i e^{-x_i / s} \right) = -s \ln <e^{-x/s}> // Substituting this result into the second equation gets us to // s = \bar{x} - \frac{ <x e^{-x/s}> }{ <e^{x/s}> } // which involves only s. We can use a one-dimensional root-finder to determine s, then determine m // from the first equation. // To avoid exponentiating potentially large x_i, it's better to write the problem in terms // of d_i, where x_i = \bar{x} + d_i. // m = \bar{x} - s \ln <e^{-d/s}> // s = -\frac{ <d e^{-d/s}> }{ <e^{-d/s}> } // To get the covariance matrix, we need the curvature matrix at the minimum, so take more derivatives // \frac{\partial^2 \ln L}{\partial m^2} = - \frac{1}{s} \sum_i e^{-z_i} = - \frac{n}{s^2} // \frac{\partial^2 \ln L}{\partial m \partial s} = - \frac{n}{s^2} <z e^{-z}> // \frac{\partial^2 \ln L}{\partial s^2} = - \frac{n}{s^2} ( <z^2 e^{-z}> + 1 ) // Several crucial pieces of this analysis are taken from Mahdi and Cenac, "Estimating Parameters of Gumbel Distribution // "using the method of moments, probability weighted moments, and maximum likelihood", Revista de Mathematica: // Teoria y Aplicaciones 12 (2005) 151-156 (http://revistas.ucr.ac.cr/index.php/matematica/article/viewFile/259/239) // We will be needed the sample mean and standard deviation int n; double mean, stdDev; Univariate.ComputeMomentsUpToSecond(sample, out n, out mean, out stdDev); stdDev = Math.Sqrt(stdDev / n); // Use the method of moments to get an initial estimate of s. double s0 = Math.Sqrt(6.0) / Math.PI * stdDev; // Define the function to zero Func <double, double> fnc = (double s) => { double u, v; MaximumLikelihoodHelper(sample, n, mean, s, out u, out v); return(s + v / u); }; // Zero it to compute the best-fit s double s1 = FunctionMath.FindZero(fnc, s0); // Compute the corresponding best-fit m double u1, v1; MaximumLikelihoodHelper(sample, n, mean, s1, out u1, out v1); double m1 = mean - s1 * Math.Log(u1); // Compute the curvature matrix double w1 = 0.0; double w2 = 0.0; foreach (double x in sample) { double z = (x - m1) / s1; double e = Math.Exp(-z); w1 += z * e; w2 += z * z * e; } w1 /= sample.Count; w2 /= sample.Count; SymmetricMatrix C = new SymmetricMatrix(2); C[0, 0] = (n - 2) / (s1 * s1); C[0, 1] = (n - 2) / (s1 * s1) * w1; C[1, 1] = (n - 2) / (s1 * s1) * (w2 + 1.0); SymmetricMatrix CI = C.CholeskyDecomposition().Inverse(); // The use of (n-2) here in place of n is a very ad hoc attempt to increase accuracy. // Compute goodness-of-fit GumbelDistribution dist = new GumbelDistribution(m1, s1); TestResult test = sample.KolmogorovSmirnovTest(dist); return(new GumbelFitResult(m1, s1, CI[0, 0], CI[1, 1], CI[0, 1], test)); }
public double PriceVIXOption(VIXOption Option, Func <double, double> epsilon, double stock_0) { int n = 500; double T = Option.maturity; Grid grid = new Grid(0, T, (int)Math.Abs(T * n)); int n_T = grid.get_timeNmbrStep(); int kappa = 2; //Correlation : SymmetricMatrix correl = MakeCorrel(n, grid); SquareMatrix choleskyCorrel = correl.CholeskyDecomposition().SquareRootMatrix(); double rho = 0.1;//correlation between the two Brownian Func <double, double> payoff; switch (Option.type) { case VIXOption.OptionType.Call: payoff = S => Math.Max(S - Option.strike, 0); break; case VIXOption.OptionType.Put: payoff = S => Math.Max(Option.strike - S, 0); break; default: payoff = S => Math.Max(S - Option.strike, 0); break; } double price = 0.0; double McNbSimulation = 1E5; for (int mc = 1; mc <= McNbSimulation; mc++) { // 1-simulation of volterra Hybrid Scheme ColumnVector Z; ColumnVector volterra; //ColumnVector Z_Brownian; HybridScheme hybridscheme = new HybridScheme(choleskyCorrel, grid, kappa, H); hybridscheme.simulate(out Z, out volterra); GaussianSimulator simulator = new GaussianSimulator(); //Z_Brownian = ExtractBrownian(kappa, n_T, Z, volterra); ColumnVector Variance = new ColumnVector(n_T + 1); Variance[0] = epsilon(grid.t(0)); for (int i = 1; i <= grid.get_timeNmbrStep(); i++) { Variance[i] = epsilon(grid.t(i)) * Math.Exp(2 * vi * Ch * volterra[i - 1] - Math.Pow(vi * Ch, 2) * Math.Pow(grid.t(i), 2 * H)); } double X = Math.Log(stock_0); for (int i = 0; i < n_T; i++) { double dW = (rho * Z[i] + Math.Sqrt(1 - Math.Pow(rho, 2)) * Math.Sqrt(grid.get_Step()) * simulator.Next()); X = X - 0.5 * Variance[i] * grid.get_Step() + Math.Sqrt(Variance[i]) * dW; } double S = Math.Exp(X); price += payoff(S); } price /= McNbSimulation; return(price); }
// routines for maximum likelyhood fitting /// <summary> /// Computes the Gamma distribution that best fits the given sample. /// </summary> /// <param name="sample">The sample to fit.</param> /// <returns>The best fit parameters.</returns> /// <remarks> /// <para>The returned fit parameters are the <see cref="ShapeParameter"/> and <see cref="ScaleParameter"/>, in that order. /// These are the same parameters, in the same order, that are required by the <see cref="GammaDistribution(double,double)"/> constructor to /// specify a new Gamma distribution.</para> /// </remarks> /// <exception cref="ArgumentNullException"><paramref name="sample"/> is null.</exception> /// <exception cref="InvalidOperationException"><paramref name="sample"/> contains non-positive values.</exception> /// <exception cref="InsufficientDataException"><paramref name="sample"/> contains fewer than three values.</exception> public static FitResult FitToSample(Sample sample) { if (sample == null) { throw new ArgumentNullException("sample"); } if (sample.Count < 3) { throw new InsufficientDataException(); } // The log likelyhood of a sample given k and s is // \log L = (k-1) \sum_i \log x_i - \frac{1}{s} \sum_i x_i - N \log \Gamma(k) - N k \log s // Differentiating, // \frac{\partial \log L}{\partial s} = \frac{1}{s^2} \sum_i x_i - \frac{Nk}{s} // \frac{\partial \log L}{\partial k} = \sum_i \log x_i - N \psi(k) - N \log s // Setting the first equal to zero gives // k s = N^{-1} \sum_i x_i = <x> // \psi(k) + \log s = N^{-1} \sum_i \log x_i = <log x> // Inserting the first into the second gives a single equation for k // \log k - \psi(k) = \log <x> - <\log x> // Note the RHS need only be computed once. // \log k > \psi(k) for all k, so the RHS had better be positive. They get // closer for large k, so smaller RHS will produce a larger k. double s = 0.0; foreach (double x in sample) { if (x <= 0.0) { throw new InvalidOperationException(); } s += Math.Log(x); } s = Math.Log(sample.Mean) - s / sample.Count; // We can get an initial guess for k from the method of moments // \frac{\mu^2}{\sigma^2} = k double k0 = MoreMath.Sqr(sample.Mean) / sample.Variance; // Since 1/(2k) < \log(k) - \psi(k) < 1/k, we could get a bound; that // might be better to avoid the solver running into k < 0 territory double k1 = FunctionMath.FindZero(k => (Math.Log(k) - AdvancedMath.Psi(k) - s), k0); double s1 = sample.Mean / k1; // Curvature of the log likelyhood is straightforward // \frac{\partial^2 \log L}{\partial s^2} = -\frac{2}{s^3} \sum_i x_i + \frac{Nk}{s^2} = - \frac{Nk}{s^2} // \frac{\partial^2 \log L}{\partial k \partial s} = - \frac{N}{s} // \frac{\partial^2 \log L}{\partial k^2} = - N \psi'(k) // This gives the curvature matrix and thus via inversion the covariance matrix. SymmetricMatrix B = new SymmetricMatrix(2); B[0, 0] = sample.Count * AdvancedMath.Psi(1, k1); B[0, 1] = sample.Count / s1; B[1, 1] = sample.Count * k1 / MoreMath.Sqr(s1); SymmetricMatrix C = B.CholeskyDecomposition().Inverse(); // Do a KS test for goodness-of-fit TestResult test = sample.KolmogorovSmirnovTest(new GammaDistribution(k1, s1)); return(new FitResult(new double[] { k1, s1 }, C, test)); }
// the internal linear regression routine, which assumes inputs are entirely valid private FitResult LinearRegression_Internal(int outputIndex) { // to do a fit, we need more data than parameters if (Count < Dimension) { throw new InsufficientDataException(); } // construct the design matrix SymmetricMatrix D = new SymmetricMatrix(Dimension); for (int i = 0; i < Dimension; i++) { for (int j = 0; j <= i; j++) { if (i == outputIndex) { if (j == outputIndex) { D[i, j] = Count; } else { D[i, j] = storage[j].Mean * Count; } } else { if (j == outputIndex) { D[i, j] = storage[i].Mean * Count; } else { double Dij = 0.0; for (int k = 0; k < Count; k++) { Dij += storage[i][k] * storage[j][k]; } D[i, j] = Dij; } } } } // construct the right hand side ColumnVector b = new ColumnVector(Dimension); for (int i = 0; i < Dimension; i++) { if (i == outputIndex) { b[i] = storage[i].Mean * Count; } else { double bi = 0.0; for (int k = 0; k < Count; k++) { bi += storage[outputIndex][k] * storage[i][k]; } b[i] = bi; } } // solve the system for the linear model parameters CholeskyDecomposition CD = D.CholeskyDecomposition(); ColumnVector parameters = CD.Solve(b); // find total sum of squares, with dof = # points - 1 (minus one for the variance-minimizing mean) double totalSumOfSquares = storage[outputIndex].Variance * Count; // find remaining unexplained sum of squares, with dof = # points - # parameters double unexplainedSumOfSquares = 0.0; for (int r = 0; r < Count; r++) { double y = 0.0; for (int c = 0; c < Dimension; c++) { if (c == outputIndex) { y += parameters[c]; } else { y += parameters[c] * storage[c][r]; } } unexplainedSumOfSquares += MoreMath.Sqr(y - storage[outputIndex][r]); } int unexplainedDegreesOfFreedom = Count - Dimension; double unexplainedVariance = unexplainedSumOfSquares / unexplainedDegreesOfFreedom; // find explained sum of squares, with dof = # parameters - 1 double explainedSumOfSquares = totalSumOfSquares - unexplainedSumOfSquares; int explainedDegreesOfFreedom = Dimension - 1; double explainedVariance = explainedSumOfSquares / explainedDegreesOfFreedom; // compute F statistic from sums of squares double F = explainedVariance / unexplainedVariance; Distribution fDistribution = new FisherDistribution(explainedDegreesOfFreedom, unexplainedDegreesOfFreedom); SymmetricMatrix covariance = unexplainedVariance * CD.Inverse(); return(new FitResult(parameters, covariance, new TestResult("F", F, TestType.RightTailed, fDistribution))); }
public ColumnVector VIXfuture_HybridMethod(double T, Func <double, double> epsilon) { //Result ColumnVector VIXFutures; //For Exécution Time DateTime start = DateTime.Now; //Input: int kappa = 2; // the Gri t_0 ..... t_{100} = T int n = 500; Grid grid = new Grid(0, T, (int)Math.Abs(T * n)); int n_T = grid.get_timeNmbrStep(); n_tH = Math.Pow(n_T, H - 0.5); //optimizeMC //The second Grid t_0=T, t_1 ..... t_N = T + Delta int Nbstep = 20; //Correlation : SymmetricMatrix correl = MakeCorrel(n, grid); SquareMatrix choleskyCorrel = correl.CholeskyDecomposition().SquareRootMatrix(); int period = 100; VIXFutures = new ColumnVector(grid.get_timeNmbrStep() / period); //-------------------------------------------------------------------------------------------------------------------------------------------------------------- //-----------------------------------MC---------------------------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------------------------------------------------------------- var MCnbofSimulation = 3.0E4; HybridScheme hybridscheme = new HybridScheme(choleskyCorrel, grid, kappa, H); for (int mc = 1; mc <= MCnbofSimulation; mc++) { // 1-simulation of volterra Hybrid Scheme ColumnVector Z; ColumnVector volterra; ColumnVector Z_Brownian; hybridscheme.simulateFFT(out Z, out volterra); //2 - extract the path of the Brownian motion Z driving the Volterra process Z_Brownian = ExtractBrownian(kappa, n_T, Z, volterra); //Grid secondGrid2 = new Grid(grid.t(n_T), grid.t(n_T) + Delta, 20); //ColumnVector volterraT2 = EulerMEthode(grid, secondGrid2, n_T, volterra, Z_Brownian); //double VIX_future_i2 = VIX_Calculate(epsilon, secondGrid2, volterraT2); //VIXFutures[0] += VIX_future_i2; //DateTime starttestM = DateTime.Now; //for (int zz = 1; zz <= 1.0E4; zz++) //{ // hybridscheme.simulateFFT(out Z, out volterra); //} //TimeSpan timeExecutiontestM = DateTime.Now - starttestM; //DateTime starttest = DateTime.Now; //for (int zz = 1; zz <= 1.0E4; zz++) //{ // for (int i = 1; i <= volterra.Count() / period; i++) // { // int N_i2 = i * period; // //3- approximate the continuous-time process V^T by the discrete-time version V^T~ defined via the forward Euler scheme // double[] volterraT2 = EulerMEthode(grid, secondGrid, N_i2, volterra, Z_Brownian); // //double VIX_future_i2 = VIX_Calculate(epsilon, secondGrid, volterraT2); // //VIXFutures[i - 1] += VIX_future_i2; // } //} //TimeSpan timeExecutiontest = DateTime.Now - starttest; for (int i = 1; i <= volterra.Count() / period; i++) { int N_i = i * period; Grid secondGrid = new Grid(grid.t(N_i), grid.t(N_i) + Delta, Nbstep); //3- approximate the continuous-time process V^T by the discrete-time version V^T~ defined via the forward Euler scheme double[] volterraT = EulerMEthode(grid, secondGrid, N_i, volterra, Z_Brownian); double VIX_future_i = VIX_Calculate(epsilon, secondGrid, volterraT); VIXFutures[i - 1] += VIX_future_i; } }// ENd of MC //MC Mean for (int i = 0; i < VIXFutures.Count(); i++) { VIXFutures[i] /= MCnbofSimulation; } TimeSpan timeExecution = DateTime.Now - start; return(VIXFutures); }
/// <summary> /// Finds the Beta distribution that best fits the given sample. /// </summary> /// <param name="sample">The sample to fit.</param> /// <returns>The best fit parameters.</returns> /// <exception cref="ArgumentNullException"><paramref name="sample"/> is <see langword="null"/>.</exception> /// <exception cref="InsufficientDataException"><paramref name="sample"/> contains fewer than three values.</exception> /// <exception cref="InvalidOperationException">Not all the entries in <paramref name="sample" /> lie between zero and one.</exception> public static BetaFitResult FitToBeta(this IReadOnlyList <double> sample) { if (sample == null) { throw new ArgumentNullException(nameof(sample)); } if (sample.Count < 3) { throw new InsufficientDataException(); } // maximum likelihood calculation // \log L = \sum_i \left[ (\alpha-1) \log x_i + (\beta-1) \log (1-x_i) - \log B(\alpha,\beta) \right] // using \frac{\partial B(a,b)}{\partial a} = \psi(a) - \psi(a+b), we have // \frac{\partial \log L}{\partial \alpha} = \sum_i \log x_i - N \left[ \psi(\alpha) - \psi(\alpha+\beta) \right] // \frac{\partial \log L}{\partial \beta} = \sum_i \log (1-x_i) - N \left[ \psi(\beta) - \psi(\alpha+\beta) \right] // set equal to zero to get equations for \alpha, \beta // \psi(\alpha) - \psi(\alpha+\beta) = <\log x> // \psi(\beta) - \psi(\alpha+\beta) = <\log (1-x)> // compute the mean log of x and (1-x) // these are the (logs of) the geometric means double ga = 0.0; double gb = 0.0; foreach (double value in sample) { if ((value <= 0.0) || (value >= 1.0)) { throw new InvalidOperationException(); } ga += Math.Log(value); gb += Math.Log(1.0 - value); } ga /= sample.Count; gb /= sample.Count; // define the function to zero Func <IReadOnlyList <double>, IReadOnlyList <double> > f = delegate(IReadOnlyList <double> x) { double pab = AdvancedMath.Psi(x[0] + x[1]); return(new double[] { AdvancedMath.Psi(x[0]) - pab - ga, AdvancedMath.Psi(x[1]) - pab - gb }); }; // guess initial values using the method of moments // M1 = \frac{\alpha}{\alpha+\beta} C2 = \frac{\alpha\beta}{(\alpha+\beta)^2 (\alpha+\beta+1)} // implies // \alpha = M1 \left( \frac{M1 (1-M1)}{C2} - 1 \right) // \beta = (1 - M1) \left( \frac{M1 (1-M1)}{C2} -1 \right) int n; double m, v; ComputeMomentsUpToSecond(sample, out n, out m, out v); v = v / n; double mm = 1.0 - m; double q = m * mm / v - 1.0; double[] x0 = new double[] { m *q, mm *q }; // find the parameter values that zero the two equations ColumnVector ab = MultiFunctionMath.FindZero(f, x0); double a = ab[0]; double b = ab[1]; // take more derivatives of \log L to get curvature matrix // \frac{\partial^2 \log L}{\partial\alpha^2} = - N \left[ \psi'(\alpha) - \psi'(\alpha+\beta) \right] // \frac{\partial^2 \log L}{\partial\beta^2} = - N \left[ \psi'(\beta) - \psi'(\alpha+\beta) \right] // \frac{\partial^2 \log L}{\partial \alpha \partial \beta} = - N \psi'(\alpha+\beta) // covariance matrix is inverse of curvature matrix SymmetricMatrix C = new SymmetricMatrix(2); C[0, 0] = sample.Count * (AdvancedMath.Psi(1, a) - AdvancedMath.Psi(1, a + b)); C[1, 1] = sample.Count * (AdvancedMath.Psi(1, b) - AdvancedMath.Psi(1, a + b)); C[0, 1] = sample.Count * AdvancedMath.Psi(1, a + b); CholeskyDecomposition CD = C.CholeskyDecomposition(); if (CD == null) { throw new DivideByZeroException(); } C = CD.Inverse(); // do a KS test on the result BetaDistribution distribution = new BetaDistribution(a, b); TestResult test = sample.KolmogorovSmirnovTest(distribution); return(new BetaFitResult(ab, C, distribution, test)); }
/// <summary> /// Computes the Weibull distribution that best fits the given sample. /// </summary> /// <param name="sample">The sample to fit.</param> /// <returns>The best fit parameters.</returns> /// <remarks> /// <para>The returned fit parameters are the <see cref="ShapeParameter"/> and <see cref="ScaleParameter"/>, in that order. /// These are the same parameters, in the same order, that are required by the <see cref="WeibullDistribution(double,double)"/> constructor to /// specify a new Weibull distribution.</para> /// </remarks> /// <exception cref="ArgumentNullException"><paramref name="sample"/> is null.</exception> /// <exception cref="InvalidOperationException"><paramref name="sample"/> contains non-positive values.</exception> /// <exception cref="InsufficientDataException"><paramref name="sample"/> contains fewer than three values.</exception> public static FitResult FitToSample(Sample sample) { if (sample == null) { throw new ArgumentNullException("sample"); } if (sample.Count < 3) { throw new InsufficientDataException(); } if (sample.Minimum <= 0.0) { throw new InvalidOperationException(); } // The log likelyhood function is // \log L = N \log k + (k-1) \sum_i \log x_i - N K \log \lambda - \sum_i \left(\frac{x_i}{\lambda}\right)^k // Taking derivatives, we get // \frac{\partial \log L}{\partial \lambda} = - \frac{N k}{\lambda} + \sum_i \frac{k}{\lambda} \left(\frac{x_i}{\lambda}\right)^k // \frac{\partial \log L}{\partial k} =\frac{N}{k} + \sum_i \left[ 1 - \left(\frac{x_i}{\lambda}\right)^k \right] \log \left(\frac{x_i}{\lambda}\right) // Setting the first expression to zero and solving for \lambda gives // \lambda = \left( N^{-1} \sum_i x_i^k \right)^{1/k} = ( < x^k > )^{1/k} // which allows us to reduce the problem from 2D to 1D. // By the way, using the expression for the moment < x^k > of the Weibull distribution, you can show there is // no bias to this result even for finite samples. // Setting the second expression to zero gives // \frac{1}{k} = \frac{1}{N} \sum_i \left[ \left( \frac{x_i}{\lambda} \right)^k - 1 \right] \log \left(\frac{x_i}{\lambda}\right) // which, given the equation for \lambda as a function of k derived from the first expression, is an implicit equation for k. // It cannot be solved in closed form, but we have now reduced our problem to finding a root in one-dimension. // We need a starting guess for k. // The method of moments equations are not solvable for the parameters in closed form // but the scale parameter drops out of the ratio of the 1/3 and 2/3 quantile points // and the result is easily solved for the shape parameter // k = \frac{\log 2}{\log\left(\frac{x_{2/3}}{x_{1/3}}\right)} double x1 = sample.InverseLeftProbability(1.0 / 3.0); double x2 = sample.InverseLeftProbability(2.0 / 3.0); double k0 = Global.LogTwo / Math.Log(x2 / x1); // Given the shape paramter, we could invert the expression for the mean to get // the scale parameter, but since we have an expression for \lambda from k, we // dont' need it. //double s0 = sample.Mean / AdvancedMath.Gamma(1.0 + 1.0 / k0); // Simply handing our 1D function to a root-finder works fine until we start to encounter large k. For large k, // even just computing \lambda goes wrong because we are taking x_i^k which overflows. Horst Rinne, "The Weibull // Distribution: A Handbook" describes a way out. Basically, we first move to variables z_i = \log(x_i) and // then w_i = z_i - \bar{z}. Then lots of factors of e^{k \bar{z}} cancel out and, even though we still do // have some e^{k w_i}, the w_i are small and centered around 0 instead of large and centered around \lambda. Sample transformedSample = sample.Copy(); transformedSample.Transform(x => Math.Log(x)); double zbar = transformedSample.Mean; transformedSample.Transform(z => z - zbar); // After this change of variable the 1D function to zero becomes // g(k) = \sum_i ( 1 - k w_i ) e^{k w_i} // It's easy to show that g(0) = n and g(\infinity) = -\infinity, so it must cross zero. It's also easy to take // a derivative // g'(k) = - k \sum_i w_i^2 e^{k w_i} // so we can apply Newton's method. int i = 0; double k1 = k0; while (true) { i++; double g = 0.0; double gp = 0.0; foreach (double w in transformedSample) { double e = Math.Exp(k1 * w); g += (1.0 - k1 * w) * e; gp -= k1 * w * w * e; } double dk = -g / gp; k1 += dk; if (Math.Abs(dk) <= Global.Accuracy * Math.Abs(k1)) { break; } if (i >= Global.SeriesMax) { throw new NonconvergenceException(); } } // The corresponding lambda can also be expressed in terms of zbar and w's. double t = 0.0; foreach (double w in transformedSample) { t += Math.Exp(k1 * w); } t /= transformedSample.Count; double lambda1 = Math.Exp(zbar) * Math.Pow(t, 1.0 / k1); // We need the curvature matrix at the minimum of our log likelyhood function // to determine the covariance matrix. Taking more derivatives... // \frac{\partial^2 \log L} = \frac{N k}{\lambda^2} - \sum_i \frac{k(k+1) x_i^k}{\lambda^{k+2}} // = - \frac{N k^2}{\lambda^2} // The second expression follows by inserting the first-derivative-equal-zero relation into the first. // For k=1, this agrees with the variance formula for the mean of the best-fit exponential. // Derivatives involving k are less simple. // We end up needing the means < (x/lambda)^k log(x/lambda) > and < (x/lambda)^k log^2(x/lambda) > double mpl = 0.0; double mpl2 = 0.0; foreach (double x in sample) { double r = x / lambda1; double p = Math.Pow(r, k1); double l = Math.Log(r); double pl = p * l; double pl2 = pl * l; mpl += pl; mpl2 += pl2; } mpl = mpl / sample.Count; mpl2 = mpl2 / sample.Count; // See if we can't do any better here. Transforming to zbar and w's looked ugly, but perhaps it // can be simplified? One interesting observation: if we take expectation values (which gives // the Fisher information matrix) the entries become simple: // B_{\lambda \lambda} = \frac{N k^2}{\lambda^2} // B_{\lambda k} = -\Gamma'(2) \frac{N}{\lambda} // B_{k k } = [1 + \Gamma''(2)] \frac{N}{k^2} // Would it be bad to just use these directly? // Construct the curvature matrix and invert it. SymmetricMatrix B = new SymmetricMatrix(2); B[0, 0] = sample.Count * MoreMath.Sqr(k1 / lambda1); B[0, 1] = -sample.Count * k1 / lambda1 * mpl; B[1, 1] = sample.Count * (1.0 / MoreMath.Pow2(k1) + mpl2); SymmetricMatrix C = B.CholeskyDecomposition().Inverse(); // Do a KS test to compare sample to best-fit distribution Distribution distribution = new WeibullDistribution(lambda1, k1); TestResult test = sample.KolmogorovSmirnovTest(distribution); // return the result return(new FitResult(new double[] { lambda1, k1 }, C, test)); }
public void TestCorrelatedSimulation()//for Hybrid Methode { StreamWriter Z_text = new StreamWriter("C:\\Users\\Marouane\\Desktop\\M2IF\\rough volatility\\FileZ.txt"); StreamWriter Z1_text = new StreamWriter("C:\\Users\\Marouane\\Desktop\\M2IF\\rough volatility\\FileZ1.txt"); StreamWriter Z2_text = new StreamWriter("C:\\Users\\Marouane\\Desktop\\M2IF\\rough volatility\\FileZ2.txt"); rBergomiVIXfuture model = new rBergomiVIXfuture(); int n = 500; double T = 0.5; Grid grid = new Grid(0, T, (int)Math.Abs(T * n)); //Correl SymmetricMatrix correl = model.MakeCorrel(n, grid); SquareMatrix choleskyCorrel = correl.CholeskyDecomposition().SquareRootMatrix(); //Gaussian Simulator var simulator = new GaussianSimulator(); double mc = 1.0E5; ColumnVector Z_test = new ColumnVector((int)mc); ColumnVector Z_test_1 = new ColumnVector((int)mc); ColumnVector Z_test_2 = new ColumnVector((int)mc); ColumnVector G = new ColumnVector((int)mc); for (int i = 1; i <= mc; i++) { DoubleVector Z = new DoubleVector(grid.get_timeNmbrStep()); RectangularMatrix Z_k = new RectangularMatrix(2, grid.get_timeNmbrStep()); HybridScheme hybridscheme = new HybridScheme(choleskyCorrel, grid, 2, 0.07); hybridscheme.SimulateCorrelated(Z, Z_k);//Simulate 3 Correlated Gaussians Z_i; Z_k_{1,i}; Z_k_{2,i} i:= 0, ..., n_T int mid = (int)grid.get_timeNmbrStep() / 2; G[i - 1] = simulator.Next(); Z_test[i - 1] = Z[mid]; Z_test_1[i - 1] = Z_k[0, mid]; Z_test_2[i - 1] = Z_k[1, mid]; string Ztext_ = ""; string Ztext1_ = ""; string Ztext2_ = ""; for (int k = 0; k < Z.RowCount; k++) { Ztext_ += (Z[k].ToString() + "; "); Ztext1_ += (Z_k[0, k].ToString() + "; "); Ztext2_ += (Z_k[1, k].ToString() + "; "); } Z_text.WriteLine(Ztext_); Z1_text.WriteLine(Ztext1_); Z2_text.WriteLine(Ztext2_); } Z_text.Close(); Z1_text.Close(); Z2_text.Close(); BivariateSample mybivariate = new BivariateSample(); mybivariate.Add(Z_test, Z_test_2); double correlcoef = mybivariate.CorrelationCoefficient; double correlreal = correl[2, 0]; Sample mysample = new Sample(Z_test); double mean = mysample.Mean; //must be =0 double variance = mysample.Variance; //must be =1/n var result = mysample.KolmogorovSmirnovTest(new NormalDistribution()); Sample mysampleG = new Sample(G); var result2 = mysampleG.KolmogorovSmirnovTest(new NormalDistribution()); }
public double VIXfuture_TruncatedChlsky(double T, Func <double, double> epsilon) { DateTime start = DateTime.Now; //StreamWriter sw = new StreamWriter("C:\\Users\\Marouane\\Desktop\\M2IF\\rough volatility\\rBergomiFile.txt"); //Grid int N = 100; int S = 7; Grid grid = new Grid(T, T + Delta, N); #region Correlation "Correl Matrix Construction" // Small Covariance Matrix t_0 , ... , t_7 SymmetricMatrix covM_Small = new SymmetricMatrix(S); //the full Covariance Matrix SymmetricMatrix covM = new SymmetricMatrix(N + 1); //Setting for integrale approximations EvaluationSettings settings = new EvaluationSettings(); settings.AbsolutePrecision = 1.0E-7; settings.RelativePrecision = 0.0; // Small Covariance Calcul t_0,.....,t_7 for (int i = 0; i < S; i++) { covM_Small[i, i] = (Math.Pow(grid.t(i), 2 * H) - Math.Pow(grid.t(i) - T, 2 * H)) / (2 * H); for (int j = 0; j < i; j++) { Func <double, double> covfunc = t => Math.Pow((grid.t(j) - t) * (grid.t(i) - t), H - 0.5); IntegrationResult integresult = FunctionMath.Integrate(covfunc, Meta.Numerics.Interval.FromEndpoints(0.0, T), settings); covM_Small[i, j] = integresult.Value; } } // full COrrelation Calcul for (int i = 0; i <= N; i++) { covM[i, i] = (Math.Pow(grid.t(i), 2 * H) - Math.Pow(grid.t(i) - T, 2 * H)) / (2 * H); for (int j = 0; j < i; j++) { Func <double, double> covfunc = t => Math.Pow((grid.t(j) - t) * (grid.t(i) - t), H - 0.5); IntegrationResult integresult = FunctionMath.Integrate(covfunc, Meta.Numerics.Interval.FromEndpoints(0.0, T), settings); covM[i, j] = integresult.Value; } } Func <int, int, double> corrf_Small = (i, j) => covM_Small[i, j] / (Math.Sqrt(covM_Small[i, i] * covM_Small[j, j])); SymmetricMatrix Correl_Small = new SymmetricMatrix(S); Correl_Small.Fill(corrf_Small); Func <int, int, double> corrf = (i, j) => covM[i, j] / (Math.Sqrt(covM[i, i] * covM[j, j])); SymmetricMatrix Correl = new SymmetricMatrix(N + 1); Correl.Fill(corrf); #endregion CholeskyDecomposition cholesky = Correl_Small.CholeskyDecomposition(); SquareMatrix choleskyCorrel_Small = new SquareMatrix(S); choleskyCorrel_Small = cholesky.SquareRootMatrix(); GaussianSimulator simulator = new GaussianSimulator(); double VIX = 0.0; var MC = 1.0E5; for (int mc = 1; mc < MC; mc++) { ColumnVector GaussianVector = new ColumnVector(S); // Simulating Volterra at 8 first steps on [T, T+Delta] for (int i = 0; i < S; i++) { GaussianVector[i] = simulator.Next(); } ColumnVector Volterra_small = choleskyCorrel_Small * GaussianVector; //Adjusting the variance of Volterra Processus for (int i = 0; i < S; i++) { Volterra_small[i] = Volterra_small[i] * Math.Sqrt((Math.Pow(grid.t(i), 2 * H) - Math.Pow(grid.t(i) - grid.t(0), 2 * H)) / (2 * H)); } // Simulating VOlterra on t_8 ... t_N with the truncated Formula double[] Volterra = new double[N + 1]; for (int i = 0; i <= N; i++) { if (i < S) { Volterra[i] = Volterra_small[i]; } //Tranceted Formula else { Volterra[i] = Math.Sqrt(covM[i, i]) * (Correl[i, i - 1] * Volterra[i - 1] / Math.Sqrt(covM[i - 1, i - 1]) + Math.Sqrt(1 - Math.Pow(Correl[i, i - 1], 2)) * simulator.Next()); } } double VIX_ = VIX_Calculate(epsilon, grid, Volterra); VIX += VIX_; } //sw.Close(); VIX /= MC; TimeSpan dur = DateTime.Now - start; return(VIX); }
// We need a goodness-of-fit measurement internal LinearLogisticRegressionResult(IReadOnlyList <double> x, IReadOnlyList <bool> y) { Debug.Assert(x != null); Debug.Assert(y != null); Debug.Assert(x.Count == y.Count); // check size of data set int n = x.Count; if (n < 3) { throw new InsufficientDataException(); } // The linear logistic model is: // p_i = \sigma(t_i) \quad t_i = a + b x_i // So the log likelihood of the data set under the model is: // \ln L = \sum_{{\rm true} i} \ln p_i + \sum_{{\rm false} i} \ln (1 - p_i) // = \sum_{{\rm true} i} \ln \sigma(t_i) + \sum_{{\rm false} i} \ln (1 - \sigma(t_i)) // Taking derivatives: // \frac{\partial L}{\partial a} = \sum_{{\rm true} i} \frac{\sigma'(t_i)}{\sigma(t_i)} // + \sum_{{\rm false} i} \frac{-\sigma'(t_i)}{1 - \sigma(t_i)} // \frac{\partial L}{\partial b} = \sum_{{\rm true} i} \frac{\sigma'(t_i)}{\sigma(t_i)} x_i // + \sum_{{\rm false} i} \frac{-\sigma'(t_i)}{1 - \sigma(t_i)} x_i // Using \sigma(t) = \frac{1}{1 + e^{-t}}, we can derive: // \frac{\sigma'(t)}{\sigma(t)} = \sigma(-t) // \frac{\sigma'(t)}{1 - \sigma(t)} = \sigma(t) // So this becomes // \frac{\partial L}{\partial a} = \sum_i \pm \sigma(\mp t_i) // \frac{\partial L}{\partial b} = \sum_i \pm \sigma(\mp t_i) x_i // where the upper sign is for true values and the lower sign is for false values. // Find the simultaneous zeros of these equations to obtain the likelihood-maximizing a, b. // To get the curvature matrix, we need the second derivatives. // \frac{\partial^2 L}{\partial a^2} = - \sum_i \sigma'(\mp t_i) // \frac{\partial^2 L}{\partial a \partial b} = - \sum_i \sigma'(\mp t_i) x_i // \frac{\partial^2 L}{\partial b^2} = - \sum_i \sigma'(\mp t_i) x_i^2 // We need an initial guess at the parameters. Begin with the Ansatz of the logistic model: // \frac{p}{1-p} = e^{\alpha + \beta x} // Differentiate and do some algebra to get: // \frac{\partial p}{\partial x} = \beta p ( 1 - p) // Evaluating at means, and noting that p (1 - p) = var(y) and that, in a development around the means, // cov(p, x) = \frac{\partial p}{\partial x} var(x) // we get // \beta = \frac{cov(y, x)}{var(x) var(y)} // This approximation gets the sign right, but it looks like it usually gets the magnitude quite wrong. // The problem with the approach is that var(y) = p (1 - p) assumes y are chosen with fixed p, but they aren't. // We need to re-visit this analysis. double xMean, yMean, xxSum, yySum, xySum; Bivariate.ComputeBivariateMomentsUpToTwo(x, y.Select(z => z ? 1.0 : 0.0), out n, out xMean, out yMean, out xxSum, out yySum, out xySum); double p = yMean; double b0 = xySum / xxSum / yySum * n; double a0 = Math.Log(p / (1.0 - p)) - b0 * xMean; Func <IReadOnlyList <double>, IReadOnlyList <double> > J = (IReadOnlyList <double> a) => { double dLda = 0.0; double dLdb = 0.0; for (int i = 0; i < n; i++) { double t = a[0] + a[1] * x[i]; if (y[i]) { double s = Sigma(-t); dLda += s; dLdb += s * x[i]; } else { double s = Sigma(t); dLda -= s; dLdb -= s * x[i]; } } return(new double[] { dLda, dLdb }); }; ColumnVector b = MultiFunctionMath.FindZero(J, new double[] { a0, b0 }); SymmetricMatrix C = new SymmetricMatrix(2); for (int i = 0; i < n; i++) { double t = b[0] + b[1] * x[i]; if (y[i]) { t = -t; } double e = Math.Exp(-t); double sp = e / MoreMath.Sqr(1.0 + e); C[0, 0] += sp; C[0, 1] += sp * x[i]; C[1, 1] += sp * x[i] * x[i]; } CholeskyDecomposition CD = C.CholeskyDecomposition(); if (CD == null) { throw new DivideByZeroException(); } C = CD.Inverse(); best = b; covariance = C; }