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
- BilinearForm
- Coefficient
- EIT
- LinearForm
- LinearFormGrad
- ProjectToBoundary
- ReactionNeumann
- SecondOrderEllipticCoefficientPDE
- SolveSystem
Inherited members
class SecondOrderEllipticCoefficientPDE (domain, sol_domain, bdr_val=None, a_bdr_val=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 uThe 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
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): 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.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): 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) 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() 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): 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()
Ancestors
Inherited members
class SolveSystem (domain, bf)
-
Solve the system \begin(align) Lu = f \text{ in } \Omega, \ u = 0 \text{ in } \partial\Omega \begin{align} given f.
Parameters
domain
:NgsSpace
- the underlying NgsSpace.
bf
:ngs.BilinearForm
- The bilinear form describing L
Expand source code
class SolveSystem(NGSolveOperator): r"""Solve the system \begin(align*) Lu = f \text{ in } \Omega, \\ u = 0 \text{ in } \partial\Omega \begin{align*} given f. Parameters ---------- domain : NgsSpace the underlying NgsSpace. bf : ngs.BilinearForm The bilinear form describing \(L\) """ def __init__(self, domain, bf): super().__init__(domain=domain, codomain=domain, linear=True) self.bf=bf self.prec = ngs.Preconditioner(self.bf, 'local') self.gfu=ngs.GridFunction(self.domain.fes) self.gfu_adj=ngs.GridFunction(self.domain.fes) self.gfu_eval=ngs.GridFunction(self.domain.fes) u, v=self.domain.fes.TnT() self.f = ngs.LinearForm(self.domain.fes) self.f += self.gfu * v * ngs.dx self.f_adj = ngs.LinearForm(self.domain.fes) self.f_adj += self.gfu_adj * v * ngs.dx def _eval(self, argument): self._read_in(argument, self.gfu) self.f.Assemble() self._solve_dirichlet_problem(self.bf, self.f, self.gfu_eval, self.prec) return self.gfu_eval.vec.FV().NumPy().copy() def _adjoint(self, argument, return_numpy=True): f_read = ngs.LinearForm(self.domain.fes) #f_read.Assemble() f_read.vec.FV().NumPy()[:]=argument self.gfu_adj.vec.data=self.bf.mat.CreateTranspose().Inverse()*f_read.vec self.f_adj.Assemble() if return_numpy: return self.f_adj.vec.FV().NumPy()[:] else: return self.f_adj
Ancestors
Inherited members
class LinearForm (domain)
-
Expand source code
class LinearForm(NGSolveOperator): def __init__(self, domain): super().__init__(domain=domain, codomain=domain, linear=True) self.gfu=ngs.GridFunction(self.domain.fes) self.gfu_adj=ngs.GridFunction(self.domain.fes) u, v=self.domain.fes.TnT() self.f = ngs.LinearForm(self.domain.fes) self.f += self.gfu * v * ngs.dx self.f_adj = ngs.LinearForm(self.domain.fes) self.f_adj += self.gfu_adj * v * ngs.dx def _eval(self, argument): self._read_in(argument, self.gfu) self.f.Assemble() return self.f.vec.FV().NumPy()[:] def _adjoint(self, argument): return self._eval(argument)
Ancestors
Inherited members
class LinearFormGrad (domain, gfu_eval)
-
Expand source code
class LinearFormGrad(NGSolveOperator): def __init__(self, domain, gfu_eval): super().__init__(domain=domain, codomain=domain, linear=True) self.gfu=ngs.GridFunction(self.domain.fes) self.gfu_adj=ngs.GridFunction(self.domain.fes) self.gfu_eval=gfu_eval u, v=self.domain.fes.TnT() self.f = ngs.LinearForm(self.domain.fes) self.f += ngs.grad(self.gfu) * ngs.grad(self.gfu_eval) * v * ngs.dx self.f_adj = ngs.LinearForm(self.domain.fes) self.f_adj += self.gfu_adj * ngs.grad(self.gfu_eval) * ngs.grad(v) * ngs.dx def _eval(self, argument): self._read_in(argument, self.gfu) self.f.Assemble() return self.f.vec.FV().NumPy()[:] def _adjoint(self, argument): self._read_in(argument, self.gfu_adj) self.f_adj.Assemble() return self.f_adj.vec.FV().NumPy()[:]
Ancestors
Inherited members
class BilinearForm (domain, bf)
-
Expand source code
class BilinearForm(NGSolveOperator): def __init__(self, domain, bf): super().__init__(domain=domain, codomain=domain, linear=True) self.bf=bf self.gfu=ngs.GridFunction(self.domain.fes) self.gfu_adj=ngs.GridFunction(self.domain.fes) self.f_eval = ngs.LinearForm(self.domain.fes) self.f_adj = ngs.LinearForm(self.domain.fes) def _eval(self, argument): self.f_eval.vec.FV().NumPy()[:]=argument self.gfu.vec.data=self.bf.mat.Inverse()*self.f_eval.vec return self.gfu.vec.FV().NumPy()[:] def _adjoint(self, argument): self.f_adj.vec.FV().NumPy()[:]=argument.conj() self.gfu_adj.vec.data=self.bf.mat.CreateTranspose().Inverse()*self.f_adj.vec return self.gfu_adj.vec.FV().NumPy()[:].conj()
Ancestors
Inherited members
class Coefficient (domain, rhs, bc=None, codomain=None, diffusion=False, reaction=True)
-
Diffusion and reaction coefficient problem
Identification of a diffusion coefficient:
PDE: -div(a grad u)=rhs in Omega u = 0 on dOmega
Evaluate: F: a \mapsto u
Derivative
-div (a grad v)=div (h grad u) in Omega v = 0 on dOmega
Der: F'[u]: h \mapsto v
Adjoint
div (a grad w)=q in Omega w=0 on dOmega
Adj: F'[s]^*: q \mapsto -grad(u) grad(w)
Identification of a reaction coefficient:
PDE: -Delta u +c u=f in Omega u = 0 on dOmega
Evaluate: F: c \mapsto u
Derivative
-Delta v + c v=-h u in Omega v = 0 on dOmega
Der: F'[u]: h \mapsto v
Adjoint
-Delta w+c w=q in Omega w=0 on dOmega
Adj: F'[s]^: q \mapsto -u^ w
Expand source code
class Coefficient(NGSolveOperator): r"""Diffusion and reaction coefficient problem Identification of a diffusion coefficient: PDE: -div(a grad u)=rhs in Omega u = 0 on dOmega Evaluate: F: a \mapsto u Derivative: -div (a grad v)=div (h grad u) in Omega v = 0 on dOmega Der: F'[u]: h \mapsto v Adjoint: div (a grad w)=q in Omega w=0 on dOmega Adj: F'[s]^*: q \mapsto -grad(u) grad(w) Identification of a reaction coefficient: PDE: -Delta u +c u=f in Omega u = 0 on dOmega Evaluate: F: c \mapsto u Derivative: -Delta v + c v=-h u in Omega v = 0 on dOmega Der: F'[u]: h \mapsto v Adjoint: -Delta w+c w=q in Omega w=0 on dOmega Adj: F'[s]^*: q \mapsto -u^* w """ def __init__( self, domain, rhs, bc=None, codomain=None, diffusion=False, reaction=True ): assert diffusion or reaction assert (diffusion and reaction) is False codomain = codomain or domain #Need to know the boundary to calculate Dirichlet bdr condition assert codomain.bdr is not None self.rhs = rhs super().__init__(domain, codomain) self.diffusion = diffusion self.reaction = reaction self.dim = domain.fes.mesh.dim bc = bc or 0 # Define mesh and finite element space 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) # return value of derivative self.gfu_adjoint = ngs.GridFunction(self.fes_domain) # grid function for returning values in adjoint self.gfu_bf = ngs.GridFunction(self.fes_domain) # grid function for defining integrator (bilinearform) self.gfu_lf = ngs.GridFunction(self.fes_codomain) # grid function for defining right hand side (Linearform) self.gfu_inner_adj = ngs.GridFunction(self.fes_codomain) #computations in adjoint self.gfu_inner_deriv = ngs.GridFunction(self.fes_domain) #inner computations in derivative #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) if self.diffusion: self.a += ngs.grad(u) * ngs.grad(v) * self.gfu_bf * ngs.dx elif self.reaction: self.a += (ngs.grad(u) * ngs.grad(v) + u * v * self.gfu_bf) * ngs.dx # Define Linearform, will be assembled later self.f = ngs.LinearForm(self.fes_codomain) self.f += self.gfu_lf * v * ngs.dx if self.reaction: self.lf=LinearForm(self.codomain) self.gfu_eval.Set(bc, definedon=self.fes_codomain.mesh.Boundaries(codomain.bdr)) #Initialize Preconditioner for solving the Dirichlet problems self.prec = ngs.Preconditioner(self.a, 'local') #Initialize homogenous Dirichlet problems for derivative and adjoint self.gfu_inner_adj.Set(0) self.gfu_inner_deriv.Set(0) def _eval(self, diff, differentiate=False, adjoint_derivative=False): # Assemble Bilinearform self._read_in(diff, self.gfu_bf) self.a.Assemble() if differentiate: self.bf=BilinearForm(self.codomain, self.a) # Assemble Linearform self.gfu_lf.Set(self.rhs) self.f.Assemble() # Solve system self._solve_dirichlet_problem(self.a, self.f, self.gfu_eval, self.prec) if differentiate and self.diffusion: self.lf=LinearFormGrad(self.codomain, self.gfu_eval) return self.gfu_eval.vec.FV().NumPy().copy() def _derivative(self, argument): # Bilinearform already defined from _eval # Translate arguments in Coefficient Function and interpolate to codomain self._read_in(argument, self.gfu_inner_deriv) if self.diffusion: self.gfu_deriv.Set(self.gfu_inner_deriv) return (self.bf*self.lf)(self.gfu_lf.vec.FV().NumPy()[:]) elif self.reaction: self.gfu_deriv.Set(-self.gfu_inner_deriv * self.gfu_eval) return (self.bf*self.lf)(self.gfu_deriv.vec.FV().NumPy()[:]) def _adjoint(self, argument): if self.reaction: self.gfu_inner_adj.vec.FV().NumPy()[:]=self.lf._adjoint(self.bf._adjoint(argument)) self.gfu_adjoint.Set( -self.gfu_eval * self.gfu_inner_adj ) return self.gfu_adjoint.vec.FV().NumPy().copy() elif self.diffusion: self.gfu_inner_adj.vec.FV().NumPy()[:]=self.lf._adjoint(self.bf._adjoint(argument)) self.gfu_adjoint.Set( self.gfu_inner_adj ) return self.gfu_adjoint.vec.FV().NumPy().copy()
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)
-
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
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
Ancestors
Inherited members
class ReactionNeumann (domain, g, codomain=None)
-
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)
Expand source code
class ReactionNeumann(NGSolveOperator): """ 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
Ancestors
Inherited members