com.opengamma.analytics.financial.model.finitedifference.CrankNicolsonFiniteDifference2D.java Source code

Java tutorial

Introduction

Here is the source code for com.opengamma.analytics.financial.model.finitedifference.CrankNicolsonFiniteDifference2D.java

Source

/**
 * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.analytics.financial.model.finitedifference;

import org.apache.commons.lang.Validate;

import com.opengamma.analytics.math.cube.Cube;

/**
 * <b>Note</b> this is for testing purposes and is not recommended for actual use
 */
@SuppressWarnings("deprecation")
public class CrankNicolsonFiniteDifference2D implements ConvectionDiffusionPDESolver2D {

    private final double _theta;

    /**
     * Sets up a standard Crank-Nicolson scheme for 2-D (two spatial dimensions) PDEs
     */
    public CrankNicolsonFiniteDifference2D() {
        _theta = 0.5;
    }

    /**
     * Sets up a scheme that is the weighted average of an explicit and an implicit scheme
     * @param theta The weight. theta = 0 - fully explicit, theta = 0.5 - Crank-Nicolson, theta = 1.0 - fully implicit
     */
    public CrankNicolsonFiniteDifference2D(final double theta) {
        Validate.isTrue(theta >= 0 && theta <= 1.0, "theta must be in the range 0 to 1");
        _theta = theta;
    }

    @Override
    public double[][] solve(final ConvectionDiffusion2DPDEDataBundle pdeData, final int tSteps, final int xSteps,
            final int ySteps, final double tMax, final BoundaryCondition2D xLowerBoundary,
            final BoundaryCondition2D xUpperBoundary, final BoundaryCondition2D yLowerBoundary,
            final BoundaryCondition2D yUpperBoundary) {
        return solve(pdeData, tSteps, xSteps, ySteps, tMax, xLowerBoundary, xUpperBoundary, yLowerBoundary,
                yUpperBoundary, null);
    }

