Module conduction.solver.diffusionNd_serial

Copyright 2017 Ben Mather

This file is part of Conduction https://git.dias.ie/itherc/conduction/

Conduction is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version.

Conduction is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with Conduction. If not, see http://www.gnu.org/licenses/.

Expand source code
"""
Copyright 2017 Ben Mather

This file is part of Conduction <https://git.dias.ie/itherc/conduction/>

Conduction is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or any later version.

Conduction is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with Conduction.  If not, see <http://www.gnu.org/licenses/>.
"""

try: range = xrange
except: pass

import numpy as np
from scipy import sparse

from . import ConductionND_serial as ConductionND

class DiffusionND(ConductionND):
    """
    Implicit N-dimensional solver for the time-dependent heat equation
    over a structured grid using PETSc data structures (inherits ConductionND).

    Parameters
    ----------
     minCoord : tuple, minimum Cartesian coordinates at edge of domain
     maxCoord : tuple, maximum Cartesian coordinates at edge of domain
     res      : tuple, resolution in each dimension
     theta    : float, diffusion number that controls time discretisation [0, 1]
        0.0 = backward Euler
        0.5 = Crank-Nicholson (default, most accurate)
        1.0 = forward Euler
     kwargs   : dict, keyword arguments to pass to KSP method and preconditioner
        see PETSc documentaion for KSPType and PCType options...
        http://www.mcs.anl.gov/petsc/petsc-current/docs/manualpages/KSP/KSPType.html
        http://www.mcs.anl.gov/petsc/petsc-current/docs/manualpages/PC/PCType.html
    """
    def __init__(self, minCoord, maxCoord, res, theta=0.5, **kwargs):

        super(DiffusionND, self).__init__(minCoord, maxCoord, res, **kwargs)

        # theta rule in time, centred differences in space
        if theta < 0 or theta > 1:
            raise ValueError("theta must be in the range [0, 1]")
        self.theta = theta

        # get maximum spacing
        delta = np.zeros(self.dim)
        all_delta = np.zeros(self.dim)
        for i in range(self.dim):
            dx = np.diff(self.grid_coords[i])
            delta[i] = dx.max()

        self.delta = max(delta)


    def calculate_dt(self):
        """
        Calculate optimal timestep size
        """
        kappa = self.diffusivity
        delta = self.delta
        max_kappa = kappa.max()
        dt = np.sum(delta**2)/(4.0*max_kappa)
        return dt


    def construct_matrix_dt(self, in_place=True, derivative=False, scale=1.0):
        """
        Construct the coefficient matrix
        i.e. matrix A in Ax = b

        This extends the ConductionND method by including time-dependence
        through a scale variable.
        """

        if scale > 0:
            mat = super(DiffusionND, self).construct_matrix(derivative)
            mat *= -scale
        else:
            # preallocate empty matrix
            mat = sparse.coo_matrix(self.sizes)

        diag = mat.diagonal()
        diag += 1.0 # add self node

        # overwrite Dirichlet BCs
        diag[self.dirichlet_mask] = 1.0
        mat.setdiag(diag)
        return mat.tocsr()


    def construct_rhs_dt(self, scale=1.0):
        """
        Construct the right-hand-side vector
        i.e. vector b in Ax = b

        This extends the ConductionND method by including time-dependence
        through a scale variable.
        """
        rhs = np.empty(self.nn)

        if scale > 0:
            # vectorise stencil similar to matrix construction
            n = self.n
            cols = self.cols
            index = self.index
            vals = self.vals

            temp  = self.temperature[:].reshape(n)
            kappa = self.diffusivity[:].reshape(n)
            k = np.pad(kappa, self.width, 'constant', constant_values=0)
            T = np.pad(temp,  self.width, 'constant', constant_values=0)

            for i in range(0, self.stencil_width):
                obj = self.closure[i]

                cols[i] = index[obj].ravel()

                distance = np.linalg.norm(self.coords[cols[i]] - self.coords, axis=1)
                distance[distance==0] = 1e-12 # protect against dividing by zero
                delta = scale/distance**2

                vals[i] = delta*(0.5*(k[obj] + kappa)*(T[obj] - temp)).ravel()
            
            # zero off-grid coordinates
            vals[cols < 0] = 0.0
            vals[-1] = 0.0 # centre point
            vec = vals.sum(axis=0)

            # add heat sources
            vec += scale*self.heat_sources[:]
        else:
            vec = np.zeros(self.nn)

        # add current temperature
        vec += self.temperature[:]

        # enforce BCs
        for wall in self.bc:
            val  = self.bc[wall]['val']
            flux = self.bc[wall]['flux']
            mask = self.bc[wall]['mask']
            if flux:
                vec[mask] += val
            else:
                vec[mask] = val

        rhs[:] = vec
        return rhs


    def timestep(self, steps=1, dt=None):
        """
        Solve a timestep
        """
        if type(dt) == type(None):
            dt = self.calculate_dt()

        theta = self.theta
        Lscale = dt*theta
        Rscale = dt*(1.0 - theta)

        # construct a constant matrix
        mat = self.construct_matrix_dt(scale=Lscale)

        for step in range(steps):
            rhs = self.construct_rhs_dt(scale=Rscale)
            T = self.solve(mat, rhs)

        return T

