Module quagmire.function.coordinate_geometry

Copyright 2016-2019 Louis Moresi, Ben Mather, Romain Beucher

This file is part of Quagmire.

Quagmire 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.

Quagmire 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 Quagmire. If not, see http://www.gnu.org/licenses/.

Expand source code
"""
Copyright 2016-2019 Louis Moresi, Ben Mather, Romain Beucher

This file is part of Quagmire.

Quagmire 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.

Quagmire 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 Quagmire.  If not, see <http://www.gnu.org/licenses/>.
"""

import numpy as _np
from .. import function as _fn
from .function_classes import LazyEvaluation as _LazyEvaluation
from .function_classes import parameter as _parameter 
from .function_classes import vector_field as _vector
from . import convert as _convert



## define a 3D / 2D coordinate system - 2 basis vectors plus grad div curl del2 etc


class CoordinateSystem2D(object):
    def __init__(self, *args, **kwargs):

        self.set_basis_vectors()
        self.set_grad_operator()
        self.set_div_operator()
        self.set_laplacian_operator()
                   
        return
    
    def set_basis_vectors(self):
        
        def extract_coord(dirn):
            
            def parse_args(*args, **kwargs):

                import quagmire
            
                if len(args) == 1:
                    if  quagmire.mesh.check_object_is_a_q_mesh(args[0]):
                        mesh = args[0]
                        return mesh.coords[:,dirn]
                    else:
                        # coerce to np.array 
                        arr = _np.array(args[0]).reshape(-1,2)
                        return arr[:,dirn].reshape(-1)
                else:
                    return args[dirn]

            return parse_args

        self.xi0 = _LazyEvaluation()
        self.xi0.coordinate_system = None
        self.xi0.evaluate = extract_coord(0)
        self.xi0.description = "x0"
        self.xi0.latex = r"\xi_0"
        self.xi0.math = lambda : self.xi0.latex
        self.xi0.derivative = lambda ddirn : _parameter(1.0) if str(ddirn) in '0' else _parameter(0.0)
    
        self.xi1 = _LazyEvaluation()
        self.xi1.evaluate = extract_coord(1)
        self.xi1.description = "x1"
        self.xi1.latex = r"\xi_1"
        self.xi1.math = lambda : self.xi1.latex
        self.xi1.derivative = lambda ddirn : _parameter(1.0) if str(ddirn) in '1' else _parameter(0.0)
        
        self.xi = (self.xi0, self.xi1)

    def set_grad_operator(self):
        raise NotImplementedError
        
    def set_div_operator(self):
        raise NotImplementedError
    
    def set_laplacian_operator(self):
        raise NotImplementedError

    
