class ReciprocalPropExample(Props.HasModel): sigma = Props.PhysicalProperty("Electrical conductivity (S/m)") rho = Props.PhysicalProperty("Electrical resistivity (Ohm m)") Props.Reciprocal(sigma, rho)
class PetaInvProblem(Problem.BaseProblem): surveyPair = Survey.BaseSurvey P = None J = None time = None we = None ColeCole = "Debye" eta, etaMap, etaDeriv = Props.Invertible( "Chargeability" ) tau, tauMap, tauDeriv = Props.Invertible( "Tau" ) def __init__(self, mesh, **kwargs): Problem.BaseProblem.__init__(self, mesh, **kwargs) def fields(self, m, f=None): self.model = m self.J = petaJconvfun(self.eta, self.tau, self.we, self.time, self.P, ColeCole = self.ColeCole) return petaconvfun(self.eta, self.tau, self.we, self.time, self.P, ColeCole = self.ColeCole) def Jvec(self, m, v, f=None): jvec = self.J.dot(v) return jvec def Jtvec(self, m, v, f=None): jtvec = (self.J.T.dot(v)) return jtvec
class Haverkamp_k(BaseHydraulicConductivity): Ks, KsMap, KsDeriv = Props.Invertible("Saturated hydraulic conductivity", default=9.44e-03) A, AMap, ADeriv = Props.Invertible("fitting parameter", default=1.175e+06) gamma, gammaMap, gammaDeriv = Props.Invertible("fitting parameter", default=4.74) def _get_params(self): return self.Ks, self.A, self.gamma def __call__(self, u): Ks, A, gamma = self._get_params() P_p, P_n = _get_projections(u) # Compute the positive/negative domains f_p = P_p * np.ones(len(u)) * Ks # ensures scalar Ks works f_n = P_n * Ks * A / (A + abs(u)**gamma) return f_p + f_n def derivU(self, u): Ks, A, gamma = self._get_params() g = -(Ks * A * gamma * abs(u)**(gamma - 1) * np.sign(u)) / ( (A + abs(u)**gamma)**2) g[u >= 0] = 0 return Utils.sdiag(g) def derivM(self, u): return self._derivKs(u) + self._derivA(u) + self._derivGamma(u) def _derivKs(self, u): if self.KsMap is None: return Utils.Zero() Ks, A, gamma = self._get_params() P_p, P_n = _get_projections(u) # Compute the positive/negative domains dKs_dm_p = P_p * self.KsDeriv dKs_dm_n = P_n * Utils.sdiag(A / (A + abs(u)**gamma)) * self.KsDeriv return dKs_dm_p + dKs_dm_n def _derivA(self, u): if self.AMap is None: return Utils.Zero() Ks, A, gamma = self._get_params() ddm = Ks / (A + abs(u)**gamma) - Ks * A / (A + abs(u)**gamma)**2 ddm[u >= 0] = 0 dA_dm = Utils.sdiag(ddm) * self.ADeriv return dA_dm def _derivGamma(self, u): if self.gammaMap is None: return Utils.Zero() Ks, A, gamma = self._get_params() ddm = -(A * Ks * np.log(abs(u)) * abs(u)**gamma) / (A + abs(u)**gamma)**2 ddm[u >= 0] = 0 dGamma_dm = Utils.sdiag(ddm) * self.gammaDeriv return dGamma_dm
class ReciprocalMappingExample(Props.HasModel): sigma, sigmaMap, sigmaDeriv = Props.Invertible( "Electrical conductivity (S/m)") rho, rhoMap, rhoDeriv = Props.Invertible("Electrical resistivity (Ohm m)") Props.Reciprocal(sigma, rho)
class ReciprocalExample(Props.HasModel): sigma, sigmaMap, sigmaDeriv = Props.Invertible( "Electrical conductivity (S/m)") rho = Props.PhysicalProperty("Electrical resistivity (Ohm m)") Props.Reciprocal(sigma, rho)
class ReciprocalPropExampleDefaults(Props.HasModel): sigma = Props.PhysicalProperty("Electrical conductivity (S/m)", default=np.r_[1., 2., 3.]) rho = Props.PhysicalProperty("Electrical resistivity (Ohm m)") Props.Reciprocal(sigma, rho)
class ComplicatedInversion(Props.HasModel): Ks, KsMap, KsDeriv = Props.Invertible("Saturated hydraulic conductivity", default=24.96) A, AMap, ADeriv = Props.Invertible("fitting parameter", default=1.175e+06) gamma, gammaMap, gammaDeriv = Props.Invertible("fitting parameter", default=4.74)
class SimpleExample(Props.HasModel): sigmaMap = Props.Mapping("Mapping to the inversion model.") sigma = Props.PhysicalProperty("Electrical conductivity (S/m)", mapping=sigmaMap) sigmaDeriv = Props.Derivative("Derivative of sigma wrt the model.", physical_property=sigma)
class SEInvProblem(Problem.BaseProblem): sigmaInf, sigmaInfMap, sigmaInfDeriv = Props.Invertible( "Electrical conductivity at infinite frequency (S/m)" ) eta, etaMap, etaDeriv = Props.Invertible( "Cole-Cole chargeability (V/V)" ) tau, tauMap, tauDeriv = Props.Invertible( "Cole-Cole time constant (s)" ) c, cMap, cDeriv = Props.Invertible( "Cole-Cole frequency dependency" ) P = None J = None time = None def __init__(self, mesh, **kwargs): Problem.BaseProblem.__init__(self, mesh, **kwargs) def fields(self, m=None, f=None): if m is not None: self.model = m self.J = self.get_peta_deriv(self.time) return self.get_peta(self.time) def Jvec(self, m, v, f=None): jvec = self.J.dot(v) return jvec def Jtvec(self, m, v, f=None): jtvec = self.J.T.dot(v) return jtvec def get_peta(self, time): return self.eta*np.exp(-(time/self.tau)**self.c) def get_peta_deriv(self, time): kerneleta = lambda t, eta, tau, c: np.exp(-(time/tau)**c) kerneltau = lambda t, eta, tau, c: (c*eta/tau)*((t/tau)**c)*np.exp(-(t/tau)**c) kernelc = lambda t, eta, tau, c: -eta*((t/tau)**c)*np.exp(-(t/tau)**c)*np.log(t/tau) tempeta = kerneleta(time, self.eta, self.tau, self.c).reshape([-1,1]) temptau = kerneltau(time, self.eta, self.tau, self.c).reshape([-1,1]) tempc = kernelc(time, self.eta, self.tau, self.c).reshape([-1,1]) J = tempeta * self.etaDeriv + temptau * self.tauDeriv + tempc * self.cDeriv return J
class BaseSPProblem(BaseDCProblem): h, hMap, hDeriv = Props.Invertible("Hydraulic Head (m)") q, qMap, qDeriv = Props.Invertible("Streaming current source (A/m^3)") jsx, jsxMap, jsxDeriv = Props.Invertible( "Streaming current density in x-direction (A/m^2)") jsy, jsyMap, jsyDeriv = Props.Invertible( "Streaming current density in y-direction (A/m^2)") jsz, jszMap, jszDeriv = Props.Invertible( "Streaming current density in z-direction (A/m^2)") sigma = Props.PhysicalProperty("Electrical conductivity (S/m)") rho = Props.PhysicalProperty("Electrical resistivity (Ohm m)") Props.Reciprocal(sigma, rho) modelType = None surveyPair = Survey fieldsPair = FieldsDC @property def deleteTheseOnModelUpdate(self): toDelete = [] return toDelete def evalq(self, Qv, vel): MfQviI = self.mesh.getFaceInnerProduct(1.0 / Qv, invMat=True) Mf = self.mesh.getFaceInnerProduct() return self.Div * (Mf * (MfQviI * vel))
class Problem3D_e(Problem3DEM_e): _solutionType = 'eSolution' _formulation = 'EB' fieldsPair = Fields3D_e sigmaInf, sigmaInfMap, sigmaInfDeriv = Props.Invertible( "Electrical conductivity at infinite frequency(S/m)") chi = Props.PhysicalProperty("Magnetic susceptibility", default=0.) eta, etaMap, etaDeriv = Props.Invertible( "Electrical chargeability (V/V), 0 <= eta < 1", default=0.) tau, tauMap, tauDeriv = Props.Invertible("Time constant (s)", default=1.) c, cMap, cDeriv = Props.Invertible("Frequency Dependency, 0 < c < 1", default=0.5) h, hMap, hDeriv = Props.Invertible("Receiver Height (m), h > 0", ) def __init__(self, mesh, **kwargs): Problem3DEM_e.__init__(self, mesh, **kwargs) def MeSigma(self, freq): """ Edge inner product matrix for \\(\\sigma\\). Used in the E-B formulation """ sigma = ColeColePelton(freq, self.sigmaInf, self.eta, self.tau, self.c) return self.mesh.getEdgeInnerProduct(sigma) def MeSigmaI(self, freq): """ Inverse of the edge inner product matrix for \\(\\sigma\\). """ sigma = ColeColePelton(freq, self.sigmaInf, self.eta, self.tau, self.c) return self.mesh.getEdgeInnerProduct(sigma, invMat=True) def getA(self, freq): """ System matrix .. math :: \mathbf{A} = \mathbf{C}^{\\top} \mathbf{M_{\mu^{-1}}^f} \mathbf{C} + i \omega \mathbf{M^e_{\sigma}} :param float freq: Frequency :rtype: scipy.sparse.csr_matrix :return: A """ MfMui = self.MfMui MeSigma = self.MeSigma(freq) C = self.mesh.edgeCurl return C.T * MfMui * C + 1j * omega(freq) * MeSigma
class Problem3DIP_Linear_singletime(Problem3DIP_Linear): peta, petaMap, petaDeriv = Props.Invertible( "Peudo-chargeability" ) def forward(self, m, f=None): self.model = m if self.J is None: self.getJ(f=f) ntime = len(self.survey.times) self.model = m return self.J.dot(self.actMap.P.T * self.peta) def Jvec(self, m, v, f=None): return self.J.dot(self.actMap.P.T * self.petaDeriv * v) def Jtvec(self, m, v, f=None): return self.petaDeriv.T * (self.actMap.P*(self.J.T.dot(v)))
class StraightRayProblem(Problem.LinearProblem): slowness, slownessMap, slownessDeriv = Props.Invertible( "Slowness model (1/v)") @property def A(self): if getattr(self, '_A', None) is not None: return self._A self._A = sp.lil_matrix((self.survey.nD, self.mesh.nC)) row = 0 for tx in self.survey.txList: for rx in tx.rxList: for loc_i in range(rx.locs.shape[0]): inds, V = lineintegral(self.mesh, tx.loc, rx.locs[loc_i, :]) self._A[inds * 0 + row, inds] = V row += 1 return self._A def fields(self, m): self.model = m return self.A * self.slowness def Jvec(self, m, v, f=None): self.model = m # mt = self.model.transformDeriv # return self.A * ( mt * v ) return self.A * self.slownessDeriv * v def Jtvec(self, m, v, f=None): self.model = m # mt = self.model.transformDeriv # return mt.T * ( self.A.T * v ) return self.slownessDeriv.T * self.A.T * v
class EM1D(Problem.BaseProblem): """ Pseudo analytic solutions for frequency and time domain EM problems assumingLayered earth (1D). """ surveyPair = BaseEM1DSurvey mapPair = Maps.IdentityMap chi = None hankel_filter = 'key_101_2009' # Default: Hankel filter hankel_pts_per_dec = None # Default: Standard DLF verbose = False fix_Jmatrix = False _Jmatrix_sigma = None _Jmatrix_height = None _pred = None sigma, sigmaMap, sigmaDeriv = Props.Invertible( "Electrical conductivity at infinite frequency(S/m)" ) chi = Props.PhysicalProperty( "Magnetic susceptibility", default=0. ) eta, etaMap, etaDeriv = Props.Invertible( "Electrical chargeability (V/V), 0 <= eta < 1", default=0. ) tau, tauMap, tauDeriv = Props.Invertible( "Time constant (s)", default=1. ) c, cMap, cDeriv = Props.Invertible( "Frequency Dependency, 0 < c < 1", default=0.5 ) h, hMap, hDeriv = Props.Invertible( "Receiver Height (m), h > 0", ) def __init__(self, mesh, **kwargs): Problem.BaseProblem.__init__(self, mesh, **kwargs) # Check input arguments. If self.hankel_filter is not a valid filter, # it will set it to the default (key_201_2009). ht, htarg = check_hankel('fht', [self.hankel_filter, self.hankel_pts_per_dec], 1) self.fhtfilt = htarg[0] # Store filter self.hankel_filter = self.fhtfilt.name # Store name self.hankel_pts_per_dec = htarg[1] # Store pts_per_dec if self.verbose: print(">> Use "+self.hankel_filter+" filter for Hankel Transform") if self.hankel_pts_per_dec != 0: raise NotImplementedError() def hz_kernel_vertical_magnetic_dipole( self, lamda, f, n_layer, sig, chi, depth, h, z, flag, output_type='response' ): """ Kernel for vertical magnetic component (Hz) due to vertical magnetic diopole (VMD) source in (kx,ky) domain """ u0 = lamda coefficient_wavenumber = 1/(4*np.pi)*lamda**3/u0 n_frequency = self.survey.n_frequency n_layer = self.survey.n_layer n_filter = self.n_filter if output_type == 'sensitivity_sigma': drTE = np.zeros([n_layer, n_frequency, n_filter], dtype=np.complex128, order='F') rte_fortran.rte_sensitivity(f, lamda, sig, chi, depth, self.survey.half_switch, drTE, n_layer, n_frequency, n_filter) kernel = drTE * np.exp(-u0*(z+h)) * coefficient_wavenumber else: rTE = np.empty([n_frequency, n_filter], dtype=np.complex128, order='F') rte_fortran.rte_forward(f, lamda, sig, chi, depth, self.survey.half_switch, rTE, n_layer, n_frequency, n_filter) kernel = rTE * np.exp(-u0*(z+h)) * coefficient_wavenumber if output_type == 'sensitivity_height': kernel *= -2*u0 return kernel # Note # Here only computes secondary field. # I am not sure why it does not work if we add primary term. # This term can be analytically evaluated, where h = 0. # kernel = ( # 1./(4*np.pi) * # (np.exp(u0*(z-h))+rTE * np.exp(-u0*(z+h)))*lamda**3/u0 # ) # TODO: make this to take a vector rather than a single frequency def hz_kernel_circular_loop( self, lamda, f, n_layer, sig, chi, depth, h, z, I, a, flag, output_type='response' ): """ Kernel for vertical magnetic component (Hz) at the center due to circular loop source in (kx,ky) domain .. math:: H_z = \\frac{Ia}{2} \int_0^{\infty} [e^{-u_0|z+h|} + \\r_{TE}e^{u_0|z-h|}] \\frac{\lambda^2}{u_0} J_1(\lambda a)] d \lambda """ n_frequency = self.survey.n_frequency n_layer = self.survey.n_layer n_filter = self.n_filter w = 2*np.pi*f u0 = lamda radius = np.empty([n_frequency, n_filter], order='F') radius[:, :] = np.tile(a.reshape([-1, 1]), (1, n_filter)) coefficient_wavenumber = I*radius*0.5*lamda**2/u0 if output_type == 'sensitivity_sigma': drTE = np.zeros([n_layer, n_frequency, n_filter], dtype=np.complex128, order='F') rte_fortran.rte_sensitivity(f, lamda, sig, chi, depth, self.survey.half_switch, drTE, n_layer, n_frequency, n_filter) kernel = drTE * np.exp(-u0*(z+h)) * coefficient_wavenumber else: rTE = np.empty([n_frequency, n_filter], dtype=np.complex128, order='F') rte_fortran.rte_forward(f, lamda, sig, chi, depth, self.survey.half_switch, rTE, n_layer, n_frequency, n_filter) if flag == 'secondary': kernel = rTE * np.exp(-u0*(z+h)) * coefficient_wavenumber else: kernel = rTE * ( np.exp(-u0*(z+h)) + np.exp(u0*(z-h)) ) * coefficient_wavenumber if output_type == 'sensitivity_height': kernel *= -2*u0 return kernel def hz_kernel_horizontal_electric_dipole( self, lamda, f, n_layer, sig, chi, depth, h, z, flag, output_type='response' ): """ Kernel for vertical magnetic field (Hz) due to horizontal electric diopole (HED) source in (kx,ky) domain """ n_frequency = self.survey.n_frequency n_layer = self.survey.n_layer n_filter = self.n_filter u0 = lamda coefficient_wavenumber = 1/(4*np.pi)*lamda**2/u0 if output_type == 'sensitivity_sigma': drTE = np.zeros([n_layer, n_frequency, n_filter], dtype=np.complex128, order='F') rte_fortran.rte_sensitivity(f, lamda, sig, chi, depth, self.survey.half_switch, drTE, n_layer, n_frequency, n_filter) kernel = drTE * np.exp(-u0*(z+h)) * coefficient_wavenumber else: rTE = np.empty([n_frequency, n_filter], dtype=np.complex128, order='F') rte_fortran.rte_forward(f, lamda, sig, chi, depth, self.survey.half_switch, rTE, n_layer, n_frequency, n_filter) kernel = rTE * np.exp(-u0*(z+h)) * coefficient_wavenumber if output_type == 'sensitivity_height': kernel *= -2*u0 return kernel # make it as a property? def sigma_cole(self): """ Computes Pelton's Cole-Cole conductivity model in frequency domain. Parameter --------- n_filter: int the number of filter values f: ndarray frequency (Hz) Return ------ sigma_complex: ndarray (n_layer x n_frequency x n_filter) Cole-Cole conductivity values at given frequencies """ n_layer = self.survey.n_layer n_frequency = self.survey.n_frequency n_filter = self.n_filter f = self.survey.frequency sigma = np.tile(self.sigma.reshape([-1, 1]), (1, n_frequency)) if np.isscalar(self.eta): eta = self.eta tau = self.tau c = self.c else: eta = np.tile(self.eta.reshape([-1, 1]), (1, n_frequency)) tau = np.tile(self.tau.reshape([-1, 1]), (1, n_frequency)) c = np.tile(self.c.reshape([-1, 1]), (1, n_frequency)) w = np.tile( 2*np.pi*f, (n_layer, 1) ) sigma_complex = np.empty([n_layer, n_frequency], dtype=np.complex128, order='F') sigma_complex[:, :] = ( sigma - sigma*eta/(1+(1-eta)*(1j*w*tau)**c) ) sigma_complex_tensor = np.empty([n_layer, n_frequency, n_filter], dtype=np.complex128, order='F') sigma_complex_tensor[:, :, :] = np.tile(sigma_complex.reshape( (n_layer, n_frequency, 1)), (1, 1, n_filter) ) return sigma_complex_tensor @property def n_filter(self): """ Length of filter """ return self.fhtfilt.base.size def forward(self, m, output_type='response'): """ Return Bz or dBzdt """ self.model = m n_frequency = self.survey.n_frequency flag = self.survey.field_type n_layer = self.survey.n_layer depth = self.survey.depth I = self.survey.I n_filter = self.n_filter # Get lambd and offset, will depend on pts_per_dec if self.survey.src_type == "VMD": r = self.survey.offset else: # a is the radius of the loop r = self.survey.a * np.ones(n_frequency) # Use function from empymod # size of lambd is (n_frequency x n_filter) lambd = np.empty([self.survey.frequency.size, n_filter], order='F') lambd[:, :], _ = get_spline_values(self.fhtfilt, r, self.hankel_pts_per_dec) # lambd, _ = get_spline_values(self.fhtfilt, r, self.hankel_pts_per_dec) # TODO: potentially store f = np.empty([self.survey.frequency.size, n_filter], order='F') f[:,:] = np.tile(self.survey.frequency.reshape([-1, 1]), (1, n_filter)) # h is an inversion parameter if self.hMap is not None: h = self.h else: h = self.survey.h z = h + self.survey.dz chi = self.chi if np.isscalar(self.chi): chi = np.ones_like(self.sigma) * self.chi # TODO: potentially store sig = self.sigma_cole() if output_type == 'response': # for simulation if self.survey.src_type == 'VMD': hz = self.hz_kernel_vertical_magnetic_dipole( lambd, f, n_layer, sig, chi, depth, h, z, flag, output_type=output_type ) # kernels for each bessel function # (j0, j1, j2) PJ = (hz, None, None) # PJ0 elif self.survey.src_type == 'CircularLoop': hz = self.hz_kernel_circular_loop( lambd, f, n_layer, sig, chi, depth, h, z, I, r, flag, output_type=output_type ) # kernels for each bessel function # (j0, j1, j2) PJ = (None, hz, None) # PJ1 # TODO: This has not implemented yet! elif self.survey.src_type == "piecewise_line": # Need to compute y hz = self.hz_kernel_horizontal_electric_dipole( lambd, f, n_layer, sig, chi, depth, h, z, I, r, flag, output_type=output_type ) # kernels for each bessel function # (j0, j1, j2) PJ = (None, hz, None) # PJ1 else: raise Exception("Src options are only VMD or CircularLoop!!") elif output_type == 'sensitivity_sigma': # for simulation if self.survey.src_type == 'VMD': hz = self.hz_kernel_vertical_magnetic_dipole( lambd, f, n_layer, sig, chi, depth, h, z, flag, output_type=output_type ) PJ = (hz, None, None) # PJ0 elif self.survey.src_type == 'CircularLoop': hz = self.hz_kernel_circular_loop( lambd, f, n_layer, sig, chi, depth, h, z, I, r, flag, output_type=output_type ) PJ = (None, hz, None) # PJ1 else: raise Exception("Src options are only VMD or CircularLoop!!") r = np.tile(r, (n_layer, 1)) elif output_type == 'sensitivity_height': # for simulation if self.survey.src_type == 'VMD': hz = self.hz_kernel_vertical_magnetic_dipole( lambd, f, n_layer, sig, chi, depth, h, z, flag, output_type=output_type ) PJ = (hz, None, None) # PJ0 elif self.survey.src_type == 'CircularLoop': hz = self.hz_kernel_circular_loop( lambd, f, n_layer, sig, chi, depth, h, z, I, r, flag, output_type=output_type ) PJ = (None, hz, None) # PJ1 else: raise Exception("Src options are only VMD or CircularLoop!!") # Carry out Hankel DLF # ab=66 => 33 (vertical magnetic src and rec) # For response # HzFHT size = (n_frequency,) # For sensitivity # HzFHT size = (n_layer, n_frequency) HzFHT = dlf(PJ, lambd, r, self.fhtfilt, self.hankel_pts_per_dec, factAng=None, ab=33) if output_type == "sensitivity_sigma": return HzFHT.T return HzFHT # @profile def fields(self, m): f = self.forward(m, output_type='response') self.survey._pred = Utils.mkvc(self.survey.projectFields(f)) return f def getJ_height(self, m, f=None): """ """ if self.hMap is None: return Utils.Zero() if self._Jmatrix_height is not None: return self._Jmatrix_height else: if self.verbose: print (">> Compute J height ") dudz = self.forward(m, output_type="sensitivity_height") self._Jmatrix_height = ( self.survey.projectFields(dudz) ).reshape([-1, 1]) return self._Jmatrix_height # @profile def getJ_sigma(self, m, f=None): if self.sigmaMap is None: return Utils.Zero() if self._Jmatrix_sigma is not None: return self._Jmatrix_sigma else: if self.verbose: print (">> Compute J sigma") dudsig = self.forward(m, output_type="sensitivity_sigma") self._Jmatrix_sigma = self.survey.projectFields(dudsig) if self._Jmatrix_sigma.ndim == 1: self._Jmatrix_sigma = self._Jmatrix_sigma.reshape([-1, 1]) return self._Jmatrix_sigma def getJ(self, m, f=None): return ( self.getJ_sigma(m, f=f) * self.sigmaDeriv + self.getJ_height(m, f=f) * self.hDeriv ) def Jvec(self, m, v, f=None): """ Computing Jacobian^T multiplied by vector. """ J_sigma = self.getJ_sigma(m, f=f) J_height = self.getJ_height(m, f=f) Jv = np.dot(J_sigma, self.sigmaMap.deriv(m, v)) if self.hMap is not None: Jv += np.dot(J_height, self.hMap.deriv(m, v)) return Jv def Jtvec(self, m, v, f=None): """ Computing Jacobian^T multiplied by vector. """ J_sigma = self.getJ_sigma(m, f=f) J_height = self.getJ_height(m, f=f) Jtv = self.sigmaDeriv.T*np.dot(J_sigma.T, v) if self.hMap is not None: Jtv += self.hDeriv.T*np.dot(J_height.T, v) return Jtv @property def deleteTheseOnModelUpdate(self): toDelete = [] if self.fix_Jmatrix is False: if self._Jmatrix_sigma is not None: toDelete += ['_Jmatrix_sigma'] if self._Jmatrix_height is not None: toDelete += ['_Jmatrix_height'] return toDelete def depth_of_investigation_christiansen_2012(self, std, thres_hold=0.8): pred = self.survey._pred.copy() delta_d = std * np.log(abs(self.survey.dobs)) J = self.getJ(self.model) J_sum = abs(Utils.sdiag(1/delta_d/pred) * J).sum(axis=0) S = np.cumsum(J_sum[::-1])[::-1] active = S-thres_hold > 0. doi = abs(self.survey.depth[active]).max() return doi, active def get_threshold(self, uncert): _, active = self.depth_of_investigation(uncert) JtJdiag = self.get_JtJdiag(uncert) delta = JtJdiag[active].min() return delta def get_JtJdiag(self, uncert): J = self.getJ(self.model) JtJdiag = (np.power((Utils.sdiag(1./uncert)*J), 2)).sum(axis=0) return JtJdiag
class BaseEMIPProblem(BaseEMProblem): sigmaInf, sigmaInfMap, sigmaInfDeriv = Props.Invertible( "Electrical conductivity at infinite frequency (S/m)") eta, etaMap, etaDeriv = Props.Invertible("Cole-Cole chargeability (V/V)") tau, tauMap, tauDeriv = Props.Invertible("Cole-Cole time constant (s)") c, cMap, cDeriv = Props.Invertible("Cole-Cole frequency dependency") surveyPair = Survey.BaseSurvey #: The survey to pair with. dataPair = Survey.Data #: The data to pair with. mapPair = Maps.IdentityMap #: Type of mapping to pair with Solver = SimpegSolver #: Type of solver to pair with solverOpts = {} #: Solver options verbose = False #################################################### # Mass Matrices #################################################### @property def deleteTheseOnModelUpdate(self): toDelete = [] if self.sigmaInfMap is not None: toDelete += [ '_MeSigmaInf', '_MeSigmaInfI', '_MeSigma0', '_MeSigma0I' ] if hasattr(self, 'muMap') or hasattr(self, 'muiMap'): if self.muMap is not None or self.muiMap is not None: toDelete += ['_MeMu', '_MeMuI', '_MfMui', '_MfMuiI'] return toDelete #################################################### # Electrical Conductivity #################################################### @property def MeSigmaInf(self): """ Edge inner product matrix for \\(\\sigmaInf\\). Used in the E-B formulation """ if getattr(self, '_MeSigmaInf', None) is None: self._MeSigmaInf = self.mesh.getEdgeInnerProduct(self.sigmaInf) return self._MeSigmaInf # TODO: This should take a vector def MeSigmaInfDeriv(self, u): """ Derivative of MeSigmaInf with respect to the model """ if self.sigmaInfMap is None: return Utils.Zero() return (self.mesh.getEdgeInnerProductDeriv(self.sigmaInf)(u) * self.sigmaInfDeriv) @property def MeSigmaInfI(self): """ Inverse of the edge inner product matrix for \\(\\sigmaInf\\). """ if getattr(self, '_MeSigmaInfI', None) is None: self._MeSigmaInfI = self.mesh.getEdgeInnerProduct(self.sigmaInf, invMat=True) return self._MeSigmaInfI # TODO: This should take a vector def MeSigmaInfIDeriv(self, u): """ Derivative of :code:`MeSigmaInfI` with respect to the model """ if self.sigmaInfMap is None: return Utils.Zero() if len(self.sigmaInf.shape) > 1: if self.sigmaInf.shape[1] > self.mesh.dim: raise NotImplementedError( "Full anisotropy is not implemented for MeSigmaInfIDeriv.") dMeSigmaInfI_dI = -self.MeSigmaInfI**2 dMe_dsig = self.mesh.getEdgeInnerProductDeriv(self.sigmaInf)(u) return dMeSigmaInfI_dI * (dMe_dsig * self.sigmaInfDeriv) @property def MeSigma0(self): """ Edge inner product matrix for \\(\\sigma0\\). Used in the E-B formulation """ if getattr(self, '_MeSigma0', None) is None: self._MeSigma0 = self.mesh.getEdgeInnerProduct(self.sigmaInf * (1. - self.eta)) return self._MeSigma0 @property def MeSigma0(self): """ Edge inner product matrix for \\(\\sigma0\\). Used in the E-B formulation """ if getattr(self, '_MeSigma0', None) is None: self._MeSigma0 = self.mesh.getEdgeInnerProduct(self.sigmaInf * (1. - self.eta))
class BaseIPProblem(BaseEMProblem): sigma = Props.PhysicalProperty("Electrical conductivity (S/m)") rho = Props.PhysicalProperty("Electrical resistivity (Ohm m)") Props.Reciprocal(sigma, rho) eta, etaMap, etaDeriv = Props.Invertible("Electrical Chargeability") surveyPair = Survey fieldsPair = FieldsDC Ainv = None _f = None storeJ = False _Jmatrix = None sign = None def fields(self, m): if self.verbose is True: print(">> Compute fields") if self._f is None: self._f = self.fieldsPair(self.mesh, self.survey) if self.Ainv is None: A = self.getA() self.Ainv = self.Solver(A, **self.solverOpts) RHS = self.getRHS() u = self.Ainv * RHS Srcs = self.survey.srcList self._f[Srcs, self._solutionType] = u return self._f def getJ(self, m, f=None): """ Generate Full sensitivity matrix """ self.model = m if self.verbose: print("Calculating J and storing") if self._Jmatrix is not None: return self._Jmatrix else: if f is None: f = self.fields(m) self._Jmatrix = (self._Jtvec(m, v=None, f=f)).T # delete fields after computing sensitivity del f # Not sure why this is a problem # if self._f is not None: # del self._f # clean all factorization if self.Ainv is not None: self.Ainv.clean() return self._Jmatrix def Jvec(self, m, v, f=None): self.model = m # When sensitivity matrix J is stored if self.storeJ: J = self.getJ(m, f=f) Jv = Utils.mkvc(np.dot(J, v)) return self.sign * Jv else: if f is None: f = self.fields(m) Jv = [] for src in self.survey.srcList: u_src = f[src, self._solutionType] # solution vector dA_dm_v = self.getADeriv(u_src.flatten(), v, adjoint=False) dRHS_dm_v = self.getRHSDeriv(src, v) du_dm_v = self.Ainv * (-dA_dm_v + dRHS_dm_v) for rx in src.rxList: df_dmFun = getattr(f, '_{0!s}Deriv'.format(rx.projField), None) df_dm_v = df_dmFun(src, du_dm_v, v, adjoint=False) Jv.append(rx.evalDeriv(src, self.mesh, f, df_dm_v)) # Conductivity (d u / d log sigma) - EB form # Resistivity (d u / d log rho) - HJ form return self.sign * np.hstack(Jv) def Jtvec(self, m, v, f=None): """ Compute adjoint sensitivity matrix (J^T) and vector (v) product. """ # When sensitivity matrix J is stored if self.storeJ: J = self.getJ(m, f=f) Jtv = Utils.mkvc(np.dot(J.T, v)) return self.sign * Jtv else: self.model = m if f is None: f = self.fields(m) return self._Jtvec(m, v=v, f=f) def _Jtvec(self, m, v=None, f=None): """ Compute adjoint sensitivity matrix (J^T) and vector (v) product. Full J matrix can be computed by inputing v=None """ if v is not None: # Ensure v is a data object. if not isinstance(v, self.dataPair): v = self.dataPair(self.survey, v) Jtv = np.zeros(m.size) else: # This is for forming full sensitivity matrix Jtv = np.zeros((self.model.size, self.survey.nD), order='F') istrt = int(0) iend = int(0) for isrc, src in enumerate(self.survey.srcList): u_src = f[src, self._solutionType] if self.storeJ: # TODO: use logging package sys.stdout.write(("\r %d / %d") % (isrc + 1, self.survey.nSrc)) sys.stdout.flush() for rx in src.rxList: if v is not None: PTv = rx.evalDeriv( src, self.mesh, f, v[src, rx], adjoint=True) # wrt f, need possibility wrt m df_duTFun = getattr(f, '_{0!s}Deriv'.format(rx.projField), None) df_duT, df_dmT = df_duTFun(src, None, PTv, adjoint=True) ATinvdf_duT = self.Ainv * df_duT dA_dmT = self.getADeriv(u_src.flatten(), ATinvdf_duT, adjoint=True) dRHS_dmT = self.getRHSDeriv(src, ATinvdf_duT, adjoint=True) du_dmT = -dA_dmT + dRHS_dmT Jtv += (df_dmT + du_dmT).astype(float) else: P = rx.getP(self.mesh, rx.projGLoc(f)).toarray() ATinvdf_duT = self.Ainv * (P.T) dA_dmT = self.getADeriv(u_src, ATinvdf_duT, adjoint=True) iend = istrt + rx.nD if rx.nD == 1: Jtv[:, istrt] = dA_dmT else: Jtv[:, istrt:iend] = dA_dmT istrt += rx.nD # Conductivity ((d u / d log sigma).T) - EB form # Resistivity ((d u / d log rho).T) - HJ form if v is not None: return self.sign * Utils.mkvc(Jtv) else: return Jtv return def getSourceTerm(self): """ takes concept of source and turns it into a matrix """ """ Evaluates the sources, and puts them in matrix form :rtype: (numpy.ndarray, numpy.ndarray) :return: q (nC or nN, nSrc) """ Srcs = self.survey.srcList if self._formulation == 'EB': n = self.mesh.nN # return NotImplementedError elif self._formulation == 'HJ': n = self.mesh.nC q = np.zeros((n, len(Srcs))) for i, src in enumerate(Srcs): q[:, i] = src.eval(self) return q def delete_these_for_sensitivity(self): del self._Jmatrix, self._MfRhoI, self._MeSigma @property def deleteTheseOnModelUpdate(self): toDelete = [] return toDelete @property def MfRhoDerivMat(self): """ Derivative of MfRho with respect to the model """ if getattr(self, '_MfRhoDerivMat', None) is None: drho_dlogrho = Utils.sdiag(self.rho) * self.etaDeriv self._MfRhoDerivMat = self.mesh.getFaceInnerProductDeriv( np.ones(self.mesh.nC))(np.ones(self.mesh.nF)) * drho_dlogrho return self._MfRhoDerivMat def MfRhoIDeriv(self, u, v, adjoint=False): """ Derivative of :code:`MfRhoI` with respect to the model. """ dMfRhoI_dI = -self.MfRhoI**2 if self.storeInnerProduct: if adjoint: return self.MfRhoDerivMat.T * (Utils.sdiag(u) * (dMfRhoI_dI.T * v)) else: return dMfRhoI_dI * (Utils.sdiag(u) * (self.MfRhoDerivMat * v)) else: dMf_drho = self.mesh.getFaceInnerProductDeriv(self.rho)(u) drho_dlogrho = Utils.sdiag(self.rho) * self.etaDeriv if adjoint: return drho_dlogrho.T * (dMf_drho.T * (dMfRhoI_dI.T * v)) else: return dMfRhoI_dI * (dMf_drho * (drho_dlogrho * v)) @property def MeSigmaDerivMat(self): """ Derivative of MeSigma with respect to the model """ if getattr(self, '_MeSigmaDerivMat', None) is None: dsigma_dlogsigma = Utils.sdiag(self.sigma) * self.etaDeriv self._MeSigmaDerivMat = self.mesh.getEdgeInnerProductDeriv( np.ones(self.mesh.nC))(np.ones( self.mesh.nE)) * dsigma_dlogsigma return self._MeSigmaDerivMat # TODO: This should take a vector def MeSigmaDeriv(self, u, v, adjoint=False): """ Derivative of MeSigma with respect to the model times a vector (u) """ if self.storeInnerProduct: if adjoint: return self.MeSigmaDerivMat.T * (Utils.sdiag(u) * v) else: return Utils.sdiag(u) * (self.MeSigmaDerivMat * v) else: dsigma_dlogsigma = Utils.sdiag(self.sigma) * self.etaDeriv if adjoint: return ( dsigma_dlogsigma.T * (self.mesh.getEdgeInnerProductDeriv(self.sigma)(u).T * v)) else: return (self.mesh.getEdgeInnerProductDeriv(self.sigma)(u) * (dsigma_dlogsigma * v))
class BaseFDEMProblem(BaseEMProblem): """ We start by looking at Maxwell's equations in the electric field \\\(\\\mathbf{e}\\\) and the magnetic flux density \\\(\\\mathbf{b}\\\) .. math :: \mathbf{C} \mathbf{e} + i \omega \mathbf{b} = \mathbf{s_m} \\\\ {\mathbf{C}^{\\top} \mathbf{M_{\mu^{-1}}^f} \mathbf{b} - \mathbf{M_{\sigma}^e} \mathbf{e} = \mathbf{s_e}} if using the E-B formulation (:code:`Problem3D_e` or :code:`Problem3D_b`). Note that in this case, :math:`\mathbf{s_e}` is an integrated quantity. If we write Maxwell's equations in terms of \\\(\\\mathbf{h}\\\) and current density \\\(\\\mathbf{j}\\\) .. math :: \mathbf{C}^{\\top} \mathbf{M_{\\rho}^f} \mathbf{j} + i \omega \mathbf{M_{\mu}^e} \mathbf{h} = \mathbf{s_m} \\\\ \mathbf{C} \mathbf{h} - \mathbf{j} = \mathbf{s_e} if using the H-J formulation (:code:`Problem3D_j` or :code:`Problem3D_h`). Note that here, :math:`\mathbf{s_m}` is an integrated quantity. The problem performs the elimination so that we are solving the system for \\\(\\\mathbf{e},\\\mathbf{b},\\\mathbf{j} \\\) or \\\(\\\mathbf{h}\\\) """ surveyPair = SurveyFDEM fieldsPair = FieldsFDEM mu, muMap, muDeriv = Props.Invertible("Magnetic Permeability (H/m)", default=mu_0) mui, muiMap, muiDeriv = Props.Invertible( "Inverse Magnetic Permeability (m/H)") Props.Reciprocal(mu, mui) def fields(self, m=None): """ Solve the forward problem for the fields. :param numpy.ndarray m: inversion model (nP,) :rtype: numpy.ndarray :return f: forward solution """ if m is not None: self.model = m f = self.fieldsPair(self.mesh, self.survey) for freq in self.survey.freqs: A = self.getA(freq) rhs = self.getRHS(freq) Ainv = self.Solver(A, **self.solverOpts) u = Ainv * rhs Srcs = self.survey.getSrcByFreq(freq) f[Srcs, self._solutionType] = u Ainv.clean() return f def Jvec(self, m, v, f=None): """ Sensitivity times a vector. :param numpy.ndarray m: inversion model (nP,) :param numpy.ndarray v: vector which we take sensitivity product with (nP,) :param SimPEG.EM.FDEM.FieldsFDEM.FieldsFDEM u: fields object :rtype: numpy.ndarray :return: Jv (ndata,) """ if f is None: f = self.fields(m) self.model = m # Jv = self.dataPair(self.survey) Jv = [] for freq in self.survey.freqs: A = self.getA(freq) # create the concept of Ainv (actually a solve) Ainv = self.Solver(A, **self.solverOpts) for src in self.survey.getSrcByFreq(freq): u_src = f[src, self._solutionType] dA_dm_v = self.getADeriv(freq, u_src, v, adjoint=False) dRHS_dm_v = self.getRHSDeriv(freq, src, v) du_dm_v = Ainv * (-dA_dm_v + dRHS_dm_v) for rx in src.rxList: Jv.append( rx.evalDeriv(src, self.mesh, f, du_dm_v=du_dm_v, v=v)) Ainv.clean() return np.hstack(Jv) def Jtvec(self, m, v, f=None): """ Sensitivity transpose times a vector :param numpy.ndarray m: inversion model (nP,) :param numpy.ndarray v: vector which we take adjoint product with (nP,) :param SimPEG.EM.FDEM.FieldsFDEM.FieldsFDEM u: fields object :rtype: numpy.ndarray :return: Jv (ndata,) """ if f is None: f = self.fields(m) self.model = m # Ensure v is a data object. if not isinstance(v, self.dataPair): v = self.dataPair(self.survey, v) Jtv = np.zeros(m.size) for freq in self.survey.freqs: AT = self.getA(freq).T ATinv = self.Solver(AT, **self.solverOpts) for src in self.survey.getSrcByFreq(freq): u_src = f[src, self._solutionType] for rx in src.rxList: df_duT, df_dmT = rx.evalDeriv(src, self.mesh, f, v=v[src, rx], adjoint=True) ATinvdf_duT = ATinv * df_duT dA_dmT = self.getADeriv(freq, u_src, ATinvdf_duT, adjoint=True) dRHS_dmT = self.getRHSDeriv(freq, src, ATinvdf_duT, adjoint=True) du_dmT = -dA_dmT + dRHS_dmT df_dmT = df_dmT + du_dmT # TODO: this should be taken care of by the reciever? if rx.component is 'real': Jtv += np.array(df_dmT, dtype=complex).real elif rx.component is 'imag': Jtv += -np.array(df_dmT, dtype=complex).real else: raise Exception('Must be real or imag') ATinv.clean() return Utils.mkvc(Jtv) def getSourceTerm(self, freq): """ Evaluates the sources for a given frequency and puts them in matrix form :param float freq: Frequency :rtype: tuple :return: (s_m, s_e) (nE or nF, nSrc) """ Srcs = self.survey.getSrcByFreq(freq) if self._formulation is 'EB': s_m = np.zeros((self.mesh.nF, len(Srcs)), dtype=complex) s_e = np.zeros((self.mesh.nE, len(Srcs)), dtype=complex) elif self._formulation is 'HJ': s_m = np.zeros((self.mesh.nE, len(Srcs)), dtype=complex) s_e = np.zeros((self.mesh.nF, len(Srcs)), dtype=complex) for i, src in enumerate(Srcs): smi, sei = src.eval(self) s_m[:, i] = s_m[:, i] + smi s_e[:, i] = s_e[:, i] + sei return s_m, s_e
class Vangenuchten_k(BaseHydraulicConductivity): Ks, KsMap, KsDeriv = Props.Invertible("Saturated hydraulic conductivity", default=24.96) I, IMap, IDeriv = Props.Invertible("", default=0.5) n, nMap, nDeriv = Props.Invertible( "measure of the pore-size distribution, >1", default=1.56) alpha, alphaMap, alphaDeriv = Props.Invertible( "related to the inverse of the air entry suction [L-1], >0", default=0.036) def _get_params(self): alpha = self.alpha I = self.I n = self.n Ks = self.Ks m = 1.0 - 1.0 / n return Ks, alpha, I, n, m def __call__(self, u): Ks, alpha, I, n, m = self._get_params() P_p, P_n = _get_projections(u) # Compute the positive/negative domains theta_e = 1.0 / ((1.0 + abs(alpha * u)**n)**m) f_p = P_p * np.ones(len(u)) * Ks # ensures scalar Ks works f_n = P_n * Ks * theta_e**I * ((1.0 - (1.0 - theta_e**(1.0 / m))**m)**2) return f_p + f_n def derivM(self, u): """derivative with respect to m .. code:: import sympy as sy alpha, u, n, I, Ks, theta_r, theta_s = sy.symbols( 'alpha u n I Ks theta_r theta_s', real=True ) m = 1.0 - 1.0/n theta_e = 1.0 / ((1.0 + sy.functions.Abs(alpha * u) ** n) ** m) f_n = Ks * theta_e ** I * ( (1.0 - (1.0 - theta_e ** (1.0 / m)) ** m) ** 2 ) f_n = ( ( theta_s - theta_r ) / ( (1.0 + abs(alpha * u)**n) ** (1.0 - 1.0 / n) ) + theta_r ) """ return (self._derivKs(u) + self._derivI(u) + self._derivN(u) + self._derivAlpha(u)) def _derivKs(self, u): if self.KsMap is None: return Utils.Zero() Ks, alpha, I, n, m = self._get_params() P_p, P_n = _get_projections(u) # Compute the positive/negative domains theta_e = 1.0 / ((1.0 + abs(alpha * u)**n)**m) dKs_dm_p = P_p * self.KsDeriv dKs_dm_n = P_n * Utils.sdiag(theta_e**I * ( (1.0 - (1.0 - theta_e**(1.0 / m))**m)**2)) * self.KsDeriv return dKs_dm_p + dKs_dm_n def _derivAlpha(self, u): if self.alphaMap is None: return Utils.Zero() Ks, alpha, I, n, m = self._get_params() ddm = I * u * n * Ks * abs(alpha * u)**(n - 1) * np.sign(alpha * u) * ( 1.0 / n - 1 ) * ((abs(alpha * u)**n + 1)**(1.0 / n - 1))**(I - 1) * ( (1 - 1.0 / ((abs(alpha * u)**n + 1)**(1.0 / n - 1))**(1.0 / (1.0 / n - 1)))** (1 - 1.0 / n) - 1)**2 * (abs(alpha * u)**n + 1)**(1.0 / n - 2) - ( 2 * u * n * Ks * abs(alpha * u)**(n - 1) * np.sign(alpha * u) * (1.0 / n - 1) * ((abs(alpha * u)**n + 1)**(1.0 / n - 1))**I * ((1 - 1.0 / ((abs(alpha * u)**n + 1)**(1.0 / n - 1))** (1.0 / (1.0 / n - 1)))**(1 - 1.0 / n) - 1) * (abs(alpha * u)**n + 1)**(1.0 / n - 2)) / ( ((abs(alpha * u)**n + 1)** (1.0 / n - 1))**(1.0 / (1.0 / n - 1) + 1) * (1 - 1.0 / ((abs(alpha * u)**n + 1)**(1.0 / n - 1))** (1.0 / (1.0 / n - 1)))**(1.0 / n)) ddm[u >= 0] = 0 dA = Utils.sdiag(ddm) * self.alphaDeriv return dA def _derivN(self, u): if self.nMap is None: return Utils.Zero() Ks, alpha, I, n, m = self._get_params() ddm = 1.0 * I * Ks * ( 1.0 * (abs(alpha * u)**n + 1.0)**(-1.0 + 1.0 / n))**I * ( (-1.0 + 1.0 / n) * np.log(abs(alpha * u)) * abs(alpha * u)**n / (abs(alpha * u)**n + 1.0) - 1.0 * np.log(abs(alpha * u)**n + 1.0) / n**2) * ( -(-(1.0 * (abs(alpha * u)**n + 1.0)**(-1.0 + 1.0 / n))** (1.0 / (1.0 - 1.0 / n)) + 1.0)**(1.0 - 1.0 / n) + 1.0 )**2 * (abs(alpha * u)**n + 1.0)**(-1.0 + 1.0 / n) * ( abs(alpha * u)**n + 1.0)**(1.0 - 1.0 / n) - 2 * Ks * ( 1.0 * (abs(alpha * u)**n + 1.0)**(-1.0 + 1.0 / n))**I * ( -(1.0 * (abs(alpha * u)**n + 1.0)**(-1.0 + 1.0 / n))** (1.0 / (1.0 - 1.0 / n)) + 1.0)**(1.0 - 1.0 / n) * ( -(1.0 * (abs(alpha * u)**n + 1.0)** (-1.0 + 1.0 / n))**(1.0 / (1.0 - 1.0 / n)) * (1.0 - 1.0 / n) * (1.0 * ((-1.0 + 1.0 / n) * np.log(abs(alpha * u)) * abs(alpha * u)**n / (abs(alpha * u)**n + 1.0) - 1.0 * np.log(abs(alpha * u)**n + 1.0) / n**2) * (abs(alpha * u)**n + 1.0)**(-1.0 + 1.0 / n) * (abs(alpha * u)**n + 1.0)**(1.0 - 1.0 / n) / (1.0 - 1.0 / n) - 1.0 * np.log(1.0 * (abs(alpha * u)**n + 1.0)** (-1.0 + 1.0 / n)) / (n**2 * (1.0 - 1.0 / n)**2)) / (-(1.0 * (abs(alpha * u)**n + 1.0)** (-1.0 + 1.0 / n))**(1.0 / (1.0 - 1.0 / n)) + 1.0) + 1.0 * np.log(-(1.0 * (abs(alpha * u)**n + 1.0)** (-1.0 + 1.0 / n))** (1.0 / (1.0 - 1.0 / n)) + 1.0) / n**2) * (-(-(1.0 * (abs(alpha * u)**n + 1.0)** (-1.0 + 1.0 / n))** (1.0 / (1.0 - 1.0 / n)) + 1.0)** (1.0 - 1.0 / n) + 1.0) ddm[u >= 0] = 0 dn = Utils.sdiag(ddm) * self.nDeriv return dn def _derivI(self, u): if self.IMap is None: return Utils.Zero() Ks, alpha, I, n, m = self._get_params() ddm = Ks * (1.0 * (abs(alpha * u)**n + 1.0)**(-1.0 + 1.0 / n))**I * ( -(-(1.0 * (abs(alpha * u)**n + 1.0)**(-1.0 + 1.0 / n))** (1.0 / (1.0 - 1.0 / n)) + 1.0)**(1.0 - 1.0 / n) + 1.0)**2 * np.log(1.0 * (abs(alpha * u)**n + 1.0)**(-1.0 + 1.0 / n)) ddm[u >= 0] = 0 dI = Utils.sdiag(ddm) * self.IDeriv return dI def derivU(self, u): Ks, alpha, I, n, m = self._get_params() ddm = I * alpha * n * Ks * abs(alpha * u)**(n - 1.0) * np.sign( alpha * u) * (1.0 / n - 1.0) * ( (abs(alpha * u)**n + 1)**(1.0 / n - 1))**(I - 1) * ( (1 - 1.0 / ((abs(alpha * u)**n + 1)**(1.0 / n - 1))** (1.0 / (1.0 / n - 1)))**(1 - 1.0 / n) - 1)**2 * (abs(alpha * u)**n + 1)**(1.0 / n - 2) - ( 2 * alpha * n * Ks * abs(alpha * u)** (n - 1) * np.sign(alpha * u) * (1.0 / n - 1) * ((abs(alpha * u)**n + 1)**(1.0 / n - 1))**I * ((1 - 1.0 / ((abs(alpha * u)**n + 1)**(1.0 / n - 1))** (1.0 / (1.0 / n - 1)))**(1 - 1.0 / n) - 1) * (abs(alpha * u)**n + 1)**(1.0 / n - 2)) / ( ((abs(alpha * u)**n + 1)** (1.0 / n - 1))**(1.0 / (1.0 / n - 1) + 1) * (1 - 1.0 / ((abs(alpha * u)**n + 1)**(1.0 / n - 1))** (1.0 / (1.0 / n - 1)))**(1.0 / n)) ddm[u >= 0] = 0 g = Utils.sdiag(ddm) return g
class ShortcutExample(Props.HasModel): sigma, sigmaMap, sigmaDeriv = Props.Invertible( "Electrical conductivity (S/m)")
class GravityIntegral(Problem.LinearProblem): rho, rhoMap, rhoDeriv = Props.Invertible( "Specific density (g/cc)", default=1. ) # surveyPair = Survey.LinearSurvey forwardOnly = False # Is TRUE, forward matrix not stored to memory actInd = None #: Active cell indices provided rx_type = 'z' silent = False memory_saving_mode = False parallelized = False n_cpu = None progress_index = -1 gtgdiag = None aa = [] def __init__(self, mesh, **kwargs): Problem.BaseProblem.__init__(self, mesh, **kwargs) def fields(self, m): self.model = self.rhoMap*m if self.forwardOnly: # Compute the linear operation without forming the full dense G fields = self.Intrgl_Fwr_Op() return mkvc(fields) else: vec = np.dot(self.G, (self.model).astype(np.float32)) return vec.astype(np.float64) def mapping(self): """ Return rhoMap """ return self.rhoMap def getJtJdiag(self, m, W=None): """ Return the diagonal of JtJ """ if self.gtgdiag is None: if W is None: w = np.ones(self.G.shape[1]) else: w = W.diagonal() dmudm = self.rhoMap.deriv(m) self.gtgdiag = np.zeros(dmudm.shape[1]) for ii in range(self.G.shape[0]): self.gtgdiag += (w[ii]*self.G[ii, :]*dmudm)**2. return self.gtgdiag def getJ(self, m, f=None): """ Sensitivity matrix """ return self.G def Jvec(self, m, v, f=None): dmudm = self.rhoMap.deriv(m) return self.G.dot(dmudm*v) def Jtvec(self, m, v, f=None): dmudm = self.rhoMap.deriv(m) return dmudm.T * (self.G.T.dot(v)) @property def G(self): if not self.ispaired: raise Exception('Need to pair!') if getattr(self, '_G', None) is None: print("Begin linear forward calculation: " + self.rx_type) start = time.time() self._G = self.Intrgl_Fwr_Op() print("Linear forward calculation ended in: " + str(time.time()-start) + " sec") return self._G def Intrgl_Fwr_Op(self, m=None, rx_type='z'): """ Gravity forward operator in integral form flag = 'z' | 'xyz' Return _G = Linear forward modeling operation Created on March, 15th 2016 @author: dominiquef """ if m is not None: self.model = self.rhoMap*m if getattr(self, 'actInd', None) is not None: if self.actInd.dtype == 'bool': inds = np.asarray([inds for inds, elem in enumerate(self.actInd, 1) if elem], dtype=int) - 1 else: inds = self.actInd else: inds = np.asarray(range(self.mesh.nC)) self.nC = len(inds) # Create active cell projector P = sp.sparse.csr_matrix( (np.ones(self.nC), (inds, range(self.nC))), shape=(self.mesh.nC, self.nC) ) # Create vectors of nodal location # (lower and upper corners for each cell) if isinstance(self.mesh, Mesh.TreeMesh): # Get upper and lower corners of each cell bsw = (self.mesh.gridCC - np.kron(self.mesh.vol.T**(1/3)/2, np.ones(3)).reshape((self.mesh.nC, 3))) tne = (self.mesh.gridCC + np.kron(self.mesh.vol.T**(1/3)/2, np.ones(3)).reshape((self.mesh.nC, 3))) xn1, xn2 = bsw[:, 0], tne[:, 0] yn1, yn2 = bsw[:, 1], tne[:, 1] zn1, zn2 = bsw[:, 2], tne[:, 2] else: xn = self.mesh.vectorNx yn = self.mesh.vectorNy zn = self.mesh.vectorNz yn2, xn2, zn2 = np.meshgrid(yn[1:], xn[1:], zn[1:]) yn1, xn1, zn1 = np.meshgrid(yn[:-1], xn[:-1], zn[:-1]) self.Yn = P.T*np.c_[Utils.mkvc(yn1), Utils.mkvc(yn2)] self.Xn = P.T*np.c_[Utils.mkvc(xn1), Utils.mkvc(xn2)] self.Zn = P.T*np.c_[Utils.mkvc(zn1), Utils.mkvc(zn2)] self.rxLoc = self.survey.srcField.rxList[0].locs self.nD = int(self.rxLoc.shape[0]) # if self.n_cpu is None: # self.n_cpu = multiprocessing.cpu_count() # Switch to determine if the process has to be run in parallel job = Forward( rxLoc=self.rxLoc, Xn=self.Xn, Yn=self.Yn, Zn=self.Zn, n_cpu=self.n_cpu, forwardOnly=self.forwardOnly, model=self.model, rx_type=self.rx_type, parallelized=self.parallelized ) G = job.calculate() return G @property def mapPair(self): """ Call for general mapping of the problem """ return self.rhoMap
class BaseIPProblem(BaseEMProblem): sigma = Props.PhysicalProperty("Electrical conductivity (S/m)") rho = Props.PhysicalProperty("Electrical resistivity (Ohm m)") Props.Reciprocal(sigma, rho) eta, etaMap, etaDeriv = Props.Invertible("Electrical Chargeability") surveyPair = Survey fieldsPair = FieldsDC Ainv = None f = None Ainv = None def fields(self, m): if m is not None: self.model = m if self.f is None: self.f = self.fieldsPair(self.mesh, self.survey) if self.Ainv is None: A = self.getA() self.Ainv = self.Solver(A, **self.solverOpts) RHS = self.getRHS() u = self.Ainv * RHS Srcs = self.survey.srcList self.f[Srcs, self._solutionType] = u return self.f def Jvec(self, m, v, f=None): if f is None: f = self.fields(m) self.model = m Jv = [] A = self.getA() for src in self.survey.srcList: u_src = f[src, self._solutionType] # solution vector dA_dm_v = self.getADeriv(u_src, v) dRHS_dm_v = self.getRHSDeriv(src, v) du_dm_v = self.Ainv * (-dA_dm_v + dRHS_dm_v) for rx in src.rxList: df_dmFun = getattr(f, '_{0!s}Deriv'.format(rx.projField), None) df_dm_v = df_dmFun(src, du_dm_v, v, adjoint=False) # Jv[src, rx] = rx.evalDeriv(src, self.mesh, f, df_dm_v) Jv.append(rx.evalDeriv(src, self.mesh, f, df_dm_v)) # Conductivity (d u / d log sigma) if self._formulation == 'EB': # return -Utils.mkvc(Jv) return -np.hstack(Jv) # Conductivity (d u / d log rho) if self._formulation == 'HJ': # return Utils.mkvc(Jv) return np.hstack(Jv) def Jtvec(self, m, v, f=None): if f is None: f = self.fields(m) self.model = m # Ensure v is a data object. if not isinstance(v, self.dataPair): v = self.dataPair(self.survey, v) Jtv = np.zeros(m.size) AT = self.getA() for src in self.survey.srcList: u_src = f[src, self._solutionType] for rx in src.rxList: PTv = rx.evalDeriv( src, self.mesh, f, v[src, rx], adjoint=True) # wrt f, need possibility wrt m df_duTFun = getattr(f, '_{0!s}Deriv'.format(rx.projField), None) df_duT, df_dmT = df_duTFun(src, None, PTv, adjoint=True) ATinvdf_duT = self.Ainv * df_duT dA_dmT = self.getADeriv(u_src, ATinvdf_duT, adjoint=True) dRHS_dmT = self.getRHSDeriv(src, ATinvdf_duT, adjoint=True) du_dmT = -dA_dmT + dRHS_dmT Jtv += (df_dmT + du_dmT).astype(float) # Conductivity ((d u / d log sigma).T) if self._formulation == 'EB': return -Utils.mkvc(Jtv) # Conductivity ((d u / d log rho).T) if self._formulation == 'HJ': return Utils.mkvc(Jtv) def getSourceTerm(self): """ takes concept of source and turns it into a matrix """ """ Evaluates the sources, and puts them in matrix form :rtype: (numpy.ndarray, numpy.ndarray) :return: q (nC or nN, nSrc) """ Srcs = self.survey.srcList if self._formulation == 'EB': n = self.mesh.nN # return NotImplementedError elif self._formulation == 'HJ': n = self.mesh.nC q = np.zeros((n, len(Srcs))) for i, src in enumerate(Srcs): q[:, i] = src.eval(self) return q @property def deleteTheseOnModelUpdate(self): toDelete = [] return toDelete # assume log rho or log cond @property def MeSigma(self): """ Edge inner product matrix for \\(\\sigma\\). Used in the E-B formulation """ if getattr(self, '_MeSigma', None) is None: self._MeSigma = self.mesh.getEdgeInnerProduct(self.sigma) return self._MeSigma @property def MfRhoI(self): """ Inverse of :code:`MfRho` """ if getattr(self, '_MfRhoI', None) is None: self._MfRhoI = self.mesh.getFaceInnerProduct(self.rho, invMat=True) return self._MfRhoI def MfRhoIDeriv(self, u): """ Derivative of :code:`MfRhoI` with respect to the model. """ dMfRhoI_dI = -self.MfRhoI**2 dMf_drho = self.mesh.getFaceInnerProductDeriv(self.rho)(u) drho_dlogrho = Utils.sdiag(self.rho) * self.etaDeriv return dMfRhoI_dI * (dMf_drho * drho_dlogrho) # TODO: This should take a vector def MeSigmaDeriv(self, u): """ Derivative of MeSigma with respect to the model """ dsigma_dlogsigma = Utils.sdiag(self.sigma) * self.etaDeriv return self.mesh.getEdgeInnerProductDeriv( self.sigma)(u) * dsigma_dlogsigma
class DebyeDecProblem(Problem.BaseProblem): sigmaInf, sigmaInfMap, sigmaInfDeriv = Props.Invertible( "Scalar, Conductivity at infinite frequency (S/m)" ) eta, etaMap, etaDeriv = Props.Invertible( "Array, Chargeability (V/V)" ) tau = Props.PhysicalProperty( "Array, Time constant (s)", default=0.1 ) nfreq = None ntau = None G = None frequency = None tau = None f = None InvertOnlyEta = False def __init__(self, mesh, **kwargs): Problem.BaseProblem.__init__(self, mesh, **kwargs) self.omega = 2*np.pi*self.frequency self.nfreq = self.frequency.size self.tau = self.mesh.gridN self.ntau = self.mesh.nN if self.sigmaInfMap == None: self.InvertOnlyEta = True print ("Assume sigmaInf is known") @property def X(self): """ Denominator in Cole-Cole model """ if getattr(self, '_X', None) is None: X = np.zeros((self.nfreq, self.ntau), dtype=complex) for itau in range(self.ntau): X[:,itau] = 1./(1.+(1.-self.eta[itau])*(1j*self.omega*self.tau[itau])) self._X = X return self._X def fields(self, m=None): if m is not None: self._X = None self.model = m f = self.sigmaInf - self.sigmaInf*(np.dot(self.X, self.eta)) self.f = f return f def get_petaImpulse(self, time, m): etas = m taus = self.tau b = -1. / ((1.-etas)*taus) a = -etas*b t_temp = np.atleast_2d(time).T temp = np.exp(np.dot(t_temp, np.atleast_2d(b))) out = np.dot(temp, a) return out def get_petaStepon(self, time, m): etas = m taus = self.tau b = -1. / ((1.-etas)*taus) t_temp = np.atleast_2d(time).T temp = np.exp(np.dot(t_temp, np.atleast_2d(b))) out = np.dot(temp, etas) return out def get_Expb(self, time, m): etas = m taus = self.tau b = -1. / ((1.-etas)*taus) e = etas / b t_temp = np.atleast_2d(time).T temp = 1.-np.exp(np.dot(t_temp, np.atleast_2d(b))) out = np.dot(temp, e) return out def dsig_dm(self, v, adjoint=False): if not adjoint: deta_dm_v = self.etaDeriv*v if self.InvertOnlyEta: return self.dsig_deta(deta_dm_v) else: dsigmaInf_dm_v = self.sigmaInfDeriv*v return self.dsig_dsigmaInf(dsigmaInf_dm_v) + self.dsig_deta(deta_dm_v) elif adjoint: dsig_detaT_v = self.dsig_deta(v, adjoint=adjoint) dsig_dm_v = self.etaDeriv.T * dsig_detaT_v if not self.InvertOnlyEta: dsig_dsigmaInfT_v = self.dsig_dsigmaInf(v, adjoint=adjoint) dsig_dm_v += self.sigmaInfDeriv.T * dsig_dsigmaInfT_v return dsig_dm_v def dsig_deta(self, v, adjoint=False): """ NxM matrix vec I*eta*omega*tau/(I*omega*tau*(-eta + 1) + 1)**2 + 1/(I*omega*tau*(-eta + 1) + 1) """ if not adjoint: dsig_deta_v = -self.sigmaInf*np.dot(self.X, v) temp_v = Utils.sdiag(self.tau*self.eta) * v dsig_deta_v -= np.dot(Utils.sdiag(self.sigmaInf*1j*self.omega)*self.X**2, temp_v) return dsig_deta_v elif adjoint: dsig_detaT_v = -self.sigmaInf*np.dot(self.X.conj().T, v) tempa_v = Utils.sdiag(self.sigmaInf*1j*self.omega).conj() * v temp_v = np.dot((self.X**2).conj().T, tempa_v) dsig_detaT_v -= Utils.sdiag(self.tau*self.eta) * temp_v return dsig_detaT_v.real def dsig_dsigmaInf(self, v, adjoint=False): """ Nx1 matrix vec """ if not adjoint: dsig_dsigmaInf_v = (1.-np.dot(self.X, self.eta)) * v return dsig_dsigmaInf_v elif adjoint: e = np.ones_like(v) dsig_dsigmaInfT_v = np.dot(e, v) - np.dot(self.eta, np.dot(self.X.conj().T, v)) return np.r_[dsig_dsigmaInfT_v.real] def Jvec(self, m, v, f=None): if f is None: f = self.fields(m=m) dsig_dm_v = self.dsig_dm(v) Jv = self.survey.evalDeriv(dsig_dm_v, f, adjoint=False) return Jv def Jtvec(self, m, v, f=None): if f is None: f = self.fields(m=m) dP_dsigT_v = self.survey.evalDeriv(v, f, adjoint=True) Jtv = self.dsig_dm(dP_dsigT_v, adjoint=True) return Jtv
class BaseIPProblem_2D(BaseDCProblem_2D): sigma = Props.PhysicalProperty( "Electrical conductivity (S/m)" ) rho = Props.PhysicalProperty( "Electrical resistivity (Ohm m)" ) Props.Reciprocal(sigma, rho) eta, etaMap, etaDeriv = Props.Invertible( "Electrical Chargeability (V/V)" ) surveyPair = Survey fieldsPair = Fields_ky _Jmatrix = None _f = None sign = None def fields(self, m): if self.verbose: print(">> Compute DC fields") if self._f is None: self._f = self.fieldsPair(self.mesh, self.survey) Srcs = self.survey.srcList for iky in range(self.nky): ky = self.kys[iky] A = self.getA(ky) self.Ainv[iky] = self.Solver(A, **self.solverOpts) RHS = self.getRHS(ky) u = self.Ainv[iky] * RHS self._f[Srcs, self._solutionType, iky] = u return self._f def Jvec(self, m, v, f=None): self.model = m J = self.getJ(m, f=f) Jv = J.dot(v) return self.sign * Jv def Jtvec(self, m, v, f=None): self.model = m J = self.getJ(m, f=f) Jtv = J.T.dot(v) return self.sign * Jtv @property def deleteTheseOnModelUpdate(self): toDelete = [] return toDelete @property def MeSigmaDerivMat(self): """ Derivative of MeSigma with respect to the model """ if getattr(self, '_MeSigmaDerivMat', None) is None: dsigma_dlogsigma = Utils.sdiag(self.sigma)*self.etaDeriv self._MeSigmaDerivMat = self.mesh.getEdgeInnerProductDeriv( np.ones(self.mesh.nC) )(np.ones(self.mesh.nE)) * dsigma_dlogsigma return self._MeSigmaDerivMat # TODO: This should take a vector def MeSigmaDeriv(self, u, v, adjoint=False): """ Derivative of MeSigma with respect to the model times a vector (u) """ if self.storeInnerProduct: if adjoint: return self.MeSigmaDerivMat.T * (Utils.sdiag(u)*v) else: return Utils.sdiag(u)*(self.MeSigmaDerivMat * v) else: dsigma_dlogsigma = Utils.sdiag(self.sigma)*self.etaDeriv if adjoint: return ( dsigma_dlogsigma.T * ( self.mesh.getEdgeInnerProductDeriv(self.sigma)(u).T * v ) ) else: return ( self.mesh.getEdgeInnerProductDeriv(self.sigma)(u) * (dsigma_dlogsigma * v) ) @property def MccRhoiDerivMat(self): """ Derivative of MccRho with respect to the model """ if getattr(self, '_MccRhoiDerivMat', None) is None: rho = self.rho vol = self.mesh.vol drho_dlogrho = Utils.sdiag(rho)*self.etaDeriv self._MccRhoiDerivMat = ( Utils.sdiag(vol*(-1./rho**2))*drho_dlogrho ) return self._MccRhoiDerivMat def MccRhoiDeriv(self, u, v, adjoint=False): """ Derivative of :code:`MccRhoi` with respect to the model. """ if len(self.rho.shape) > 1: if self.rho.shape[1] > self.mesh.dim: raise NotImplementedError( "Full anisotropy is not implemented for MccRhoiDeriv." ) if self.storeInnerProduct: if adjoint: return self.MccRhoiDerivMat.T * (Utils.sdiag(u) * v) else: return Utils.sdiag(u) * (self.MccRhoiDerivMat * v) else: vol = self.mesh.vol rho = self.rho drho_dlogrho = Utils.sdiag(rho)*self.etaDeriv if adjoint: return drho_dlogrho.T * (Utils.sdia(u*vol*(-1./rho**2)) * v) else: return Utils.sdiag(u*vol*(-1./rho**2))*(drho_dlogrho * v) @property def MnSigmaDerivMat(self): """ Derivative of MnSigma with respect to the model """ if getattr(self, '_MnSigmaDerivMat', None) is None: sigma = self.sigma vol = self.mesh.vol dsigma_dlogsigma = Utils.sdiag(sigma)*self.etaDeriv self._MnSigmaDerivMat = ( self.mesh.aveN2CC.T * Utils.sdiag(vol) * dsigma_dlogsigma ) return self._MnSigmaDerivMat def MnSigmaDeriv(self, u, v, adjoint=False): """ Derivative of MnSigma with respect to the model times a vector (u) """ if self.storeInnerProduct: if adjoint: return self.MnSigmaDerivMat.T * (Utils.sdiag(u)*v) else: return Utils.sdiag(u)*(self.MnSigmaDerivMat * v) else: sigma = self.sigma vol = self.mesh.vol dsigma_dlogsigma = Utils.sdiag(sigma)*self.etaDeriv if adjoint: return dsigma_dlogsigma.T * (vol * (self.mesh.aveN2CC * (u*v))) else: dsig_dm_v = dsigma_dlogsigma * v return ( u * (self.mesh.aveN2CC.T * (vol * dsig_dm_v)) )
class GlobalEM1DProblem(Problem.BaseProblem): """ The GlobalProblem allows you to run a whole bunch of SubProblems, potentially in parallel, potentially of different meshes. This is handy for working with lots of sources, """ sigma, sigmaMap, sigmaDeriv = Props.Invertible( "Electrical conductivity (S/m)") h, hMap, hDeriv = Props.Invertible("Receiver Height (m), h > 0", ) chi = Props.PhysicalProperty("Magnetic susceptibility (H/m)", ) eta = Props.PhysicalProperty( "Electrical chargeability (V/V), 0 <= eta < 1") tau = Props.PhysicalProperty("Time constant (s)") c = Props.PhysicalProperty("Frequency Dependency, 0 < c < 1") _Jmatrix_sigma = None _Jmatrix_height = None run_simulation = None n_cpu = None hz = None parallel = False parallel_jvec_jtvec = False verbose = False fix_Jmatrix = False invert_height = None def __init__(self, mesh, **kwargs): Utils.setKwargs(self, **kwargs) self.mesh = mesh if PARALLEL: if self.parallel: print(">> Use multiprocessing for parallelization") if self.n_cpu is None: self.n_cpu = multiprocessing.cpu_count() print((">> n_cpu: %i") % (self.n_cpu)) else: print(">> Serial version is used") else: print(">> Serial version is used") if self.hz is None: raise Exception("Input vertical thickness hz !") if self.hMap is None: self.invert_height = False else: self.invert_height = True @property def n_layer(self): return self.hz.size @property def n_sounding(self): return self.survey.n_sounding @property def rx_locations(self): return self.survey.rx_locations @property def src_locations(self): return self.survey.src_locations @property def data_index(self): return self.survey.data_index @property def topo(self): return self.survey.topo @property def offset(self): return self.survey.offset @property def a(self): return self.survey.a @property def I(self): return self.survey.I @property def field_type(self): return self.survey.field_type @property def rx_type(self): return self.survey.rx_type @property def src_type(self): return self.survey.src_type @property def half_switch(self): return self.survey.half_switch @property def Sigma(self): if getattr(self, '_Sigma', None) is None: # Ordering: first z then x self._Sigma = self.sigma.reshape((self.n_sounding, self.n_layer)) return self._Sigma @property def Chi(self): if getattr(self, '_Chi', None) is None: # Ordering: first z then x if self.chi is None: self._Chi = np.zeros((self.n_sounding, self.n_layer), dtype=float, order='C') else: self._Chi = self.chi.reshape((self.n_sounding, self.n_layer)) return self._Chi @property def Eta(self): if getattr(self, '_Eta', None) is None: # Ordering: first z then x if self.eta is None: self._Eta = np.zeros((self.n_sounding, self.n_layer), dtype=float, order='C') else: self._Eta = self.eta.reshape((self.n_sounding, self.n_layer)) return self._Eta @property def Tau(self): if getattr(self, '_Tau', None) is None: # Ordering: first z then x if self.tau is None: self._Tau = 1e-3 * np.ones( (self.n_sounding, self.n_layer), dtype=float, order='C') else: self._Tau = self.tau.reshape((self.n_sounding, self.n_layer)) return self._Tau @property def C(self): if getattr(self, '_C', None) is None: # Ordering: first z then x if self.c is None: self._C = np.ones((self.n_sounding, self.n_layer), dtype=float, order='C') else: self._C = self.c.reshape((self.n_sounding, self.n_layer)) return self._C @property def H(self): if self.hMap is None: return np.ones(self.n_sounding) else: return self.h def fields(self, m): if self.verbose: print("Compute fields") self.survey._pred = self.forward(m) return [] def forward(self, m): self.model = m if self.verbose: print(">> Compute response") if self.parallel: pool = Pool(self.n_cpu) # This assumes the same # of layer for each of soundings result = pool.map(self.run_simulation, [ self.input_args(i, jac_switch='forward') for i in range(self.n_sounding) ]) pool.close() pool.join() else: result = [ self.run_simulation(self.input_args(i, jac_switch='forward')) for i in range(self.n_sounding) ] return np.hstack(result) def getJ_sigma(self, m): """ Compute d F / d sigma """ if self._Jmatrix_sigma is not None: return self._Jmatrix_sigma if self.verbose: print(">> Compute J sigma") self.model = m if self.parallel: pool = Pool(self.n_cpu) self._Jmatrix_sigma = pool.map(self.run_simulation, [ self.input_args(i, jac_switch='sensitivity_sigma') for i in range(self.n_sounding) ]) pool.close() pool.join() if self.parallel_jvec_jtvec is False: self._Jmatrix_sigma = sp.block_diag( self._Jmatrix_sigma).tocsr() else: # _Jmatrix_sigma is block diagnoal matrix (sparse) self._Jmatrix_sigma = sp.block_diag([ self.run_simulation( self.input_args(i, jac_switch='sensitivity_sigma')) for i in range(self.n_sounding) ]).tocsr() return self._Jmatrix_sigma def getJ_height(self, m): """ Compute d F / d height """ if self.hMap is None: return Utils.Zero() if self._Jmatrix_height is not None: return self._Jmatrix_height if self.verbose: print(">> Compute J height") self.model = m if self.parallel: pool = Pool(self.n_cpu) self._Jmatrix_height = pool.map(self.run_simulation, [ self.input_args(i, jac_switch="sensitivity_height") for i in range(self.n_sounding) ]) pool.close() pool.join() if self.parallel_jvec_jtvec is False: self._Jmatrix_height = sp.block_diag( self._Jmatrix_height).tocsr() else: self._Jmatrix_height = sp.block_diag([ self.run_simulation( self.input_args(i, jac_switch='sensitivity_height')) for i in range(self.n_sounding) ]).tocsr() return self._Jmatrix_height def Jvec(self, m, v, f=None): J_sigma = self.getJ_sigma(m) J_height = self.getJ_height(m) # This is deprecated at the moment # if self.parallel and self.parallel_jvec_jtvec: # # Extra division of sigma is because: # # J_sigma = dF/dlog(sigma) # # And here sigmaMap also includes ExpMap # v_sigma = Utils.sdiag(1./self.sigma) * self.sigmaMap.deriv(m, v) # V_sigma = v_sigma.reshape((self.n_sounding, self.n_layer)) # pool = Pool(self.n_cpu) # Jv = np.hstack( # pool.map( # dot, # [(J_sigma[i], V_sigma[i, :]) for i in range(self.n_sounding)] # ) # ) # if self.hMap is not None: # v_height = self.hMap.deriv(m, v) # V_height = v_height.reshape((self.n_sounding, self.n_layer)) # Jv += np.hstack( # pool.map( # dot, # [(J_height[i], V_height[i, :]) for i in range(self.n_sounding)] # ) # ) # pool.close() # pool.join() # else: Jv = J_sigma * (Utils.sdiag(1. / self.sigma) * (self.sigmaDeriv * v)) if self.hMap is not None: Jv += J_height * (self.hDeriv * v) return Jv def Jtvec(self, m, v, f=None): J_sigma = self.getJ_sigma(m) J_height = self.getJ_height(m) # This is deprecated at the moment # if self.parallel and self.parallel_jvec_jtvec: # pool = Pool(self.n_cpu) # Jtv = np.hstack( # pool.map( # dot, # [(J_sigma[i].T, v[self.data_index[i]]) for i in range(self.n_sounding)] # ) # ) # if self.hMap is not None: # Jtv_height = np.hstack( # pool.map( # dot, # [(J_sigma[i].T, v[self.data_index[i]]) for i in range(self.n_sounding)] # ) # ) # # This assumes certain order for model, m = (sigma, height) # Jtv = np.hstack((Jtv, Jtv_height)) # pool.close() # pool.join() # return Jtv # else: # Extra division of sigma is because: # J_sigma = dF/dlog(sigma) # And here sigmaMap also includes ExpMap Jtv = self.sigmaDeriv.T * (Utils.sdiag(1. / self.sigma) * (J_sigma.T * v)) if self.hMap is not None: Jtv += self.hDeriv.T * (J_height.T * v) return Jtv @property def deleteTheseOnModelUpdate(self): toDelete = [] if self.sigmaMap is not None: toDelete += ['_Sigma'] if self.fix_Jmatrix is False: if self._Jmatrix_sigma is not None: toDelete += ['_Jmatrix_sigma'] if self._Jmatrix_height is not None: toDelete += ['_Jmatrix_height'] return toDelete
class Vangenuchten_theta(BaseWaterRetention): theta_r, theta_rMap, theta_rDeriv = Props.Invertible( "residual water content [L3L-3]", default=0.078) theta_s, theta_sMap, theta_sDeriv = Props.Invertible( "saturated water content [L3L-3]", default=0.430) n, nMap, nDeriv = Props.Invertible( "measure of the pore-size distribution, >1", default=1.56) alpha, alphaMap, alphaDeriv = Props.Invertible( "related to the inverse of the air entry suction [L-1], >0", default=0.036) def _get_params(self): return self.theta_r, self.theta_s, self.alpha, self.n def __call__(self, u): theta_r, theta_s, alpha, n = self._get_params() f = ((theta_s - theta_r) / ((1.0 + abs(alpha * u)**n)**(1.0 - 1.0 / n)) + theta_r) if np.isscalar(theta_s): f[u >= 0] = theta_s else: f[u >= 0] = theta_s[u >= 0] return f def derivM(self, u): """derivative with respect to m .. code:: import sympy as sy alpha, u, n, I, Ks, theta_r, theta_s = sy.symbols( 'alpha u n I Ks theta_r theta_s', real=True ) m = 1.0 - 1.0/n theta_e = 1.0 / ((1.0 + sy.functions.Abs(alpha * u) ** n) ** m) f_n = ( ( theta_s - theta_r ) / ( (1.0 + abs(alpha * u)**n) ** (1.0 - 1.0 / n) ) + theta_r ) """ return (self._derivTheta_r(u) + self._derivTheta_s(u) + self._derivN(u) + self._derivAlpha(u)) def _derivTheta_r(self, u): if self.theta_rMap is None: return Utils.Zero() theta_r, theta_s, alpha, n = self._get_params() ddm = -(abs(alpha * u)**n + 1.0)**(-1.0 + 1.0 / n) + 1 ddm[u >= 0] = 0 dT = Utils.sdiag(ddm) * self.theta_rDeriv return dT def _derivTheta_s(self, u): if self.theta_sMap is None: return Utils.Zero() theta_r, theta_s, alpha, n = self._get_params() P_p, P_n = _get_projections(u) # Compute the positive/negative domains dT_p = P_p * self.theta_sDeriv dT_n = P_n * Utils.sdiag( (abs(alpha * u)**n + 1.0)**(-1.0 + 1.0 / n)) * self.theta_sDeriv return dT_p + dT_n def _derivN(self, u): if self.nMap is None: return Utils.Zero() theta_r, theta_s, alpha, n = self._get_params() ddm = (-theta_r + theta_s) * ( (-1.0 + 1.0 / n) * np.log(abs(alpha * u)) * abs(alpha * u)**n / (abs(alpha * u)**n + 1.0) - 1.0 * np.log(abs(alpha * u)**n + 1.0) / n**2) * (abs(alpha * u)**n + 1.0)**(-1.0 + 1.0 / n) ddm[u >= 0] = 0 dN = Utils.sdiag(ddm) * self.nDeriv return dN def _derivAlpha(self, u): if self.alphaMap is None: return Utils.Zero() theta_r, theta_s, alpha, n = self._get_params() ddm = n * u * (-1.0 + 1.0 / n) * (-theta_r + theta_s) * ( abs(alpha * u)**n + 1.0)**(-1.0 + 1.0 / n) * abs( alpha * u)**n * np.sign(alpha * u) / ( (abs(alpha * u)**n + 1.0) * abs(alpha * u)) ddm[u >= 0] = 0 dA = Utils.sdiag(ddm) * self.alphaDeriv return dA def derivU(self, u): theta_r, theta_s, alpha, n = self._get_params() g = -alpha * n * abs(alpha * u)**(n - 1) * np.sign( alpha * u) * (1. / n - 1) * (theta_r - theta_s) * ( abs(alpha * u)**n + 1)**(1. / n - 2) g[u >= 0] = 0 g = Utils.sdiag(g) return g
class BaseEMProblem(Problem.BaseProblem): sigma, sigmaMap, sigmaDeriv = Props.Invertible( "Electrical conductivity (S/m)") rho, rhoMap, rhoDeriv = Props.Invertible("Electrical resistivity (Ohm m)") Props.Reciprocal(sigma, rho) mu = Props.PhysicalProperty("Magnetic Permeability (H/m)", default=mu_0) mui = Props.PhysicalProperty("Inverse Magnetic Permeability (m/H)") Props.Reciprocal(mu, mui) surveyPair = Survey.BaseSurvey #: The survey to pair with. dataPair = Survey.Data #: The data to pair with. mapPair = Maps.IdentityMap #: Type of mapping to pair with Solver = SimpegSolver #: Type of solver to pair with solverOpts = {} #: Solver options verbose = False #################################################### # Make A Symmetric #################################################### @property def _makeASymmetric(self): if getattr(self, '__makeASymmetric', None) is None: self.__makeASymmetric = True return self.__makeASymmetric #################################################### # Mass Matrices #################################################### @property def _clear_on_mu_update(self): return ['_MeMu', '_MeMuI', '_MfMui', '_MfMuiI'] @property def _clear_on_sigma_update(self): return ['_MeSigma', '_MeSigmaI', '_MfRho', '_MfRhoI'] @property def deleteTheseOnModelUpdate(self): toDelete = [] if self.sigmaMap is not None or self.rhoMap is not None: toDelete += self._clear_on_sigma_update if hasattr(self, 'muMap') or hasattr(self, 'muiMap'): if self.muMap is not None or self.muiMap is not None: toDelete += self._clear_on_mu_update return toDelete @properties.observer('mu') def _clear_mu_mats_on_mu_update(self, change): if change['previous'] is change['value']: return if (isinstance(change['previous'], np.ndarray) and isinstance(change['value'], np.ndarray) and np.allclose(change['previous'], change['value'])): return for mat in self._clear_on_mu_update: if hasattr(self, mat): delattr(self, mat) @properties.observer('mui') def _clear_mu_mats_on_mui_update(self, change): if change['previous'] is change['value']: return if (isinstance(change['previous'], np.ndarray) and isinstance(change['value'], np.ndarray) and np.allclose(change['previous'], change['value'])): return for mat in self._clear_on_mu_update: if hasattr(self, mat): delattr(self, mat) @properties.observer('sigma') def _clear_sigma_mats_on_sigma_update(self, change): if change['previous'] is change['value']: return if (isinstance(change['previous'], np.ndarray) and isinstance(change['value'], np.ndarray) and np.allclose(change['previous'], change['value'])): return for mat in self._clear_on_sigma_update: if hasattr(self, mat): delattr(self, mat) @properties.observer('rho') def _clear_sigma_mats_on_rho_update(self, change): if change['previous'] is change['value']: return if (isinstance(change['previous'], np.ndarray) and isinstance(change['value'], np.ndarray) and np.allclose(change['previous'], change['value'])): return for mat in self._clear_on_sigma_update: if hasattr(self, mat): delattr(self, mat) @property def Me(self): """ Edge inner product matrix """ if getattr(self, '_Me', None) is None: self._Me = self.mesh.getEdgeInnerProduct() return self._Me @property def MeI(self): """ Edge inner product matrix """ if getattr(self, '_MeI', None) is None: self._MeI = self.mesh.getEdgeInnerProduct(invMat=True) return self._MeI @property def Mf(self): """ Face inner product matrix """ if getattr(self, '_Mf', None) is None: self._Mf = self.mesh.getFaceInnerProduct() return self._Mf @property def MfI(self): """ Face inner product matrix """ if getattr(self, '_MfI', None) is None: self._MfI = self.mesh.getFaceInnerProduct(invMat=True) return self._MfI @property def Vol(self): if getattr(self, '_Vol', None) is None: self._Vol = Utils.sdiag(self.mesh.vol) return self._Vol #################################################### # Magnetic Permeability #################################################### @property def MfMui(self): """ Face inner product matrix for \\(\\mu^{-1}\\). Used in the E-B formulation """ if getattr(self, '_MfMui', None) is None: self._MfMui = self.mesh.getFaceInnerProduct(self.mui) return self._MfMui def MfMuiDeriv(self, u): """ Derivative of :code:`MfMui` with respect to the model. """ if self.muiMap is None: return Utils.Zero() return (self.mesh.getFaceInnerProductDeriv(self.mui)(u) * self.muiDeriv) @property def MfMuiI(self): """ Inverse of :code:`MfMui`. """ if getattr(self, '_MfMuiI', None) is None: self._MfMuiI = self.mesh.getFaceInnerProduct(self.mui, invMat=True) return self._MfMuiI # TODO: This should take a vector def MfMuiIDeriv(self, u): """ Derivative of :code:`MfMui` with respect to the model """ if self.muiMap is None: return Utils.Zero() if len(self.mui.shape) > 1: if self.mui.shape[1] > self.mesh.dim: raise NotImplementedError( "Full anisotropy is not implemented for MfMuiIDeriv.") dMfMuiI_dI = -self.MfMuiI**2 dMf_dmui = self.mesh.getEdgeInnerProductDeriv(self.mui)(u) return dMfMuiI_dI * (dMf_dmui * self.muiDeriv) @property def MeMu(self): """ Edge inner product matrix for \\(\\mu\\). Used in the H-J formulation """ if getattr(self, '_MeMu', None) is None: self._MeMu = self.mesh.getEdgeInnerProduct(self.mu) return self._MeMu def MeMuDeriv(self, u): """ Derivative of :code:`MeMu` with respect to the model. """ if self.muMap is None: return Utils.Zero() return (self.mesh.getEdgeInnerProductDeriv(self.mu)(u) * self.muDeriv) @property def MeMuI(self): """ Inverse of :code:`MeMu` """ if getattr(self, '_MeMuI', None) is None: self._MeMuI = self.mesh.getEdgeInnerProduct(self.mu, invMat=True) return self._MeMuI # TODO: This should take a vector def MeMuIDeriv(self, u): """ Derivative of :code:`MeMuI` with respect to the model """ if self.muMap is None: return Utils.Zero() if len(self.mu.shape) > 1: if self.mu.shape[1] > self.mesh.dim: raise NotImplementedError( "Full anisotropy is not implemented for MeMuIDeriv.") dMeMuI_dI = -self.MeMuI**2 dMe_dmu = self.mesh.getEdgeInnerProductDeriv(self.mu)(u) return dMeMuI_dI * (dMe_dmu * self.muDeriv) #################################################### # Electrical Conductivity #################################################### @property def MeSigma(self): """ Edge inner product matrix for \\(\\sigma\\). Used in the E-B formulation """ if getattr(self, '_MeSigma', None) is None: self._MeSigma = self.mesh.getEdgeInnerProduct(self.sigma) return self._MeSigma # TODO: This should take a vector def MeSigmaDeriv(self, u): """ Derivative of MeSigma with respect to the model """ if self.sigmaMap is None: return Utils.Zero() return (self.mesh.getEdgeInnerProductDeriv(self.sigma)(u) * self.sigmaDeriv) @property def MeSigmaI(self): """ Inverse of the edge inner product matrix for \\(\\sigma\\). """ if getattr(self, '_MeSigmaI', None) is None: self._MeSigmaI = self.mesh.getEdgeInnerProduct(self.sigma, invMat=True) return self._MeSigmaI # TODO: This should take a vector def MeSigmaIDeriv(self, u): """ Derivative of :code:`MeSigmaI` with respect to the model """ if self.sigmaMap is None: return Utils.Zero() if len(self.sigma.shape) > 1: if self.sigma.shape[1] > self.mesh.dim: raise NotImplementedError( "Full anisotropy is not implemented for MeSigmaIDeriv.") dMeSigmaI_dI = -self.MeSigmaI**2 dMe_dsig = self.mesh.getEdgeInnerProductDeriv(self.sigma)(u) return dMeSigmaI_dI * (dMe_dsig * self.sigmaDeriv) @property def MfRho(self): """ Face inner product matrix for \\(\\rho\\). Used in the H-J formulation """ if getattr(self, '_MfRho', None) is None: self._MfRho = self.mesh.getFaceInnerProduct(self.rho) return self._MfRho # TODO: This should take a vector def MfRhoDeriv(self, u): """ Derivative of :code:`MfRho` with respect to the model. """ if self.rhoMap is None: return Utils.Zero() return (self.mesh.getFaceInnerProductDeriv(self.rho)(u) * self.rhoDeriv) @property def MfRhoI(self): """ Inverse of :code:`MfRho` """ if getattr(self, '_MfRhoI', None) is None: self._MfRhoI = self.mesh.getFaceInnerProduct(self.rho, invMat=True) return self._MfRhoI # TODO: This should take a vector def MfRhoIDeriv(self, u): """ Derivative of :code:`MfRhoI` with respect to the model. """ if self.rhoMap is None: return Utils.Zero() if len(self.rho.shape) > 1: if self.rho.shape[1] > self.mesh.dim: raise NotImplementedError( "Full anisotropy is not implemented for MfRhoIDeriv.") dMfRhoI_dI = -self.MfRhoI**2 dMf_drho = self.mesh.getFaceInnerProductDeriv(self.rho)(u) return dMfRhoI_dI * (dMf_drho * self.rhoDeriv)
class Haverkamp_theta(BaseWaterRetention): theta_r, theta_rMap, theta_rDeriv = Props.Invertible( "residual water content [L3L-3]", default=0.075) theta_s, theta_sMap, theta_sDeriv = Props.Invertible( "saturated water content [L3L-3]", default=0.287) alpha, alphaMap, alphaDeriv = Props.Invertible("", default=1.611e+06) beta, betaMap, betaDeriv = Props.Invertible("", default=3.96) def _get_params(self): return self.theta_r, self.theta_s, self.alpha, self.beta def __call__(self, u): theta_r, theta_s, alpha, beta = self._get_params() f = (alpha * (theta_s - theta_r) / (alpha + abs(u)**beta) + theta_r) if np.isscalar(theta_s): f[u >= 0] = theta_s else: f[u >= 0] = theta_s[u >= 0] return f def derivM(self, u): """derivative with respect to m .. code:: import sympy as sy alpha, u, beta, theta_r, theta_s = sy.symbols( 'alpha u beta theta_r theta_s', real=True ) f_n = ( alpha * (theta_s - theta_r) / (alpha + abs(u)**beta) + theta_r ) """ return (self._derivTheta_r(u) + self._derivTheta_s(u) + self._derivAlpha(u) + self._derivBeta(u)) def _derivTheta_r(self, u): if self.theta_rMap is None: return Utils.Zero() theta_r, theta_s, alpha, beta = self._get_params() ddm = -alpha / (alpha + abs(u)**beta) + 1 ddm[u >= 0] = 0 dT = Utils.sdiag(ddm) * self.theta_rDeriv return dT def _derivTheta_s(self, u): if self.theta_sMap is None: return Utils.Zero() theta_r, theta_s, alpha, beta = self._get_params() P_p, P_n = _get_projections(u) # Compute the positive/negative domains dT_p = P_p * self.theta_sDeriv dT_n = P_n * Utils.sdiag(alpha / (alpha + abs(u)**beta)) * self.theta_sDeriv return dT_p + dT_n def _derivAlpha(self, u): if self.alphaMap is None: return Utils.Zero() theta_r, theta_s, alpha, beta = self._get_params() ddm = -alpha * (-theta_r + theta_s) / (alpha + abs(u)**beta)**2 + ( -theta_r + theta_s) / (alpha + abs(u)**beta) ddm[u >= 0] = 0 dA = Utils.sdiag(ddm) * self.alphaDeriv return dA def _derivBeta(self, u): if self.betaMap is None: return Utils.Zero() theta_r, theta_s, alpha, beta = self._get_params() ddm = -alpha * (-theta_r + theta_s) * np.log( abs(u)) * abs(u)**beta / (alpha + abs(u)**beta)**2 ddm[u >= 0] = 0 dN = Utils.sdiag(ddm) * self.betaDeriv return dN def derivU(self, u): theta_r, theta_s, alpha, beta = self._get_params() g = (alpha * ((theta_s - theta_r) / (alpha + abs(u)**beta)**2) * (-beta * abs(u)**(beta - 1) * np.sign(u))) g[u >= 0] = 0 g = Utils.sdiag(g) return g
class BaseSIPProblem(BaseEMProblem): eta, etaMap, etaDeriv = Props.Invertible("Electrical Chargeability") tau, tauMap, tauDeriv = Props.Invertible("time constant", default=0.1) taui, tauiMap, tauiDeriv = Props.Invertible("inverse time constant") Props.Reciprocal(tau, taui) c, cMap, cDeriv = Props.Invertible("frequency dependency", default=1.) surveyPair = Survey fieldsPair = FieldsDC dataPair = Data Ainv = None sigma = None rho = None f = None Ainv = None def DebyeTime(self, t): peta = self.eta * np.exp(-self.taui * t) return peta def EtaDeriv(self, t, v, adjoint=False): v = np.array(v, dtype=float) if adjoint: return self.etaDeriv.T * (np.exp(-self.taui * t) * v) else: return np.exp(-self.taui * t) * (self.etaDeriv * v) def TauiDeriv(self, t, v, adjoint=False): v = np.array(v, dtype=float) if adjoint: return -self.tauiDeriv.T * (self.eta * t * np.exp(-self.taui * t) * v) else: return -self.eta * t * np.exp( -self.taui * t) * (self.tauiDeriv * v) def fields(self, m): self.model = m if self.f is None: self.f = self.fieldsPair(self.mesh, self.survey) if self.Ainv is None: A = self.getA() self.Ainv = self.Solver(A, **self.solverOpts) RHS = self.getRHS() u = self.Ainv * RHS Srcs = self.survey.srcList self.f[Srcs, self._solutionType] = u return self.f def forward(self, m, f=None): if f is None: f = self.fields(m) self.model = m Jv = [] # A = self.getA() for tind in range(len(self.survey.times)): # Pseudo-chareability t = self.survey.times[tind] v = self.DebyeTime(t) for src in self.survey.srcList: u_src = f[src, self._solutionType] # solution vector dA_dm_v = self.getADeriv(u_src, v) dRHS_dm_v = self.getRHSDeriv(src, v) du_dm_v = self.Ainv * (-dA_dm_v + dRHS_dm_v) for rx in src.rxList: timeindex = rx.getTimeP(self.survey.times) if timeindex[tind]: df_dmFun = getattr(f, '_{0!s}Deriv'.format(rx.projField), None) df_dm_v = df_dmFun(src, du_dm_v, v, adjoint=False) Jv.append(rx.evalDeriv(src, self.mesh, f, df_dm_v)) # Jv[src, rx, t] = rx.evalDeriv(src, self.mesh, f, df_dm_v) # Conductivity (d u / d log sigma) if self._formulation == 'EB': # return -Utils.mkvc(Jv) return -np.hstack(Jv) # Resistivity (d u / d log rho) if self._formulation == 'HJ': # return Utils.mkvc(Jv) return np.hstack(Jv) def Jvec(self, m, v, f=None): if f is None: f = self.fields(m) self.model = m Jv = [] # A = self.getA() JvAll = [] for tind in range(len(self.survey.times)): t = self.survey.times[tind] v0 = self.EtaDeriv(t, v) v1 = self.TauiDeriv(t, v) for src in self.survey.srcList: u_src = f[src, self._solutionType] # solution vector dA_dm_v0 = self.getADeriv(u_src, v0) dRHS_dm_v0 = self.getRHSDeriv(src, v0) du_dm_v0 = self.Ainv * (-dA_dm_v0 + dRHS_dm_v0) dA_dm_v1 = self.getADeriv(u_src, v1) dRHS_dm_v1 = self.getRHSDeriv(src, v1) du_dm_v1 = self.Ainv * (-dA_dm_v1 + dRHS_dm_v1) for rx in src.rxList: timeindex = rx.getTimeP(self.survey.times) if timeindex[tind]: df_dmFun = getattr(f, '_{0!s}Deriv'.format(rx.projField), None) df_dm_v0 = df_dmFun(src, du_dm_v0, v0, adjoint=False) df_dm_v1 = df_dmFun(src, du_dm_v1, v1, adjoint=False) # Jv[src, rx, t] = rx.evalDeriv(src, self.mesh, f, df_dm_v0) # Jv[src, rx, t] += rx.evalDeriv(src, self.mesh, f, df_dm_v1) Jv.append( rx.evalDeriv(src, self.mesh, f, df_dm_v0) + rx.evalDeriv(src, self.mesh, f, df_dm_v1)) # Conductivity (d u / d log sigma) if self._formulation == 'EB': # return -Jv.tovec() return -np.hstack(Jv) # Resistivity (d u / d log rho) if self._formulation == 'HJ': # return Jv.tovec() return np.hstack(Jv) def Jtvec(self, m, v, f=None): if f is None: f = self.fields(m) self.model = m # Ensure v is a data object. if not isinstance(v, self.dataPair): v = self.dataPair(self.survey, v) Jtv = np.zeros(m.size) for tind in range(len(self.survey.times)): t = self.survey.times[tind] for src in self.survey.srcList: u_src = f[src, self._solutionType] for rx in src.rxList: timeindex = rx.getTimeP(self.survey.times) if timeindex[tind]: PTv = rx.evalDeriv( src, self.mesh, f, v[src, rx, t], adjoint=True) # wrt f, need possibility wrt m df_duTFun = getattr(f, '_{0!s}Deriv'.format(rx.projField), None) df_duT, df_dmT = df_duTFun(src, None, PTv, adjoint=True) ATinvdf_duT = self.Ainv * df_duT dA_dmT = self.getADeriv(u_src, ATinvdf_duT, adjoint=True) dRHS_dmT = self.getRHSDeriv(src, ATinvdf_duT, adjoint=True) du_dmT = -dA_dmT + dRHS_dmT Jtv += self.EtaDeriv( self.survey.times[tind], du_dmT, adjoint=True) + self.TauiDeriv( self.survey.times[tind], du_dmT, adjoint=True) # Conductivity ((d u / d log sigma).T) if self._formulation == 'EB': return -Jtv # Conductivity ((d u / d log rho).T) if self._formulation == 'HJ': return Jtv def getSourceTerm(self): """ takes concept of source and turns it into a matrix """ """ Evaluates the sources, and puts them in matrix form :rtype: (numpy.ndarray, numpy.ndarray) :return: q (nC or nN, nSrc) """ Srcs = self.survey.srcList if self._formulation == 'EB': n = self.mesh.nN # return NotImplementedError elif self._formulation == 'HJ': n = self.mesh.nC q = np.zeros((n, len(Srcs))) for i, src in enumerate(Srcs): q[:, i] = src.eval(self) return q @property def deleteTheseOnModelUpdate(self): toDelete = [] return toDelete # assume log rho or log cond @property def MeSigma(self): """ Edge inner product matrix for \\(\\sigma\\). Used in the E-B formulation """ if getattr(self, '_MeSigma', None) is None: self._MeSigma = self.mesh.getEdgeInnerProduct(self.sigma) return self._MeSigma @property def MfRhoI(self): """ Inverse of :code:`MfRho` """ if getattr(self, '_MfRhoI', None) is None: self._MfRhoI = self.mesh.getFaceInnerProduct(self.rho, invMat=True) return self._MfRhoI def MfRhoIDeriv(self, u): """ Derivative of :code:`MfRhoI` with respect to the model. """ dMfRhoI_dI = -self.MfRhoI**2 dMf_drho = self.mesh.getFaceInnerProductDeriv(self.rho)(u) drho_dlogrho = Utils.sdiag(self.rho) return dMfRhoI_dI * (dMf_drho * drho_dlogrho) # TODO: This should take a vector def MeSigmaDeriv(self, u): """ Derivative of MeSigma with respect to the model """ dsigma_dlogsigma = Utils.sdiag(self.sigma) return self.mesh.getEdgeInnerProductDeriv( self.sigma)(u) * dsigma_dlogsigma
class EM1D(Problem.BaseProblem): """ Pseudo analytic solutions for frequency and time domain EM problems assumingLayered earth (1D). """ surveyPair = BaseEM1DSurvey mapPair = Maps.IdentityMap WT1 = None WT0 = None YBASE = None chi = None jacSwitch = True filter_type = 'key_101' verbose = False fix_Jmatrix = False _Jmatrix_sigma = None _Jmatrix_height = None _pred = None sigma, sigmaMap, sigmaDeriv = Props.Invertible( "Electrical conductivity at infinite frequency(S/m)") chi = Props.PhysicalProperty("Magnetic susceptibility", default=0.) eta, etaMap, etaDeriv = Props.Invertible( "Electrical chargeability (V/V), 0 <= eta < 1", default=0.) tau, tauMap, tauDeriv = Props.Invertible("Time constant (s)", default=1.) c, cMap, cDeriv = Props.Invertible("Frequency Dependency, 0 < c < 1", default=0.5) h, hMap, hDeriv = Props.Invertible("Receiver Height (m), h > 0", ) def __init__(self, mesh, **kwargs): Problem.BaseProblem.__init__(self, mesh, **kwargs) if self.filter_type == 'key_201': if self.verbose: print(">> Use Key 201 filter for Hankel Tranform") fht = filters.key_201_2009() self.WT0 = np.empty(201, complex) self.WT1 = np.empty(201, complex) self.YBASE = np.empty(201, complex) self.WT0 = fht.j0 self.WT1 = fht.j1 self.YBASE = fht.base elif self.filter_type == 'key_101': if self.verbose: print(">> Use Key 101 filter for Hankel Tranform") fht = filters.key_101_2009() self.WT0 = np.empty(101, complex) self.WT1 = np.empty(101, complex) self.YBASE = np.empty(101, complex) self.WT0 = fht.j0 self.WT1 = fht.j1 self.YBASE = fht.base elif self.filter_type == 'anderson_801': if self.verbose: print(">> Use Anderson 801 filter for Hankel Tranform") fht = filters.anderson_801_1982() self.WT0 = np.empty(801, complex) self.WT1 = np.empty(801, complex) self.YBASE = np.empty(801, complex) self.WT0 = fht.j0 self.WT1 = fht.j1 self.YBASE = fht.base else: raise NotImplementedError() def hz_kernel_vertical_magnetic_dipole(self, lamda, f, n_layer, sig, chi, depth, h, z, flag, output_type='response'): """ Kernel for vertical magnetic component (Hz) due to vertical magnetic diopole (VMD) source in (kx,ky) domain """ u0 = lamda rTE = np.zeros(lamda.size, dtype=complex) coefficient_wavenumber = 1 / (4 * np.pi) * lamda**3 / u0 if output_type == 'sensitivity_sigma': drTE = np.zeros((n_layer, lamda.size), dtype=complex) drTE = rTEfunjac(n_layer, f, lamda, sig, chi, depth, self.survey.half_switch) kernel = drTE * np.exp(-u0 * (z + h)) * coefficient_wavenumber else: rTE = rTEfunfwd(n_layer, f, lamda, sig, chi, depth, self.survey.half_switch) kernel = rTE * np.exp(-u0 * (z + h)) * coefficient_wavenumber if output_type == 'sensitivity_height': kernel *= -2 * u0 return kernel # Note # Here only computes secondary field. # I am not sure why it does not work if we add primary term. # This term can be analytically evaluated, where h = 0. # kernel = ( # 1./(4*np.pi) * # (np.exp(u0*(z-h))+rTE * np.exp(-u0*(z+h)))*lamda**3/u0 # ) def hz_kernel_circular_loop(self, lamda, f, n_layer, sig, chi, depth, h, z, I, a, flag, output_type='response'): """ Kernel for vertical magnetic component (Hz) at the center due to circular loop source in (kx,ky) domain .. math:: H_z = \\frac{Ia}{2} \int_0^{\infty} [e^{-u_0|z+h|} + \\r_{TE}e^{u_0|z-h|}] \\frac{\lambda^2}{u_0} J_1(\lambda a)] d \lambda """ w = 2 * np.pi * f rTE = np.empty(lamda.size, dtype=complex) u0 = lamda coefficient_wavenumber = I * a * 0.5 * lamda**2 / u0 if output_type == 'sensitivity_sigma': drTE = np.empty((n_layer, lamda.size), dtype=complex) drTE = rTEfunjac(n_layer, f, lamda, sig, chi, depth, self.survey.half_switch) kernel = drTE * np.exp(-u0 * (z + h)) * coefficient_wavenumber else: rTE = rTEfunfwd(n_layer, f, lamda, sig, chi, depth, self.survey.half_switch) if flag == 'secondary': kernel = rTE * np.exp(-u0 * (z + h)) * coefficient_wavenumber else: kernel = rTE * (np.exp(-u0 * (z + h)) + np.exp(u0 * (z - h))) * coefficient_wavenumber if output_type == 'sensitivity_height': kernel *= -2 * u0 return kernel def hz_kernel_horizontal_electric_dipole(self, lamda, f, n_layer, sig, chi, depth, h, z, flag, output_type='response'): """ Kernel for vertical magnetic field (Hz) due to horizontal electric diopole (HED) source in (kx,ky) domain """ u0 = lamda rTE = np.zeros(lamda.size, dtype=complex) coefficient_wavenumber = 1 / (4 * np.pi) * lamda**2 / u0 if output_type == 'sensitivity_sigma': drTE = np.zeros((n_layer, lamda.size), dtype=complex) drTE = rTEfunjac(n_layer, f, lamda, sig, chi, depth, self.survey.half_switch) kernel = drTE * np.exp(-u0 * (z + h)) * coefficient_wavenumber else: rTE = rTEfunfwd(n_layer, f, lamda, sig, chi, depth, self.survey.half_switch) kernel = rTE * np.exp(-u0 * (z + h)) * coefficient_wavenumber if output_type == 'sensitivity_height': kernel *= -2 * u0 return kernel def sigma_cole(self, f): """ Computes Pelton's Cole-Cole conductivity model in frequency domain. Parameter --------- f: ndarray frequency (Hz) Return ------ sigma_complex: ndarray Cole-Cole conductivity values at given frequencies. """ w = 2 * np.pi * f sigma_complex = (self.sigma - self.sigma * self.eta / (1 + (1 - self.eta) * (1j * w * self.tau)**self.c)) return sigma_complex def forward(self, m, output_type='response'): """ Return Bz or dBzdt """ f = self.survey.frequency n_frequency = self.survey.n_frequency flag = self.survey.field_type r = self.survey.offset self.model = m n_layer = self.survey.n_layer depth = self.survey.depth nfilt = self.YBASE.size # h is an inversion parameter if self.hMap is not None: h = self.h else: h = self.survey.h z = h + self.survey.dz HzFHT = np.empty(n_frequency, dtype=complex) chi = self.chi if np.isscalar(self.chi): chi = np.ones_like(self.sigma) * self.chi if output_type == 'response': if self.verbose: print('>> Compute response') # for simulation hz = np.empty(nfilt, complex) if self.survey.src_type == 'VMD': r = self.survey.offset for ifreq in range(n_frequency): sig = self.sigma_cole(f[ifreq]) hz = self.hz_kernel_vertical_magnetic_dipole( self.YBASE / r[ifreq], f[ifreq], n_layer, sig, chi, depth, h, z, flag, output_type=output_type) HzFHT[ifreq] = np.dot(hz, self.WT0) / r[ifreq] elif self.survey.src_type == 'CircularLoop': I = self.survey.I a = self.survey.a for ifreq in range(n_frequency): sig = self.sigma_cole(f[ifreq]) hz = self.hz_kernel_circular_loop(self.YBASE / a, f[ifreq], n_layer, sig, chi, depth, h, z, I, a, flag, output_type=output_type) HzFHT[ifreq] = np.dot(hz, self.WT1) / a elif self.survey.src_type == "piecewise_line": for ifreq in range(n_frequency): sig = self.sigma_cole(f[ifreq]) # Need to compute y hz = self.hz_kernel_horizontal_electric_dipole( self.YBASE / r[ifreq] * y, f[ifreq], n_layer, sig, chi, depth, h, z, I, a, flag, output_type=output_type) HzFHT[ifreq] = np.dot(hz, self.WT1) / a else: raise Exception("Src options are only VMD or CircularLoop!!") return HzFHT elif output_type == 'sensitivity_sigma': dHzFHT_dsig = np.empty((n_frequency, n_layer), dtype=complex) dhz = np.empty((nfilt, n_layer), complex) if self.survey.src_type == 'VMD': r = self.survey.offset for ifreq in range(n_frequency): sig = self.sigma_cole(f[ifreq]) dhz = self.hz_kernel_vertical_magnetic_dipole( self.YBASE / r[ifreq], f[ifreq], n_layer, sig, chi, depth, h, z, flag, output_type=output_type) dHzFHT_dsig[ifreq, :] = np.dot(dhz, self.WT0) / r[ifreq] elif self.survey.src_type == 'CircularLoop': I = self.survey.I a = self.survey.a for ifreq in range(n_frequency): sig = self.sigma_cole(f[ifreq]) dhz = self.hz_kernel_circular_loop(self.YBASE / a, f[ifreq], n_layer, sig, chi, depth, h, z, I, a, flag, output_type=output_type) dHzFHT_dsig[ifreq, :] = np.dot(dhz, self.WT1) / a else: raise Exception("Src options are only VMD or CircularLoop!!") return dHzFHT_dsig elif output_type == 'sensitivity_height': dHzFHT_dh = np.empty((n_frequency, 1), dtype=complex) dhz = np.empty(nfilt, complex) if self.survey.src_type == 'VMD': r = self.survey.offset for ifreq in range(n_frequency): sig = self.sigma_cole(f[ifreq]) dhz = self.hz_kernel_vertical_magnetic_dipole( self.YBASE / r[ifreq], f[ifreq], n_layer, sig, chi, depth, h, z, flag, output_type=output_type) dHzFHT_dh[ifreq] = np.dot(dhz, self.WT0) / r[ifreq] elif self.survey.src_type == 'CircularLoop': I = self.survey.I a = self.survey.a for ifreq in range(n_frequency): sig = self.sigma_cole(f[ifreq]) dhz = self.hz_kernel_circular_loop(self.YBASE / a, f[ifreq], n_layer, sig, chi, depth, h, z, I, a, flag, output_type=output_type) dHzFHT_dh[ifreq] = np.dot(dhz, self.WT1) / a else: raise Exception("Src options are only VMD or CircularLoop!!") return dHzFHT_dh # @profile def fields(self, m): f = self.forward(m, output_type='response') self.survey._pred = Utils.mkvc(self.survey.projectFields(f)) return f def getJ_height(self, m, f=None): """ """ if self.hMap is None: return Utils.Zero() if self._Jmatrix_height is not None: return self._Jmatrix_height else: if self.verbose: print(">> Compute J height ") dudz = self.forward(m, output_type="sensitivity_height") self._Jmatrix_height = (self.survey.projectFields(dudz)).reshape( [-1, 1]) return self._Jmatrix_height # @profile def getJ_sigma(self, m, f=None): if self.sigmaMap is None: return Utils.Zero() if self._Jmatrix_sigma is not None: return self._Jmatrix_sigma else: if self.verbose: print(">> Compute J sigma") dudsig = self.forward(m, output_type="sensitivity_sigma") self._Jmatrix_sigma = self.survey.projectFields(dudsig) if self._Jmatrix_sigma.ndim == 1: self._Jmatrix_sigma = self._Jmatrix_sigma.reshape([-1, 1]) return self._Jmatrix_sigma def getJ(self, m, f=None): return (self.getJ_sigma(m, f=f) * self.sigmaDeriv + self.getJ_height(m, f=f) * self.hDeriv) def Jvec(self, m, v, f=None): """ Computing Jacobian^T multiplied by vector. """ J_sigma = self.getJ_sigma(m, f=f) J_height = self.getJ_height(m, f=f) if self.hMap is None: Jv = np.dot(J_sigma, self.sigmaMap.deriv(m, v)) else: Jv = np.dot(J_sigma, self.sigmaMap.deriv(m, v)) Jv += np.dot(J_height, self.hMap.deriv(m, v)) return Jv def Jtvec(self, m, v, f=None): """ Computing Jacobian^T multiplied by vector. """ J_sigma = self.getJ_sigma(m, f=f) J_height = self.getJ_height(m, f=f) if self.hMap is None: Jtv = self.sigmaMap.deriv(m, np.dot(J_sigma.T, v)) else: Jtv = (self.sigmaDeriv.T * np.dot(J_sigma.T, v)) Jtv += self.hDeriv.T * np.dot(J_height.T, v) return Jtv @property def deleteTheseOnModelUpdate(self): toDelete = [] if self.fix_Jmatrix is False: if self._Jmatrix_sigma is not None: toDelete += ['_Jmatrix_sigma'] if self._Jmatrix_height is not None: toDelete += ['_Jmatrix_height'] return toDelete def depth_of_investigation(self, uncert, thres_hold=0.8): thres_hold = 0.8 J = self.getJ(self.model) S = np.cumsum(abs(np.dot(J.T, 1. / uncert))[::-1])[::-1] active = S - 0.8 > 0. doi = abs(self.survey.depth[active]).max() return doi, active def get_threshold(self, uncert): _, active = self.depth_of_investigation(uncert) JtJdiag = self.get_JtJdiag(uncert) delta = JtJdiag[active].min() return delta def get_JtJdiag(self, uncert): J = self.getJ(self.model) JtJdiag = ((Utils.sdiag(1. / uncert) * J)**2).sum(axis=0) return JtJdiag
class Problem3D_Diff(Problem.BaseProblem): """ Gravity in differential equations! """ _depreciate_main_map = 'rhoMap' rho, rhoMap, rhoDeriv = Props.Invertible( "Specific density (g/cc)", default=1. ) solver = None def __init__(self, mesh, **kwargs): Problem.BaseProblem.__init__(self, mesh, **kwargs) self.mesh.setCellGradBC('dirichlet') self._Div = self.mesh.cellGrad @property def MfI(self): return self._MfI @property def Mfi(self): return self._Mfi def makeMassMatrices(self, m): self.model = m self._Mfi = self.mesh.getFaceInnerProduct() self._MfI = Utils.sdiag(1. / self._Mfi.diagonal()) def getRHS(self, m): """ """ Mc = Utils.sdiag(self.mesh.vol) self.model = m rho = self.rho return Mc * rho def getA(self, m): """ GetA creates and returns the A matrix for the Gravity nodal problem The A matrix has the form: .. math :: \mathbf{A} = \Div(\MfMui)^{-1}\Div^{T} """ return -self._Div.T * self.Mfi * self._Div def fields(self, m): """ Return magnetic potential (u) and flux (B) u: defined on the cell nodes [nC x 1] gField: defined on the cell faces [nF x 1] After we compute u, then we update B. .. math :: \mathbf{B}_s = (\MfMui)^{-1}\mathbf{M}^f_{\mu_0^{-1}}\mathbf{B}_0-\mathbf{B}_0 -(\MfMui)^{-1}\Div^T \mathbf{u} """ from scipy.constants import G as NewtG self.makeMassMatrices(m) A = self.getA(m) RHS = self.getRHS(m) if self.solver is None: m1 = sp.linalg.interface.aslinearoperator( Utils.sdiag(1 / A.diagonal()) ) u, info = sp.linalg.bicgstab(A, RHS, tol=1e-6, maxiter=1000, M=m1) else: print("Solving with Paradiso") Ainv = self.solver(A) u = Ainv * RHS gField = 4. * np.pi * NewtG * 1e+8 * self._Div * u return {'G': gField, 'u': u}