Classes

class DiffusionND (minCoord, maxCoord, res, theta=0.5, **kwargs)

Implicit N-dimensional solver for the time-dependent heat equation over a structured grid using PETSc data structures (inherits ConductionND).

Parameters

minCoord : tuple, minimum Cartesian coordinates at edge of domain maxCoord : tuple, maximum Cartesian coordinates at edge of domain res : tuple, resolution in each dimension theta : float, diffusion number that controls time discretisation [0, 1] 0.0 = backward Euler 0.5 = Crank-Nicholson (default, most accurate) 1.0 = forward Euler kwargs : dict, keyword arguments to pass to KSP method and preconditioner see PETSc documentaion for KSPType and PCType options… http://www.mcs.anl.gov/petsc/petsc-current/docs/manualpages/KSP/KSPType.html http://www.mcs.anl.gov/petsc/petsc-current/docs/manualpages/PC/PCType.html

Expand source code
class DiffusionND(ConductionND):
    """
    Implicit N-dimensional solver for the time-dependent heat equation
    over a structured grid using PETSc data structures (inherits ConductionND).

    Parameters
    ----------
     minCoord : tuple, minimum Cartesian coordinates at edge of domain
     maxCoord : tuple, maximum Cartesian coordinates at edge of domain
     res      : tuple, resolution in each dimension
     theta    : float, diffusion number that controls time discretisation [0, 1]
        0.0 = backward Euler
        0.5 = Crank-Nicholson (default, most accurate)
        1.0 = forward Euler
     kwargs   : dict, keyword arguments to pass to KSP method and preconditioner
        see PETSc documentaion for KSPType and PCType options...
        http://www.mcs.anl.gov/petsc/petsc-current/docs/manualpages/KSP/KSPType.html
        http://www.mcs.anl.gov/petsc/petsc-current/docs/manualpages/PC/PCType.html
    """
    def __init__(self, minCoord, maxCoord, res, theta=0.5, **kwargs):

        super(DiffusionND, self).__init__(minCoord, maxCoord, res, **kwargs)

        # theta rule in time, centred differences in space
        if theta < 0 or theta > 1:
            raise ValueError("theta must be in the range [0, 1]")
        self.theta = theta

        # get maximum spacing
        delta = np.zeros(self.dim)
        all_delta = np.zeros(self.dim)
        for i in range(self.dim):
            dx = np.diff(self.grid_coords[i])
            delta[i] = dx.max()

        self.delta = max(delta)


    def calculate_dt(self):
        """
        Calculate optimal timestep size
        """
        kappa = self.diffusivity
        delta = self.delta
        max_kappa = kappa.max()
        dt = np.sum(delta**2)/(4.0*max_kappa)
        return dt


    def construct_matrix_dt(self, in_place=True, derivative=False, scale=1.0):
        """
        Construct the coefficient matrix
        i.e. matrix A in Ax = b

        This extends the ConductionND method by including time-dependence
        through a scale variable.
        """

        if scale > 0:
            mat = super(DiffusionND, self).construct_matrix(derivative)
            mat *= -scale
        else:
            # preallocate empty matrix
            mat = sparse.coo_matrix(self.sizes)

        diag = mat.diagonal()
        diag += 1.0 # add self node

        # overwrite Dirichlet BCs
        diag[self.dirichlet_mask] = 1.0
        mat.setdiag(diag)
        return mat.tocsr()


    def construct_rhs_dt(self, scale=1.0):
        """
        Construct the right-hand-side vector
        i.e. vector b in Ax = b

        This extends the ConductionND method by including time-dependence
        through a scale variable.
        """
        rhs = np.empty(self.nn)

        if scale > 0:
            # vectorise stencil similar to matrix construction
            n = self.n
            cols = self.cols
            index = self.index
            vals = self.vals

            temp  = self.temperature[:].reshape(n)
            kappa = self.diffusivity[:].reshape(n)
            k = np.pad(kappa, self.width, 'constant', constant_values=0)
            T = np.pad(temp,  self.width, 'constant', constant_values=0)

            for i in range(0, self.stencil_width):
                obj = self.closure[i]

                cols[i] = index[obj].ravel()

                distance = np.linalg.norm(self.coords[cols[i]] - self.coords, axis=1)
                distance[distance==0] = 1e-12 # protect against dividing by zero
                delta = scale/distance**2

                vals[i] = delta*(0.5*(k[obj] + kappa)*(T[obj] - temp)).ravel()
            
            # zero off-grid coordinates
            vals[cols < 0] = 0.0
            vals[-1] = 0.0 # centre point
            vec = vals.sum(axis=0)

            # add heat sources
            vec += scale*self.heat_sources[:]
        else:
            vec = np.zeros(self.nn)

        # add current temperature
        vec += self.temperature[:]

        # enforce BCs
        for wall in self.bc:
            val  = self.bc[wall]['val']
            flux = self.bc[wall]['flux']
            mask = self.bc[wall]['mask']
            if flux:
                vec[mask] += val
            else:
                vec[mask] = val

        rhs[:] = vec
        return rhs


    def timestep(self, steps=1, dt=None):
        """
        Solve a timestep
        """
        if type(dt) == type(None):
            dt = self.calculate_dt()

        theta = self.theta
        Lscale = dt*theta
        Rscale = dt*(1.0 - theta)

        # construct a constant matrix
        mat = self.construct_matrix_dt(scale=Lscale)

        for step in range(steps):
            rhs = self.construct_rhs_dt(scale=Rscale)
            T = self.solve(mat, rhs)

        return T

