Module regpy.operators.ngsolve

PDE forward operators using NGSolve

Classes

class NGSolveOperator (domain, codomain, linear=False)
Expand source code
class NGSolveOperator(Operator):
    def __init__(self, domain, codomain, linear = False):
        super().__init__(domain = domain, codomain = codomain, linear = linear)
        self.gfu_read_in = ngs.GridFunction(self.domain.fes)

    '''Reads in a coefficient vector of the domain and interpolates in the codomain.
    The result is saved in gfu'''
    def _read_in(self, vector, gfu,definedonelements=None):
        """Read in of a numpy array into a ngsolve grid function. Note You can also read into
        ngsolve LinearForm.

        Parameters
        ----------
        vector : numpy.array
            Numpy array to be put into the `GridFunction`
        gfu : ngsolve.GridFunction or ngsolve.LinearForm
            ngsolve element to be written into.
        definedonelements : pyngcore.pyngcore.BitArray, optional
            BitArray representing the finite elements to be projected onto, by default None
        """
        gfu.vec.FV().NumPy()[:] = vector
        if definedonelements is not None:
            ngs.Projector(definedonelements, range=True).Project(gfu.vec)

    def _solve_dirichlet_problem(self, bf, lf, gf, prec):
        r"""Solves the problem 
        \begin{align*}
        b(u,v) = f(v) \;\forall v\; test\; functions,\\
        u|_\Gamma = g
        \end{align*}
        The boundary values are given by what `gf` has as values on the boundary. 

        Parameters
        ----------
        bf : ngs.BilinearForm
            the bilinear form of the problem.
        lf : ngs.LinearFrom
            the linear form of the problem. 
        gf : ngs.GridFunction
            The grid functions on which to solve the solution will be put into these and they have to satisfy 
            the boundary condition that you want.
        prec : ngs.Preconditioner
            preconditioner to be used with ngsolve.
        """
        gf.vec.data += bf.mat.Inverse(freedofs=self.codomain.fes.FreeDofs()) * (lf.vec - bf.mat * gf.vec)

Ancestors

Subclasses

Inherited members

class SecondOrderEllipticCoefficientPDE (domain, sol_domain, bdr_val=None, a_bdr_val=None, adj_lf=True, cu_lf=True)
Expand source code
class SecondOrderEllipticCoefficientPDE(NGSolveOperator):
    r"""Provides a general setup for the forward problems mapping PDE coefficients to their solutions.
    That is we assume that as variational formulation one can use 
    \[
    \forall v:    b_0(u,v) + b_a(u,v) = F(v)
    \] 
    with \(b_0\) a bilinear form independent of the coefficient \(a\) and \(F\) some linear form.  
    Furthermore \(b_a\) the bilinear form that depends on \(a\) has to be linear in \(a\) so that 
    one can define a bilinear form \(c_u(a,v)=b_a(u,v)\). More over we may assume some Dirichlet 
    boundary conditions on \(u\).That is 
    \[
        F: a \mapsto u 
    \]

    The Frechét derivative \(F'[a]h\) in direction \(h\) is then given as the variational 
    solution \(u'\) to 
    \[
    \forall v: b_0(u',v) + b_a(u',v) = -c_u(h,v)
    \]
    with \(u=F(a)\). That is
    \[
        F'[a]: h \mapsto u' 
    \]

    It's adjoint \(F'[a]^\ast g\) is given as the linear form \(-c_u(\cdot,w)\) for \(u=F(a)\) and 
    \(w\) solving the problem 
    \[
    \forall v:  b_0(v,w) + b_a(v,w) = <g,v>.
    \]
    That is
    \[
        F'[a]^\ast: g \mapsto -c_u(\cdot,w)
    \]

    Notes
    -----
    A subclass implemented by a user has to at least implement the subroutine `_bf` which is the 
    implementation of the bilinear form depending on the coefficient \(a\). Optional can the 
    independent bilinear form \(b_0\) be implemented as formal integrator in `_bf_0` and the 
    linear form can be implemented in `_lf`. 

    Parameters
    ----------
    domain : NgsSpace
        The NgsSpace on which the coefficients defined are.
    sol_domain : NgsSpace
        The NgsSpace on which the PDE solutions defined are.
    bdr_val : array type, optional
        Boundary value of the PDE solution of the forward evaluation, by default None
    a_bdr_val : array type, optional
        Boundary value of the coefficients, by default None
    """
    def __init__(self, domain, sol_domain, bdr_val = None, a_bdr_val = None,adj_lf=True,cu_lf=True):
        super().__init__(domain, sol_domain, linear = False)
        self.gfu_a=ngs.GridFunction(self.domain.fes)
        self.gfu_h=ngs.GridFunction(self.domain.fes)
        self.gfu_a_bdr = ngs.GridFunction(self.domain.fes)
        if a_bdr_val is not None:
            assert self.domain.bdr is not None 
            assert self.domain.is_on_boundary(a_bdr_val)
            self._read_in(a_bdr_val,self.gfu_a_bdr)
        self.gfu_deriv=ngs.GridFunction(self.codomain.fes)
        self.gfu_adj_help=ngs.GridFunction(self.codomain.fes)
        if bdr_val is not None and bdr_val in self.codomain:
            self.gfu_eval=self.codomain.to_ngs(bdr_val)
        else:
            self.gfu_eval=ngs.GridFunction(self.codomain.fes)

        self.adj_lf=adj_lf
        self.cu_lf=cu_lf
        self.u_a, self.v_a = self.domain.fes.TnT()
        self.u, self.v = self.codomain.fes.TnT()
        

    def _eval(self, a, differentiate=False, adjoint_derivative=False):
        self._read_in(a, self.gfu_a,definedonelements=self.domain.fes.FreeDofs())
        #self.gfu_a.vec.data = self.gfu_a.vec + self.gfu_a_bdr.vec
        self.bf_mat = ngs.BilinearForm(self.codomain.fes)
        self.bf_mat += self._bf(self.gfu_a,self.u,self.v) 
        if self._bf_0() is not None:
            self.bf_mat += self._bf_0()
        self.bf_mat.Assemble()
        
        self.bf_mat_inv = self.bf_mat.mat.Inverse(freedofs=self.codomain.fes.FreeDofs())
        self.gfu_eval.vec.data += self.bf_mat_inv * (self._lf().vec - self.bf_mat.mat * self.gfu_eval.vec)
        
        return self.gfu_eval.vec.FV().NumPy().copy()
    
    def _derivative(self, h):
        self._read_in(h, self.gfu_h,definedonelements=self.domain.fes.FreeDofs())
        lf = ngs.LinearForm(self.codomain.fes)
        
        lf = self._c_u(h)
        self.gfu_deriv.vec.data += self.bf_mat_inv * (-lf.vec - self.bf_mat.mat * self.gfu_deriv.vec)
        return self.gfu_deriv.vec.FV().NumPy().copy()

    def _adjoint(self, g):
        lf = ngs.LinearForm(self.codomain.fes).Assemble()
        self._read_in(g,lf)
        self.gfu_adj_help.vec.data += self.bf_mat.mat.CreateTranspose().Inverse(freedofs=self.codomain.fes.FreeDofs()) * (lf.vec - self.bf_mat.mat.CreateTranspose() * self.gfu_adj_help.vec)
        if self.adj_lf:
            lf_adj = ngs.LinearForm(self.domain.fes)
            lf_adj += -1*self._bf(self.v_a,self.gfu_eval,self.gfu_adj_help)
            lf_adj.Assemble()
            ngs.Projector(self.domain.fes.FreeDofs(), range=True).Project(lf_adj.vec)    
            return lf_adj.vec.FV().NumPy().copy()
        else:   
            grid_adj=ngs.GridFunction(self.domain.fes)
            strain1= ngs.Sym(ngs.Grad(self.gfu_eval))
    
            strain2=ngs.Sym(ngs.Grad(self.gfu_adj_help))
            grid_adj=self._eval_adj(strain1,strain2)  
            
            ngs.Projector(self.domain.fes.FreeDofs(), range=True).Project(grid_adj.vec)
            return grid_adj.vec.FV().NumPy().copy()
    
    def _bf(self,a,u,v):
        r"""Implementation of \(b_a\) as `ngsolve.comp.SumOfIntegrals` that is something similar to
        `a*ngs.grad(u)*ngs.grad(v)*ngs.dx` where `u` ist used as trial functions and `v` as test 
        functions. This method has to be implemented by 

        Parameters
        ----------
        a : ngsolve.CoefficientFunction or ngsolve.GridFunction
            the coefficient in the PDE
        u : ngsolve.comp.ProxyFunction
            Trial functions for PDE
        v : ngsolve.comp.ProxyFunction
            Test functions for PDE

        Returns
        ------
        ngsolve.comp.SumOfIntegrals
            the formal Integration formula of the bilinear form depending on the coefficient
        """
        raise NotImplementedError
    
    def _bf_0(self):
        r"""Implementation of \(b_0\) as `ngsolve.comp.SumOfIntegrals` is an optional method to be 
        overwritten with subclasses.  

        Returns
        -------
        ngsolve.comp.SumOfIntegrals
            the formal Integration formula of the bilinear form independent of the coefficient
        """
        return None
    
    def _lf(self):
        r"""The Linear form of the PDE \(F\) implemented as a fixed Linear form. Note that the 
        Linear form has to be defined on the `codomain` as this is the domain of the solution of 
        the PDE. By default this is the empty Linear form. 

        Returns
        ------
        ngsolve.Linearform
            Linear form of the PDE \(F\) as `ngsolve.LinearForm`
        """
        return ngs.LinearForm(self.codomain.fes).Assemble()
        
    def _c_u(self,h,c_u_help=False):
        self._read_in(h, self.gfu_h,definedonelements=self.domain.fes.FreeDofs())
        lf = ngs.LinearForm(self.codomain.fes)
        lf += self._bf(self.gfu_h,self.gfu_eval,self.v)
        return lf.Assemble()

    def _eval_adj(self,u,v):
        return None