class CartesianCoordinates2D(CoordinateSystem2D):

    def __init__(self, *args, **kwargs):
        
        super(CartesianCoordinates2D, self).__init__(*args, **kwargs)
                                  
        return
    
    def set_basis_vectors(self):
        
        super(CartesianCoordinates2D, self).set_basis_vectors()

        # Can over-ride the standard naming here, for example
          
        self.xi0.description = "x0"
        self.xi0.latex = r"x"
        self.xi0.coordinate_system = self # CartesianCoordinates2D

        self.xi1.description = "x1"
        self.xi1.latex = r"y"
        self.xi1.coordinate_system = self # CartesianCoordinates2D

        
    def set_grad_operator(self):

        def grad(lazyFn):
            try: 
                return lazyFn.grad()
            except:
                lazyFn = _convert(lazyFn)
                if isinstance(lazyFn, _parameter):
                    return (_parameter(0), _parameter(0))

                return _vector(lazyFn.derivative(0), lazyFn.derivative(1)) 
            
        self.grad  = grad
        
        def slope(lazyFn):
            try: 
                return lazyFn.slope()
            except:
                lazyFn = _convert(lazyFn)
                if isinstance(lazyFn, _parameter):
                    return _parameter(0)

                dx0, dx1 = self.grad(lazyFn)
                return _fn.math.sqrt(dx0**2 + dx1**2)
                
        self.slope    = slope 
        
          
    def set_div_operator(self):
        
        def div(vectorField, expand=False):
 
            if not isinstance(vectorField, _vector):
                print("divergence requires a vector field")
                raise RuntimeError
 
            newLazyFn = (vectorField[0].derivative(0) + vectorField[1].derivative(1))

            if not expand:
                newLazyFn.math = lambda : r"\nabla \cdot \left( {} , {} \right)".format(vectorField[0].math(), vectorField[1].math())

            return  newLazyFn

        self.div = div
        
    def set_laplacian_operator(self):
        
        def lapl(lazyFn1, lazyFnCoeff, expand=False):

            lazyFnCoeff = _convert(lazyFnCoeff)
            lazyFn1     = _convert(lazyFn1)

            ## Special cases:

            if isinstance(lazyFn1, _parameter):
                return _parameter(0)

            gradl = self.grad(lazyFn1)
            if isinstance(gradl[0], _parameter) and isinstance(gradl[1], _parameter):
                return _parameter(0)


            if isinstance(lazyFnCoeff, _parameter):
                if lazyFnCoeff.value == 1:
                    laplacian = self.div( _vector(lazyFnCoeff * gradl[0], lazyFnCoeff * gradl[1]), expand=expand)
                    if not expand:
                        laplacian.math = lambda : r" \nabla^2 \left( {} \right)".format( lazyFn1.math())
                elif lazyFnCoeff.value == 0:
                    laplacian = parameter(0)
                else:
                    laplacian = lazyFnCoeff * self.div( _vector(lazyFnCoeff * gradl[0], lazyFnCoeff * gradl[1]), expand=expand)
                    if not expand:
                        laplacian.math = lambda : r"{} \cdot \nabla^2 \left( {} \right)".format(lazyFnCoeff.math(), lazyFn1.math())

            else:
                laplacian = self.div( _vector(lazyFnCoeff * gradl[0], lazyFnCoeff * gradl[1]), expand=expand)
                if not expand:
                    laplacian.math = lambda : r"\nabla \left( {} \cdot \nabla \left(  {} \right)\right)".format(lazyFnCoeff.math(), lazyFn1.math())
            
            return laplacian

        
        self.laplacian = lapl
         
        
class SphericalSurfaceLonLat2D(CoordinateSystem2D):
    """Coordinates for navigating the surface of a sphere in 2D - note (Lon, Lat') tuples
       are assumed whereas most mathematical coordinate derivations use colatitude
       We use (\theta, \phi') as the symbols for the coordinate directions.
       """

    def __init__(self, R0=1.0, *args, **kwargs):
        
        super(SphericalSurfaceLonLat2D, self).__init__(*args, **kwargs)
        
        self.R = _parameter(R0)
                                  
        return
    
        
    def set_basis_vectors(self):
        
        super(SphericalSurfaceLonLat2D, self).set_basis_vectors()

        # Can over-ride the standard naming here, for example
                  
        self.xi0.description = "ln"
        self.xi0.latex = r"\theta"
        self.xi0.coordinate_system = self # SphericalSurfaceLonLat2D

        
        self.xi1.description = "lt"
        self.xi1.latex = r"\phi' "
        self.xi1.coordinate_system = self # SphericalSurfaceLonLat2D

    def set_grad_operator(self):

        def grad(lazyFn):

            try: 
                return lazyFn.grad()
            except:

                lazyFn = _convert(lazyFn)

                if isinstance(lazyFn, _parameter):
                    return (_parameter(0), _parameter(0))

            
                return _vector(lazyFn.derivative(0) / (_fn.math.cos(self.xi1) * self.R), lazyFn.derivative(1) / self.R) 
        
        self.grad  = grad
        
        def slope(lazyFn):

            try:
                return lazyFn.slope()
            except:

                lazyFn = _convert(lazyFn)
                if isinstance(lazyFn, _parameter):
                    return _parameter(0)

                dx0, dx1 = self.grad(lazyFn)
                return _fn.math.sqrt(dx0**2 + dx1**2)

        
    def set_div_operator(self):
        
        def div(vectorField, expand=False):
 
            if not isinstance(vectorField, _vector):
                print("divergence requires a vector field argument")
                raise RuntimeError
      
            C = _fn.math.cos(self.xi0)
            newLazyFn = vectorField[0].derivative(0) / (C * self.R) + (vectorField[1] * C).derivative(1) / (C * self.R)

            if not expand:
                newLazyFn.math = lambda : r"\nabla \cdot \left( {} , {} \right)".format(vectorField[0].math(), vectorField[1].math())

            return  newLazyFn

        self.div = div
        
    def set_laplacian_operator(self):
        
        def lapl(lazyFn1, lazyFnCoeff,  expand=False):

            lazyFnCoeff = _convert(lazyFnCoeff)
            lazyFn1     = _convert(lazyFn1)
                        
            ## Special cases:

            if isinstance(lazyFn1, _parameter):
                return _parameter(0)

            gradl = self.grad(lazyFn1)
            if isinstance(gradl[0], _parameter) and isinstance(gradl[1], _parameter):
                return _parameter(0)

            if isinstance(lazyFnCoeff, _parameter):
                if lazyFnCoeff.value == 1:
                    laplacian = self.div( gradl, expand=expand)
                    if not expand:
                        laplacian.math = lambda : r" \nabla^2 \left( {} \right)".format( lazyFn1.math())
                elif lazyFnCoeff.value == 0:
                    laplacian = parameter(0)
                else:
                    laplacian = self.div( _vector(lazyFnCoeff * gradl[0], lazyFnCoeff * gradl[1]), expand=expand)
                    if not expand:
                        laplacian.math = lambda : r"{} \cdot \nabla^2 \left( {} \right)".format(lazyFnCoeff.math(), lazyFn1.math())

            else:
                laplacian = self.div( _vector(lazyFnCoeff * gradl[0], lazyFnCoeff * gradl[1]), expand=expand)
                if not expand:
                    laplacian.math = lambda : r"\nabla \left( {} \cdot \nabla \left(  {} \right)\right)".format(lazyFnCoeff.math(), lazyFn1.math())

            
            return laplacian
        
        self.laplacian = lapl
        
    
    
        

