def getR2(y, y_fitted, chi=None): """ calculates the coefficient of determination R^2 for `y_fitted` as prediction for `y` over a region marked by chi>0 defined by R^2=1 - S_res/S_tot with S_res=int(chi*(y-y_fitted*1)**2, S_tot=int(chi*(y-m(y)*1)**2), m(y)=int(chi*y)/int(chi) If R^2=1 then `y_fitted` is predicts `y` exactly. If R^2 then `y_fitted` does not make a better prediction than the mean. :param y: target distribution :type y: `esys.escript.Scalar` :param y_fitted: fitted distribution :type y_fitted: `esys.escript.Scalar` :param chi: marker/weighting for region of interest :type chi: `esys.escript.Scalar` or None :rtype: `float` """ if chi is None: chi = Scalar(1., Function(y_fitted.getFunctionSpace().getDomain())) ybar = integrate(chi * y) / integrate(chi) S_res = integrate(chi * (y - y_fitted)**2) S_tot = integrate(chi * (y - ybar)**2) if S_tot > 0: R2 = 1 - S_res / S_tot else: if S_res > 0: R2 = 0. else: R2 = 1. return R2
def getDualProduct(self, m, r): """ returns the dual product of a gradient represented by X=r[1] and Y=r[0] with a level set function m: *Y_i*m_i + X_ij*m_{i,j}* :type m: `Data` :type r: `ArithmeticTuple` :rtype: ``float`` """ A = 0 if not r[0].isEmpty(): A += integrate(inner(r[0], m)) if not r[1].isEmpty(): A += integrate(inner(r[1], grad(m))) return A
def getSourceScaling(self, u): """ returns the scaling factor s required to rescale source F to minimize defect ``|s * u- data|^2`` :param u: value of pressure solution (real and imaginary part) :type u: ``escript.Data`` of shape (2,) :rtype: `complex` """ uTu = escript.integrate(self.__weight * escript.length(u)**2) uTar = escript.integrate(self.__weight * ( u[0]*self.__data[0]+u[1]*self.__data[1]) ) uTai = escript.integrate(self.__weight * ( u[0]*self.__data[1]-u[1]*self.__data[0]) ) if uTu > 0: return complex(uTar/uTu, uTai/uTu) else: return complex(1.,0)
def getDualProduct(self, m, r): """ returns the dual product of a gradient represented by X=r[1] and Y=r[0] with a level set function m: *Y_i*m_i + X_ij*m_{i,j}* :type m: `Data` :type r: `ArithmeticTuple` :rtype: ``float`` """ A=0 if not r[0].isEmpty(): A+=integrate(inner(r[0], m)) if not r[1].isEmpty(): A+=integrate(inner(r[1], grad(m))) return A
def getValue(self, m, grad_m): """ returns the value of the cost function J with respect to m. This equation is specified in the inversion cookbook. :rtype: ``float`` """ if m != self.__pre_input: raise RuntimeError("Attempt to change point using getValue") # substituting cached values m = self.__pre_input grad_m = self.__pre_args mu = self.__mu mu_c = self.__mu_c DIM = self.getDomain().getDim() numLS = self.getNumLevelSets() A = 0 if self.__w0 is not None: r = inner(escript.integrate(m**2 * self.__w0), mu) self.logger.debug("J_R[m^2] = %e" % r) A += r if self.__w1 is not None: if numLS == 1: r = escript.integrate(inner(grad_m**2, self.__w1)) * mu self.logger.debug("J_R[grad(m)] = %e" % r) A += r else: for k in range(numLS): r = mu[k] * escript.integrate( inner(grad_m[k, :]**2, self.__w1[k, :])) self.logger.debug("J_R[grad(m)][%d] = %e" % (k, r)) A += r if numLS > 1: for k in range(numLS): gk = grad_m[k, :] len_gk = escript.length(gk) for l in range(k): gl = grad_m[l, :] r = mu_c[l, k] * escript.integrate(self.__wc[l, k] * ( (len_gk * escript.length(gl))**2 - inner(gk, gl)**2)) self.logger.debug("J_R[cross][%d,%d] = %e" % (l, k, r)) A += r return A / 2
def getDefect(self, rho, Hx, g_Hx): """ Returns the defect value. :param rho: a suggestion for resistivity :type rho: ``Data`` of shape () :param Hx: magnetic field :type Hx: ``Data`` of shape (2,) :param g_Hx: gradient of magnetic field :type g_Hx: ``Data`` of shape (2,2) :rtype: ``float`` """ x = g_Hx.getFunctionSpace().getX() Hx = escript.interpolate(Hx, x.getFunctionSpace()) u0 = Hx[0] u1 = Hx[1] u01 = g_Hx[0, 1] u11 = g_Hx[1, 1] scale = rho / (u0**2 + u1**2) Z = self._Z A = escript.integrate( self._weight * (Z[0]**2 + Z[1]**2 + scale * (-2 * Z[0] * (u0 * u01 + u1 * u11) + 2 * Z[1] * (u1 * u01 - u0 * u11) + rho * (u01**2 + u11**2)))) return A / 2
def getDefect(self, sigma, Ex, dExdz): """ Returns the defect value. :param sigma: a suggestion for conductivity :type sigma: ``Data`` of shape () :param Ex: electric field :type Ex: ``Data`` of shape (2,) :param dExdz: vertical derivative of electric field :type dExdz: ``Data`` of shape (2,) :rtype: ``float`` """ x = dExdz.getFunctionSpace().getX() Ex = escript.interpolate(Ex, x.getFunctionSpace()) u0 = Ex[0] u1 = Ex[1] u01 = dExdz[0] u11 = dExdz[1] scale = self._weight / (u01**2 + u11**2) Z = self._Z A = escript.integrate( scale * ((Z[0]**2 + Z[1]**2) * (u01**2 + u11**2) + 2 * Z[1] * (u0 * u11 - u01 * u1) - 2 * Z[0] * (u0 * u01 + u11 * u1) + u0**2 + u1**2)) return A / 2
def rescaleWeights(self, scale=1., sigma_scale=1.): """ rescales the weights such that :math: integrate( ( w omega**2 * sigma_scale * data * ((1/L_j)**2)**-1) +1 )/(data*omega**2 * ((1/L_j)**2)**-1) * sigma_scale )=scale :param scale: scale of data weighting factors :type scale: positive ``float`` :param sigma_scale: scale of 1/vp**2 velocity. :type sigma_scale: ``Scalar`` """ raise Warning("rescaleWeights is not tested yet.") if not scale > 0: raise ValueError("Value for scale must be positive.") if not sigma_scale * omega**2 * d > 0: raise ValueError( "Rescaling of weights failed due to zero denominator.") # copy back original weights before rescaling #self.__weight=[1.*ow for ow in self.__origweight] L2 = 1 / escript.length(1 / self.edge_length)**2 d = Lsup(escript.length(data)) A = escript.integrate(self.__weight * (sigma_scale * omega**2 * d + 1) / (sigma_scale * omega**2 * d)) if A > 0: self.__weight *= 1. / A if self.scaleF: self.__data *= escript.sqrt(A) else: raise ValueError("Rescaling of weights failed.")
def getValue(self, m, grad_m): """ returns the value of the cost function J with respect to m. This equation is specified in the inversion cookbook. :rtype: ``float`` """ if m != self.__pre_input: raise RuntimeError("Attempt to change point using getValue") # substituting cached values m = self.__pre_input grad_m = self.__pre_args mu = self.__mu mu_c = self.__mu_c DIM = self.getDomain().getDim() numLS = self.getNumLevelSets() A = 0 if self.__w0 is not None: r = inner(integrate(m ** 2 * self.__w0), mu) self.logger.debug("J_R[m^2] = %e" % r) A += r if self.__w1 is not None: if numLS == 1: r = integrate(inner(grad_m ** 2, self.__w1)) * mu self.logger.debug("J_R[grad(m)] = %e" % r) A += r else: for k in range(numLS): r = mu[k] * integrate(inner(grad_m[k, :] ** 2, self.__w1[k, :])) self.logger.debug("J_R[grad(m)][%d] = %e" % (k, r)) A += r if numLS > 1: for k in range(numLS): gk = grad_m[k, :] len_gk = length(gk) for l in range(k): gl = grad_m[l, :] r = mu_c[l, k] * integrate(self.__wc[l, k] * ((len_gk * length(gl)) ** 2 - inner(gk, gl) ** 2)) self.logger.debug("J_R[cross][%d,%d] = %e" % (l, k, r)) A += r return A / 2
def getNorm(self, m): """ returns the norm of ``m``. :param m: level set function :type m: `Data` :rtype: ``float`` """ return sqrt(integrate(length(m)**2) / self.__vol_d)
def getNorm(self, m): """ returns the norm of ``m``. :param m: level set function :type m: `Data` :rtype: ``float`` """ return sqrt(integrate(length(m) ** 2) / self.__vol_d)
def getValue(self, m, grad_m): """ returns the value of the cost function J with respect to m. This equation is specified in the inversion cookbook. :rtype: ``float`` """ mu = self.__mu mu_c = self.__mu_c DIM = self.getDomain().getDim() numLS = self.getNumLevelSets() A = 0 if self.__w0 is not None: r = inner(integrate(m**2 * self.__w0), mu) self.logger.debug("J_R[m^2] = %e" % r) A += r if self.__w1 is not None: if numLS == 1: r = integrate(inner(grad_m**2, self.__w1)) * mu self.logger.debug("J_R[grad(m)] = %e" % r) A += r else: for k in range(numLS): r = mu[k] * integrate( inner(grad_m[k, :]**2, self.__w1[k, :])) self.logger.debug("J_R[grad(m)][%d] = %e" % (k, r)) A += r if numLS > 1: for k in range(numLS): gk = grad_m[k, :] len_gk = length(gk) for l in range(k): gl = grad_m[l, :] r = mu_c[l, k] * integrate(self.__wc[l, k] * ( (len_gk * length(gl))**2 - inner(gk, gl)**2)) self.logger.debug("J_R[cross][%d,%d] = %e" % (l, k, r)) A += r return A / 2
def getArguments(self, sigma): """ Returns precomputed values shared by `getDefect()` and `getGradient()`. :param sigma: a suggestion for complex 1/V**2 :type sigma: ``escript.Data`` of shape (2,) :return: solution, uTar, uTai, uTu :rtype: ``escript.Data`` of shape (2,), 3 x `float` """ pde=self.setUpPDE() D=pde.getCoefficient('D') D[0,0]=-self.__omega**2 * sigma[0] D[0,1]= self.__omega**2 * sigma[1] D[1,0]=-self.__omega**2 * sigma[1] D[1,1]=-self.__omega**2 * sigma[0] pde.setValue(D=D, Y=self.__F, y=self.__f, y_dirac=self.__f_dirac) u=pde.getSolution() uTar=escript.integrate(self.__weight * ( u[0]*self.__data[0]+u[1]*self.__data[1]) ) uTai=escript.integrate(self.__weight * ( u[0]*self.__data[1]-u[1]*self.__data[0]) ) uTu = escript.integrate( self.__weight * escript.length(u)**2 ) return u, uTar, uTai, uTu
def getDefect(self, sigma, u, uTar, uTai, uTu): """ Returns the defect value. :param sigma: a suggestion for complex 1/V**2 :type sigma: ``escript.Data`` of shape (2,) :param u: a u vector :type u: ``escript.Data`` of shape (2,) :param uTar: equals `integrate( w * (data[0]*u[0]+data[1]*u[1]))` :type uTar: `float` :param uTai: equals `integrate( w * (data[1]*u[0]-data[0]*u[1]))` :type uTa: `float` :param uTu: equals `integrate( w * (u,u))` :type uTu: `float` :rtype: ``float`` """ # assuming integrate(w * length(data)**2) =1 if self.scaleF and abs(uTu) >0: A = 1.-(uTar**2 + uTai**2)/uTu else: A = escript.integrate(self.__weight*escript.length(self.__data)**2)- 2 * uTar + uTu return A/2
def RegionalCalculation(reg_mask): """ Calculates the "regional" from the entire FEILDS model excluding the selected region and outputs gravity at the specified altitude... see above for the "residual" """ # read in a gravity data grid to define data computation space G_DATA = os.path.join(DATADIR,'Final_BouguerTC_UC15K_qrtdeg.nc') FS=ReducedFunction(dom) nValues=[NX, NY, 1] first = [0, 0, cell_at_altitude] multiplier = [1, 1, 1] reverse = [0, 0, 0] byteorder = BYTEORDER_NATIVE gdata = readBinaryGrid(G_DATA, FS, shape=(), fill=-999999, byteOrder=byteorder, dataType=DATATYPE_FLOAT32, first=first, numValues=nValues, multiplier=multiplier, reverse=reverse) print("Grid successfully read") # get the masking and units sorted out for the data-space g_mask = whereNonZero(gdata+999999) gdata=gdata*g_mask * GRAV_UNITS # if people choose to have air in their region we exclude it from the # specified gravity calculation region if h_top < 0.: reg_mask = reg_mask+mask_air live_model = initial_model* whereNonPositive(reg_mask) dead_model = initial_model* wherePositive(reg_mask) if UseMean is True: # calculate the mean density within the selected region BackgroundDensity = integrate(dead_model)/integrate(wherePositive(reg_mask)) print("Density mean for selected region equals = %s"%BackgroundDensity) live_model = live_model + BackgroundDensity * wherePositive(reg_mask) # create mapping rho_mapping = DensityMapping(dom, rho0=live_model) # invert sign of gravity field to account for escript's coordinate system gdata = -GRAV_UNITS * gdata # turn the scalars into vectors (vertical direction) d=kronecker(DIM)[DIM-1] w=safeDiv(1., g_mask) gravity_model=GravityModel(dom, w*d, gdata*d, fixPotentialAtBottom=False, coordinates=COORDINATES) gravity_model.rescaleWeights(rho_scale=rho_mapping.getTypicalDerivative()) phi,_ = gravity_model.getArguments(live_model) g_init = -gravity_model.getCoordinateTransformation().getGradient(phi) g_init = interpolate(g_init, gdata.getFunctionSpace()) print("Computed gravity: %s"%(g_init[2])) fn=os.path.join(OUTPUTDIR,'regional-gravity') if SiloOutput is True: saveSilo(fn, density=live_model, gravity_init=g_init, g_initz=-g_init[2], gravitymask=g_mask, modelmask=reg_mask) print('SILO file written with the following fields: density (kg/m^3), gravity vector (m/s^2), gz (m/s^2), gravitymask, modelmask') # to compare calculated data against input dataset. # Not used by default but should work if the input dataset is correct #gslice = g_init[2]*wherePositive(g_mask) #g_dash = integrate(gslice)/integrate(wherePositive(g_mask)) #gdataslice = gdata*wherePositive(g_mask) #gdata_dash = integrate(gdataslice)/integrate(wherePositive(g_mask)) #misfit=(gdataslice-gdata_dash)-(gslice-g_dash) saveDataCSV(fn+".csv", mask=g_mask, gz=-g_init[2], Long=datacoords[0], Lat=datacoords[1], h=datacoords[2]) print('CSV file written with the following fields: Longitude (degrees) Latitude (degrees), h (100km), gz (m/s^2)')
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)
def __init__(self, domain, omega, x, Z, eta=None, w0=1., mu=4 * math.pi * 1e-7, sigma0=0.01, airLayerLevel=None, fixAirLayer=False, coordinates=None, tol=1e-8, saveMemory=False, directSolver=True): """ initializes a new forward model. :param domain: domain of the model :type domain: `Domain` :param omega: frequency :type omega: positive ``float`` :param x: coordinates of measurements :type x: ``list`` of ``tuple`` with ``float`` :param Z: measured impedance (possibly scaled) :type Z: ``list`` of ``complex`` :param eta: spatial confidence radius :type eta: positive ``float`` or ``list`` of positive ``float`` :param w0: confidence factors for meassurements. :type w0: ``None`` or a list of positive ``float`` :param mu: permeability :type mu: ``float`` :param sigma0: background conductivity :type sigma0: ``float`` :param airLayerLevel: position of the air layer from to bottom of the domain. If not set the air layer starts at the top of the domain :type airLayerLevel: ``float`` or ``None`` :param fixAirLayer: fix air layer (TM mode) :type fixAirLayer: ``bool`` :param coordinates: defines coordinate system to be used (not supported yet) :type coordinates: `ReferenceSystem` or `SpatialCoordinateTransformation` :param tol: tolerance of underlying PDE :type tol: positive ``float`` :param saveMemory: if true stiffness matrix is deleted after solution of the PDE to minimize memory use. This will require more compute time as the matrix needs to be reallocated at each iteration. :type saveMemory: ``bool`` :param directSolver: if true a direct solver (rather than an iterative solver) will be used to solve the PDE :type directSolver: ``bool`` """ super(MT2DBase, self).__init__() self.__trafo = coords.makeTransformation(domain, coordinates) if not self.getCoordinateTransformation().isCartesian(): raise ValueError( "Non-Cartesian coordinates are not supported yet.") if len(x) != len(Z): raise ValueError( "Number of data points and number of impedance values don't match." ) if eta is None: eta = escript.sup(domain.getSize()) * 0.45 if isinstance(eta, float) or isinstance(eta, int): eta = [float(eta)] * len(Z) elif not len(eta) == len(Z): raise ValueError( "Number of confidence radii and number of impedance values don't match." ) if isinstance(w0, float) or isinstance(w0, int): w0 = [float(w0)] * len(Z) elif not len(w0) == len(Z): raise ValueError( "Number of confidence factors and number of impedance values don't match." ) self.__domain = domain self._omega_mu = omega * mu self._ks = escript.sqrt(self._omega_mu * sigma0 / 2.) xx = escript.Function(domain).getX() totalS = 0 self._Z = [ escript.Scalar(0., escript.Function(domain)), escript.Scalar(0., escript.Function(domain)) ] self._weight = escript.Scalar(0., escript.Function(domain)) for s in range(len(Z)): chi = self.getWeightingFactor(xx, 1., x[s], eta[s]) f = escript.integrate(chi) if f < eta[s]**2 * 0.01: raise ValueError( "Zero weight (almost) for data point %s. Change eta or refine mesh." % (s, )) w02 = w0[s] / f totalS += w02 self._Z[0] += chi * Z[s].real self._Z[1] += chi * Z[s].imag self._weight += chi * w02 / (abs(Z[s])**2) if not totalS > 0: raise ValueError( "Scaling of weight factors failed as sum is zero.") DIM = domain.getDim() z = domain.getX()[DIM - 1] self._ztop = escript.sup(z) self._zbottom = escript.inf(z) if airLayerLevel is None: airLayerLevel = self._ztop self._airLayerLevel = airLayerLevel # botton: mask0 = escript.whereZero(z - self._zbottom) r = mask0 * [ escript.exp(self._ks * (self._zbottom - airLayerLevel)) * escript.cos(self._ks * (self._zbottom - airLayerLevel)), escript.exp(self._ks * (self._zbottom - airLayerLevel)) * escript.sin(self._ks * (self._zbottom - airLayerLevel)) ] #top: if fixAirLayer: mask1 = escript.whereNonNegative(z - airLayerLevel) r += mask1 * [1, 0] else: mask1 = escript.whereZero(z - self._ztop) r += mask1 * [ self._ks * (self._ztop - airLayerLevel) + 1, self._ks * (self._ztop - airLayerLevel) ] self._q = (mask0 + mask1) * [1, 1] self._r = r #==================================== self.__tol = tol self._directSolver = directSolver self._saveMemory = saveMemory self.__pde = None if not saveMemory: self.__pde = self.setUpPDE()
def __init__( self, domain, numLevelSets=1, w0=None, w1=None, wc=None, location_of_set_m=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 = 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 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.0 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.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 = 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 = 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 = 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(boundingBoxEdgeLengths(domain)) ** 2 L4 = 1 / np.sum(1 / L2s) ** 2 if numLevelSets == 1: A = 0 if w0 is not None: A = integrate(w0) if w1 is not None: A += 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 = integrate(w0[k]) if w1 is not None: A += 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 = 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 = vol(self.__domain)
def __init__(self, domain, omega, w, data, F, coordinates=None, fixAtBottom=False, tol=1e-10, saveMemory=True, scaleF=True): """ initializes a new forward model with acoustic wave form inversion. :param domain: domain of the model :type domain: `Domain` :param w: weighting factors :type w: ``Scalar`` :param data: real and imaginary part of data :type data: ``escript.Data`` of shape (2,) :param F: real and imaginary part of source given at Dirac points, on surface or at volume. :type F: ``escript.Data`` of shape (2,) :param coordinates: defines coordinate system to be used (not supported yet) :type coordinates: `ReferenceSystem` or `SpatialCoordinateTransformation` :param tol: tolerance of underlying PDE :type tol: positive ``float`` :param saveMemory: if true stiffness matrix is deleted after solution of PDE to minimize memory requests. This will require more compute time as the matrix needs to be reallocated. :type saveMemory: ``bool`` :param scaleF: if true source F is scaled to minimize defect. :type scaleF: ``bool`` :param fixAtBottom: if true pressure is fixed to zero at the bottom of the domain :type fixAtBottom: ``bool`` """ super(AcousticWaveForm, self).__init__() self.__trafo = edc.makeTransformation(domain, coordinates) if not self.getCoordinateTransformation().isCartesian(): raise ValueError( "Non-Cartesian Coordinates are not supported yet.") if not isinstance(data, escript.Data): raise ValueError("data must be an escript.Data object.") if not data.getFunctionSpace() == escript.FunctionOnBoundary(domain): raise ValueError("data must be defined on boundary") if not data.getShape() == (2, ): raise ValueError( "data must have shape (2,) (real and imaginary part).") if w is None: w = 1. if not isinstance(w, escript.Data): w = escript.Data(w, escript.FunctionOnBoundary(domain)) else: if not w.getFunctionSpace() == escript.FunctionOnBoundary(domain): raise ValueError("Weights must be defined on boundary.") if not w.getShape() == (): raise ValueError("Weights must be scalar.") self.__domain = domain self.__omega = omega self.__weight = w self.__data = data self.scaleF = scaleF if scaleF: A = escript.integrate(self.__weight * escript.length(self.__data)**2) if A > 0: self.__data *= 1. / escript.sqrt(A) self.__BX = escript.boundingBox(domain) self.edge_lengths = np.asarray(escript.boundingBoxEdgeLengths(domain)) if not isinstance(F, escript.Data): F = escript.interpolate(F, escript.DiracDeltaFunctions(domain)) if not F.getShape() == (2, ): raise ValueError( "Source must have shape (2,) (real and imaginary part).") self.__F = escript.Data() self.__f = escript.Data() self.__f_dirac = escript.Data() if F.getFunctionSpace() == escript.DiracDeltaFunctions(domain): self.__f_dirac = F elif F.getFunctionSpace() == escript.FunctionOnBoundary(domain): self.__f = F else: self.__F = F self.__tol = tol self.__fixAtBottom = fixAtBottom self.__pde = None if not saveMemory: self.__pde = self.setUpPDE()