Ancestors

Methods

def calculate_dt(self)

Calculate optimal timestep size

Expand source code
def calculate_dt(self):
    """
    Calculate optimal timestep size
    """
    kappa = self.diffusivity
    delta = self.delta
    max_kappa = kappa.max()
    dt = np.sum(delta**2)/(4.0*max_kappa)
    return dt
def construct_matrix_dt(self, in_place=True, derivative=False, scale=1.0)

Construct the coefficient matrix i.e. matrix A in Ax = b

This extends the ConductionND method by including time-dependence through a scale variable.

Expand source code
def construct_matrix_dt(self, in_place=True, derivative=False, scale=1.0):
    """
    Construct the coefficient matrix
    i.e. matrix A in Ax = b

    This extends the ConductionND method by including time-dependence
    through a scale variable.
    """

    if scale > 0:
        mat = super(DiffusionND, self).construct_matrix(derivative)
        mat *= -scale
    else:
        # preallocate empty matrix
        mat = sparse.coo_matrix(self.sizes)

    diag = mat.diagonal()
    diag += 1.0 # add self node

    # overwrite Dirichlet BCs
    diag[self.dirichlet_mask] = 1.0
    mat.setdiag(diag)
    return mat.tocsr()
def construct_rhs_dt(self, scale=1.0)

Construct the right-hand-side vector i.e. vector b in Ax = b

This extends the ConductionND method by including time-dependence through a scale variable.

Expand source code
def construct_rhs_dt(self, scale=1.0):
    """
    Construct the right-hand-side vector
    i.e. vector b in Ax = b

    This extends the ConductionND method by including time-dependence
    through a scale variable.
    """
    rhs = np.empty(self.nn)

    if scale > 0:
        # vectorise stencil similar to matrix construction
        n = self.n
        cols = self.cols
        index = self.index
        vals = self.vals

        temp  = self.temperature[:].reshape(n)
        kappa = self.diffusivity[:].reshape(n)
        k = np.pad(kappa, self.width, 'constant', constant_values=0)
        T = np.pad(temp,  self.width, 'constant', constant_values=0)

        for i in range(0, self.stencil_width):
            obj = self.closure[i]

            cols[i] = index[obj].ravel()

            distance = np.linalg.norm(self.coords[cols[i]] - self.coords, axis=1)
            distance[distance==0] = 1e-12 # protect against dividing by zero
            delta = scale/distance**2

            vals[i] = delta*(0.5*(k[obj] + kappa)*(T[obj] - temp)).ravel()
        
        # zero off-grid coordinates
        vals[cols < 0] = 0.0
        vals[-1] = 0.0 # centre point
        vec = vals.sum(axis=0)

        # add heat sources
        vec += scale*self.heat_sources[:]
    else:
        vec = np.zeros(self.nn)

    # add current temperature
    vec += self.temperature[:]

    # enforce BCs
    for wall in self.bc:
        val  = self.bc[wall]['val']
        flux = self.bc[wall]['flux']
        mask = self.bc[wall]['mask']
        if flux:
            vec[mask] += val
        else:
            vec[mask] = val

    rhs[:] = vec
    return rhs
def timestep(self, steps=1, dt=None)

Solve a timestep

Expand source code
def timestep(self, steps=1, dt=None):
    """
    Solve a timestep
    """
    if type(dt) == type(None):
        dt = self.calculate_dt()

    theta = self.theta
    Lscale = dt*theta
    Rscale = dt*(1.0 - theta)

    # construct a constant matrix
    mat = self.construct_matrix_dt(scale=Lscale)

    for step in range(steps):
        rhs = self.construct_rhs_dt(scale=Rscale)
        T = self.solve(mat, rhs)

    return T

Inherited members