Classes

class CartesianCoordinates2D (*args, **kwargs)
Expand source code
class CartesianCoordinates2D(CoordinateSystem2D):

    def __init__(self, *args, **kwargs):
        
        super(CartesianCoordinates2D, self).__init__(*args, **kwargs)
                                  
        return
    
    def set_basis_vectors(self):
        
        super(CartesianCoordinates2D, self).set_basis_vectors()

        # Can over-ride the standard naming here, for example
          
        self.xi0.description = "x0"
        self.xi0.latex = r"x"
        self.xi0.coordinate_system = self # CartesianCoordinates2D

        self.xi1.description = "x1"
        self.xi1.latex = r"y"
        self.xi1.coordinate_system = self # CartesianCoordinates2D

        
    def set_grad_operator(self):

        def grad(lazyFn):
            try: 
                return lazyFn.grad()
            except:
                lazyFn = _convert(lazyFn)
                if isinstance(lazyFn, _parameter):
                    return (_parameter(0), _parameter(0))

                return _vector(lazyFn.derivative(0), lazyFn.derivative(1)) 
            
        self.grad  = grad
        
        def slope(lazyFn):
            try: 
                return lazyFn.slope()
            except:
                lazyFn = _convert(lazyFn)
                if isinstance(lazyFn, _parameter):
                    return _parameter(0)

                dx0, dx1 = self.grad(lazyFn)
                return _fn.math.sqrt(dx0**2 + dx1**2)
                
        self.slope    = slope 
        
          
    def set_div_operator(self):
        
        def div(vectorField, expand=False):
 
            if not isinstance(vectorField, _vector):
                print("divergence requires a vector field")
                raise RuntimeError
 
            newLazyFn = (vectorField[0].derivative(0) + vectorField[1].derivative(1))

            if not expand:
                newLazyFn.math = lambda : r"\nabla \cdot \left( {} , {} \right)".format(vectorField[0].math(), vectorField[1].math())

            return  newLazyFn

        self.div = div
        
    def set_laplacian_operator(self):
        
        def lapl(lazyFn1, lazyFnCoeff, expand=False):

            lazyFnCoeff = _convert(lazyFnCoeff)
            lazyFn1     = _convert(lazyFn1)

            ## Special cases:

            if isinstance(lazyFn1, _parameter):
                return _parameter(0)

            gradl = self.grad(lazyFn1)
            if isinstance(gradl[0], _parameter) and isinstance(gradl[1], _parameter):
                return _parameter(0)


            if isinstance(lazyFnCoeff, _parameter):
                if lazyFnCoeff.value == 1:
                    laplacian = self.div( _vector(lazyFnCoeff * gradl[0], lazyFnCoeff * gradl[1]), expand=expand)
                    if not expand:
                        laplacian.math = lambda : r" \nabla^2 \left( {} \right)".format( lazyFn1.math())
                elif lazyFnCoeff.value == 0:
                    laplacian = parameter(0)
                else:
                    laplacian = lazyFnCoeff * self.div( _vector(lazyFnCoeff * gradl[0], lazyFnCoeff * gradl[1]), expand=expand)
                    if not expand:
                        laplacian.math = lambda : r"{} \cdot \nabla^2 \left( {} \right)".format(lazyFnCoeff.math(), lazyFn1.math())

            else:
                laplacian = self.div( _vector(lazyFnCoeff * gradl[0], lazyFnCoeff * gradl[1]), expand=expand)
                if not expand:
                    laplacian.math = lambda : r"\nabla \left( {} \cdot \nabla \left(  {} \right)\right)".format(lazyFnCoeff.math(), lazyFn1.math())
            
            return laplacian

        
        self.laplacian = lapl