Provides a general setup for the forward problems mapping PDE coefficients to their solutions. That is we assume that as variational formulation one can use \forall v: b_0(u,v) + b_a(u,v) = F(v) with b_0 a bilinear form independent of the coefficient a and F some linear form.
Furthermore b_a the bilinear form that depends on a has to be linear in a so that one can define a bilinear form c_u(a,v)=b_a(u,v). More over we may assume some Dirichlet boundary conditions on u.That is F: a \mapsto u

The Frechét derivative F'[a]h in direction h is then given as the variational solution u' to \forall v: b_0(u',v) + b_a(u',v) = -c_u(h,v) with u=F(a). That is F'[a]: h \mapsto u'

It's adjoint F'[a]^\ast g is given as the linear form -c_u(\cdot,w) for u=F(a) and w solving the problem \forall v: b_0(v,w) + b_a(v,w) = <g,v>. That is F'[a]^\ast: g \mapsto -c_u(\cdot,w)

Notes

A subclass implemented by a user has to at least implement the subroutine _bf which is the implementation of the bilinear form depending on the coefficient a. Optional can the independent bilinear form b_0 be implemented as formal integrator in _bf_0 and the linear form can be implemented in _lf.

Parameters

domain : NgsSpace
The NgsSpace on which the coefficients defined are.
sol_domain : NgsSpace
The NgsSpace on which the PDE solutions defined are.
bdr_val : array type, optional
Boundary value of the PDE solution of the forward evaluation, by default None
a_bdr_val : array type, optional
Boundary value of the coefficients, by default None

Ancestors

Inherited members

