예제 #1
0
def test(N):

    x = np.linspace(0, 1, N)

    pg.tic()
    mesh = pg.createGrid(x, x, x)
    print(mesh)
    pg.toc()

    A = pg.RSparseMatrix()
    A.fillStiffnessMatrix(mesh)
    pg.toc()
예제 #2
0
def sparseMatrix2Array(matrix, indices=True, getInCRS=True):
    """Extract indices and value from sparse matrix (SparseMap or CRS)

    Get python Arrays from SparseMatrix or SparseMapMatrix in either CRS
    convention (row index, column Start_End, values) or row index, column
    index, values.

    Parameters
    ----------

    matrix: pg.SparseMapMatrix or pg.SparseMatrix
        Input matrix to be transformed to numpy arrays.

    indices: boolean (True)
        Decides weather the indices of the matrix will be returned or not.

    getInCSR: boolean (True)
        If returned, the indices can have the format of a compressed row
        storage (CSR), the default or uncompressed lists with column and row
        indices.

    Returns
    -------

    vals: numpy.ndarray
        Entries of the matrix.

    indices: list, list
        Optional. Reurns additional array with the indices for reconstructing
        the matrix in the defined format.

    """
    io_warn = 'Only working for pygimli.SparseMapMatrix or CSR shaped ' +\
              'pygimli.Sparse matrices. Import type is {}'
    assert isinstance(matrix, pg.SparseMapMatrix) or\
        isinstance(matrix, pg.SparseMatrix), io_warn.format(type(matrix))

    if not isinstance(matrix, pg.SparseMatrix):
        matrix = pg.RSparseMatrix(matrix)

    vals = np.array(matrix.vecVals())
    if indices is True:
        rows = list(matrix.vecRowIdx())
        cols = list(matrix.vecColPtr())
        if getInCRS:
            return list(rows), list(cols), vals
        else:
            rr, cc = convertCRSIndex2Map(rows, cols)
            return rr, cc, vals
    else:
        return vals
예제 #3
0
파일: solver.py 프로젝트: lirhlirh/gimli
def createStiffnessMatrix(mesh, a=None):
    """Create the Stiffness matrix.

    Calculates the scaled stiffness matrix for the given mesh scaled
    with the per cell values a.

    ..math::
            ...

    Parameters
    ----------
    mesh : :gimliapi:`GIMLI::Mesh`
        Arbitrary mesh to calculate the stiffness for.
        Type of base and shape functions depends on the cell types.

    a : array, either complex or real, callable
        Per cell values., e.g., physical parameter. If None given default is 1.

    Returns
    -------
    A : :gimliapi:`GIMLI::RSparseMatrix`
        Stiffness matrix
    """

    if a is None:
        a = pg.RVector(mesh.cellCount(), 1.0)

    A = None

    if isinstance(a[0], float) or isinstance(a[0], np.float64):
        A = pg.RSparseMatrix()
        A.fillStiffnessMatrix(mesh, a)
        return A
    else:
        A = pg.CSparseMatrix()

    # create matrix structure regarding the mesh
    A.buildSparsityPattern(mesh)

    # define a local element matrix
    A_l = pg.ElementMatrix()
    for c in mesh.cells():
        A_l.ux2uy2uz2(c)
        A.add(A_l, scale=a[c.id()])


#        if c.id() == 0:
#            print(c.id(), A_l)
    return A
예제 #4
0
def linsolve(A, b, verbose=False):
    r"""
    Direct solution after :math:`\textbf{x}` using cholmod:

    .. math::
        \textbf{A}\textbf{x} = \textbf{b}

    If :math:`\textbf{A}` is symmetric, sparse and positive definite.

    Parameters
    ----------
    A : :gimliapi:`GIMLI::RSparseMatrix` | :gimliapi:`GIMLI::RSparseMapMatrix`|
        numpy.array
        System matrix. Need to be symmetric, sparse and positive definite.

    b : iterable array
        Right hand side of the equation.

    verbose : bool [False]
        Be verbose.

    Returns
    -------

    x : :gimliapi:`GIMLI::RVector`
        Solution vector
    """
    x = pg.RVector(len(b), .0)

    if isinstance(A, pg.RSparseMapMatrix):
        S = pg.RSparseMatrix(A)
        solver = pg.LinSolver(S, verbose=verbose)
        solver.solve(b, x)
    elif isinstance(A, np.ndarray):
        return np.linalg.solve(A, b)
    else:
        solver = pg.LinSolver(A, verbose=verbose)
        solver.solve(b, x)

    return x