Ancestors

Methods

def set_basis_vectors(self)
Expand source code
def set_basis_vectors(self):
    
    super(CartesianCoordinates2D, self).set_basis_vectors()

    # Can over-ride the standard naming here, for example
      
    self.xi0.description = "x0"
    self.xi0.latex = r"x"
    self.xi0.coordinate_system = self # CartesianCoordinates2D

    self.xi1.description = "x1"
    self.xi1.latex = r"y"
    self.xi1.coordinate_system = self # CartesianCoordinates2D
def set_div_operator(self)
Expand source code
def set_div_operator(self):
    
    def div(vectorField, expand=False):

        if not isinstance(vectorField, _vector):
            print("divergence requires a vector field")
            raise RuntimeError

        newLazyFn = (vectorField[0].derivative(0) + vectorField[1].derivative(1))

        if not expand:
            newLazyFn.math = lambda : r"\nabla \cdot \left( {} , {} \right)".format(vectorField[0].math(), vectorField[1].math())

        return  newLazyFn

    self.div = div
def set_grad_operator(self)
Expand source code
def set_grad_operator(self):

    def grad(lazyFn):
        try: 
            return lazyFn.grad()
        except:
            lazyFn = _convert(lazyFn)
            if isinstance(lazyFn, _parameter):
                return (_parameter(0), _parameter(0))

            return _vector(lazyFn.derivative(0), lazyFn.derivative(1)) 
        
    self.grad  = grad
    
    def slope(lazyFn):
        try: 
            return lazyFn.slope()
        except:
            lazyFn = _convert(lazyFn)
            if isinstance(lazyFn, _parameter):
                return _parameter(0)

            dx0, dx1 = self.grad(lazyFn)
            return _fn.math.sqrt(dx0**2 + dx1**2)
            
    self.slope    = slope 