class ElasticAnisotrop (domain, sol_domain, rhs, bdr_val=None, bdr_N=None, bdr_N_val=None)
Expand source code
class ElasticAnisotrop(NGSolveOperator):
    r'''Operator for solving the general elasticity problem with 4th order material tensor
    tensor is symmetrized, but corresponding FE-space has dimension d x d x d x d.
    '''
    def __init__(self, domain, sol_domain, rhs, bdr_val = None, bdr_N=None, bdr_N_val=None):
        super().__init__(domain, sol_domain, linear = False)
        
        #gridfunctions in domain of material tensor
        #gridfunction for material tensor
        self.gfu_a=ngs.GridFunction(self.domain.fes)
        #gridfunction for h-value in derivative
        self.gfu_h=ngs.GridFunction(self.domain.fes)
        #gridfunction for adjoint
        self.gfu_adj=ngs.GridFunction(self.domain.fes)
        
        #dimension of the finite element mesh
        self.dim=self.codomain.fes.mesh.dim
        
        #gridfunctions in codomain of finite eleament solutions 
        #derivative
        self.gfu_deriv=ngs.GridFunction(self.codomain.fes)
        #solution of adjoint FE problem for adjoint of derivative
        self.gfu_adj_help=ngs.GridFunction(self.codomain.fes)
        #rhs of adjoint FE-problem
        self.gfu_adj_rhs=ngs.GridFunction(self.codomain.fes)
        #Neumann boundary conditions
        self.gfu_neumann=ngs.GridFunction(self.codomain.fes)
        #solution of forward problem
        self.gfu_eval=ngs.GridFunction(self.codomain.fes)
       
       #Dirichlet boundary conditions 
        if bdr_val is not None and bdr_val in self.codomain:            
            self.gfu_eval.Set(bdr_val)
        else:    
            if self.dim==2:
                self.gfu_eval.Set((0,0))
            elif self.dim==3:    
                self.gfu_eval.Set((0,0,0))
            else:
                raise NotImplementedError    

        #Neumann boundary conditions
        self.bdr_N=bdr_N
        if self.bdr_N is not None and bdr_N_val is not None:
            #assert bdr_N_val in self.codomain 
            self.gfu_neumann.Set(bdr_N_val)   
        
        #right hand side of forward problem
        self.rhs=rhs

        #Dirichlet boundary conditions for derivative and adjoint
        if self.dim==2:
            self.gfu_deriv.Set((0,0))
            self.gfu_adj_help.Set((0,0))
        elif self.dim==3:    
            self.gfu_deriv.Set((0,0,0))
            self.gfu_adj_help.Set((0,0,0))
        else:
            raise NotImplementedError   
        
        #test and trial functions
        self.u, self.v = self.codomain.fes.TnT()
        
        

    def _eval(self, a, differentiate=False, adjoint_derivative=False):
        
        #a: fourth order tensor in terms of flat numpy array
        self._read_in(a, self.gfu_a,definedonelements=self.domain.fes.FreeDofs())
        
        self.bf_mat = ngs.BilinearForm(self.codomain.fes)
        self.prec=ngs.Preconditioner(self.bf_mat, "local")
        
        #use matrix-vector multiplication for tensor contraction
        dim2=self.dim*self.dim
       
        a_mat=ngs.CF(self.gfu_a,dims=(dim2,dim2))
        
        u_vec=ngs.CF(ngs.Sym(ngs.Grad(self.u)),dims=(dim2,))
        v_vec=ngs.CF(ngs.Sym(ngs.Grad(self.v)),dims=(dim2,))
        sigu=a_mat*u_vec
        sigv=a_mat*v_vec
       
        
        #Bilinear form to be used in other methods
        self.bf_mat += (ngs.InnerProduct(sigu,ngs.Sym(ngs.Grad(self.v)))+ngs.InnerProduct(sigv,ngs.Sym(ngs.Grad(self.u))))*0.5 * ngs.dx
       
        with ngs.TaskManager():
            self.bf_mat.Assemble()            
        #Linear form
        lf = ngs.LinearForm(self.codomain.fes)
        lf += ngs.InnerProduct(self.rhs, self.v)* ngs.dx
        if self.bdr_N is not None:
             lf += ngs. InnerProduct(self.gfu_neumann,self.v)* ngs.ds(self.bdr_N)
        
        with ngs.TaskManager():
            lf.Assemble()

        #Solution of bdr problem, inverse  of bilinear form will be used in other methods
        self.bf_mat_inv = self.bf_mat.mat.Inverse(freedofs=self.codomain.fes.FreeDofs())
        self.gfu_eval.vec.data += self.bf_mat_inv * (lf.vec - self.bf_mat.mat * self.gfu_eval.vec)
        
        #result of application of forward operator
        return self.gfu_eval.vec.FV().NumPy().copy()
    
    def _derivative(self, h):

        #h: fourth order tensor in terms of flat numpy array
        self._read_in(h, self.gfu_h,definedonelements=self.domain.fes.FreeDofs())
        lf = ngs.LinearForm(self.codomain.fes)
        
        #use matrix-vector multiplication for tensor contraction
        dim2=self.dim*self.dim
        a_mat=ngs.CF(self.gfu_h,dims=(dim2,dim2))
        
        u_vec=ngs.CF(ngs.Sym(ngs.Grad(self.gfu_eval)),dims=(dim2,))
        v_vec=ngs.CF(ngs.Sym(ngs.Grad(self.v)),dims=(dim2,))
        sigu=a_mat*u_vec
        sigv=a_mat*v_vec
        
        #rhs for pde determining Frechet derivative 
        lf +=(ngs.InnerProduct(sigu,ngs.Sym(ngs.Grad(self.v)))+ngs.InnerProduct(sigv,ngs.Sym(ngs.Grad(self.gfu_eval))))*0.5 * ngs.dx
        lf.Assemble()
       
        #solution of pde for frechet derivative
        self.gfu_deriv.vec.data += self.bf_mat_inv * (-lf.vec - self.bf_mat.mat * self.gfu_deriv.vec)
        
        return self.gfu_deriv.vec.FV().NumPy().copy()

    def _adjoint(self, g):
        #solve adjoint forward PDE
        lf = ngs.LinearForm(self.codomain.fes)
        self._read_in(g,self.gfu_adj_rhs)
        
        lf +=ngs.InnerProduct(self.gfu_adj_rhs, self.v) * ngs.dx
        lf.Assemble()
        self.gfu_adj_help.vec.data += self.bf_mat.mat.CreateTranspose().Inverse(freedofs=self.codomain.fes.FreeDofs()) * (lf.vec - self.bf_mat.mat.CreateTranspose() * self.gfu_adj_help.vec)
        
         
        gfu_adj=ngs.GridFunction(self.domain.fes)
        
        #adjoint is symmetrized dyadic product
        strain1= ngs.Sym(ngs.Grad(self.gfu_eval))
        strain2=ngs.Sym(ngs.Grad(self.gfu_adj_help))
        
        list_adj=[]
        dim2=self.dim*self.dim
        for i in range(dim2):
            
            for j in range (dim2):
                sijkl=-0.5*(strain1[i]*strain2[j]+strain2[i]*strain1[j])
                list_adj.append(sijkl)
       
        #direct transfer to gfu_adj does not work because sijkl are gridfunctions
        help1=ngs.CF(tuple(list_adj))
        gfu_adj.Set(help1)
        ngs.Projector(self.domain.fes.FreeDofs(), range=True).Project(gfu_adj.vec)
        
        return gfu_adj.vec.FV().NumPy().copy()

