public double ADIPrice(string scheme, double thet, HParam param, double S0, double V0, double K, double r, double q, double[] S, double[] V, double[] T, string GridType)
        {
            // Alternating Direction Implicit (ADI) scheme for the Heston model.
            // INPUTS
            //   scheme = 'DO' Douglas, 'CS' Craig-Sneyd,
            //            'MSC' Modified CS, 'HV' Hundsdorfer-Verwer
            //   thet  = weighing parameter 0 = Explicit Scheme
            //           1 = Implicit Scheme, 0.5 = Crank-Nicolson
            //  param = Heston parameters
            //  S0 = Spot price on which to price
            //  V0 = Volatility on which to price
            //  K = Strike price
            //  r = Risk free rate
            //  q = Dividend yield
            //  S = Stock price grid - uniform
            //  V = Volatility grid - uniform
            //  T = Maturity grid - uniform

            Interpolation IP = new Interpolation();
            MatrixOps     MO = new MatrixOps();

            // Heston parameters
            double kappa  = param.kappa;
            double theta  = param.theta;
            double sigma  = param.sigma;
            double v0     = param.v0;
            double rho    = param.rho;
            double lambda = param.lambda;

            // Length of grids;
            int    NS = S.Length;
            int    NV = V.Length;
            int    NT = T.Length;
            double dt = T[1] - T[0];

            // Build the matrices for the derivatives and other matrices
            BuildDerivativesNU BN = new BuildDerivativesNU();
            BuildDerivativesU  BU = new BuildDerivativesU();
            LMatrices          LMat;

            if (GridType == "Uniform")
            {
                LMat = BU.BuildDerivatives(S, V, T);
            }
            else
            {
                LMat = BN.BuildDerivativesNonUniform(S, V, T);
            }

            double[,] derS  = LMat.derS;
            double[,] derSS = LMat.derSS;
            double[,] derV1 = LMat.derV1;
            double[,] derV2 = LMat.derV2;
            double[,] derVV = LMat.derVV;
            double[,] derSV = LMat.derSV;
            double[,] R     = LMat.R;

            // Decompose the derivatives matrices and create the identity matrix
            int N = NS * NV;

            double[,] A0 = new double[N, N];
            double[,] A1 = new double[N, N];
            double[,] A2 = new double[N, N];
            double[,] I  = new double[N, N];
            for (int i = 0; i <= N - 1; i++)
            {
                I[i, i] = 1.0;
                for (int j = 0; j <= N - 1; j++)
                {
                    A0[i, j] = rho * sigma * derSV[i, j];
                    A1[i, j] = (r - q) * derS[i, j] + 0.5 * derSS[i, j] - 0.5 * r * R[i, j];
                    A2[i, j] = kappa * theta * derV1[i, j] - kappa * derV2[i, j] + 0.5 * sigma * sigma * derVV[i, j] - 0.5 * r * R[i, j];
                }
            }

            // Initialize the u vector, create identity matrix
            // u plays the role of U(t-1)
            double[] U = new double[N];
            double[] u = new double[N];

            // U(0) vector - value of U(T) at maturity
            double[] Si = new double[N];
            int      k  = 0;

            for (int v = 0; v <= NV - 1; v++)
            {
                for (int s = 0; s <= NS - 1; s++)
                {
                    Si[k] = S[s];
                    U[k]  = Math.Max(Si[k] - K, 0.0);
                    k    += 1;
                }
            }

            // Matrices for the ADI method
            double[] Y0  = new double[N];
            double[] Y1  = new double[N];
            double[] Y2  = new double[N];
            double[] Y0_ = new double[N];
            double[] Y1_ = new double[N];
            double[] Y2_ = new double[N];
            double[] Y0h = new double[N];

            //// Loop through the time increment
            for (int t = 1; t <= NT - 1; t++)
            {
                u = U;

                // Vectors common to all ADI schemes
                double[,] SumA = MO.MAdd(MO.MAdd(A0, A1), A2);
                double[,] M1   = MO.MMultS(SumA, dt);
                double[,] IM1  = MO.MAdd(I, M1);
                Y0             = MO.MVMult(IM1, u);

                double[,] dtA1     = MO.MMultS(A1, dt * thet);
                double[,] IminusA1 = MO.MSub(I, dtA1);
                double[,] A1dt     = MO.MMultS(A1, dt * thet);
                double[] A1u       = MO.MVMult(A1dt, u);
                double[] Y0minusA1 = MO.VSub(Y0, A1u);
                Y1 = MO.MVMult(MO.MInvLU(IminusA1), Y0minusA1);

                double[,] dtA2     = MO.MMultS(A2, dt * thet);
                double[,] IminusA2 = MO.MSub(I, dtA2);
                double[,] A2dt     = MO.MMultS(A2, dt * thet);
                double[] A2u       = MO.MVMult(A2dt, u);
                double[] Y1minusA2 = MO.VSub(Y1, A2u);
                Y2 = MO.MVMult(MO.MInvLU(IminusA2), Y1minusA2);

                if (scheme == "DO")
                {
                    // Douglas ADI scheme
                    U = Y2;
                }
                else if (scheme == "CS")
                {
                    // Craig-Sneyd ADI scheme
                    double[] A0Y2 = MO.MVMult(A0, Y2);
                    double[] A0u  = MO.MVMult(A0, u);
                    double[] A0A0 = MO.VSub(A0Y2, A0u);
                    double[] dtA  = MO.VMultS(A0A0, dt * 0.5);
                    Y0_ = MO.VAdd(Y0, dtA);

                    double[] Y0_minusA1 = MO.VSub(Y0_, A1u);
                    Y1_ = MO.MVMult(MO.MInvLU(IminusA1), Y0_minusA1);

                    double[] Y1_minusA2 = MO.VSub(Y1_, A2u);
                    Y2_ = MO.MVMult(MO.MInvLU(IminusA2), Y1_minusA2);

                    U = Y2_;
                }
                else if (scheme == "MCS")
                {
                    // Modified Craig-Sneyd ADI scheme
                    double[] A0Y2 = MO.MVMult(A0, Y2);
                    double[] A0u  = MO.MVMult(A0, u);
                    double[] A0A0 = MO.VSub(A0Y2, A0u);
                    double[] dtA  = MO.VMultS(A0A0, dt * thet);
                    Y0h = MO.VAdd(Y0, dtA);

                    double[] SumAY2 = MO.MVMult(SumA, Y2);
                    double[] SumAu  = MO.MVMult(SumA, u);
                    double[] ThetDt = MO.VMultS(MO.VSub(SumAY2, SumAu), (0.5 - thet) * dt);
                    Y0_ = MO.VAdd(Y0h, ThetDt);

                    double[] Y0_minusA1 = MO.VSub(Y0_, A1u);
                    Y1_ = MO.MVMult(MO.MInvLU(IminusA1), Y0_minusA1);

                    double[] Y1_minusA2 = MO.VSub(Y1_, A2u);
                    Y2_ = MO.MVMult(MO.MInvLU(IminusA2), Y1_minusA2);

                    U = Y2_;
                }
                else if (scheme == "HV")
                {
                    // Hundsdorfer-Verver ADI scheme
                    double[] A0Y2 = MO.MVMult(SumA, Y2);
                    double[] A0u  = MO.MVMult(SumA, u);
                    double[] A0A0 = MO.VSub(A0Y2, A0u);
                    double[] dtA  = MO.VMultS(A0A0, dt * 0.5);
                    Y0_ = MO.VAdd(Y0, dtA);

                    double[] A1Y2         = MO.VMultS(MO.MVMult(A1, Y2), thet * dt);
                    double[] Y0_minusA1Y2 = MO.VSub(Y0_, A1Y2);
                    Y1_ = MO.MVMult(MO.MInvLU(IminusA1), Y0_minusA1Y2);

                    double[] A2Y2         = MO.VMultS(MO.MVMult(A2, Y2), thet * dt);
                    double[] Y1_minusA2Y2 = MO.VSub(Y1_, A2Y2);
                    Y2_ = MO.MVMult(MO.MInvLU(IminusA2), Y1_minusA2Y2);

                    U = Y2_;
                }
            }
            // Restack the U vector to output a matrix
            double[,] UU = new double[NS, NV];
            k            = 0;
            for (int v = 0; v <= NV - 1; v++)
            {
                for (int s = 0; s <= NS - 1; s++)
                {
                    UU[s, v] = U[k];
                    k       += 1;
                }
            }

            // Interpolate to get the price at S0 and v0
            return(IP.interp2(V, S, UU, V0, S0));
        }