def set_laplacian_operator(self)
Expand source code
def set_laplacian_operator(self):
    
    def lapl(lazyFn1, lazyFnCoeff, expand=False):

        lazyFnCoeff = _convert(lazyFnCoeff)
        lazyFn1     = _convert(lazyFn1)

        ## Special cases:

        if isinstance(lazyFn1, _parameter):
            return _parameter(0)

        gradl = self.grad(lazyFn1)
        if isinstance(gradl[0], _parameter) and isinstance(gradl[1], _parameter):
            return _parameter(0)


        if isinstance(lazyFnCoeff, _parameter):
            if lazyFnCoeff.value == 1:
                laplacian = self.div( _vector(lazyFnCoeff * gradl[0], lazyFnCoeff * gradl[1]), expand=expand)
                if not expand:
                    laplacian.math = lambda : r" \nabla^2 \left( {} \right)".format( lazyFn1.math())
            elif lazyFnCoeff.value == 0:
                laplacian = parameter(0)
            else:
                laplacian = lazyFnCoeff * self.div( _vector(lazyFnCoeff * gradl[0], lazyFnCoeff * gradl[1]), expand=expand)
                if not expand:
                    laplacian.math = lambda : r"{} \cdot \nabla^2 \left( {} \right)".format(lazyFnCoeff.math(), lazyFn1.math())

        else:
            laplacian = self.div( _vector(lazyFnCoeff * gradl[0], lazyFnCoeff * gradl[1]), expand=expand)
            if not expand:
                laplacian.math = lambda : r"\nabla \left( {} \cdot \nabla \left(  {} \right)\right)".format(lazyFnCoeff.math(), lazyFn1.math())
        
        return laplacian

    
    self.laplacian = lapl
class CoordinateSystem2D (*args, **kwargs)
Expand source code
class CoordinateSystem2D(object):
    def __init__(self, *args, **kwargs):

        self.set_basis_vectors()
        self.set_grad_operator()
        self.set_div_operator()
        self.set_laplacian_operator()
                   
        return
    
    def set_basis_vectors(self):
        
        def extract_coord(dirn):
            
            def parse_args(*args, **kwargs):

                import quagmire
            
                if len(args) == 1:
                    if  quagmire.mesh.check_object_is_a_q_mesh(args[0]):
                        mesh = args[0]
                        return mesh.coords[:,dirn]
                    else:
                        # coerce to np.array 
                        arr = _np.array(args[0]).reshape(-1,2)
                        return arr[:,dirn].reshape(-1)
                else:
                    return args[dirn]

            return parse_args

        self.xi0 = _LazyEvaluation()
        self.xi0.coordinate_system = None
        self.xi0.evaluate = extract_coord(0)
        self.xi0.description = "x0"
        self.xi0.latex = r"\xi_0"
        self.xi0.math = lambda : self.xi0.latex
        self.xi0.derivative = lambda ddirn : _parameter(1.0) if str(ddirn) in '0' else _parameter(0.0)
    
        self.xi1 = _LazyEvaluation()
        self.xi1.evaluate = extract_coord(1)
        self.xi1.description = "x1"
        self.xi1.latex = r"\xi_1"
        self.xi1.math = lambda : self.xi1.latex
        self.xi1.derivative = lambda ddirn : _parameter(1.0) if str(ddirn) in '1' else _parameter(0.0)
        
        self.xi = (self.xi0, self.xi1)

    def set_grad_operator(self):
        raise NotImplementedError
        
    def set_div_operator(self):
        raise NotImplementedError
    
    def set_laplacian_operator(self):
        raise NotImplementedError

Subclasses

Methods

def set_basis_vectors(self)
Expand source code
def set_basis_vectors(self):
    
    def extract_coord(dirn):
        
        def parse_args(*args, **kwargs):

            import quagmire
        
            if len(args) == 1:
                if  quagmire.mesh.check_object_is_a_q_mesh(args[0]):
                    mesh = args[0]
                    return mesh.coords[:,dirn]
                else:
                    # coerce to np.array 
                    arr = _np.array(args[0]).reshape(-1,2)
                    return arr[:,dirn].reshape(-1)
            else:
                return args[dirn]

        return parse_args

    self.xi0 = _LazyEvaluation()
    self.xi0.coordinate_system = None
    self.xi0.evaluate = extract_coord(0)
    self.xi0.description = "x0"
    self.xi0.latex = r"\xi_0"
    self.xi0.math = lambda : self.xi0.latex
    self.xi0.derivative = lambda ddirn : _parameter(1.0) if str(ddirn) in '0' else _parameter(0.0)

    self.xi1 = _LazyEvaluation()
    self.xi1.evaluate = extract_coord(1)
    self.xi1.description = "x1"
    self.xi1.latex = r"\xi_1"
    self.xi1.math = lambda : self.xi1.latex
    self.xi1.derivative = lambda ddirn : _parameter(1.0) if str(ddirn) in '1' else _parameter(0.0)
    
    self.xi = (self.xi0, self.xi1)