예제 #5
0
def createMassMatrix(mesh, b=None):
    """
    Assemble mass element matrix.

    TODO remove b .. not necessary .. b should be scaled in final equation
    not here.


    Calculates the mass element matrix for the given mesh scaled with the
    per cell values b.

    ..math::
            ...

    Parameters
    ----------
    mesh : :gimliapi:`GIMLI::Mesh`

        Arbitrary mesh to calculate the mass element matrix.
        Type of base and shape functions depends on the cell types.

    b : array
        Per cell values. If None given default is 1.

    Returns
    -------
    A : :gimliapi:`GIMLI::RSparseMatrix`
        Mass element matrix
    """

    # need callable here
    if b is None:
        b = pg.RVector(mesh.cellCount(), 1.0)

    B = pg.RSparseMatrix()
    B.fillMassMatrix(mesh, b)
    return B
예제 #6
0
파일: seismics.py 프로젝트: ziogibom/gimli
def solvePressureWave(mesh, velocities, times, sourcePos, uSource, verbose):
    r"""
    Solve pressure wave equation.

    Solve pressure wave for a given source function

    .. math::
        \frac{\partial^2 u}{\partial t^2} & = \diverg(a\grad u) + f\\
        finalize equation


    Parameters
    ----------
    mesh : :gimliapi:`GIMLI::Mesh`
        Mesh to solve on

    velocities : array
        velocities for each cell of the mesh

    time : array
        Time base definition

    sourcePos : RVector3
        Source position

    uSource : array
        u(t, sourcePos) source movement of length(times)
        Usually a Ricker wavelet of the desired seismic signal frequency.

    Returns
    -------
    u : RMatrix

        Return

    Examples
    --------
    See TODO write example
    """
    A = pg.RSparseMatrix()
    M = pg.RSparseMatrix()

    #    F = pg.RVector(mesh.nodeCount(), 0.0)
    rhs = pg.RVector(mesh.nodeCount(), 0.0)
    u = pg.RMatrix(len(times), mesh.nodeCount())
    v = pg.RMatrix(len(times), mesh.nodeCount())

    sourceID = mesh.findNearestNode(sourcePos)

    if len(uSource) != len(times):
        raise Exception("length of uSource does not fit length of times: " +
                        str(uSource) + " != " + len(times))

    A.fillStiffnessMatrix(mesh, velocities * velocities)
    M.fillMassMatrix(mesh)
    #    M.fillMassMatrix(mesh, velocities)

    FV = 0
    if FV:
        A, rhs = pygimli.solver.diffusionConvectionKernel(mesh,
                                                          velocities *
                                                          velocities,
                                                          sparse=1)

        M = pygimli.solver.identity(len(rhs))

        u = pg.RMatrix(len(times), mesh.cellCount())
        v = pg.RMatrix(len(times), mesh.cellCount())
        sourceID = mesh.findCell(sourcePos).id()

    dt = times[1] - times[0]

    theta = 0.51
    #theta = 1.
    S1 = M + dt * dt * theta * theta * A
    S2 = M

    solver1 = pg.LinSolver(S1, verbose=False)
    solver2 = pg.LinSolver(S2, verbose=False)
    swatch = pg.Stopwatch(True)

    #    ut = pg.RVector(mesh.nodeCount(), .0)
    #    vt = pg.RVector(mesh.nodeCount(), .0)

    timeIter1 = np.zeros(len(times))
    timeIter2 = np.zeros(len(times))
    timeIter3 = np.zeros(len(times))
    timeIter4 = np.zeros(len(times))

    progress = pg.utils.ProgressBar(its=len(times), width=40, sign='+')

    for n in range(1, len(times)):
        u[n - 1, sourceID] = uSource[n - 1]

        # solve for u
        tic = time.time()
        # + * dt*dt * F
        rhs = dt * M * v[n - 1] + (M - dt * dt * theta *
                                   (1. - theta) * A) * u[n - 1]
        timeIter1[n - 1] = time.time() - tic

        tic = time.time()
        u[n] = solver1.solve(rhs)
        timeIter2[n - 1] = time.time() - tic

        # solve for v
        tic = time.time()
        rhs = M * v[n - 1] - dt * \
            ((1. - theta) * A * u[n - 1] + theta * A * u[n])  # + dt * F
        timeIter3[n - 1] = time.time() - tic

        tic = time.time()
        v[n] = solver2.solve(rhs)
        timeIter4[n - 1] = time.time() - tic

        #         same as above
        #        rhs = M * v[n-1] - dt * A * u[n-1] + dt * F
        #        v[n] = solver1.solve(rhs)

        t1 = swatch.duration(True)

        if verbose:
            progress(n)

    return u