    @Override
    public double[][] solve(final ConvectionDiffusion2DPDEDataBundle pdeData, final int tSteps, final int xSteps,
            final int ySteps, final double tMax, final BoundaryCondition2D xLowerBoundary,
            final BoundaryCondition2D xUpperBoundary, final BoundaryCondition2D yLowerBoundary,
            final BoundaryCondition2D yUpperBoundary, final Cube<Double, Double, Double, Double> freeBoundary) {

        final double dt = tMax / (tSteps);
        final double dx = (xUpperBoundary.getLevel() - xLowerBoundary.getLevel()) / (xSteps);
        final double dy = (yUpperBoundary.getLevel() - yLowerBoundary.getLevel()) / (ySteps);
        final double dtdx2 = dt / dx / dx;
        final double dtdx = dt / dx;
        final double dtdy2 = dt / dy / dy;
        final double dtdy = dt / dy;
        final double dtdxdy = dt / dy / dx;
        final int size = (xSteps + 1) * (ySteps + 1);

        final double[][] v = new double[xSteps + 1][ySteps + 1];
        final double[] u = new double[size];
        final double[] x = new double[xSteps + 1];
        final double[] y = new double[ySteps + 1];
        final double[] q = new double[size];

        double currentX = 0;
        double currentY = 0;
        int index;

        for (int i = 0; i <= xSteps; i++) {
            currentX = xLowerBoundary.getLevel() + i * dx;
            x[i] = currentX;
        }
        for (int j = 0; j <= ySteps; j++) {
            currentY = yLowerBoundary.getLevel() + j * dy;
            y[j] = currentY;
            final int offset = j * (xSteps + 1);
            for (int i = 0; i <= xSteps; i++) {
                u[offset + i] = pdeData.getInitialValue(x[i], currentY);
            }
        }

        double t = 0.0;
        double a, b, c, d, e, f;
        final double[][] w = new double[size][9];

        for (int n = 0; n < tSteps; n++) {
            t += dt;

            for (int i = 1; i < xSteps; i++) {
                for (int j = 1; j < ySteps; j++) {
                    index = j * (xSteps + 1) + i;
                    a = pdeData.getA(t, x[i], y[j]);
                    b = pdeData.getB(t, x[i], y[j]);
                    c = pdeData.getC(t, x[i], y[j]);
                    d = pdeData.getD(t, x[i], y[j]);
                    e = pdeData.getE(t, x[i], y[j]);
                    f = pdeData.getF(t, x[i], y[j]);

                    double sum = 0;
                    if (_theta != 1.0) {
                        sum -= a * dtdx2 * (u[index + 1] - 2 * u[index] + u[index - 1]);
                        sum -= b * dtdx / 2 * (u[index + 1] - u[index - 1]);
                        sum -= c * dt * u[index];
                        sum -= d * dtdy2 * (u[index + xSteps + 1] - 2 * u[index] + u[index - xSteps - 1]);
                        sum -= e * dtdxdy / 4 * (u[index + xSteps + 2] + u[index - xSteps - 2] - u[index - xSteps]
                                - u[index + xSteps]);
                        sum -= f * dtdy / 2 * (u[index + xSteps + 1] - u[index - xSteps - 1]);

                        // sum += dtdxdy * e / 4.0 * u[index - xSteps - 2];
                        // sum += (dtdy2 * d - 0.5 * dtdy * f) * u[index - xSteps - 1];
                        // sum += -dtdxdy * e / 4.0 * u[index - xSteps];
                        // sum += (dtdx2 * a - 0.5 * dtdx * b) * u[index - 1];
                        // sum += -(2 * dtdx2 * a - dt * c) - (2 * dtdy2 * d) * u[index];
                        // sum += (dtdx2 * a + 0.5 * dtdx * b) * u[index + 1];
                        // sum += -dtdxdy * e / 4.0 * u[index + xSteps];
                        // sum += (dtdy2 * d + 0.5 * dtdy * f) * u[index + xSteps + 1];
                        // sum += dtdxdy * e / 4.0 * u[index + xSteps + 2];
                        sum *= (1 - _theta);
                    }
                    sum += u[index];

                    // sum = v[i][j];

                    q[index] = sum;

                    w[index][0] = _theta * dtdxdy * e / 4.0;
                    w[index][1] = _theta * (dtdy2 * d - 0.5 * dtdy * f);
                    w[index][2] = -_theta * dtdxdy * e / 4.0;

                    w[index][3] = _theta * (dtdx2 * a - 0.5 * dtdx * b);
                    w[index][4] = 1 - _theta * ((2 * dtdx2 * a - dt * c) + (2 * dtdy2 * d));
                    w[index][5] = _theta * (dtdx2 * a + 0.5 * dtdx * b);

                    w[index][6] = -_theta * dtdxdy * e / 4.0;
                    w[index][7] = _theta * (dtdy2 * d + 0.5 * dtdy * f);
                    w[index][8] = _theta * dtdxdy * e / 4.0;

                }
            }

            // The y boundary conditions
            final double[][][] yBoundary = new double[2][xSteps + 1][];

            for (int i = 0; i <= xSteps; i++) {
                yBoundary[0][i] = yLowerBoundary.getLeftMatrixCondition(t, x[i]);
                yBoundary[1][i] = yUpperBoundary.getLeftMatrixCondition(t, x[i]);

                double[] temp = yLowerBoundary.getRightMatrixCondition(t, x[i]);
                double sum = 0;
                for (int k = 0; k < temp.length; k++) {
                    final int offset = k * (xSteps + 1);
                    sum += temp[k] * u[offset + i];
                }
                q[i] = sum + yLowerBoundary.getConstant(t, x[i], dy);

                temp = yUpperBoundary.getRightMatrixCondition(t, x[i]);
                sum = 0;
                for (int k = 0; k < temp.length; k++) {
                    final int offset = (ySteps - k) * (xSteps + 1);
                    sum += temp[k] * u[offset + i];
                }
                q[i + ySteps * (xSteps + 1)] = sum + yUpperBoundary.getConstant(t, x[i], dy);
            }

            // The x boundary conditions
            final double[][][] xBoundary = new double[2][ySteps - 1][];

            for (int j = 1; j < ySteps; j++) {
                xBoundary[0][j - 1] = xLowerBoundary.getLeftMatrixCondition(t, y[j]);
                xBoundary[1][j - 1] = xUpperBoundary.getLeftMatrixCondition(t, y[j]);

                double[] temp = xLowerBoundary.getRightMatrixCondition(t, y[j]);
                int offset = j * (xSteps + 1);
                double sum = 0;
                for (int k = 0; k < temp.length; k++) {
                    sum += temp[k] * u[offset + k];
                }
                q[offset] = sum + xLowerBoundary.getConstant(t, y[j], dx);

                temp = xUpperBoundary.getRightMatrixCondition(t, y[j]);
                offset = (j + 1) * (xSteps + 1) - 1;
                sum = 0;
                for (int k = 0; k < temp.length; k++) {
                    sum += temp[k] * u[offset - k];
                }
                q[offset] = sum + xUpperBoundary.getConstant(t, y[j], dx);
            }

            // SOR
            final double omega = 1.0;
            double scale = 1.0;
            double errorSqr = Double.POSITIVE_INFINITY;
            double sum;
            int l;
            while (errorSqr / (scale + 1e-10) > 1e-18) {
                errorSqr = 0.0;
                scale = 0.0;
                // solve for the innards first
                for (int i = 1; i < xSteps; i++) {
                    for (int j = 1; j < ySteps; j++) {
                        l = j * (xSteps + 1) + i;
                        sum = 0;
                        sum += w[l][0] * u[l - xSteps - 2];
                        sum += w[l][1] * u[l - xSteps - 1];
                        sum += w[l][2] * u[l - xSteps];
                        sum += w[l][3] * u[l - 1];
                        sum += w[l][4] * u[l];
                        sum += w[l][5] * u[l + 1];
                        sum += w[l][6] * u[l + xSteps];
                        sum += w[l][7] * u[l + xSteps + 1];
                        sum += w[l][8] * u[l + xSteps + 2];

                        final double correction = omega / w[l][4] * (q[l] - sum);
                        // if (freeBoundary != null) {
                        // correction = Math.max(correction, freeBoundary.getZValue(t, x[j]) - f[j]);
                        // }
                        errorSqr += correction * correction;
                        u[l] += correction;
                        scale += u[l] * u[l];
                    }
                }

                // the lower y boundary
                for (int i = 0; i <= xSteps; i++) {
                    sum = 0;
                    l = i;
                    final double[] temp = yBoundary[0][i];
                    for (int k = 0; k < temp.length; k++) {
                        final int offset = k * (xSteps + 1);
                        sum += temp[k] * u[offset + i];
                    }
                    final double correction = omega / temp[0] * (q[l] - sum);
                    errorSqr += correction * correction;
                    u[l] += correction;
                    scale += u[l] * u[l];
                }

                // the upper y boundary
                for (int i = 0; i <= xSteps; i++) {
                    sum = 0;
                    l = (xSteps + 1) * ySteps + i;
                    final double[] temp = yBoundary[1][i];
                    for (int k = 0; k < temp.length; k++) {
                        final int offset = (ySteps - k) * (xSteps + 1);
                        sum += temp[k] * u[offset + i];
                    }
                    final double correction = omega / temp[0] * (q[l] - sum);
                    errorSqr += correction * correction;
                    u[l] += correction;
                    scale += u[l] * u[l];
                }

                // the lower x boundary
                for (int j = 1; j < ySteps; j++) {
                    sum = 0;
                    l = j * (xSteps + 1);
                    final double[] temp = xBoundary[0][j - 1];
                    for (int k = 0; k < temp.length; k++) {
                        sum += temp[k] * u[l + k];
                    }
                    final double correction = omega / temp[0] * (q[l] - sum);
                    errorSqr += correction * correction;
                    u[l] += correction;
                    scale += u[l] * u[l];
                }

                // the upper x boundary
                for (int j = 1; j < ySteps; j++) {
                    sum = 0;
                    l = (j + 1) * (xSteps + 1) - 1;
                    final double[] temp = xBoundary[1][j - 1];
                    for (int k = 0; k < temp.length; k++) {
                        sum += temp[k] * u[l - k];
                    }
                    final double correction = omega / temp[0] * (q[l] - sum);
                    errorSqr += correction * correction;
                    u[l] += correction;
                    scale += u[l] * u[l];
                }

            } // end of SOR

        } // time loop

        // unpack vector to matrix
        for (int j = 0; j <= ySteps; j++) {
            final int offset = j * (xSteps + 1);
            for (int i = 0; i <= xSteps; i++) {
                v[i][j] = u[offset + i];
            }
        }
        return v;

    }

