def doInitialization(self): self.__pde = lpde.LinearPDE(self.domain) self.__pde.setSymmetryOn() self.__pde.setReducedOrderOn() self.__pde.getSolverOptions().setSolverMethod( lpde.SolverOptions.LUMPING) self.__pde.setValue(D=self.heat_capacity * self.density)
def setUpPDE(self): """ Creates and returns the underlying PDE. :rtype: `lpde.LinearPDE` """ if self.__pde is None: if not HAVE_DIRECT: raise ValueError( "Either this build of escript or the current MPI configuration does not support direct solvers." ) pde = lpde.LinearPDE(self.__domain, numEquations=2) D = pde.createCoefficient('D') A = pde.createCoefficient('A') A[0, :, 0, :] = escript.kronecker(self.__domain.getDim()) A[1, :, 1, :] = escript.kronecker(self.__domain.getDim()) pde.setValue(A=A, D=D) if self.__fixAtBottom: DIM = self.__domain.getDim() z = self.__domain.getX()[DIM - 1] pde.setValue(q=whereZero(z - self.__BX[DIM - 1][0]) * [1, 1]) pde.getSolverOptions().setSolverMethod(lpde.SolverOptions.DIRECT) pde.getSolverOptions().setTolerance(self.__tol) pde.setSymmetryOff() else: pde = self.__pde pde.resetRightHandSideCoefficients() return pde
def setUpPDE(self): """ Return the underlying PDE. :rtype: `LinearPDE` """ if self.__pde is None: DIM = self.__domain.getDim() pde = lpde.LinearPDE(self.__domain, numEquations=2) if self._directSolver == True: pde.getSolverOptions().setSolverMethod( lpde.SolverOptions.DIRECT) D = pde.createCoefficient('D') A = pde.createCoefficient('A') pde.setValue(A=A, D=D, q=self._q) pde.getSolverOptions().setTolerance(self.__tol) pde.setSymmetryOff() else: pde = self.__pde pde.resetRightHandSideCoefficients() pde.setValue(X=pde.createCoefficient('X'), Y=pde.createCoefficient('Y')) return pde
def __setSolver(self, mode, domain, sigma, boundary_mask, boundary_value, f): """ DESCRIPTION: ----------- Setups the coupled PDE for real and complex part. ARGUMENTS: ---------- mode :: string with TE or TM mode domain :: escript object with mesh domain sigma :: escript object with conductivity model boundary_mask :: escript object with boundary mask boundary_value :: dictionary with real/imag boundary values f :: sounding frequency RETURNS: -------- mt2d_fields :: dictionary with solved PDE, magnetotelluric fields real/imag """ # Constants: pi = cmath.pi # Ratio circle circumference to diameter. mu0 = 4 * pi * 1e-7 # Free space permeability in V.s/(A.m). wm = 2 * pi * f * mu0 # Angular frequency times mu0. # --- # Setup the coupled PDE for real/imaginary parts: # --- # Initialise the PDE object for two coupled equations (real/imaginary). mtpde = pde.LinearPDE(domain, numEquations=2) # If set, solve the 2D case using the direct solver: if MT_2D._solver.upper() == "DIRECT": mtpde.getSolverOptions().setSolverMethod( pde.SolverOptions().DIRECT) else: mtpde.getSolverOptions().setSolverMethod( pde.SolverOptions().DEFAULT) # Now initialise the PDE coefficients 'A' and 'D', # as well as the Dirichlet variables 'q' and 'r': A = mtpde.createCoefficient("A") D = mtpde.createCoefficient("D") q = mtpde.createCoefficient("q") r = mtpde.createCoefficient("r") # Set the appropriate values for the coefficients depending on the mode: if mode.upper() == "TE": a_val = 1.0 d_val = wm * sigma elif mode.upper() == "TM": a_val = 1.0 / sigma d_val = wm # --- # Define the PDE parameters, mind boundary conditions. # --- # Now define the rank-4 coefficient A: for i in range(domain.getDim()): A[0, i, 0, i] = a_val A[1, i, 1, i] = a_val # And define the elements of 'D' which are decomposed into real/imaginary values: D[0, 0] = 0 D[1, 0] = d_val D[0, 1] = -d_val D[1, 1] = 0 # Set Dirichlet boundaries and values: q[0] = boundary_mask r[0] = boundary_value['real'] q[1] = boundary_mask r[1] = boundary_value['imag'] # --- # Solve the PDE # --- mtpde.setValue(A=A, D=D, q=q, r=r) pde_solution = mtpde.getSolution() # And return the real and imaginary parts individually: mt2d_fields = {"real": pde_solution[0], "imag": pde_solution[1]} #<Note>: the electric field is returned for TE-mode. # the magnetic field is returned for TM-mode. return mt2d_fields
def __init__(self, domain, numLevelSets=1, w0=None, w1=None, wc=None, location_of_set_m=escript.Data(), useDiagonalHessianApproximation=False, tol=1e-8, coordinates=None, scale=None, scale_c=None): """ initialization. :param domain: domain :type domain: `Domain` :param numLevelSets: number of level sets :type numLevelSets: ``int`` :param w0: weighting factor for the m**2 term. If not set zero is assumed. :type w0: ``Scalar`` if ``numLevelSets`` == 1 or `Data` object of shape (``numLevelSets`` ,) if ``numLevelSets`` > 1 :param w1: weighting factor for the grad(m_i) terms. If not set zero is assumed :type w1: ``Vector`` if ``numLevelSets`` == 1 or `Data` object of shape (``numLevelSets`` , DIM) if ``numLevelSets`` > 1 :param wc: weighting factor for the cross gradient terms. If not set zero is assumed. Used for the case if ``numLevelSets`` > 1 only. Only values ``wc[l,k]`` in the lower triangle (l<k) are used. :type wc: `Data` object of shape (``numLevelSets`` , ``numLevelSets``) :param location_of_set_m: marks location of zero values of the level set function ``m`` by a positive entry. :type location_of_set_m: ``Scalar`` if ``numLevelSets`` == 1 or `Data` object of shape (``numLevelSets`` ,) if ``numLevelSets`` > 1 :param useDiagonalHessianApproximation: if True cross gradient terms between level set components are ignored when calculating approximations of the inverse of the Hessian Operator. This can speed-up the calculation of the inverse but may lead to an increase of the number of iteration steps in the inversion. :type useDiagonalHessianApproximation: ``bool`` :param tol: tolerance when solving the PDE for the inverse of the Hessian Operator :type tol: positive ``float`` :param coordinates: defines coordinate system to be used :type coordinates: ReferenceSystem` or `SpatialCoordinateTransformation` :param scale: weighting factor for level set function variation terms. If not set one is used. :type scale: ``Scalar`` if ``numLevelSets`` == 1 or `Data` object of shape (``numLevelSets`` ,) if ``numLevelSets`` > 1 :param scale_c: scale for the cross gradient terms. If not set one is assumed. Used for the case if ``numLevelSets`` > 1 only. Only values ``scale_c[l,k]`` in the lower triangle (l<k) are used. :type scale_c: `Data` object of shape (``numLevelSets``,``numLevelSets``) """ if w0 is None and w1 is None: raise ValueError("Values for w0 or for w1 must be given.") if wc is None and numLevelSets > 1: raise ValueError("Values for wc must be given.") self.__pre_input = None self.__pre_args = None self.logger = logging.getLogger('inv.%s' % self.__class__.__name__) self.__domain = domain DIM = self.__domain.getDim() self.__numLevelSets = numLevelSets self.__trafo = makeTransformation(domain, coordinates) self.__pde = linearPDEs.LinearPDE(self.__domain, numEquations=self.__numLevelSets, numSolutions=self.__numLevelSets) self.__pde.getSolverOptions().setTolerance(tol) self.__pde.setSymmetryOn() self.__pde.setValue( A=self.__pde.createCoefficient('A'), D=self.__pde.createCoefficient('D'), ) try: self.__pde.setValue(q=location_of_set_m) except linearPDEs.IllegalCoefficientValue: raise ValueError( "Unable to set location of fixed level set function.") # =========== check the shape of the scales: ======================== if scale is None: if numLevelSets == 1: scale = 1. else: scale = np.ones((numLevelSets, )) else: scale = np.asarray(scale) if numLevelSets == 1: if scale.shape == (): if not scale > 0: raise ValueError("Value for scale must be positive.") else: raise ValueError("Unexpected shape %s for scale." % scale.shape) else: if scale.shape is (numLevelSets, ): if not min(scale) > 0: raise ValueError( "All values for scale must be positive.") else: raise ValueError("Unexpected shape %s for scale." % scale.shape) if scale_c is None or numLevelSets < 2: scale_c = np.ones((numLevelSets, numLevelSets)) else: scale_c = np.asarray(scale_c) if scale_c.shape == (numLevelSets, numLevelSets): if not all([[scale_c[l, k] > 0. for l in range(k)] for k in range(1, numLevelSets)]): raise ValueError( "All values in the lower triangle of scale_c must be positive." ) else: raise ValueError("Unexpected shape %s for scale." % scale_c.shape) # ===== check the shape of the weights: ============================= if w0 is not None: w0 = escript.interpolate( w0, self.__pde.getFunctionSpaceForCoefficient('D')) s0 = w0.getShape() if numLevelSets == 1: if not s0 == (): raise ValueError("Unexpected shape %s for weight w0." % (s0, )) else: if not s0 == (numLevelSets, ): raise ValueError("Unexpected shape %s for weight w0." % (s0, )) if not self.__trafo.isCartesian(): w0 *= self.__trafo.getVolumeFactor() if not w1 is None: w1 = escript.interpolate( w1, self.__pde.getFunctionSpaceForCoefficient('A')) s1 = w1.getShape() if numLevelSets == 1: if not s1 == (DIM, ): raise ValueError("Unexpected shape %s for weight w1." % (s1, )) else: if not s1 == (numLevelSets, DIM): raise ValueError("Unexpected shape %s for weight w1." % (s1, )) if not self.__trafo.isCartesian(): f = self.__trafo.getScalingFactors( )**2 * self.__trafo.getVolumeFactor() if numLevelSets == 1: w1 *= f else: for i in range(numLevelSets): w1[i, :] *= f if numLevelSets == 1: wc = None else: wc = escript.interpolate( wc, self.__pde.getFunctionSpaceForCoefficient('A')) sc = wc.getShape() if not sc == (numLevelSets, numLevelSets): raise ValueError("Unexpected shape %s for weight wc." % (sc, )) if not self.__trafo.isCartesian(): raise ValueError( "Non-cartesian coordinates for cross-gradient term is not supported yet." ) # ============= now we rescale weights: ============================= L2s = np.asarray(escript.boundingBoxEdgeLengths(domain))**2 L4 = 1 / np.sum(1 / L2s)**2 if numLevelSets == 1: A = 0 if w0 is not None: A = escript.integrate(w0) if w1 is not None: A += escript.integrate(inner(w1, 1 / L2s)) if A > 0: f = scale / A if w0 is not None: w0 *= f if w1 is not None: w1 *= f else: raise ValueError("Non-positive weighting factor detected.") else: # numLevelSets > 1 for k in range(numLevelSets): A = 0 if w0 is not None: A = escript.integrate(w0[k]) if w1 is not None: A += escript.integrate(inner(w1[k, :], 1 / L2s)) if A > 0: f = scale[k] / A if w0 is not None: w0[k] *= f if w1 is not None: w1[k, :] *= f else: raise ValueError( "Non-positive weighting factor for level set component %d detected." % k) # and now the cross-gradient: if wc is not None: for l in range(k): A = escript.integrate(wc[l, k]) / L4 if A > 0: f = scale_c[l, k] / A wc[l, k] *= f # else: # raise ValueError("Non-positive weighting factor for cross-gradient level set components %d and %d detected."%(l,k)) self.__w0 = w0 self.__w1 = w1 self.__wc = wc self.__pde_is_set = False if self.__numLevelSets > 1: self.__useDiagonalHessianApproximation = useDiagonalHessianApproximation else: self.__useDiagonalHessianApproximation = True self._update_Hessian = True self.__num_tradeoff_factors = numLevelSets + ( (numLevelSets - 1) * numLevelSets) // 2 self.setTradeOffFactors() self.__vol_d = escript.vol(self.__domain)