Operator for solving the general elasticity problem with 4th order material tensor tensor is symmetrized, but corresponding FE-space has dimension d x d x d x d.

Ancestors

Inherited members

class MapIsotropic (domain, codomain, nu=0.3)
Expand source code
class MapIsotropic(NGSolveOperator):
    r"""maps physical input parameters to subspace in material tensor space
    isotropic elasticity
    """
    def __init__(self, domain, codomain,nu=0.3 ):
        super().__init__(domain = domain, codomain = codomain, linear = True)
        #material parameters        
        self.gfu_a=ngs.GridFunction(self.domain.fes)
        #4th order material tensor 
        self.gfu_tens=ngs.GridFunction(self.codomain.fes)
        #container for adjoint
        self.gfu_map=ngs.GridFunction(self.codomain.fes)
        
        #number of free parameters
        self.dim_domain=1
        
        #material tensor has 16 components in 2D case and 81 in 3D        
        self.dim=self.codomain.fes.mesh.dim
        self.dim_codomain=self.dim**4
        
        self.nu=nu
        
        #container for material data
        self.list_mat=[]
        for i in range(self.dim_codomain):
            self.list_mat.append(ngs.CF(0))
        #assign generalized Lame constants to material tensor               
        if self.dim_codomain==16:
            self.list_lam1=[0,15]
            self.list_lam4=[3,12]
            self.list_mu=[5,6,9,10]
        else:
            self.list_lam1=[0,40,80]
            self.list_lam4=[4,8,36,44,72,76]
            self.list_mu=[10,12,20,24,28,30,50,52,56,60,68,70]
    
    def _eval(self,argument):    
        self._read_in(argument, self.gfu_a)
        
        #elastic constants
        E=self.gfu_a
        lam1=E*(1-self.nu)/((1+self.nu)*(1-2*self.nu)) 
        lam4=E*self.nu/((1+self.nu)*(1-2*self.nu)) 
        mu=E/(2*(1+self.nu)) 
        
        #definition of material tensor
        for i in self.list_lam1:
            self.list_mat[i]=lam1
            
        for i in self.list_lam4:
            self.list_mat[i]=lam4
        
        for i in self.list_mu:
            self.list_mat[i]=mu
        #convert to gridfunction for further use
        self.gfu_tens.Set(tuple(self.list_mat))       
        #flat numpy-array from gridfunction
        return self.gfu_tens.vec.FV().NumPy().copy()
    
    def _adjoint(self,argument):
        self._read_in(argument, self.gfu_map)
        #material tensor/E
        gfu_help=ngs.GridFunction(self.codomain.fes)
        help1=self._eval(self.domain.ones())
        self._read_in(help1, gfu_help)
        #adjoint
        help2=ngs.InnerProduct(self.gfu_map,gfu_help)
        self.gfu_a.Set(help2)
        return self.gfu_a.vec.FV().NumPy().copy()

maps physical input parameters to subspace in material tensor space isotropic elasticity

Ancestors

Inherited members

class MapCubic (domain, codomain, nu=0.3)
Expand source code
class MapCubic(NGSolveOperator):
    r"""projects physical input parameters to subspace in material tensor space
    cubic elasticity
    """
    def __init__(self, domain, codomain,nu=0.3 ):
        super().__init__(domain = domain, codomain = codomain, linear = True)
        #grid function for physical parameters        
        self.gfu_a=ngs.GridFunction(self.domain.fes)
        #grid function for material tensor
        self.gfu_tens=ngs.GridFunction(self.codomain.fes)
        #grid function for input of adjoint
        self.gfu_map=ngs.GridFunction(self.codomain.fes)
        
        self.nu=nu
        #material tensor has 16 components in 2D case and 81 in 3D
       
        self.dim=self.codomain.fes.mesh.dim
        self.dim_codomain=self.dim**4
        
        #number of free parameters
        self.dim_domain = 2        
                
        #container for material data
        self.list_mat=[]
        for i in range(self.dim_codomain):
            self.list_mat.append(ngs.CF(0))
        #assign generalized Lame constants to material tensor               
        if self.dim_codomain==16:
            self.list_lam1=[0,15]
            self.list_lam4=[3,12]
            self.list_mu=[5,6,9,10]
        else:
            self.list_lam1=[0,40,80]
            self.list_lam4=[4,8,36,44,72,76]
            self.list_mu=[10,12,20,24,28,30,50,52,56,60,68,70]
        #parameter specitic components of material tensor
        help1=self.codomain.to_ngs(self._eval(self.domain.ones()))                  
        self.help_E,self.help_mu=self._split_map(help1)

    def _split_map(self,help1):
     #split projector
        map_E=[]
        map_mu=[]
        for i in range(self.dim_codomain):
            
            if i in self.list_mu:
                
                map_mu.append(help1.components[i]) 
                map_E.append(ngs.CF(0))
            elif i in self.list_lam1 or i in self.list_lam4:
                
                map_E.append(help1.components[i]) 
                map_mu.append(ngs.CF(0))
                
            else:
                
                map_E.append(ngs.CF(0))
                map_mu.append(ngs.CF(0))                
        help_E=ngs.GridFunction(self.codomain.fes)
        help_mu=ngs.GridFunction(self.codomain.fes)
        help_E.Set(tuple(map_E))
        help_mu.Set(tuple(map_mu)) 
        return help_E, help_mu
        
    def _eval(self,argument):    
        self._read_in(argument, self.gfu_a)
        
        #elastic constants
                
        self._read_in(argument, self.gfu_a)
        
        #elastic constants
        E=self.gfu_a.components[0]
        mu=self.gfu_a.components[1]
        lam1=E*(1-self.nu)/((1+self.nu)*(1-2*self.nu)) 
        lam4=E*self.nu/((1+self.nu)*(1-2*self.nu)) 
        #mu=E/(2*(1+self.nu)) 
        
        #definition of material tensor
        for i in self.list_lam1:
            self.list_mat[i]=lam1
            
        for i in self.list_lam4:
            self.list_mat[i]=lam4
        
        for i in self.list_mu:
            self.list_mat[i]=mu
        #convert to gridfunction for further use
        self.gfu_tens.Set(tuple(self.list_mat))
        #flat numpy-array from gridfunction
        return self.gfu_tens.vec.FV().NumPy().copy()
    
    def _adjoint(self,argument):
        self._read_in(argument, self.gfu_map)       
                
        adj_E=ngs.InnerProduct(self.gfu_map,self.help_E)
        adj_mu=ngs.InnerProduct(self.gfu_map, self.help_mu)
        help2=(adj_E,adj_mu)
        
        self.gfu_a.Set(help2)
        return self.gfu_a.vec.FV().NumPy().copy()