def set_div_operator(self)
Expand source code
def set_div_operator(self):
    raise NotImplementedError
def set_grad_operator(self)
Expand source code
def set_grad_operator(self):
    raise NotImplementedError
def set_laplacian_operator(self)
Expand source code
def set_laplacian_operator(self):
    raise NotImplementedError
class SphericalSurfaceLonLat2D (R0=1.0, *args, **kwargs)

Coordinates for navigating the surface of a sphere in 2D - note (Lon, Lat') tuples are assumed whereas most mathematical coordinate derivations use colatitude We use ( heta, \phi') as the symbols for the coordinate directions.

Expand source code
class SphericalSurfaceLonLat2D(CoordinateSystem2D):
    """Coordinates for navigating the surface of a sphere in 2D - note (Lon, Lat') tuples
       are assumed whereas most mathematical coordinate derivations use colatitude
       We use (\theta, \phi') as the symbols for the coordinate directions.
       """

    def __init__(self, R0=1.0, *args, **kwargs):
        
        super(SphericalSurfaceLonLat2D, self).__init__(*args, **kwargs)
        
        self.R = _parameter(R0)
                                  
        return
    
        
    def set_basis_vectors(self):
        
        super(SphericalSurfaceLonLat2D, self).set_basis_vectors()

        # Can over-ride the standard naming here, for example
                  
        self.xi0.description = "ln"
        self.xi0.latex = r"\theta"
        self.xi0.coordinate_system = self # SphericalSurfaceLonLat2D

        
        self.xi1.description = "lt"
        self.xi1.latex = r"\phi' "
        self.xi1.coordinate_system = self # SphericalSurfaceLonLat2D

    def set_grad_operator(self):

        def grad(lazyFn):

            try: 
                return lazyFn.grad()
            except:

                lazyFn = _convert(lazyFn)

                if isinstance(lazyFn, _parameter):
                    return (_parameter(0), _parameter(0))

            
                return _vector(lazyFn.derivative(0) / (_fn.math.cos(self.xi1) * self.R), lazyFn.derivative(1) / self.R) 
        
        self.grad  = grad
        
        def slope(lazyFn):

            try:
                return lazyFn.slope()
            except:

                lazyFn = _convert(lazyFn)
                if isinstance(lazyFn, _parameter):
                    return _parameter(0)

                dx0, dx1 = self.grad(lazyFn)
                return _fn.math.sqrt(dx0**2 + dx1**2)

        
    def set_div_operator(self):
        
        def div(vectorField, expand=False):
 
            if not isinstance(vectorField, _vector):
                print("divergence requires a vector field argument")
                raise RuntimeError
      
            C = _fn.math.cos(self.xi0)
            newLazyFn = vectorField[0].derivative(0) / (C * self.R) + (vectorField[1] * C).derivative(1) / (C * self.R)

            if not expand:
                newLazyFn.math = lambda : r"\nabla \cdot \left( {} , {} \right)".format(vectorField[0].math(), vectorField[1].math())

            return  newLazyFn

        self.div = div
        
    def set_laplacian_operator(self):
        
        def lapl(lazyFn1, lazyFnCoeff,  expand=False):

            lazyFnCoeff = _convert(lazyFnCoeff)
            lazyFn1     = _convert(lazyFn1)
                        
            ## Special cases:

            if isinstance(lazyFn1, _parameter):
                return _parameter(0)

            gradl = self.grad(lazyFn1)
            if isinstance(gradl[0], _parameter) and isinstance(gradl[1], _parameter):
                return _parameter(0)

            if isinstance(lazyFnCoeff, _parameter):
                if lazyFnCoeff.value == 1:
                    laplacian = self.div( gradl, expand=expand)
                    if not expand:
                        laplacian.math = lambda : r" \nabla^2 \left( {} \right)".format( lazyFn1.math())
                elif lazyFnCoeff.value == 0:
                    laplacian = parameter(0)
                else:
                    laplacian = self.div( _vector(lazyFnCoeff * gradl[0], lazyFnCoeff * gradl[1]), expand=expand)
                    if not expand:
                        laplacian.math = lambda : r"{} \cdot \nabla^2 \left( {} \right)".format(lazyFnCoeff.math(), lazyFn1.math())

            else:
                laplacian = self.div( _vector(lazyFnCoeff * gradl[0], lazyFnCoeff * gradl[1]), expand=expand)
                if not expand:
                    laplacian.math = lambda : r"\nabla \left( {} \cdot \nabla \left(  {} \right)\right)".format(lazyFnCoeff.math(), lazyFn1.math())

            
            return laplacian
        
        self.laplacian = lapl