    public double[][] solve(final ConvectionDiffusion2DPDEDataBundle pdeData, final double[] timeGrid,
            final double[] xGrid, final double[] yGrid, final BoundaryCondition2D xLowerBoundary,
            final BoundaryCondition2D xUpperBoundary, final BoundaryCondition2D yLowerBoundary,
            final BoundaryCondition2D yUpperBoundary,
            @SuppressWarnings("unused") final Cube<Double, Double, Double, Double> freeBoundary) {

        Validate.notNull(pdeData, "pde data");
        final int tNodes = timeGrid.length;
        final int xNodes = xGrid.length;
        final int yNodes = yGrid.length;
        Validate.isTrue(tNodes > 1, "need at least 2 time nodes");
        Validate.isTrue(xNodes > 2, "need at least 3 x nodes");
        Validate.isTrue(yNodes > 2, "need at least 3 y nodes");

        // check grid and boundaries are consistent
        Validate.isTrue(Math.abs(xGrid[0] - xLowerBoundary.getLevel()) < 1e-7,
                "x grid not consistent with boundary level");
        Validate.isTrue(Math.abs(xGrid[xNodes - 1] - xUpperBoundary.getLevel()) < 1e-7,
                "x grid not consistent with boundary level");
        Validate.isTrue(Math.abs(yGrid[0] - yLowerBoundary.getLevel()) < 1e-7,
                "y grid not consistent with boundary level");
        Validate.isTrue(Math.abs(yGrid[yNodes - 1] - yUpperBoundary.getLevel()) < 1e-7,
                "y grid not consistent with boundary level");

        final double[] dt = new double[tNodes - 1];
        for (int n = 0; n < tNodes - 1; n++) {
            dt[n] = timeGrid[n + 1] - timeGrid[n];
            Validate.isTrue(dt[n] > 0, "time steps must be increasing");
        }

        final double[] dx = new double[xNodes - 1];

        for (int i = 0; i < xNodes - 1; i++) {
            dx[i] = xGrid[i + 1] - xGrid[i];
            Validate.isTrue(dx[i] > 0, "x steps must be increasing");
        }

        final double[] dy = new double[yNodes - 1];
        for (int i = 0; i < yNodes - 1; i++) {
            dy[i] = yGrid[i + 1] - yGrid[i];
            Validate.isTrue(dy[i] > 0, "y steps must be increasing");
        }

        // since the space grid is time independent, we can calculate the coefficients for derivatives once
        final double[][] x1st = new double[xNodes - 2][3];
        final double[][] x2nd = new double[xNodes - 2][3];
        for (int i = 0; i < xNodes - 2; i++) {
            x1st[i][0] = -dx[i + 1] / dx[i] / (dx[i] + dx[i + 1]);
            x1st[i][1] = (dx[i + 1] - dx[i]) / dx[i] / dx[i + 1];
            x1st[i][2] = dx[i] / dx[i + 1] / (dx[i] + dx[i + 1]);
            x2nd[i][0] = 2 / dx[i] / (dx[i] + dx[i + 1]);
            x2nd[i][1] = -2 / dx[i] / dx[i + 1];
            x2nd[i][2] = 2 / dx[i + 1] / (dx[i] + dx[i + 1]);
        }

        final double[][] y1st = new double[yNodes - 2][3];
        final double[][] y2nd = new double[yNodes - 2][3];
        for (int i = 0; i < yNodes - 2; i++) {
            y1st[i][0] = -dy[i + 1] / dy[i] / (dy[i] + dy[i + 1]);
            y1st[i][1] = (dy[i + 1] - dy[i]) / dy[i] / dy[i + 1];
            y1st[i][2] = dy[i] / dy[i + 1] / (dy[i] + dy[i + 1]);
            y2nd[i][0] = 2 / dy[i] / (dy[i] + dy[i + 1]);
            y2nd[i][1] = -2 / dy[i] / dy[i + 1];
            y2nd[i][2] = 2 / dy[i + 1] / (dy[i] + dy[i + 1]);
        }

        final int size = xNodes * yNodes;

        final double[][] v = new double[xNodes][yNodes];
        final double[] u = new double[size];
        final double[] q = new double[size];

        int index = 0;
        for (int j = 0; j < yNodes; j++) {
            for (int i = 0; i < xNodes; i++) {
                u[index++] = pdeData.getInitialValue(xGrid[i], yGrid[j]);
            }
        }

        double a, b, c, d, e, f;
        final double[][] w = new double[size][9];

        for (int n = 1; n < tNodes; n++) {

            for (int i = 1; i < xNodes - 1; i++) {
                for (int j = 1; j < yNodes - 1; j++) {
                    index = j * xNodes + i;
                    a = pdeData.getA(timeGrid[n - 1], xGrid[i], yGrid[j]);
                    b = pdeData.getB(timeGrid[n - 1], xGrid[i], yGrid[j]);
                    c = pdeData.getC(timeGrid[n - 1], xGrid[i], yGrid[j]);
                    d = pdeData.getD(timeGrid[n - 1], xGrid[i], yGrid[j]);
                    e = pdeData.getE(timeGrid[n - 1], xGrid[i], yGrid[j]);
                    f = pdeData.getF(timeGrid[n - 1], xGrid[i], yGrid[j]);

                    double sum = 0;
                    if (_theta != 1.0) {

                        sum -= a * (x2nd[i - 1][0] * u[index - 1] + x2nd[i - 1][1] * u[index]
                                + x2nd[i - 1][2] * u[index + 1]);
                        sum -= b * (x1st[i - 1][0] * u[index - 1] + x1st[i - 1][1] * u[index]
                                + x1st[i - 1][2] * u[index + 1]);
                        sum -= c * u[index];
                        sum -= d * (y2nd[j - 1][0] * u[index - xNodes] + y2nd[j - 1][1] * u[index]
                                + y2nd[j - 1][2] * u[index + xNodes]);
                        sum -= e * (x1st[i - 1][0]
                                * (y1st[j - 1][0] * u[index - xNodes - 1] + y1st[j - 1][1] * u[index - 1]
                                        + y1st[j - 1][2] * u[index + xNodes - 1])
                                + x1st[i - 1][1] * (y1st[j - 1][0] * u[index - xNodes] + y1st[j - 1][1] * u[index]
                                        + y1st[j - 1][2] * u[index + xNodes])
                                + x1st[i - 1][2] * (y1st[j - 1][0] * u[index - xNodes + 1]
                                        + y1st[j - 1][1] * u[index + 1] + y1st[j - 1][2] * u[index + xNodes + 1]));
                        sum -= f * (y1st[j - 1][0] * u[index - xNodes] + y1st[j - 1][1] * u[index]
                                + y1st[j - 1][2] * u[index + xNodes]);
                        sum *= (1 - _theta) * dt[n - 1];
                    }
                    sum += u[index];

                    q[index] = sum;

                    w[index][0] = _theta * dt[n - 1] * x1st[i - 1][0] * y1st[j - 1][0] * e; // i-1,j-1
                    w[index][1] = _theta * dt[n - 1]
                            * (y2nd[j - 1][0] * d + x1st[i - 1][1] * y1st[j - 1][0] * e + y1st[j - 1][0] * f); // i,j-1
                    w[index][2] = -_theta * dt[n - 1] * x1st[i - 1][2] * y1st[j - 1][0] * e; // i+1,j-1

                    w[index][3] = _theta * dt[n - 1]
                            * (x2nd[i - 1][0] * a + x1st[i - 1][0] * b + x1st[i - 1][0] * y1st[j - 1][1] * e); // i-1,j
                    w[index][4] = 1 + _theta * dt[n - 1] * (x2nd[i - 1][1] * a + x1st[i - 1][1] * b + c
                            + y2nd[j - 1][1] * d + x1st[i - 1][1] * y1st[j - 1][1] * e + y1st[j - 1][1] * f); // i,j
                    w[index][5] = _theta * dt[n - 1]
                            * (x2nd[i - 1][2] * a + x1st[i - 1][2] * b + x1st[i - 1][2] * y1st[j - 1][1] * e); // i+1,j

                    w[index][6] = -_theta * dt[n - 1] * x1st[i - 1][0] * y1st[j - 1][2] * e; // i-1,j+1
                    w[index][7] = _theta
                            * (y2nd[j - 1][2] * d + x1st[i - 1][1] * y1st[j - 1][2] * e + y1st[j - 1][2] * f); // i,j+1
                    w[index][8] = _theta * dt[n - 1] * x1st[i - 1][2] * y1st[j - 1][2] * e; // i+1,j+1
                }
            }

            // The y boundary conditions
            final double[][][] yBoundary = new double[2][xNodes][];

            for (int i = 0; i < xNodes; i++) {
                yBoundary[0][i] = yLowerBoundary.getLeftMatrixCondition(timeGrid[n], xGrid[i]);
                yBoundary[1][i] = yUpperBoundary.getLeftMatrixCondition(timeGrid[n], xGrid[i]);

                double[] temp = yLowerBoundary.getRightMatrixCondition(timeGrid[n], xGrid[i]);
                double sum = 0;
                for (int k = 0; k < temp.length; k++) {
                    final int offset = k * xNodes;
                    sum += temp[k] * u[offset + i];
                }
                q[i] = sum + yLowerBoundary.getConstant(timeGrid[n], xGrid[i], dy[0]); // TODO need to change how boundary are calculated

                temp = yUpperBoundary.getRightMatrixCondition(timeGrid[n], xGrid[i]);
                sum = 0;
                for (int k = 0; k < temp.length; k++) {
                    final int offset = (yNodes - 1 - k) * xNodes;
                    sum += temp[k] * u[offset + i];
                }
                q[i + (yNodes - 1) * xNodes] = sum
                        + yUpperBoundary.getConstant(timeGrid[n], xGrid[i], dy[yNodes - 2]);
            }

            // The x boundary conditions
            final double[][][] xBoundary = new double[2][yNodes - 2][];

            for (int j = 1; j < yNodes - 1; j++) {
                xBoundary[0][j - 1] = xLowerBoundary.getLeftMatrixCondition(timeGrid[n], yGrid[j]);
                xBoundary[1][j - 1] = xUpperBoundary.getLeftMatrixCondition(timeGrid[n], yGrid[j]);

                double[] temp = xLowerBoundary.getRightMatrixCondition(timeGrid[n], yGrid[j]);
                int offset = j * xNodes;
                double sum = 0;
                for (int k = 0; k < temp.length; k++) {
                    sum += temp[k] * u[offset + k];
                }
                q[offset] = sum + xLowerBoundary.getConstant(timeGrid[n], yGrid[j], dx[0]);

                temp = xUpperBoundary.getRightMatrixCondition(timeGrid[n], yGrid[j]);
                offset = (j + 1) * xNodes - 1;
                sum = 0;
                for (int k = 0; k < temp.length; k++) {
                    sum += temp[k] * u[offset - k];
                }
                q[offset] = sum + xUpperBoundary.getConstant(timeGrid[n], yGrid[j], dx[xNodes - 2]);
            }

            // SOR
            final double omega = 1.0;
            double scale = 1.0;
            double errorSqr = Double.POSITIVE_INFINITY;
            double sum;
            int l;
            while (errorSqr / (scale + 1e-10) > 1e-18) {
                errorSqr = 0.0;
                scale = 0.0;
                // solve for the innards first
                for (int i = 1; i < xNodes - 1; i++) {
                    for (int j = 1; j < yNodes - 1; j++) {
                        l = j * xNodes + i;
                        sum = 0;
                        sum += w[l][0] * u[l - xNodes - 1];
                        sum += w[l][1] * u[l - xNodes];
                        sum += w[l][2] * u[l - xNodes + 1];
                        sum += w[l][3] * u[l - 1];
                        sum += w[l][4] * u[l];
                        sum += w[l][5] * u[l + 1];
                        sum += w[l][6] * u[l + xNodes - 1];
                        sum += w[l][7] * u[l + xNodes];
                        sum += w[l][8] * u[l + xNodes + 1];

                        final double correction = omega / w[l][4] * (q[l] - sum);
                        // if (freeBoundary != null) {
                        // correction = Math.max(correction, freeBoundary.getZValue(t, x[j]) - f[j]);
                        // }
                        errorSqr += correction * correction;
                        u[l] += correction;
                        scale += u[l] * u[l];
                    }
                }

                // the lower y boundary
                for (int i = 0; i < xNodes; i++) {
                    sum = 0;
                    l = i;
                    final double[] temp = yBoundary[0][i];
                    for (int k = 0; k < temp.length; k++) {
                        final int offset = k * xNodes;
                        sum += temp[k] * u[offset + i];
                    }
                    final double correction = omega / temp[0] * (q[l] - sum);
                    errorSqr += correction * correction;
                    u[l] += correction;
                    scale += u[l] * u[l];
                }

                // the upper y boundary
                for (int i = 0; i < xNodes; i++) {
                    sum = 0;
                    l = xNodes * (yNodes - 1) + i;
                    final double[] temp = yBoundary[1][i];
                    for (int k = 0; k < temp.length; k++) {
                        final int offset = (yNodes - 1 - k) * xNodes;
                        sum += temp[k] * u[offset + i];
                    }
                    final double correction = omega / temp[0] * (q[l] - sum);
                    errorSqr += correction * correction;
                    u[l] += correction;
                    scale += u[l] * u[l];
                }

                // the lower x boundary
                for (int j = 1; j < yNodes - 1; j++) {
                    sum = 0;
                    l = j * xNodes;
                    final double[] temp = xBoundary[0][j - 1];
                    for (int k = 0; k < temp.length; k++) {
                        sum += temp[k] * u[l + k];
                    }
                    final double correction = omega / temp[0] * (q[l] - sum);
                    errorSqr += correction * correction;
                    u[l] += correction;
                    scale += u[l] * u[l];
                }

                // the upper x boundary
                for (int j = 1; j < yNodes - 1; j++) {
                    sum = 0;
                    l = (j + 1) * xNodes - 1;
                    final double[] temp = xBoundary[1][j - 1];
                    for (int k = 0; k < temp.length; k++) {
                        sum += temp[k] * u[l - k];
                    }
                    final double correction = omega / temp[0] * (q[l] - sum);
                    errorSqr += correction * correction;
                    u[l] += correction;
                    scale += u[l] * u[l];
                }

            } // end of SOR

        } // time loop

        // unpack vector to matrix
        for (int j = 0; j < yNodes; j++) {
            final int offset = j * xNodes;
            for (int i = 0; i < xNodes; i++) {
                v[i][j] = u[offset + i];
            }
        }
        return v;

    }
}