Пример #2
0
        public LMatrices BuildDerivativesNonUniform(double[] S, double[] V, double[] T)
        {
            // Build the first- and second-order derivatives for the "L" operator matrix
            // for the Weighted method
            // Requires a uniform grid for S and V
            // INPUTS
            //    S = vector for uniform stock price grid
            //    V = vector for uniform volatility grid
            //    T = vector for uniform maturity grid
            //    thet = parameter for the weighted scheme
            // OUTPUTS
            //    Matrices of dimnension N x N (N=NS+NV)
            //    derS  = Matrix for first-order derivative dU/dS
            //    derSS = Matrix for second-order derivative dU2/dS2
            //    derV1 = Matrix for first-order derivative dU/dV, kappa*theta portion
            //    derV2 = Matrix for first-order derivative dU/dV, -kappa*V portion
            //    derVV = Matrix for first-order derivative dU2/dV2
            //    derSV = Matrix for first-order derivative dU2/dSdV
            //    R     = Matrix for r*S(v,t) portion of the PDE

            BuildDerivativesU BU = new BuildDerivativesU();

            // Length of stock price, volatility, and maturity
            int    NS = S.Length;
            int    NV = V.Length;
            int    NT = T.Length;
            double Smin = S[0]; double Smax = S[NS - 1];
            double Vmin = V[0]; double Vmax = V[NV - 1];
            double Tmin = T[0]; double Tmax = T[NT - 1];

            // Preliminary quantities
            // Size of the U(t) vector and L matrix
            int N = NS * NV;

            // The vectors for S and V, stacked
            double[] Si = new double[N];
            double[] Vi = new double[N];
            int      k  = 0;

            for (int v = 0; v <= NV - 1; v++)
            {
                for (int s = 0; s <= NS - 1; s++)
                {
                    Si[k] = S[s];
                    Vi[k] = V[v];
                    k    += 1;
                }
            }

            // Identification of the boundary points
            int[] VminB = new int[N];
            int[] VmaxB = new int[N];
            int[] SminB = new int[N];
            int[] SmaxB = new int[N];

            // Vmin and Vmax
            for (int v = 0; v <= NS - 2; v++)
            {
                VminB[v] = 1;
            }
            for (int v = N - NS + 1; v <= N - 2; v++)
            {
                VmaxB[v] = 1;
            }
            VmaxB[N - 1] = 1;

            // Smin and Smax
            k = 0;
            for (int v = 0; v <= NV - 1; v++)
            {
                for (int s = 0; s <= NS - 1; s++)
                {
                    if (s == 0)
                    {
                        SminB[k] = 1;
                    }
                    else if (s == NS - 1)
                    {
                        SmaxB[k] = 1;
                    }
                    k += 1;
                }
            }
            SminB[0]      = 0;
            SmaxB[NS - 1] = 0;
            SmaxB[N - 1]  = 0;

            // Identification of the non-boundary points
            int[] NB = new int[N];
            for (k = 0; k <= N - 1; k++)
            {
                if (SminB[k] == 0 & SmaxB[k] == 0 & VminB[k] == 0 & VmaxB[k] == 0)
                {
                    NB[k] = 1;
                }
            }
            NB[NS - 1] = 1;

            // Forward, backward and central differences for S
            int[] Cs = new int[N];
            int[] Fs = new int[N];
            int[] Bs = new int[N];
            k = 0;
            for (int v = 0; v <= NV - 2; v++)
            {
                for (int s = 0; s <= NS - 1; s++)
                {
                    if (s == 1)
                    {
                        Fs[k] = 1;
                    }
                    else if (s == NS - 2)
                    {
                        Bs[k] = 1;
                    }
                    else if (s >= 2 & s <= NS - 3)
                    {
                        Cs[k] = 1;
                    }
                    k += 1;
                }
            }
            Fs[1]      = 0;
            Bs[NS - 2] = 0;
            for (k = 2; k <= NS - 3; k++)
            {
                Cs[k] = 0;
            }

            // Forward, backward and central differences for V
            int[] Cv = new int[N];
            int[] Fv = new int[N];
            int[] Bv = new int[N];
            for (k = NS + 1; k <= 2 * NS - 2; k++)
            {
                Fv[k] = 1;
            }
            for (k = (NV - 2) * NS + 1; k <= (NV - 1) * NS - 2; k++)
            {
                Bv[k] = 1;
            }
            k = 0;
            for (int v = 0; v <= NV - 3; v++)
            {
                for (int s = 0; s <= NS - 1; s++)
                {
                    if (s >= 1 & s <= NS - 2)
                    {
                        Cv[k] = 1;
                    }
                    k += 1;
                }
            }
            for (k = 0; k <= 2 * NS - 1; k++)
            {
                Cv[k] = 0;
            }

            // Central difference for SV-derivatives
            int[] Csv = new int[N];
            k = 0;
            for (int v = 0; v <= NV - 2; v++)
            {
                for (int s = 0; s <= NS - 1; s++)
                {
                    if (s >= 1 & s <= NS - 2)
                    {
                        Csv[k] = 1;
                    }
                    k += 1;
                }
            }
            for (k = 0; k <= NS - 1; k++)
            {
                Csv[k] = 0;
            }

            // for(k=0;k<=N-1;k++)
            //      Console.WriteLine("{0} {1} {2} {3} {4} {5} {6} {7} {8} {9} {10} {11}",SminB[k],SmaxB[k],VminB[k],VmaxB[k],NB[k],Fs[k],Bs[k],Cs[k],Fv[k],Bv[k],Cv[k],Csv[k]);

            // Create the matrices for the derivatives
            double[,] derS  = new double[N, N];
            double[,] derSS = new double[N, N];
            double[,] derV1 = new double[N, N];
            double[,] derV2 = new double[N, N];
            double[,] derVV = new double[N, N];
            double[,] derSV = new double[N, N];
            double[,] R     = new double[N, N];

            for (int i = 0; i <= N - 1; i++)
            {
                for (int j = 0; j <= N - 1; j++)
                {
                    if (i == j)
                    {
                        R[i, j] = 1.0;
                    }
                }
            }

            int M;

            int[] I;

            double ds, dv;

            // Create the matrices for the derivatives
            // NON BOUNDARY POINTS ----------------------------------
            I = BU.Find(Cs);
            M = I.Length;
            for (k = 0; k <= M - 1; k++)
            {                                                                           // Central differences
                ds = Si[I[k]] - Si[I[k] - 1];
                derS[I[k], I[k] - 1]  = -0.5 / ds * Si[I[k]];                           // U(s-1,v)
                derSS[I[k], I[k] - 1] = 1.0 / ds / ds * Vi[I[k]] * Si[I[k]] * Si[I[k]]; // U(s-1,v)

                ds = Si[I[k] + 1] - Si[I[k]];
                derS[I[k], I[k] + 1]  = 0.5 / ds * Si[I[k]];                            // U(s+1,v)
                derSS[I[k], I[k] + 1] = 1.0 / ds / ds * Vi[I[k]] * Si[I[k]] * Si[I[k]]; // U(s+1,v)

                ds = (Si[I[k] + 1] - Si[I[k] - 1]) / 2.0;
                derS[I[k], I[k]]  = 0.0;                                             // U(s,v)
                derSS[I[k], I[k]] = -2.0 / ds / ds * Vi[I[k]] * Si[I[k]] * Si[I[k]]; // U(s,v)
            }
            I = BU.Find(Fs);
            M = I.Length;
            for (k = 0; k <= M - 1; k++)
            {                                                                       // Forward differences
                ds = (Si[I[k] + 1] - Si[I[k] - 1]) / 2.0;
                derS[I[k], I[k]]  = -1.5 / ds * Si[I[k]];                           // U(s,v)
                derSS[I[k], I[k]] = 1.0 / ds / ds * Vi[I[k]] * Si[I[k]] * Si[I[k]]; // U(s,v)

                ds = Si[I[k] + 1] - Si[I[k]];
                derS[I[k], I[k] + 1]  = 2.0 / ds * Si[I[k]];                             // U(s+1,v)
                derSS[I[k], I[k] + 1] = -2.0 / ds / ds * Vi[I[k]] * Si[I[k]] * Si[I[k]]; // U(s+1,v)

                ds = (Si[I[k] + 2] - Si[I[k]]) / 2.0;
                derS[I[k], I[k] + 2]  = -1.0 / ds * Si[I[k]];                           // U(s+2,v)
                derSS[I[k], I[k] + 2] = 1.0 / ds / ds * Vi[I[k]] * Si[I[k]] * Si[I[k]]; // U(s+2,v)
            }
            I = BU.Find(Bs);
            M = I.Length;
            for (k = 0; k <= M - 1; k++)
            {                                                                           // Backward differences
                ds = (Si[I[k]] - Si[I[k] - 2]) / 2.0;
                derS[I[k], I[k] - 2]  = -0.5 / ds * Si[I[k]];                           // U(s-2,v)
                derSS[I[k], I[k] - 2] = 1.0 / ds / ds * Vi[I[k]] * Si[I[k]] * Si[I[k]]; // U(s-2,v)

                ds = (Si[I[k]] - Si[I[k] - 1]);
                derS[I[k], I[k] - 1]  = -2.0 / ds * Si[I[k]];                            // U(s-1,v)
                derSS[I[k], I[k] - 1] = -2.0 / ds / ds * Vi[I[k]] * Si[I[k]] * Si[I[k]]; // U(s-1,v)

                ds = (Si[I[k] + 1] - Si[I[k] - 1]) / 2.0;
                derSS[I[k], I[k]] = 1.0 / ds / ds * Vi[I[k]] * Si[I[k]] * Si[I[k]]; // U(s,v)
                derS[I[k], I[k]]  = 1.5 / ds * Si[I[k]];                            // U(s,v)
            }

            // Create the matrix for V-derivatives
            I = BU.Find(Cv);
            M = I.Length;
            for (k = 0; k <= M - 1; k++)
            {                                                       // Central differences
                dv = Vi[I[k]] - Vi[I[k] - NS];
                derV1[I[k], I[k] - NS] = -0.5 / dv;                 // U(s,v-1)
                derV2[I[k], I[k] - NS] = -0.5 / dv * Vi[I[k]];      // U(s,v-1)
                derVV[I[k], I[k] - NS] = 1.0 / dv / dv * Vi[I[k]];  // U(s,v-1)

                dv = Vi[I[k] + NS] - Vi[I[k]];
                derV1[I[k], I[k] + NS] = 0.5 / dv;                     // U(s,v+1)
                derV2[I[k], I[k] + NS] = 0.5 / dv * Vi[I[k]];          // U(s,v+1)
                derVV[I[k], I[k] + NS] = 1.0 / dv / dv * Vi[I[k]];     // U(s,v+1)

                dv = (Vi[I[k] + NS] - Vi[I[k] - NS]) / 2.0;
                derVV[I[k], I[k]] = -2.0 / dv / dv * Vi[I[k]];      // U(s,v)
                derV1[I[k], I[k]] = 0.0;                            // U(s,v)
                derV2[I[k], I[k]] = 0.0;                            // U(s,v)
            }
            I = BU.Find(Fv);
            M = I.Length;
            for (k = 0; k <= M - 1; k++)
            {                                                       // Forward differences
                dv = (Vi[I[k] + NS] - Vi[I[k] - NS]) / 2.0;
                derV1[I[k], I[k]] = -1.5 / dv;                      // U(s,v)
                derV2[I[k], I[k]] = -1.5 / dv * Vi[I[k]];           // U(s,v)
                derVV[I[k], I[k]] = 1.0 / dv / dv * Vi[I[k]];       // U(s,v)

                dv = Vi[I[k] + NS] - Vi[I[k]];
                derV1[I[k], I[k] + NS] = 2.0 / dv;                   // U(s,v+1)
                derV2[I[k], I[k] + NS] = 2.0 / dv * Vi[I[k]];        // U(s,v+1)
                derVV[I[k], I[k] + NS] = -2.0 / dv / dv * Vi[I[k]];  // U(s,v+1)

                dv = (Vi[I[k] + 2 * NS] - Vi[I[k]]) / 2.0;
                derV1[I[k], I[k] + 2 * NS] = -1.0 / dv;                   // U(s,v+2)
                derV2[I[k], I[k] + 2 * NS] = -1.0 / dv * Vi[I[k]];        // U(s,v+2)
                derVV[I[k], I[k] + 2 * NS] = 1.0 / dv / dv * Vi[I[k]];    // U(s,v+2)
            }
            I = BU.Find(Bv);
            M = I.Length;
            for (k = 0; k <= M - 1; k++)
            {                                                          // Backward differences
                dv = (Vi[I[k]] - Vi[I[k] - 2 * NS]) / 2.0;
                derV1[I[k], I[k] - 2 * NS] = -0.5 / dv;                // U(s,v-2)
                derV2[I[k], I[k] - 2 * NS] = -0.5 / dv * Vi[I[k]];     // U(s,v-2)
                derVV[I[k], I[k] - 2 * NS] = 1.0 / dv / dv * Vi[I[k]]; // U(s,v-2)

                dv = Vi[I[k]] - Vi[I[k] - NS];
                derV1[I[k], I[k] - NS] = -2.0 / dv;                   // U(s,v-1)
                derV2[I[k], I[k] - NS] = -2.0 / dv * Vi[I[k]];        // U(s,v-1)
                derVV[I[k], I[k] - NS] = -2.0 / dv / dv * Vi[I[k]];   // U(s,v-1)

                dv = (Vi[I[k] + NS] - Vi[I[k] - NS]) / 2.0;
                derV1[I[k], I[k]] = 1.5 / dv;                       // U(s,v)
                derV2[I[k], I[k]] = 1.5 / dv * Vi[I[k]];            // U(s,v)
                derVV[I[k], I[k]] = 1.0 / dv / dv * Vi[I[k]];       // U(s,v)
            }
            // Create the matrix for SV-derivatives - simplified version

/*            I = Find(Csv);
 *          M = I.Length;
 *          for(k=0;k<=M-1;k++)
 *          {
 *              dv = (Vi[I[k]+NS] - Vi[I[k]-NS])/2.0;
 *              ds = (Si[I[k]+1] - Si[I[k]-1])/2.0;
 *              derSV[I[k],I[k]+NS+1] =  1.0/(4.0*ds*dv) * Vi[I[k]]*Si[I[k]];  // U(s+1,v+1)
 *              derSV[I[k],I[k]+NS-1] = -1.0/(4.0*ds*dv) * Vi[I[k]]*Si[I[k]];  // U(s-1,v+1)
 *              derSV[I[k],I[k]-NS-1] =  1.0/(4.0*ds*dv) * Vi[I[k]]*Si[I[k]];  // U(s-1,v-1)
 *              derSV[I[k],I[k]-NS+1] = -1.0/(4.0*ds*dv) * Vi[I[k]]*Si[I[k]];  // U(s+1,v-1)
 *          }
 */
            // Create the matrix for SV-derivatives
            I = BU.Find(Csv);
            M = I.Length;
            double a1, a2, a3, a4, a5, a6, a7, a8, a9, ds1, dv1;

            for (k = 0; k <= M - 1; k++)
            {
                ds = Si[I[k]] - Si[I[k] - 1];
                if (I[k] + 1 <= NS)
                {
                    ds1 = Si[I[k + 1]] - S[I[k]];    // Correct for off-grid point
                }
                else
                {
                    ds1 = ds;
                }
                dv  = Vi[I[k]] - Vi[I[k] - NS];
                dv1 = Vi[I[k] + NS] - Vi[I[k]];
                a1  = ds1 / ds / (ds + ds1) * dv1 / dv / (dv + dv1);
                a2  = -ds1 / ds / (ds + ds1) * (dv1 - dv) / dv / dv1;
                a3  = -ds1 / ds / (ds + ds1) * dv / dv1 / (dv + dv1);
                a4  = (ds1 - ds) / ds / ds1 * (-dv1) / dv / (dv + dv1);
                a5  = (ds1 - ds) / ds / ds1 * (dv1 - dv) / dv1 / dv;
                a6  = (ds1 - ds) / ds / ds1 * dv / dv1 / (dv + dv1);
                a7  = ds / ds / (ds + ds1) * (-dv1) / dv / (dv + dv1);
                a8  = ds / ds1 / (ds + ds1) * (dv1 - dv) / dv / dv1;
                a9  = ds / ds1 / (ds1 + ds) * dv / dv1 / (dv + dv1);
                derSV[I[k], I[k] - NS - 1] = a1 * Vi[I[k]] * Si[I[k]]; // U(s-1,v-1)
                derSV[I[k], I[k] - 1]      = a2 * Vi[I[k]] * Si[I[k]]; // U(s-1,v)
                derSV[I[k], I[k] + NS - 1] = a3 * Vi[I[k]] * Si[I[k]]; // U(s-1,v+1)
                derSV[I[k], I[k] - NS]     = a4 * Vi[I[k]] * Si[I[k]]; // U(s,v-1)
                derSV[I[k], I[k]]          = a5 * Vi[I[k]] * Si[I[k]]; // U(s,v)
                derSV[I[k], I[k] + NS]     = a6 * Vi[I[k]] * Si[I[k]]; // U(s,v+1)
                derSV[I[k], I[k] - NS + 1] = a7 * Vi[I[k]] * Si[I[k]]; // U(s+1,v-1)
                derSV[I[k], I[k] + 1]      = a8 * Vi[I[k]] * Si[I[k]]; // U(s+1,v)
                derSV[I[k], I[k] + NS + 1] = a9 * Vi[I[k]] * Si[I[k]]; // U(s+1,v+1)
            }
            // BOUNDARY POINTS ----------------------------------
            // Boundary for Smin
            I = BU.Find(SminB);
            M = I.Length;
            for (k = 0; k <= M - 1; k++)
            {
                derS[I[k], I[k]]  = 0.0;
                derSS[I[k], I[k]] = 0.0;
                derV1[I[k], I[k]] = 0.0;
                derV2[I[k], I[k]] = 0.0;
                derVV[I[k], I[k]] = 0.0;
                derSV[I[k], I[k]] = 0.0;
                R[I[k], I[k]]     = 0.0;
            }
            // Boundary condition for Smax
            I = BU.Find(SmaxB);
            M = I.Length;
            for (k = 0; k <= M - 1; k++)
            {
                derS[I[k], I[k]]  = Si[I[k]];
                derSS[I[k], I[k]] = 0.0;
                derSV[I[k], I[k]] = 0.0;                     // Central difference
                derV1[I[k], I[k]] = 0;                       // U(s,v)
                derV2[I[k], I[k]] = 0.0;                     // U(s,v)

                dv = Vi[I[k]] - Vi[I[k] - NS];
                derV1[I[k], I[k] - NS] = -0.5 / dv;                // U(s,v-1)
                derV2[I[k], I[k] - NS] = -0.5 / dv * Vi[I[k]];     // U(s,v-1)
                derVV[I[k], I[k] - NS] = 1.0 / dv / dv * Vi[I[k]]; // U(s,v-1)

                dv = Vi[I[k] + NS] - Vi[I[k]];
                derV1[I[k], I[k] + NS] = 0.5 / dv;                 // U(s,v+1)
                derV2[I[k], I[k] + NS] = 0.5 / dv * Vi[I[k]];      // U(s,v+1)
                derVV[I[k], I[k] + NS] = 1.0 / dv / dv * Vi[I[k]]; // U(s,v+1)

                dv = (Vi[I[k] + NS] - Vi[I[k] - NS]) / 2.0;
                derVV[I[k], I[k]] = -2.0 / dv / dv * Vi[I[k]];    // U(s,v)
            }

            // Boundary condition for Vmax.  Only the LS submatrix is non-zero
            I = BU.Find(VmaxB);
            M = I.Length;
            for (k = 0; k <= M - 1; k++)
            {
                derS[I[k], I[k]] = Si[I[k]];
            }

            // Boundary condition for Vmin
            // First row
            ds         = Si[1] - Si[0];
            derS[0, 0] = -1.5 / ds * Si[0];       // U(s,v)
            derS[0, 1] = 2.0 / ds * Si[0];        // U(s+1,v)
            derS[0, 2] = -1.0 / ds * Si[0];       // U(s+2,v)

            dv               = Vi[2 * NS] - Vi[NS];
            derV1[0, 0]      = -1.5 / dv;         // U(s,v)
            derV1[0, NS]     = 2.0 / dv;          // U(s,v+1)
            derV1[0, 2 * NS] = -1.0 / dv;         // U(s,v+2)
            // Last row
            ds = Si[N - 2] - Si[N - 3];
            derS[N - 2, N - 4] = -0.5 / ds * Si[N - 2];     // U(s-2,v)
            derS[N - 2, N - 3] = -2.0 / ds * Si[N - 2];     // U(s-1,v)
            derS[N - 2, N - 2] = 1.5 / ds * Si[N - 2];      // U(s,v)

            dv = Vi[2 * NS] - Vi[NS];
            derV1[NS - 2, NS - 2 + 2 * NS] = -0.5 / dv * Vi[NS - 2]; // U(s,v+2)
            derV1[NS - 2, NS - 2 + NS]     = 2.0 / dv * Vi[NS - 2];  // U(s,v+1)
            derV1[NS - 2, NS - 2]          = -1.5 / dv * Vi[NS - 2]; // U(s,v)
            // Other rows
            for (int i = 1; i <= NS - 3; i++)
            {
                ds             = Si[i] - Si[i - 1];
                derS[i, i - 1] = -0.5 / ds * Si[i];     // U(s-1,v)
                derS[i, i]     = 0.0;                   // U(s,v)
                derS[i, i + 1] = 0.5 / ds * Si[i];      // U(s+1,v)

                dv                   = Vi[2 * NS] - Vi[NS];
                derV1[i, i]          = -1.5 / dv;       // U(s,v)
                derV1[i, i + NS]     = 2.0 / dv;        // U(s,v+1)
                derV1[i, i + 2 * NS] = -1.0 / dv;       // U(s,v+2)
            }

            // Output the sub-matrices
            LMatrices LMat;

            LMat.derS  = derS;
            LMat.derSS = derSS;
            LMat.derV1 = derV1;
            LMat.derV2 = derV2;
            LMat.derVV = derVV;
            LMat.derSV = derSV;
            LMat.R     = R;

            return(LMat);
        }