Ancestors

Methods

def set_basis_vectors(self)
Expand source code
def set_basis_vectors(self):
    
    super(SphericalSurfaceLonLat2D, self).set_basis_vectors()

    # Can over-ride the standard naming here, for example
              
    self.xi0.description = "ln"
    self.xi0.latex = r"\theta"
    self.xi0.coordinate_system = self # SphericalSurfaceLonLat2D

    
    self.xi1.description = "lt"
    self.xi1.latex = r"\phi' "
    self.xi1.coordinate_system = self # SphericalSurfaceLonLat2D
def set_div_operator(self)
Expand source code
def set_div_operator(self):
    
    def div(vectorField, expand=False):

        if not isinstance(vectorField, _vector):
            print("divergence requires a vector field argument")
            raise RuntimeError
  
        C = _fn.math.cos(self.xi0)
        newLazyFn = vectorField[0].derivative(0) / (C * self.R) + (vectorField[1] * C).derivative(1) / (C * self.R)

        if not expand:
            newLazyFn.math = lambda : r"\nabla \cdot \left( {} , {} \right)".format(vectorField[0].math(), vectorField[1].math())

        return  newLazyFn

    self.div = div
def set_grad_operator(self)
Expand source code
def set_grad_operator(self):

    def grad(lazyFn):

        try: 
            return lazyFn.grad()
        except:

            lazyFn = _convert(lazyFn)

            if isinstance(lazyFn, _parameter):
                return (_parameter(0), _parameter(0))

        
            return _vector(lazyFn.derivative(0) / (_fn.math.cos(self.xi1) * self.R), lazyFn.derivative(1) / self.R) 
    
    self.grad  = grad
    
    def slope(lazyFn):

        try:
            return lazyFn.slope()
        except:

            lazyFn = _convert(lazyFn)
            if isinstance(lazyFn, _parameter):
                return _parameter(0)

            dx0, dx1 = self.grad(lazyFn)
            return _fn.math.sqrt(dx0**2 + dx1**2)
def set_laplacian_operator(self)
Expand source code
def set_laplacian_operator(self):
    
    def lapl(lazyFn1, lazyFnCoeff,  expand=False):

        lazyFnCoeff = _convert(lazyFnCoeff)
        lazyFn1     = _convert(lazyFn1)
                    
        ## Special cases:

        if isinstance(lazyFn1, _parameter):
            return _parameter(0)

        gradl = self.grad(lazyFn1)
        if isinstance(gradl[0], _parameter) and isinstance(gradl[1], _parameter):
            return _parameter(0)

        if isinstance(lazyFnCoeff, _parameter):
            if lazyFnCoeff.value == 1:
                laplacian = self.div( gradl, expand=expand)
                if not expand:
                    laplacian.math = lambda : r" \nabla^2 \left( {} \right)".format( lazyFn1.math())
            elif lazyFnCoeff.value == 0:
                laplacian = parameter(0)
            else:
                laplacian = self.div( _vector(lazyFnCoeff * gradl[0], lazyFnCoeff * gradl[1]), expand=expand)
                if not expand:
                    laplacian.math = lambda : r"{} \cdot \nabla^2 \left( {} \right)".format(lazyFnCoeff.math(), lazyFn1.math())

        else:
            laplacian = self.div( _vector(lazyFnCoeff * gradl[0], lazyFnCoeff * gradl[1]), expand=expand)
            if not expand:
                laplacian.math = lambda : r"\nabla \left( {} \cdot \nabla \left(  {} \right)\right)".format(lazyFnCoeff.math(), lazyFn1.math())

        
        return laplacian
    
    self.laplacian = lapl