projects physical input parameters to subspace in material tensor space cubic elasticity

Ancestors

Inherited members

class MapTransverse (domain, codomain, nu_A=0.2, nu_T=0.3, scale=0.3)
Expand source code
class MapTransverse(NGSolveOperator):
    r"""maps physical input parameters to subspace in material tensor space
    transverse isotropic elastice with isotropic plane 23
    """
    def __init__(self, domain, codomain,nu_A=0.2,nu_T=0.3,scale=0.3):
        super().__init__(domain = domain, codomain = codomain, linear = True)
               
        self.gfu_a=ngs.GridFunction(self.domain.fes)
        self.gfu_tens=ngs.GridFunction(self.codomain.fes)
        self.gfu_map=ngs.GridFunction(self.codomain.fes)
        
        self.nu_T=nu_T
        self.nu_A=nu_A
        self.scale=scale
        #material tensor      
        self.dim=self.codomain.fes.mesh.dim
        self.dim_codomain=self.dim**4
       
        #number of free parameters
        self.dim_domain = len(self.domain.fes.components)          
        
        
        #contaimer for material data
        self.list_mat=[]
        for i in range(self.dim_codomain):
            self.list_mat.append(ngs.CF(0))
            
        #assigns generalized Lame constants to material tensor
        if self.dim_codomain==16:
            
            self.list_lam4=[3,12]
            self.list_mu2=[5,6,9,10]

            #need dummy lists for adjoint
            self.list_lam6=[]
            self.list_lam2=[15]

        if self.dim_codomain==81:
            self.list_lam2=[40,80]
            self.list_lam4=[4,8,36,72]
            self.list_lam6=[44,76]
            self.list_mu1=[50,52,68,70]
            self.list_mu2=[10,12,20,24,28,30,56,60]

        help1=self.codomain.to_ngs(self._eval(self.domain.ones()))
        self.help_EA,self.help_ET,self.help_mu2=self._split_map(help1)  

    def _split_map(self,help1):
     #split material operator
        map_EA=[help1.components[0]]
        map_ET=[0]
        map_mu2=[0]
        for i in range(1,self.dim_codomain):
            
            if i in self.list_mu2:
                
                map_mu2.append(help1.components[i]) 
                map_ET.append(ngs.CF(0))
                map_EA.append(ngs.CF(0))
                
            elif self.dim==2 and (i in self.list_lam2 or i in self.list_lam4 or i in self.list_lam6): 
                map_mu2.append(ngs.CF(0))
                map_ET.append(help1.components[i])                 
                map_EA.append(ngs.CF(0))
            
            elif self.dim==3 and (i in self.list_lam2 or i in self.list_lam4 or i in self.list_lam6 or i in self.list_mu1):
                map_mu2.append(ngs.CF(0))
                map_ET.append(help1.components[i])                 
                map_EA.append(ngs.CF(0))
                
                
            else:
                map_mu2.append(ngs.CF(0)) 
                map_ET.append(ngs.CF(0))
                map_EA.append(ngs.CF(0))  
               
        help_ET=ngs.GridFunction(self.codomain.fes)
        help_EA=ngs.GridFunction(self.codomain.fes)
        help_mu2=ngs.GridFunction(self.codomain.fes)
        help_ET.Set(tuple(map_ET))
        help_EA.Set(tuple(map_EA))
        help_mu2.Set(tuple(map_mu2)) 
        return help_EA, help_ET, help_mu2
    
    def _eval(self,argument):    
        self._read_in(argument, self.gfu_a)
        #scaling factor scale is approximately the ratio between E_A and E_T times nu_A. This has to be fixed in order to obtain
        #a PDE which is linear in the material constants

        #elastic constants        
        E_A=self.gfu_a.components[0]
        E_T=self.gfu_a.components[1]
        
        D=(1-self.nu_T-2*self.scale*self.nu_A)*(1+self.nu_T)
        lam1=(1-self.nu_T**2)*E_A/D
        lam2=(1-self.nu_A*self.scale)*E_T/D
        lam4=(self.nu_A+self.nu_A*self.nu_T)*E_T/D
        lam6=(self.nu_T+self.nu_A*self.scale)*E_T/D
        self.list_mat[0]=lam1
        if self.dim==2:            
            mu2=0.5*E_A/(1+self.nu_T)
            
            self.list_mat[15]=lam2            
        if self.dim==3:            
            mu1=0.5*E_T/(1+self.nu_T)
            mu2=self.gfu_a.components[2] 
            for i in self.list_lam6:
                self.list_mat[i]=lam6
            for i in self.list_mu1:
                self.list_mat[i]=mu1
            for i in self.list_lam2:
                self.list_mat[i]=lam2    
            #definition of material tensor
        self.list_mat[0]=lam1
        
        for i in self.list_lam4:
            self.list_mat[i]=lam4             
        for i in self.list_mu2:
            self.list_mat[i]=mu2
    
        #convert to gridfunction for further use
        self.gfu_tens.Set(tuple(self.list_mat))
        #flat numpy-array from gridfunction
        return self.gfu_tens.vec.FV().NumPy().copy()
    
    def _adjoint(self,argument):
        self._read_in(argument, self.gfu_map)       
        
        adj_ET=ngs.InnerProduct(self.gfu_map,self.help_ET)
        if self.dim==2:
            adj_EA=ngs.InnerProduct(self.gfu_map,self.help_EA+self.help_mu2)
            help2=(adj_EA,adj_ET)
        if self.dim==3:
            adj_EA=ngs.InnerProduct(self.gfu_map,self.help_EA)
            adj_mu2=ngs.InnerProduct(self.gfu_map, self.help_mu2)
            help2=(adj_EA,adj_ET,adj_mu2)
       
        self.gfu_a.Set(help2)
        return self.gfu_a.vec.FV().NumPy().copy()

