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 ReciprocalPropExample(props.HasModel): sigma = props.PhysicalProperty("Electrical conductivity (S/m)") rho = props.PhysicalProperty("Electrical resistivity (Ohm m)") props.Reciprocal(sigma, rho)
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 ReciprocalPropExampleDefaults(props.HasModel): sigma = props.PhysicalProperty( "Electrical conductivity (S/m)", default=np.r_[1.0, 2.0, 3.0] ) rho = props.PhysicalProperty("Electrical resistivity (Ohm m)") props.Reciprocal(sigma, rho)
class Simulation3DDifferential(BaseSimulation): """ Secondary field approach using differential equations! """ # surveyPair = MAG.BaseMagSurvey # modelPair = MAG.BaseMagMap 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) survey = properties.Instance("a survey object", Survey, required=True) def __init__(self, mesh, **kwargs): super().__init__(mesh, **kwargs) Pbc, Pin, self._Pout = self.mesh.getBCProjWF("neumann", discretization="CC") Dface = self.mesh.faceDiv Mc = sdiag(self.mesh.vol) self._Div = Mc * Dface * Pin.T * Pin @property def MfMuI(self): return self._MfMuI @property def MfMui(self): return self._MfMui @property def MfMu0(self): return self._MfMu0 def makeMassMatrices(self, m): mu = self.muMap * m self._MfMui = self.mesh.getFaceInnerProduct(1.0 / mu) / self.mesh.dim # self._MfMui = self.mesh.getFaceInnerProduct(1./mu) # TODO: this will break if tensor mu self._MfMuI = sdiag(1.0 / self._MfMui.diagonal()) self._MfMu0 = self.mesh.getFaceInnerProduct(1.0 / mu_0) / self.mesh.dim # self._MfMu0 = self.mesh.getFaceInnerProduct(1/mu_0) @utils.requires("survey") def getB0(self): b0 = self.survey.source_field.b0 B0 = np.r_[ b0[0] * np.ones(self.mesh.nFx), b0[1] * np.ones(self.mesh.nFy), b0[2] * np.ones(self.mesh.nFz), ] return B0 def getRHS(self, m): """ .. math :: \mathbf{rhs} = \Div(\MfMui)^{-1}\mathbf{M}^f_{\mu_0^{-1}}\mathbf{B}_0 - \Div\mathbf{B}_0+\diag(v)\mathbf{D} \mathbf{P}_{out}^T \mathbf{B}_{sBC} """ B0 = self.getB0() mu = self.muMap * m chi = mu / mu_0 - 1 # Temporary fix Bbc, Bbc_const = CongruousMagBC(self.mesh, self.survey.source_field.b0, chi) self.Bbc = Bbc self.Bbc_const = Bbc_const # return self._Div*self.MfMuI*self.MfMu0*B0 - self._Div*B0 + # Mc*Dface*self._Pout.T*Bbc return self._Div * self.MfMuI * self.MfMu0 * B0 - self._Div * B0 def getA(self, m): """ GetA creates and returns the A matrix for the Magnetics problem The A matrix has the form: .. math :: \mathbf{A} = \Div(\MfMui)^{-1}\Div^{T} """ return self._Div * self.MfMuI * self._Div.T def fields(self, m): """ Return magnetic potential (u) and flux (B) u: defined on the cell center [nC x 1] B: defined on the cell center [nG 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} """ self.makeMassMatrices(m) A = self.getA(m) rhs = self.getRHS(m) Ainv = self.solver(A, **self.solver_opts) u = Ainv * rhs B0 = self.getB0() B = self.MfMuI * self.MfMu0 * B0 - B0 - self.MfMuI * self._Div.T * u Ainv.clean() return {"B": B, "u": u} @utils.timeIt def Jvec(self, m, v, u=None): """ Computing Jacobian multiplied by vector By setting our problem as .. math :: \mathbf{C}(\mathbf{m}, \mathbf{u}) = \mathbf{A}\mathbf{u} - \mathbf{rhs} = 0 And taking derivative w.r.t m .. math :: \\nabla \mathbf{C}(\mathbf{m}, \mathbf{u}) = \\nabla_m \mathbf{C}(\mathbf{m}) \delta \mathbf{m} + \\nabla_u \mathbf{C}(\mathbf{u}) \delta \mathbf{u} = 0 \\frac{\delta \mathbf{u}}{\delta \mathbf{m}} = - [\\nabla_u \mathbf{C}(\mathbf{u})]^{-1}\\nabla_m \mathbf{C}(\mathbf{m}) With some linear algebra we can have .. math :: \\nabla_u \mathbf{C}(\mathbf{u}) = \mathbf{A} \\nabla_m \mathbf{C}(\mathbf{m}) = \\frac{\partial \mathbf{A}}{\partial \mathbf{m}}(\mathbf{m})\mathbf{u} - \\frac{\partial \mathbf{rhs}(\mathbf{m})}{\partial \mathbf{m}} .. math :: \\frac{\partial \mathbf{A}}{\partial \mathbf{m}}(\mathbf{m})\mathbf{u} = \\frac{\partial \mathbf{\mu}}{\partial \mathbf{m}} \left[\Div \diag (\Div^T \mathbf{u}) \dMfMuI \\right] \dMfMuI = \diag(\MfMui)^{-1}_{vec} \mathbf{Av}_{F2CC}^T\diag(\mathbf{v})\diag(\\frac{1}{\mu^2}) \\frac{\partial \mathbf{rhs}(\mathbf{m})}{\partial \mathbf{m}} = \\frac{\partial \mathbf{\mu}}{\partial \mathbf{m}} \left[ \Div \diag(\M^f_{\mu_{0}^{-1}}\mathbf{B}_0) \dMfMuI \\right] - \diag(\mathbf{v})\mathbf{D} \mathbf{P}_{out}^T\\frac{\partial B_{sBC}}{\partial \mathbf{m}} In the end, .. math :: \\frac{\delta \mathbf{u}}{\delta \mathbf{m}} = - [ \mathbf{A} ]^{-1}\left[ \\frac{\partial \mathbf{A}}{\partial \mathbf{m}}(\mathbf{m})\mathbf{u} - \\frac{\partial \mathbf{rhs}(\mathbf{m})}{\partial \mathbf{m}} \\right] A little tricky point here is we are not interested in potential (u), but interested in magnetic flux (B). Thus, we need sensitivity for B. Now we take derivative of B w.r.t m and have .. math :: \\frac{\delta \mathbf{B}} {\delta \mathbf{m}} = \\frac{\partial \mathbf{\mu} } {\partial \mathbf{m} } \left[ \diag(\M^f_{\mu_{0}^{-1} } \mathbf{B}_0) \dMfMuI \\ - \diag (\Div^T\mathbf{u})\dMfMuI \\right ] - (\MfMui)^{-1}\Div^T\\frac{\delta\mathbf{u}}{\delta \mathbf{m}} Finally we evaluate the above, but we should remember that .. note :: We only want to evalute .. math :: \mathbf{J}\mathbf{v} = \\frac{\delta \mathbf{P}\mathbf{B}} {\delta \mathbf{m}}\mathbf{v} Since forming sensitivity matrix is very expensive in that this monster is "big" and "dense" matrix!! """ if u is None: u = self.fields(m) B, u = u["B"], u["u"] mu = self.muMap * (m) dmu_dm = self.muDeriv # dchidmu = sdiag(1 / mu_0 * np.ones(self.mesh.nC)) vol = self.mesh.vol Div = self._Div P = self.survey.projectFieldsDeriv(B) # Projection matrix B0 = self.getB0() MfMuIvec = 1 / self.MfMui.diagonal() dMfMuI = sdiag(MfMuIvec ** 2) * self.mesh.aveF2CC.T * sdiag(vol * 1.0 / mu ** 2) # A = self._Div*self.MfMuI*self._Div.T # RHS = Div*MfMuI*MfMu0*B0 - Div*B0 + Mc*Dface*Pout.T*Bbc # C(m,u) = A*m-rhs # dudm = -(dCdu)^(-1)dCdm dCdu = self.getA(m) # = A dCdm_A = Div * (sdiag(Div.T * u) * dMfMuI * dmu_dm) dCdm_RHS1 = Div * (sdiag(self.MfMu0 * B0) * dMfMuI) # temp1 = (Dface * (self._Pout.T * self.Bbc_const * self.Bbc)) # dCdm_RHS2v = (sdiag(vol) * temp1) * \ # np.inner(vol, dchidmu * dmu_dm * v) # dCdm_RHSv = dCdm_RHS1*(dmu_dm*v) + dCdm_RHS2v dCdm_RHSv = dCdm_RHS1 * (dmu_dm * v) dCdm_v = dCdm_A * v - dCdm_RHSv Ainv = self.solver(dCdu, **self.solver_opts) sol = Ainv * dCdm_v dudm = -sol dBdmv = ( sdiag(self.MfMu0 * B0) * (dMfMuI * (dmu_dm * v)) - sdiag(Div.T * u) * (dMfMuI * (dmu_dm * v)) - self.MfMuI * (Div.T * (dudm)) ) Ainv.clean() return mkvc(P * dBdmv) @utils.timeIt def Jtvec(self, m, v, u=None): """ Computing Jacobian^T multiplied by vector. .. math :: (\\frac{\delta \mathbf{P}\mathbf{B}} {\delta \mathbf{m}})^{T} = \left[ \mathbf{P}_{deriv}\\frac{\partial \mathbf{\mu} } {\partial \mathbf{m} } \left[ \diag(\M^f_{\mu_{0}^{-1} } \mathbf{B}_0) \dMfMuI \\ - \diag (\Div^T\mathbf{u})\dMfMuI \\right ]\\right]^{T} - \left[\mathbf{P}_{deriv}(\MfMui)^{-1}\Div^T\\frac{\delta\mathbf{u}}{\delta \mathbf{m}} \\right]^{T} where .. math :: \mathbf{P}_{derv} = \\frac{\partial \mathbf{P}}{\partial\mathbf{B}} .. note :: Here we only want to compute .. math :: \mathbf{J}^{T}\mathbf{v} = (\\frac{\delta \mathbf{P}\mathbf{B}} {\delta \mathbf{m}})^{T} \mathbf{v} """ if u is None: u = self.fields(m) B, u = u["B"], u["u"] mu = self.mapping * (m) dmu_dm = self.mapping.deriv(m) # dchidmu = sdiag(1 / mu_0 * np.ones(self.mesh.nC)) vol = self.mesh.vol Div = self._Div Dface = self.mesh.faceDiv P = self.survey.projectFieldsDeriv(B) # Projection matrix B0 = self.getB0() MfMuIvec = 1 / self.MfMui.diagonal() dMfMuI = sdiag(MfMuIvec ** 2) * self.mesh.aveF2CC.T * sdiag(vol * 1.0 / mu ** 2) # A = self._Div*self.MfMuI*self._Div.T # RHS = Div*MfMuI*MfMu0*B0 - Div*B0 + Mc*Dface*Pout.T*Bbc # C(m,u) = A*m-rhs # dudm = -(dCdu)^(-1)dCdm dCdu = self.getA(m) s = Div * (self.MfMuI.T * (P.T * v)) Ainv = self.solver(dCdu.T, **self.solver_opts) sol = Ainv * s Ainv.clean() # dCdm_A = Div * ( sdiag( Div.T * u )* dMfMuI *dmu_dm ) # dCdm_Atsol = ( dMfMuI.T*( sdiag( Div.T * u ) * (Div.T * dmu_dm)) ) * sol dCdm_Atsol = (dmu_dm.T * dMfMuI.T * (sdiag(Div.T * u) * Div.T)) * sol # dCdm_RHS1 = Div * (sdiag( self.MfMu0*B0 ) * dMfMuI) # dCdm_RHS1tsol = (dMfMuI.T*( sdiag( self.MfMu0*B0 ) ) * Div.T * dmu_dm) * sol dCdm_RHS1tsol = (dmu_dm.T * dMfMuI.T * (sdiag(self.MfMu0 * B0)) * Div.T) * sol # temp1 = (Dface*(self._Pout.T*self.Bbc_const*self.Bbc)) # temp1sol = (Dface.T * (sdiag(vol) * sol)) # temp2 = self.Bbc_const * (self._Pout.T * self.Bbc).T # dCdm_RHS2v = (sdiag(vol)*temp1)*np.inner(vol, dchidmu*dmu_dm*v) # dCdm_RHS2tsol = (dmu_dm.T * dchidmu.T * vol) * np.inner(temp2, temp1sol) # dCdm_RHSv = dCdm_RHS1*(dmu_dm*v) + dCdm_RHS2v # temporary fix # dCdm_RHStsol = dCdm_RHS1tsol - dCdm_RHS2tsol dCdm_RHStsol = dCdm_RHS1tsol # dCdm_RHSv = dCdm_RHS1*(dmu_dm*v) + dCdm_RHS2v # dCdm_v = dCdm_A*v - dCdm_RHSv Ctv = dCdm_Atsol - dCdm_RHStsol # B = self.MfMuI*self.MfMu0*B0-B0-self.MfMuI*self._Div.T*u # dBdm = d\mudm*dBd\mu # dPBdm^T*v = Atemp^T*P^T*v - Btemp^T*P^T*v - Ctv Atemp = sdiag(self.MfMu0 * B0) * (dMfMuI * (dmu_dm)) Btemp = sdiag(Div.T * u) * (dMfMuI * (dmu_dm)) Jtv = Atemp.T * (P.T * v) - Btemp.T * (P.T * v) - Ctv return mkvc(Jtv) @property def Qfx(self): if getattr(self, "_Qfx", None) is None: self._Qfx = self.mesh.getInterpolationMat( self.survey.receiver_locations, "Fx" ) return self._Qfx @property def Qfy(self): if getattr(self, "_Qfy", None) is None: self._Qfy = self.mesh.getInterpolationMat( self.survey.receiver_locations, "Fy" ) return self._Qfy @property def Qfz(self): if getattr(self, "_Qfz", None) is None: self._Qfz = self.mesh.getInterpolationMat( self.survey.receiver_locations, "Fz" ) return self._Qfz def projectFields(self, u): """ This function projects the fields onto the data space. Especially, here for we use total magnetic intensity (TMI) data, which is common in practice. First we project our B on to data location .. math:: \mathbf{B}_{rec} = \mathbf{P} \mathbf{B} then we take the dot product between B and b_0 .. math :: \\text{TMI} = \\vec{B}_s \cdot \hat{B}_0 """ # TODO: There can be some different tyes of data like |B| or B components = self.survey.components fields = {} if "bx" in components or "tmi" in components: fields["bx"] = self.Qfx * u["B"] if "by" in components or "tmi" in components: fields["by"] = self.Qfy * u["B"] if "bz" in components or "tmi" in components: fields["bz"] = self.Qfz * u["B"] if "tmi" in components: bx = fields["bx"] by = fields["by"] bz = fields["bz"] # Generate unit vector B0 = self.survey.source_field.b0 Bot = np.sqrt(B0[0] ** 2 + B0[1] ** 2 + B0[2] ** 2) box = B0[0] / Bot boy = B0[1] / Bot boz = B0[2] / Bot fields["tmi"] = bx * box + by * boy + bz * boz return np.concatenate([fields[comp] for comp in components]) @utils.count def projectFieldsDeriv(self, B): """ This function projects the fields onto the data space. .. math:: \\frac{\partial d_\\text{pred}}{\partial \mathbf{B}} = \mathbf{P} Especially, this function is for TMI data type """ components = self.survey.components fields = {} if "bx" in components or "tmi" in components: fields["bx"] = self.Qfx if "by" in components or "tmi" in components: fields["by"] = self.Qfy if "bz" in components or "tmi" in components: fields["bz"] = self.Qfz if "tmi" in components: bx = fields["bx"] by = fields["by"] bz = fields["bz"] # Generate unit vector B0 = self.survey.source_field.b0 Bot = np.sqrt(B0[0] ** 2 + B0[1] ** 2 + B0[2] ** 2) box = B0[0] / Bot boy = B0[1] / Bot boz = B0[2] / Bot fields["tmi"] = bx * box + by * boy + bz * boz return sp.vstack([fields[comp] for comp in components]) def projectFieldsAsVector(self, B): bfx = self.Qfx * B bfy = self.Qfy * B bfz = self.Qfz * B return np.r_[bfx, bfy, bfz]