예제 #7
0
def solveFiniteVolume(mesh,
                      a=1.0,
                      b=0.0,
                      f=0.0,
                      fn=0.0,
                      vel=None,
                      u0=0.0,
                      times=None,
                      uL=None,
                      relax=1.0,
                      ws=None,
                      scheme='CDS',
                      **kwargs):
    r"""Solve partial differential equation with Finite Volumes.

    This function is a syntactic sugar proxy for using the Finite Volume
    functionality of the library core to solve elliptic and parabolic partial
    differential of the following type:

    .. math::

        \frac{\partial u}{\partial t} + \mathbf{v}\cdot\nabla u & = \nabla\cdot(a \nabla u) + b u + f(\mathbf{r},t)\\
        u(\mathbf{r}, t) & = u_B  \quad\mathbf{r}\in\Gamma_{\text{Dirichlet}}\\
        \frac{\partial u(\mathbf{r}, t)}{\partial \mathbf{n}} & = u_{\partial \text{B}}  \quad\mathbf{r}\in\Gamma_{\text{Neumann}}\\
        u(\mathbf{r}, t=0) & = u_0 \quad\text{with} \quad\mathbf{r}\in\Omega

    The Domain :math:`\Omega` and the Boundary :math:`\Gamma` are defined
    through the given mesh with appropriate boundary marker.

    The solution :math:`u(\mathbf{r}, t)` is given for each cell in the mesh.


    TODO:

     * Refactor with solver class and Runga-Kutte solver
     * non steady boundary conditions

    Parameters
    ----------
    mesh : :gimliapi:`GIMLI::Mesh`
        Mesh represents spatial discretization of the calculation domain

    a   : value | array | callable(cell, userData)
        Stiffness weighting per cell values.

    b   : value | array | callable(cell, userData)
        Scale for mass values b

    f   : iterable(cell)
        Load vector

    fn   : iterable(cell)
        TODO What is fn

    vel : ndarray (N,dim) | RMatrix(N,dim)
        Velocity field :math:`\mathbf{v}(\mathbf{r}, t=\text{const}) = \{[v_i]_j,\}`
        with :math:`i=[1\ldots 3]` for the mesh dimension
        and :math:`j = [0\ldots N-1]` with N either the amount of cells,
        nodes, or boundaries.
        Velocities per boundary are preferred and will be interpolated
        on demand.

    u0 : value | array | callable(cell, userData)
        Starting field

    times : iterable
        Time steps to calculate for.

    ws : Workspace
        This can be an empty class that will used as an Workspace to store and
        cache data.

        If ws is given: The system matrix is taken from ws or
        calculated once and stored in ws for further usage.

        The system matrix is cached in this Workspace as ws.S
        The LinearSolver with the factorized matrix is cached in
        this Workspace as ws.solver
        The rhs vector is only stored in this Workspace as ws.rhs

    scheme : str [CDS]
        Finite volume scheme:
        :py:mod:`pygimli.solver.diffusionConvectionKernel`

    **kwargs:

        * uB : Dirichlet boundary conditions
        * duB : Neumann boundary conditions

    Returns
    -------

    u : ndarray(nTimes, nCells)
        solution field for all time steps

    """
    verbose = kwargs.pop('verbose', False)
    # The Workspace is to hold temporary data or preserve matrix rebuild
    # swatch = pg.Stopwatch(True)
    sparse = True

    workspace = pg.solver.WorkSpace()
    if ws:
        workspace = ws

    a = pg.solver.parseArgToArray(a, [mesh.cellCount(), mesh.boundaryCount()])
    f = pg.solver.parseArgToArray(f, mesh.cellCount())
    fn = pg.solver.parseArgToArray(fn, mesh.cellCount())

    boundsDirichlet = None
    boundsNeumann = None

    # BEGIN check for Courant-Friedrichs-Lewy
    if vel is not None:

        if isinstance(vel, float):
            print("Warning! .. velocity is float and no vector field")

        # we need velocities for boundaries
        if len(vel) is not mesh.boundaryCount():
            if len(vel) == mesh.cellCount():
                vel = pg.meshtools.cellDataToNodeData(mesh, vel)

            if len(vel) == mesh.nodeCount():
                vel = pg.meshtools.nodeDataToBoundaryData(mesh, vel)
            else:
                print("mesh:", mesh)
                print("vel:", vel.shape)

                raise Exception("Cannot determine data format for velocities")

        vmax = 0
        if mesh.dimension() == 3:
            vmax = np.max(np.sqrt(vel[:, 0]**2 + vel[:, 1]**2 + vel[:, 2]**2))
        else:
            vmax = np.max(np.sqrt(vel[:, 0]**2 + vel[:, 1]**2))

        if times is not None:
            dt = times[1] - times[0]
            dx = min(mesh.boundarySizes())
            c = vmax * dt / dx
            if c > 1:
                print(
                    "Courant-Friedrichs-Lewy Number:", c,
                    "but sould be lower 1 to ensure movement inside a cell "
                    "per timestep. ("
                    "vmax =", vmax, "dt =", dt, "dx =", dx, "dt <", dx / vmax,
                    " | N > ",
                    int((times[-1] - times[0]) / (dx / vmax)) + 1, ")")
    # END check for Courant-Friedrichs-Lewy

    if not hasattr(workspace, 'S'):

        if 'uB' in kwargs:
            boundsDirichlet = pg.solver.parseArgToBoundaries(
                kwargs['uB'], mesh)

        if 'duB' in kwargs:
            boundsNeumann = pg.solver.parseArgToBoundaries(kwargs['duB'], mesh)

        workspace.S, workspace.rhsBCScales = diffusionConvectionKernel(
            mesh=mesh,
            a=a,
            b=b,
            uB=boundsDirichlet,
            duB=boundsNeumann,
            # u0=u0,
            fn=fn,
            vel=vel,
            scheme=scheme,
            sparse=sparse,
            userData=kwargs.pop('userData', None))

        dof = len(workspace.rhsBCScales)
        workspace.ap = np.zeros(dof)

        # for nonlinears
        if uL is not None:
            for i in range(dof):
                val = 0.0
                if sparse:
                    val = workspace.S.getVal(i, i) / relax
                    workspace.S.setVal(i, i, val)
                else:
                    val = workspace.S[i, i] / relax
                    workspace.S[i, i] = val

                workspace.ap[i] = val

        # print('FVM kernel 2:', swatch.duration(True))
    # endif: not hasattr(workspace, 'S'):

    workspace.rhs = np.zeros(len(workspace.rhsBCScales))
    workspace.rhs[0:mesh.cellCount()] = f  # * mesh.cellSizes()

    workspace.rhs += workspace.rhsBCScales

    # for nonlinear: relax progress with scaled last result
    if uL is not None:
        workspace.rhs += (1. - relax) * workspace.ap * uL
    # print('FVM: Prep:', swatch.duration(True))

    if not hasattr(times, '__len__'):

        if sparse and not hasattr(workspace, 'solver'):
            Sm = pg.RSparseMatrix(workspace.S)
            # hold Sm until we have reference counting,
            # loosing Sm here will kill LinSolver later
            workspace.Sm = Sm
            workspace.solver = pg.LinSolver(Sm, verbose=verbose)

        u = None
        if sparse:
            u = workspace.solver.solve(workspace.rhs)
        else:
            u = np.linalg.solve(workspace.S, workspace.rhs)
        # print('FVM solve:', swatch.duration(True))
        return u[0:mesh.cellCount():1]
    else:
        theta = kwargs.pop('theta', 0.5 + 1e-6)

        if sparse:
            I = pg.solver.identity(len(workspace.rhs))
        else:
            I = np.diag(np.ones(len(workspace.rhs)))

        if verbose:
            print("Solve timesteps with Crank-Nicolson.")

        return pg.solver.crankNicolson(times,
                                       theta,
                                       workspace.S,
                                       I,
                                       f=workspace.rhs,
                                       u0=pg.solver.parseArgToArray(
                                           u0, mesh.cellCount(), mesh),
                                       verbose=verbose)