maps physical input parameters to subspace in material tensor space transverse isotropic elastice with isotropic plane 23

Ancestors

Inherited members

class ProjectToBoundary (domain, codomain=None)
Expand source code
class ProjectToBoundary(NGSolveOperator):

    def __init__(self, domain, codomain=None):
        codomain = codomain or domain
        super().__init__(domain, codomain)
        self.linear=True
        self.bdr = codomain.bdr
        self.gfu_codomain = ngs.GridFunction(self.codomain.fes)
        self.gfu_domain = ngs.GridFunction(self.domain.fes)
        try: 
            self.nr_bc = len(self.codomain.summands)
        except:
            self.nr_bc = 1

    def _eval(self, x):
        if self.nr_bc == 1:
            array = [x]
        else: 
            array = self.domain.split(x)
        toret = []
        for i in range(self.nr_bc):
            self.gfu_domain.vec.FV().NumPy()[:] = array[i]
            self.gfu_codomain.Set(self.gfu_domain, definedon=self.codomain.fes.mesh.Boundaries(self.bdr))
            toret.append(self.gfu_codomain.vec.FV().NumPy().copy())
        return np.array(toret).flatten()

    def _adjoint(self, g):
        toret = []
        if self.nr_bc == 1:
            g_tuple = [g]
        else: 
            g_tuple = self.codomain.split(g)
        for i in range(self.nr_bc):
            self.gfu_codomain.vec.FV().NumPy()[:] = g_tuple[i]
            self.gfu_domain.Set(self.gfu_codomain, definedon=self.codomain.fes.mesh.Boundaries(self.bdr))
            toret.append(self.gfu_domain.vec.FV().NumPy().copy())
        return np.array(toret).flatten()

Ancestors

Inherited members

class EIT (domain, g, codomain=None, alpha=0.01)
Expand source code
class EIT(NGSolveOperator):
    r"""Electrical Impedance Tomography Problem
    
    PDE:
    \[
    -\textrm{div}(s \nabla u)+\alpha u=0 \;\text{ in } \Omega
    \]
    \[
         s \frac{\textrm{d}u}{\textrm{d}n} = g \;\text{ on } \partial\Omega
    \]
    Evaluate: \(F\colon s \mapsto \mathrm{tr}(u)\)
    Derivative:
    \[
    -\textrm{div}(s \nabla v)+\alpha v=\textrm{div}(h \nabla u) (=:f) 
    \]
    \[
         s \frac{\textrm{d}v}{\textrm{d}n} = 0 +(-h\frac{\textrm{d}u}{\textrm{d}n} \;\text{ [second term often omitted] } 
    \]

    Der: \(F'[s]\colon h \mapsto \textrm{tr}(v)\)

    Adjoint:
    \[
    -\textrm{div}(s \nabla w)+\alpha w=0 
    \]
    \[
         s \frac{\textrm{d}w}{\textrm{d}n} = q 
    \]
    
    Adj: \(F'[s]^*\colon q \mapsto -\nabla(u) \nabla(w)\)

    Proof:
    \[(F'h, q)=\int_{\partial\Omega} [\textrm{tr}(v) q] \]
    \[= \int_{\partial\Omega} [\textrm{tr}(v) s \frac{\textrm{d}w}{\textrm{d}n}] \] 
    \[= \int_{\Omega} [\textrm{div}(v s \nabla w )]\]
    Note \(\textrm{div}(s \nabla w) = \alpha*w\), thus above equation shows:
    \[(F'h, q) = (s \nabla v, \nabla w)+\alpha (v, w) \]
    \[= \int_\Omega [\textrm{div}( s \nabla v w)] +(-\textrm{div} (s \nabla v)), w)+\alpha (v, w)\]
    \[= \int_{\partial\Omega} [s dv/dn \textrm{tr}(w)]+(f, w)\]
    \[= (f, w)-\int_{\partial\Omega} [\textrm{tr}(w) h \frac{\textrm{d}u}{\textrm{d}n}]\]
    \[= (h, -\nabla u \nabla w) + \int_\Omega [\textrm{div}(h \nabla u w)]-\int_{\partial\Omega} [\textrm{tr}(w) h \frac{\textrm{d}u}{\textrm{d}n}] \]
    The last two terms are the same! It follows: \((F'h, q) = (h, -\nabla u \nabla w)\). Hence:
    Adjoint: \(q \mapsto -\nabla u \nabla w\)
    """

    def __init__(self, domain, g, codomain=None, alpha=0.01):
        codomain = codomain or domain
        #Need to know the boundary to calculate Neumann bdr condition
        assert codomain.bdr is not None
        super().__init__(domain, codomain)
        self.g = g
        self.nr_bc = len(self.g)

        self.fes_domain = domain.fes
        self.fes_codomain = codomain.fes

        #FES and Grid Function for reading in values
        self.fes_in = ngs.H1(self.fes_codomain.mesh, order=1)
        self.gfu_in = ngs.GridFunction(self.fes_in)

        # grid functions for later use
        self.gfu_eval = ngs.GridFunction(self.fes_codomain)  # solution, return value of _eval
        self.gfu_deriv = ngs.GridFunction(self.fes_codomain)  # grid function return value of derivative
        self.gfu_adjoint = ngs.GridFunction(self.fes_domain) #grid function return value of adjoint
        
        self.gfu_bf = ngs.GridFunction(self.fes_codomain) # grid function for defining integrator (bilinearform)
        self.gfu_lf = ngs.GridFunction(self.fes_codomain)  # grid function for defining right hand side (linearform), f
        self.gfu_b = ngs.GridFunction(self.fes_codomain)

        self.gfu_inner_adjoint = ngs.GridFunction(self.fes_codomain)  # grid function for inner computations in adjoint

        self.Number = ngs.NumberSpace(self.fes_codomain.mesh)
        #r, s = self.Number.TnT()

        u, v = self.fes_codomain.TnT()

        # Define Bilinearform, will be assembled later
        self.a = ngs.BilinearForm(self.fes_codomain, symmetric=True)
        self.a += (ngs.grad(u) * ngs.grad(v) * self.gfu_bf+alpha*u*v) * ngs.dx

        #Additional condition: The integral along the boundary vanishes
        #self.a += ngs.SymbolicBFI(u * s + v * r, definedon=self.fes_codomain.mesh.Boundaries("cyc"))
        #self.fes1 = ngs.H1(self.fes_codomain.mesh, order=4, definedon=self.fes_codomain.mesh.Boundaries("cyc"))

        # Define Linearform for evaluation, will be assembled later       
        self.b = ngs.LinearForm(self.fes_codomain)
        self.b += self.gfu_b*v*ngs.ds(codomain.bdr)

        # Define Linearform for derivative, will be assembled later
        self.f_deriv = ngs.LinearForm(self.fes_codomain)
        self.f_deriv += -self.gfu_lf * ngs.grad(self.gfu_eval) * ngs.grad(v) * ngs.dx

        # Initialize preconditioner for solving the Dirichlet problems by ngs.solvers.BVP
        self.prec = ngs.Preconditioner(self.a, 'direct')
    #Weak formulation:
    #0=int_Omega [-div(s grad u) v + alpha u v]=-int_dOmega [s du/dn trace(v)]+int_Omega [s grad u grad v + alpha u v]
    #Hence: int_Omega [s grad u grad v + alpha u v] = int_dOmega [g trace(v)]
    #Left term: Bilinearform self.a
    #Righ term: Linearform self.b
    def _eval(self, diff, differentiate=False, adjoint_derivative=False):
        # Assemble Bilinearform
        self._read_in(diff, self.gfu_bf)
        self.a.Assemble()

        # Assemble Linearform, boundary term
        toret = []
        for i in range(self.nr_bc):
            self.gfu_b.Set(self.g[i])
            self.b.Assemble()

        # Solve system
            if i == 0:
                self._solve_dirichlet_problem(bf=self.a, lf=self.b, gf=self.gfu_eval, prec=self.prec)
            else: 
                self._solve_dirichlet_problem(bf=self.a, lf=self.b, gf=self.gfu_eval, prec=self.prec)

            toret.append(self.gfu_eval.vec.FV().NumPy()[:].copy())

        return np.array(toret).flatten()

#Weak Formulation:
#0 = int_Omega [-div(s grad v) w + alpha v w]-int_Omega [div (h grad u) w]
#=-int_dOmega [s dv/dn trace(w)] + int_Omega [s grad v grad w + alpha v w]-int_dOmega [h du/dn trace(w)]+int_Omega [h grad u grad w]
#=int_Omega [s grad v grad w + alpha v w]+int_Omega [h grad u grad w]
#Hence: int_Omega [s grad v grad w + alpha v w] = int_Omega [-h grad u grad w]
#Left Term: Bilinearform self.a, already defined in _eval
#Right Term: Linearform f_deriv
    def _derivative(self, h, **kwargs):
        # Bilinearform already defined from _eval

        # Assemble Linearform
        toret = []
        for i in range(self.nr_bc):
            self._read_in(h[i], self.gfu_lf)
            self.f_deriv.Assemble()

            self.gfu_deriv.Set(0)
            self._solve_dirichlet_problem(bf=self.a, lf=self.f_deriv, gf=self.gfu_deriv, prec=self.prec)

            toret.append(self.gfu_deriv.vec.FV().NumPy()[:].copy())

        return np.array(toret).flatten()

#Same problem as in _eval
    def _adjoint(self, argument):
        # Bilinearform already defined from _eval

        # Definition of Linearform
        # But it only needs to be defined on boundary
        if self.nr_bc==1:
            argument_tuple = [argument]
        else:
            argument_tuple = self.codomain.split(argument)
        toret = np.zeros(np.size(self.gfu_adjoint.vec.FV().NumPy()))
        for i in range(self.nr_bc):
            self.gfu_b.vec.FV().NumPy()[:] = argument_tuple[i]
            self.b.Assemble()

            self._solve_dirichlet_problem(bf=self.a, lf=self.b, gf=self.gfu_inner_adjoint, prec=self.prec)

            self.gfu_adjoint.Set(-ngs.grad(self.gfu_inner_adjoint) * ngs.grad(self.gfu_eval))

            toret += self.gfu_adjoint.vec.FV().NumPy().copy()

        return toret

Electrical Impedance Tomography Problem