예제 #8
0
                                        grid), rhs)
    """
        Solve for u
    """
    u[n] = solver.linsolve(S, rhs)
    """
        Solve the second equation for v 
    """

    rhs = M * v[n - 1] - A * u[n] * k * theta - k * tmpRhs
    """
        Be aware of python's #No copy at all!#. 
        So we need to take a copy of the mass element matrix ourself to keep in
        safe environment.
    """
    S = pg.RSparseMatrix(M)
    #S = M

    if n == 1:
        solver.assembleDirichletBC(
            S,
            solver.parseArgToBoundaries([grid.findBoundaryByMarker(1), 1],
                                        grid), rhs)

    else:
        solver.assembleDirichletBC(
            S,
            solver.parseArgToBoundaries([grid.findBoundaryByMarker(1), 0],
                                        grid), rhs)
        solver.assembleDirichletBC(
            S,
예제 #9
0
def solveFiniteVolume(mesh,
                      a=1.0,
                      b=0.0,
                      f=0.0,
                      fn=0.0,
                      vel=None,
                      u0=0.0,
                      times=None,
                      uL=None,
                      relax=1.0,
                      ws=None,
                      scheme='CDS',
                      **kwargs):
    """Calculate for u.

    NOTE works only for steady boundary conditions!!!

    !!Refactor with solver class and Runga-Kutte solver!!

    Parameters
    ----------
    mesh : :gimliapi:`GIMLI::Mesh`
        Mesh represents spatial discretization of the calculation domain

    a   : value | array | callable(cell, userData)
        cell values

    b   : value | array | callable(cell, userData)
        TODO What is b

    f   : iterable(cell)
        TODO What is f
    fn   : iterable(cell)

        TODO What is fn

    vel : ndarray (N,dim) | RMatrix(N,dim)
        velocity field [[v_j,]_i,] with i=[1..3] for the mesh dimension
        and j = [0 .. N-1] with N either Amount of Cells, Nodes or Boundaries.
        Velocity per boundary is preferred.

    u0 : value | array | callable(cell, userData)
        Starting field

    ws : Workspace
        This can be an empty class that will used as an Workspace to store and
        cache data.

        If ws is given: The system matrix is taken from ws or
        calculated once and stored in ws for further usage.

        The system matrix is cached in this Workspace as ws.S
        The LinearSolver with the factorized matrix is cached in
        this Workspace as ws.solver
        The rhs vector is only stored in this Workspace as ws.rhs

    scheme : str [CDS]
        Finite volume scheme:
        :py:mod:`pygimli.solver.diffusionConvectionKernel`

    **kwargs:

        * uB : Dirichlet boundary conditions
        * duB : Neumann boundary conditions

    Returns
    -------

    u : ndarray(nTimes, nCells)
        solution field for all time steps

    """
    verbose = kwargs.pop('verbose', False)
    # The Workspace is to hold temporary data or preserve matrix rebuild
    # swatch = pg.Stopwatch(True)
    sparse = True

    workspace = pg.solver.WorkSpace()
    if ws:
        workspace = ws

    a = pg.solver.parseArgToArray(a, [mesh.cellCount(), mesh.boundaryCount()])
    f = pg.solver.parseArgToArray(f, mesh.cellCount())
    fn = pg.solver.parseArgToArray(fn, mesh.cellCount())

    boundsDirichlet = None
    boundsNeumann = None

    # BEGIN check for Courant-Friedrichs-Lewy
    if vel is not None:

        if isinstance(vel, float):
            print("Warning! .. velocity is float and no vector field")

        if len(vel) != mesh.cellCount() and \
            len(vel) != mesh.nodeCount() and \
            len(vel) != mesh.boundaryCount():

            print("mesh:", mesh)
            print("vel:", vel.shape)
            raise BaseException("Velocity field has wrong dimension.")

        if len(vel) is not mesh.boundaryCount():
            if len(vel) == mesh.cellCount():
                vel = pg.meshtools.cellDataToNodeData(mesh, vel).T
            vel = pg.meshtools.nodeDataToBoundaryData(mesh, vel)

        vmax = 0
        if mesh.dimension() == 3:
            vmax = np.max(np.sqrt(vel[:, 0]**2 + vel[:, 1]**2 + vel[:, 2]**2))
        else:
            vmax = np.max(np.sqrt(vel[:, 0]**2 + vel[:, 1]**2))
        dt = times[1] - times[0]
        dx = min(mesh.boundarySizes())
        c = vmax * dt / dx
        if c > 1:
            print(
                "Courant-Friedrichs-Lewy Number:", c,
                "but sould be lower 1 to ensure movement inside a cell "
                "per timestep. ("
                "vmax =", vmax, "dt =", dt, "dx =", dx, "dt <", dx / vmax,
                " | N > ",
                int((times[-1] - times[0]) / (dx / vmax)) + 1, ")")
    # END check for Courant-Friedrichs-Lewy

    if not hasattr(workspace, 'S'):

        if 'uB' in kwargs:
            boundsDirichlet = pg.solver.parseArgToBoundaries(
                kwargs['uB'], mesh)

        if 'duB' in kwargs:
            boundsNeumann = pg.solver.parseArgToBoundaries(kwargs['duB'], mesh)

        workspace.S, workspace.rhsBCScales = diffusionConvectionKernel(
            mesh=mesh,
            a=a,
            b=b,
            uB=boundsDirichlet,
            duB=boundsNeumann,
            # u0=u0,
            fn=fn,
            vel=vel,
            scheme=scheme,
            sparse=sparse,
            userData=kwargs.pop('userData', None))

        dof = len(workspace.rhsBCScales)
        workspace.ap = np.zeros(dof)

        # for nonlinears
        if uL is not None:
            for i in range(dof):
                val = 0.0
                if sparse:
                    val = workspace.S.getVal(i, i) / relax
                    workspace.S.setVal(i, i, val)
                else:
                    val = workspace.S[i, i] / relax
                    workspace.S[i, i] = val

                workspace.ap[i] = val

        # print('FVM kernel 2:', swatch.duration(True))
    # endif: not hasattr(workspace, 'S'):

    workspace.rhs = np.zeros(len(workspace.rhsBCScales))
    workspace.rhs[0:mesh.cellCount()] = f  # * mesh.cellSizes()

    workspace.rhs += workspace.rhsBCScales

    # for nonlinear: relax progress with scaled last result
    if uL is not None:
        workspace.rhs += (1. - relax) * workspace.ap * uL
    # print('FVM: Prep:', swatch.duration(True))

    if not hasattr(times, '__len__'):

        if sparse and not hasattr(workspace, 'solver'):
            Sm = pg.RSparseMatrix(workspace.S)
            # hold Sm until we have reference counting,
            # loosing Sm here will kill LinSolver later
            workspace.Sm = Sm
            workspace.solver = pg.LinSolver(Sm, verbose=verbose)

        u = None
        if sparse:
            u = workspace.solver.solve(workspace.rhs)
        else:
            u = np.linalg.solve(workspace.S, workspace.rhs)
        # print('FVM solve:', swatch.duration(True))
        return u[0:mesh.cellCount():1]
    else:
        theta = kwargs.pop('theta', 0.5 + 1e-6)

        if sparse:
            I = pg.solver.identity(len(workspace.rhs))
        else:
            I = np.diag(np.ones(len(workspace.rhs)))

        if verbose:
            print("Solve timesteps with Crank-Nicolson.")

        return pg.solver.crankNicolson(times,
                                       theta,
                                       workspace.S,
                                       I,
                                       f=workspace.rhs,
                                       u0=pg.solver.parseArgToArray(
                                           u0, mesh.cellCount(), mesh),
                                       verbose=verbose)
예제 #10
0
def solveFiniteVolume(mesh, a=1.0, f=0.0, fn=0.0, vel=0.0, u0=None,
                      times=None,
                      uL=None, relax=1.0,
                      ws=None, scheme='CDS', **kwargs):
    """
    """
    # The Workspace is to hold temporary data or preserve matrix rebuild
    swatch = pg.Stopwatch(True)
    sparse = True

    workspace = WorkSpace()
    if ws:
        workspace = ws

    a = solver.parseArgToArray(a, [mesh.cellCount(), mesh.boundaryCount()])
    f = solver.parseArgToArray(f, mesh.cellCount())
    fn = solver.parseArgToArray(fn, mesh.cellCount())

    boundsDirichlet = None
    boundsNeumann = None

    if not hasattr(workspace, 'S'):

        if 'uBoundary' in kwargs:
            boundsDirichlet = pg.solver.parseArgToBoundaries(
                    kwargs['uBoundary'], mesh)

        if 'duBoundary' in kwargs:
            boundsNeumann = pg.solver.parseArgToBoundaries(
                    kwargs['duBoundary'], mesh)

        workspace.S, workspace.rhsBCScales = diffusionConvectionKernel(
                mesh=mesh, a=a, f=f, uBoundaries=boundsDirichlet,
                duBoundaries=boundsNeumann, u0=u0, fn=fn, vel=vel,
                scheme=scheme, sparse=sparse,
                userData=kwargs.pop('userData', None))
        print('FVM kernel 1:', swatch.duration(True))
        dof = len(workspace.rhsBCScales)

#        workspace.uDir = np.zeros(dof)

#        if u0 is not None:
#            workspace.uDir = np.array(u0)
#
#        if len(boundsDirichlet):
#            for boundary, val in boundsDirichlet.items():
#                workspace.uDir[boundary.leftCell().id()] = val

        workspace.ap = np.zeros(dof)

        # for nonlinears

        if uL is not None:
            for i in range(dof):
                val = 0.0
                if sparse:
                    val = workspace.S.getVal(i, i) / relax
                    workspace.S.setVal(i, i, val)
#                    workspace.S[i, i] /= relax
#                    workspace.ap[i] = workspace.S[i, i]
                else:
                    val = workspace.S[i, i] / relax
                    workspace.S[i, i] = val

                workspace.ap[i] = val


        print('FVM kernel 2:', swatch.duration(True))
    # endif: not hasattr(workspace, 'S'):

    workspace.rhs = np.zeros(len(workspace.rhsBCScales))
    workspace.rhs[0:mesh.cellCount()] = f  # * mesh.cellSizes()

#    if len(workspace.uDir):
    workspace.rhs += workspace.rhsBCScales

    # for nonlinear: relax progress with scaled last result
    if uL is not None:
        workspace.rhs += (1. - relax) * workspace.ap * uL
    # print('FVM: Prep:', swatch.duration(True))

    if not hasattr(times, '__len__'):

        if sparse and not hasattr(workspace, 'solver'):
            Sm = pg.RSparseMatrix(workspace.S)
            # hold Sm until we have reference counting,
            # loosing Sm here will kill LinSolver later
            workspace.Sm = Sm
            workspace.solver = pg.LinSolver(Sm, True)

        u = None
        if sparse:
            u = workspace.solver.solve(workspace.rhs)
        else:
            u = np.linalg.solve(workspace.S, workspace.rhs)
        print('FVM solve:', swatch.duration(True))
        return u[0:mesh.cellCount():1]
    else:
        theta = kwargs.pop('theta', 0.5)
        verbose = kwargs.pop('verbose', False)

        if sparse:
            I = solver.identity(len(workspace.rhs))
        else:
            I = np.diag(np.ones(len(workspace.rhs)))

        print("solve cN")
        return solver.crankNicolson(times, theta, workspace.S, I,
                                    f=workspace.rhs, u0=u0, verbose=verbose)