PDE: -\textrm{div}(s \nabla u)+\alpha u=0 \;\text{ in } \Omega s \frac{\textrm{d}u}{\textrm{d}n} = g \;\text{ on } \partial\Omega Evaluate: F\colon s \mapsto \mathrm{tr}(u) Derivative: -\textrm{div}(s \nabla v)+\alpha v=\textrm{div}(h \nabla u) (=:f) s \frac{\textrm{d}v}{\textrm{d}n} = 0 +(-h\frac{\textrm{d}u}{\textrm{d}n} \;\text{ [second term often omitted] }

Der: F'[s]\colon h \mapsto \textrm{tr}(v)

Adjoint: -\textrm{div}(s \nabla w)+\alpha w=0 s \frac{\textrm{d}w}{\textrm{d}n} = q

Adj: F'[s]^*\colon q \mapsto -\nabla(u) \nabla(w)

Proof: (F'h, q)=\int_{\partial\Omega} [\textrm{tr}(v) q] = \int_{\partial\Omega} [\textrm{tr}(v) s \frac{\textrm{d}w}{\textrm{d}n}] = \int_{\Omega} [\textrm{div}(v s \nabla w )] Note \textrm{div}(s \nabla w) = \alpha*w, thus above equation shows: (F'h, q) = (s \nabla v, \nabla w)+\alpha (v, w) = \int_\Omega [\textrm{div}( s \nabla v w)] +(-\textrm{div} (s \nabla v)), w)+\alpha (v, w) = \int_{\partial\Omega} [s dv/dn \textrm{tr}(w)]+(f, w) = (f, w)-\int_{\partial\Omega} [\textrm{tr}(w) h \frac{\textrm{d}u}{\textrm{d}n}] = (h, -\nabla u \nabla w) + \int_\Omega [\textrm{div}(h \nabla u w)]-\int_{\partial\Omega} [\textrm{tr}(w) h \frac{\textrm{d}u}{\textrm{d}n}] The last two terms are the same! It follows: (F'h, q) = (h, -\nabla u \nabla w). Hence: Adjoint: q \mapsto -\nabla u \nabla w

Ancestors

Inherited members

class ReactionNeumann (domain, g, codomain=None)
Expand source code
class ReactionNeumann(NGSolveOperator):
    r"""
    Estimation of the reaction coefficient from boundary value measurements

    PDE: -div(grad(u)) + s*u = 0 in Omega
         du/dn = g on dOmega

    Evaluate: F: s \mapsto trace(u)
    Derivative:
        -div(grad(v))+s*v = -h*u (=:f)
        dv/dn = 0 

    Der: F'[s]: h \mapsto trace(v)

    Adjoint: 
        -div(grad(w))+s*w = 0
        dw/dn = q
    Adj: F'[s]^*: q \mapsto -u*w

    proof:
    (F'h, q) = int_dOmega [trace(v) q] = int_dOmega [trace(v) dw/dn] = int_Omega [div(v grad w)] 
    = int_Omega [grad v grad w] + int_Omega [v div( grad w)] = int_Omega [div(w grad v)] - int_Omega [div(grad v) w] + int_Omega [v div (grad w)]
    = int_dOmega [trace(w) dv/dn] - int_Omega [h u w] - int_Omega [s v w] + int_Omega [v s w]
    Note that dv/dn=0 on dOmega. Hence:
    (F'h, q) = -int_Omega[h u w] = (h, -u w)
    """
    def __init__(self, domain, g, codomain=None):
        codomain = codomain or domain
        #Need to know the boundary to calculate Neumann bdr condition
        assert codomain.bdr is not None
        super().__init__(domain, codomain)
        self.g = g
        self.nr_bc = len(self.g)

        self.fes_domain = domain.fes
        self.fes_codomain = codomain.fes

        # grid functions for later use
        self.gfu_eval = ngs.GridFunction(self.fes_codomain)  # solution, return value of _eval
        self.gfu_deriv = ngs.GridFunction(self.fes_codomain)  # grid function: return value of derivative
        self.gfu_adjoint = ngs.GridFunction(self.fes_domain)  # grid function: return value of adjoint

        self.gfu_bf = ngs.GridFunction(self.fes_codomain)  # grid function for defining integrator of bilinearform
        self.gfu_lf = ngs.GridFunction(self.fes_domain) # grid function for defining linearform
        self.gfu_b = ngs.GridFunction(self.fes_codomain)  # grid function for defining the boundary term

        self.gfu_inner_adjoint = ngs.GridFunction(self.fes_codomain)  # grid function for inner computation in adjoint

        #Test and Trial Function
        u, v = self.fes_codomain.TnT()

        # Define Bilinearform, will be assembled later
        self.a = ngs.BilinearForm(self.fes_codomain, symmetric=True)
        self.a += (ngs.grad(u) * ngs.grad(v) + u * v * self.gfu_bf) * ngs.dx

        # Boundary term
        self.b = ngs.LinearForm(self.fes_codomain)
        self.b += -self.gfu_b * v.Trace() * ngs.ds(codomain.bdr)

        # Linearform (only appears in derivative)
        self.f_deriv = ngs.LinearForm(self.fes_codomain)
        self.f_deriv += -self.gfu_lf * self.gfu_eval * v * ngs.dx

        # Initialize preconditioner for solving the Dirichlet problems by ngs.solvers.BVP
        self.prec = ngs.Preconditioner(self.a, 'direct')


    def _eval(self, diff, differentiate=False, adjoint_derivative=False):
        # Assemble Bilinearform
        self._read_in(diff, self.gfu_bf)
        self.a.Assemble()

        # Assemble Linearform of boundary term
        toret = []
        for i in range(self.nr_bc):
            self.gfu_b.Set(self.g[i])
            self.b.Assemble()

        # Solve system
            if i == 0:
                self._solve_dirichlet_problem(bf=self.a, lf=self.b, gf=self.gfu_eval, prec=self.prec)
            else:
                self._solve_dirichlet_problem(bf=self.a, lf=self.b, gf=self.gfu_eval, prec=self.prec)

            toret.append(self.gfu_eval.vec.FV().NumPy()[:].copy())

        return np.array(toret).flatten()

    def _derivative(self, h):
        # Bilinearform already defined from _eval

        # Assemble Linearform of derivative
        toret = []
        for i in range(self.nr_bc):
            self._read_in(h, self.gfu_lf)
            self.f_deriv.Assemble()

            # Solve system
            self._solve_dirichlet_problem(bf=self.a, lf=self.f_deriv, gf=self.gfu_deriv, prec=self.prec)

            toret.append(self.gfu_deriv.vec.FV().NumPy()[:].copy())

        return np.array(toret).flatten()

    def _adjoint(self, argument):
        # Bilinearform already defined from _eval

        # Definition of Linearform
        # But it only needs to be defined on boundary
        if self.nr_bc==1:
            argument_tuple = [argument]
        else:
            argument_tuple = self.codomain.split(argument)
        toret = np.zeros(np.size(self.gfu_adjoint.vec.FV().NumPy()))
        for i in range(self.nr_bc):
            self.gfu_b.vec.FV().NumPy()[:] = argument_tuple[i]
            self.b.Assemble()

        # Solve system
            self._solve_dirichlet_problem(bf=self.a, lf=self.b, gf=self.gfu_inner_adjoint, prec=self.prec)

            self.gfu_adjoint.Set(self.gfu_inner_adjoint * self.gfu_eval)
        
            toret+=self.gfu_adjoint.vec.FV().NumPy().copy()

        return toret

Estimation of the reaction coefficient from boundary value measurements

PDE: -div(grad(u)) + s*u = 0 in Omega du/dn = g on dOmega

Evaluate: F: s \mapsto trace(u)

Derivative

-div(grad(v))+sv = -hu (=:f) dv/dn = 0

Der: F'[s]: h \mapsto trace(v)

Adjoint: -div(grad(w))+sw = 0 dw/dn = q Adj: F'[s]^: q \mapsto -u*w

proof: (F'h, q) = int_dOmega [trace(v) q] = int_dOmega [trace(v) dw/dn] = int_Omega [div(v grad w)] = int_Omega [grad v grad w] + int_Omega [v div( grad w)] = int_Omega [div(w grad v)] - int_Omega [div(grad v) w] + int_Omega [v div (grad w)] = int_dOmega [trace(w) dv/dn] - int_Omega [h u w] - int_Omega [s v w] + int_Omega [v s w] Note that dv/dn=0 on dOmega. Hence: (F'h, q) = -int_Omega[h u w] = (h, -u w)

Ancestors

Inherited members