agd.Metrics.Seismic.hooke
1# Copyright 2020 Jean-Marie Mirebeau, University Paris-Sud, CNRS, University Paris-Saclay 2# Distributed WITHOUT ANY WARRANTY. Licensed under the Apache License, Version 2.0, see http://www.apache.org/licenses/LICENSE-2.0 3 4import numpy as np 5import itertools 6import copy 7 8from ... import AutomaticDifferentiation as ad 9from ... import LinearParallel as lp 10from ... import FiniteDifferences as fd 11from ... import Selling 12from ...FiniteDifferences import common_field 13from .. import misc 14from ..riemann import Riemann 15from .implicit_base import ImplicitBase 16from .thomsen_data import ThomsenElasticMaterial 17 18class Hooke(ImplicitBase): 19 r""" 20 The *dual* norm defined by a Hooke tensor takes the form 21 $$ 22 F^*(x) = \max_{|y|\leq 1} \sqrt{\sum_{ijkl} c_{ijkl} x_i y_j x_k y_l} 23 $$ 24 where c is the Hooke tensor, and y ranges over the unit ball. 25 The primal norm is obtained implicitly, by solving an optimization problem. 26 27 These norms characterize the arrival time of pressure waves in elasticity. 28 They are often encountered in seismic traveltime tomography. 29 30 Member fields and __init__ arguments : 31 - hooke : an array of shape (hdim,hdim,n1,...,nk) where hdim = vdim*(vdim+1)/2 32 and vdim is the ambient space dimension. The array must be symmetric, and encodes the 33 hooke tensor c in Voigt notation. 34 - *args,**kwargs (optional) : passed to ImplicitBase 35 """ 36 def __init__(self,hooke,*args,**kwargs): 37 super(Hooke,self).__init__(*args,**kwargs) 38 self.hooke = hooke 39 self._to_common_field() 40 41 def is_definite(self): 42 return Riemann(self.hooke).is_definite() 43 44 @staticmethod 45 def _vdim(hdim): 46 """Vector dimension from Hooke tensor size""" 47 vdim = int(np.sqrt(2*hdim)) 48 if Hooke._hdim(vdim)!=hdim: 49 raise ValueError("Hooke tensor has inconsistent dimensions.") 50 return vdim 51 52 @staticmethod 53 def _hdim(vdim): 54 """Hooke tensor size from vector dimension""" 55 return (vdim*(vdim+1))//2 56 57 @property 58 def vdim(self): 59 return self._vdim(len(self.hooke)) 60 61 @property 62 def shape(self): return self.hooke.shape[2:] 63 64 def model_HFM(self): 65 d = self.vdim 66 suffix = "" if self.inverse_transformation is None else "Topographic" 67 return f"Seismic{suffix}{d}" 68 69 def flatten(self): 70 hooke = misc.flatten_symmetric_matrix(self.hooke) 71 if self.inverse_transformation is None: 72 return hooke 73 else: 74 inv_trans= self.inverse_transformation.reshape((self.vdim**2,)+self.shape) 75 return np.concatenate((hooke,inv_trans),axis=0) 76 77 @classmethod 78 def expand(cls,arr): 79 return cls(misc.expand_symmetric_matrix(arr)) 80 81 def __iter__(self): 82 yield self.hooke 83 for x in super(Hooke,self).__iter__(): 84 yield x 85 86 def with_cost(self,cost): 87 other = copy.copy(self) 88 hooke,cost = fd.common_field((self.hooke,cost),depths=(2,0)) 89 other.hooke = hooke / cost**2 90 return other 91 92 def _to_common_field(self,*args,**kwargs): 93 self.hooke,self.inverse_transformation = fd.common_field( 94 (self.hooke,self.inverse_transformation),(2,2),*args,**kwargs) 95 96 def _dual_params(self,*args,**kwargs): 97 return fd.common_field((self.hooke,),(2,),*args,**kwargs) 98 99 def _dual_level(self,v,params=None,relax=0.): 100 if params is None: params = self._dual_params(v.shape[1:]) 101 102 # Contract the hooke tensor and covector 103 hooke, = params 104 Voigt,Voigti = self._Voigt,self._Voigti 105 d = self.vdim 106 m = ad.asarray([[ 107 sum(v[j]*v[l] * hooke[Voigt[i,j],Voigt[k,l]] 108 for j in range(d) for l in range(d)) 109 for i in range(d)] for k in range(d)]) 110 111 # Evaluate det 112 s = np.exp(-relax) 113 xp = ad.cupy_generic.get_array_module(m) 114 ident = fd.as_field(xp.eye(d,dtype=m.dtype),m.shape[2:],depth=2) 115 return 1.-s -lp.det(ident - m*s) 116 117 def extract_xz(self): 118 """ 119 Extract a two dimensional Hooke tensor from a three dimensional one, 120 corresponding to a slice through the X and Z axes. 121 """ 122 assert self.vdim==3 123 h=self.hooke 124 return Hooke(ad.asarray([ 125 [h[0,0], h[0,2], h[0,4] ], 126 [h[2,0], h[2,2], h[2,4] ], 127 [h[4,0], h[4,2], h[4,4] ] 128 ])) 129 130 @classmethod 131 def from_VTI_2(cls,Vp,Vs,ε,δ): 132 """ 133 X,Z slice of a Vertical Transverse Isotropic medium 134 based on Thomsen parameters 135 """ 136 c33=Vp**2 137 c44=Vs**2 138 c11=c33*(1+2*ε) 139 c13=-c44+np.sqrt( (c33-c44)**2+2*δ*c33*(c33-c44) ) 140 return cls.from_hexagonal(c11,None,c13,c33,c44,vdim=2) 141 142 @classmethod 143 def from_Ellipse(cls,m): 144 """ 145 Rank deficient Hooke tensor, 146 equivalent, for pressure waves, to the Riemannian metric defined by $m ^ {-2}$. 147 Shear waves are infinitely slow. 148 """ 149 assert(len(m)==2) 150 a,b,c=m[0,0],m[1,1],m[0,1] 151 return Hooke(ad.asarray( [ [a*a, a*b,a*c], [a*b, b*b, b*c], [a*c, b*c, c*c] ] )) 152 153 @classmethod 154 def from_cast(cls,metric): 155 if isinstance(metric,cls): return metric 156 riemann = Riemann.from_cast(metric) 157 158 m = riemann.dual().m 159 assert not ad.is_ad(m) 160 from scipy.linalg import sqrtm 161 return cls.from_Ellipse(sqrtm(m)) 162 163 def _iter_implicit(self): 164 yield self.hooke 165 166 @property 167 def _Voigt(self): 168 """Direct Voigt indices""" 169 if self.vdim==2: return np.array([[0,2],[2,1]]) 170 elif self.vdim==3: return np.array([[0,5,4],[5,1,3],[4,3,2]]) 171 else: raise ValueError("Unsupported dimension") 172 @property 173 def _Voigti(self): 174 """Inverse Voigt indices""" 175 if self.vdim==2: return np.array([[0,0],[1,1],[0,1]]) 176 elif self.vdim==3: return np.array([[0,0],[1,1],[2,2],[1,2],[0,2],[0,1]]) 177 else: raise ValueError("Unsupported dimension") 178 179 def to_depth4(self): 180 """ 181 Produces the full Hooke tensor, of shape 182 (vdim,vdim,vdim,vdim, n1,...,nk) 183 where vdim is the ambient space dimension. 184 """ 185 Voigt = self._Voigt 186 d = self.vdim 187 return ad.array([ [ [ [ self.hooke[Voigt[i,j],Voigt[k,l]] 188 for i in range(d)] for j in range(d)] for k in range(d)] for l in range(d)]) 189 190 def rotate(self,r): 191 other = copy.copy(self) 192 hooke,r = common_field((self.hooke,r),(2,2)) 193 Voigti = self._Voigti 194 R = ad.array([ [ 195 r[i0,i1]*r[j0,j1] if i1==j1 else (r[i0,i1]*r[j0,j1]+r[j0,i1]*r[i0,j1]) 196 for (i0,j0) in Voigti] for (i1,j1) in Voigti]) 197 other.hooke = lp.dot_AA(lp.transpose(R),lp.dot_AA(hooke,R)) 198 return other 199 200 @staticmethod 201 def _Mandel_factors(vdim,shape=tuple(),a=np.sqrt(2)): 202 def f(k): return 1. if k<vdim else a 203 hdim = Hooke._hdim(vdim) 204 factors = ad.array([[f(i)*f(j) for i in range(hdim)] for j in range(hdim)]) 205 return fd.as_field(factors,shape,conditional=False) 206 207 def to_Mandel(self,a=np.sqrt(2)): 208 r"""Introduces the $\sqrt 2$ and $2$ factors involved in Mandel's notation""" 209 return self.hooke*self._Mandel_factors(self.vdim,self.shape) 210 211 @classmethod 212 def from_Mandel(cls,mandel,a=np.sqrt(2)): 213 r"""Removes the $\sqrt 2$ and $2$ factors involved in Mandel's notation""" 214 vdim = cls._vdim(len(mandel)) 215 return Hooke(mandel/cls._Mandel_factors(vdim,mandel.shape[2:],a)) 216 217 @classmethod 218 def from_orthorombic(cls,c11,c12,c13,c22,c23,c33,c44,c55,c66): 219 z=0*c11 # np.zeros_like(c11) raises issue in combination with cupy 220 return cls(ad.array([ 221 [c11,c12,c13, z, z, z], 222 [c12,c22,c23, z, z, z], 223 [c13,c23,c33, z, z, z], 224 [ z, z, z,c44, z, z], 225 [ z, z, z, z,c55, z], 226 [ z, z, z, z, z,c66] 227 ])) 228 229 @classmethod 230 def from_orthorombic2(cls,c11,c21,c22,c31,c32,c33,c44,c55,c66): 231 """Orthorombic medium with a different ordering of the first block coefficients""" 232 c12,c13,c23 = c21,c31,c32 233 return cls.from_orthorombic(c11,c12,c13,c22,c23,c33,c44,c55,c66) 234 235 @classmethod 236 def from_tetragonal(cls,c11,c12,c13,c33,c44,c66): 237 c22,c23,c55 = c11,c13,c44 238 return cls.from_orthorombic(c11,c12,c13,c22,c23,c33,c44,c55,c66) 239 240 @classmethod 241 def from_hexagonal(cls,c11,c12,c13,c33,c44,vdim=3): 242 if vdim==2: 243 z = 0*c11 244 return cls(ad.asarray( [ [c11,c13,z], [c13,c33,z], [z,z,c44] ] )) 245 c66 = (c11-c12)/2. 246 return cls.from_tetragonal(c11,c12,c13,c33,c44,c66) 247 248 @classmethod 249 def from_ThomsenElastic(cls,tem): 250 """Hooke tensor (m/s)^2 and density (g/cm^3)""" 251 if not isinstance(tem,ThomsenElasticMaterial): tem = ThomsenElasticMaterial(*tem) 252 hex,ρ = tem.to_hexagonal() 253 return cls.from_hexagonal(*hex),ρ 254 255 def to_orthorombic(self): 256 """Inverse function of from_orthorombic. No reconstruction check.""" 257 assert self.vdim==3 258 return tuple(self.hooke[i,j] for i,j in 259 ((0,0),(0,1),(0,2),(1,1),(1,2),(2,2),(3,3),(4,4),(5,5))) 260 def to_orthorombic2(self): 261 a,b,c,d,e,f,g,h,i = self.to_orthorombic() 262 return (a,b,d,c,e,f,g,h,i) 263 def to_tetragonal(self): 264 a,b,c,_a,_c,d,e,_e,f = self.to_orthorombic() 265 return (a,b,c,d,e,f) 266 def to_hexagonal(self): 267 a,b,c,d,e,_a_b_2 = self.to_tetragonal() 268 return (a,b,c,d,e) 269 270 def is_TTI(self,tol=None): 271 """ 272 Determine if the metric is in a TTI form. 273 """ 274 # Original code by F. Desquilbet, 2020 275 if tol is None: # small value (acts as zero for floats) 276 tol = max(1e-9, MA(hooke)*1e-12) 277 278 def small(arr): return np.max(np.abs(arr))<tol 279 is_Sym = small(hooke-lp.transpose(hooke)) # symmetrical 280 281 if metric.vdim==2: 282 return is_Sym and small(hooke[2,0]) and small(hooke[2,1]) 283 if metric.vdim==3: 284 return (is_Sym 285 and small((hooke[0,0]-hooke[0,1])/2-hooke[5,5]) 286 and all(small(hooke[i,j]-hooke[k,l]) 287 for ((i,j),(k,l)) in [((0,0),(1,1)), ((2,0),(2,1)), ((3,3),(4,4))]) 288 and all(small(hooke[i,j]) 289 for (i,j) in [(3,0),(4,0),(5,0),(3,1),(4,1),(5,1), 290 (3,2),(4,2),(5,2),(4,3),(5,3),(5,4)]) ) 291 292 def _Voigt_m2v(self,m,sym=True): 293 """ 294 Turns a symmetric matrix into a vector, based on Voigt convention. 295 - sym : True -> use the upper triangular part of m. 296 False -> symmetrize the matrix m (adding its transpose). 297 """ 298 assert(self.inverse_transformation is None) 299 m=ad.asarray(m) 300 vdim = self.vdim 301 assert(m.shape[:2]==(vdim,vdim)) 302 if vdim==1: 303 return m[0] 304 elif vdim==2: 305 if sym: return ad.array((m[0,0],m[1,1],2*m[0,1])) 306 else: return ad.array((m[0,0],m[1,1],m[0,1]+m[1,0])) 307 elif vdim==3: 308 if sym: return ad.array((m[0,0],m[1,1],m[2,2], 309 2*m[1,2],2*m[0,2],2*m[0,1])) 310 else: return ad.array((m1[0,0],m1[1,1],m[2,2], 311 m[1,2]+m[2,1],m[0,2]+m[2,0],m[0,1]+m[1,0])) 312 else: 313 raise ValueError("Unsupported dimension") 314 315 def dot_A(self,m,sym=True): 316 """ 317 Dot product associated with a Hooke tensor, which turns a strain tensor epsilon 318 into a stress tensor sigma. 319 320 Input: 321 - m : the strain tensor. 322 """ 323 v,hooke = fd.common_field((self._Voigt_m2v(m,sym),self.hooke),(1,2)) 324 w = lp.dot_AV(hooke,v) 325 return ad.array( ((w[0],w[2]),(w[2],w[1])) ) 326 327 def dot_AA(self,m1,m2=None,sym=True): 328 """ 329 Inner product associated with a Hooke tensor, on the space of symmetric matrices. 330 331 Inputs: 332 - m1 : first symmetric matrix 333 - m2 : second symmetric matrix. Defaults to m1. 334 """ 335 if m2 is None: 336 v1,hooke = fd.common_field((self._Voigt_m2v(m1,sym),self.hooke),(1,2)) 337 v2=v1 338 else: 339 v1,v2,hooke = fd.common_field( 340 (self._Voigt_m2v(m1,sym),self._Voigt_m2v(m2,sym),self.hooke),(1,1,2)) 341 return lp.dot_VV(v1,lp.dot_AV(hooke,v2)) 342 343 def Selling(self): 344 r""" 345 Returns a decomposition of the hooke tensor in the mathematical form 346 $$ 347 hooke = \sum_i \rho_i m_i m_i^\top, 348 $$ 349 where $\rho_i$ is a non-negative coefficient, $m_i$ is symmetric nonzero and has 350 integer entries, and $\sum_i \rho_i$ is maximal. 351 """ 352 assert(self.inverse_transformation is None) 353 if self.vdim<=2: coefs,offsets = Selling.Decomposition(self.hooke) 354 else: 355 from ... import Eikonal 356 coefs,offsets = Eikonal.VoronoiDecomposition(self.hooke) 357 if self.vdim==1: 358 moffsets = np.expand_dims(offsets,axis=0) 359 elif self.vdim==2: 360 moffsets = ad.array(((offsets[0],offsets[2]),(offsets[2],offsets[1]))) 361 elif self.vdim==3: 362 moffsets = ad.array(( #Voigt notation 363 (offsets[0],offsets[5],offsets[4]), 364 (offsets[5],offsets[1],offsets[3]), 365 (offsets[4],offsets[3],offsets[2]))) 366 else : 367 raise ValueError("Unsupported dimension") 368 return coefs,moffsets 369 370 def apply_transform(self): 371 """ 372 Applies the transformation, if any stored, to the hooke tensor. 373 374 CAUTION : this feature is required for some applications to elasticity, 375 but is incompatible with the eikonal equation solver. 376 """ 377 r = self.inverse_transformation 378 if r is None: return self 379 other = copy.copy(self) 380 other.inverse_transformation = None 381 return other.rotate(lp.transpose(r)) 382 383 @classmethod 384 def from_Lame(cls,λ,μ,vdim=2): 385 """ 386 Constructs a Hooke tensor from the Lame coefficients, in dimension 2 or 3. 387 Positive definite provided μ>0 and 2μ+dλ>0 388 """ 389 z,s = 0*λ,λ+2*μ 390 if vdim==2: return cls(ad.array([ 391 [s,λ,z], 392 [λ,s,z], 393 [z,z,μ]])) 394 elif vdim==3: return cls(ad.array([ 395 [s,λ,λ,z,z,z], 396 [λ,s,λ,z,z,z], 397 [λ,λ,s,z,z,z], 398 [z,z,z,μ,z,z], 399 [z,z,z,z,μ,z], 400 [z,z,z,z,z,μ]])) 401 else: raise ValueError("Unsupported dimension") 402 403 def contract(self,w): 404 r"""Returns the contracted tensor $\sum_{j,l}c_{ijkl} w_j w_l$.""" 405 voi = self._Voigt 406 hooke,w = fd.common_field((self.hooke,w),depths=(2,1)) 407 def c(i,j,k,l): return hooke[voi[i,j],voi[k,l]] 408 d = self.vdim; assert len(w)==d 409 return ad.array([[ 410 sum(c(i,j,k,l)*w[j]*w[l] for j in range(d) for l in range(d)) 411 for i in range(d)] for k in range(d)]) 412 413 def waves(self,k,ρ): 414 """Returns the pulsation and direction of the waves with the given wave vector.""" 415 m = np.moveaxis(self.contract(k),(0,1),(-2,-1)) 416 eVal,eVec = np.linalg.eigh(m) 417 eVec = np.moveaxis(eVec,(-2,-1),(0,1)) 418 return np.sqrt(eVal/ρ),eVec 419 420 421 422 423# Hooke tensor (m/s)^2 and density (g/cm^3) 424# Reference : Lecomte, I. (1993). Finite difference calculation of first traveltimes 425# in anisotropic media 1. Geophysical Journal International, 113(2), 318–342. 426Hooke.mica = Hooke.from_hexagonal(178.,42.4,14.5,54.9,12.2), 2.79 427Hooke.stishovite = Hooke.from_tetragonal(453,211,203,776,252,302), 4.29 428Hooke.olivine = Hooke.from_orthorombic(323.7,66.4,71.6,197.6,75.6,235.1,64.6,78.7,79.0), 3.311
19class Hooke(ImplicitBase): 20 r""" 21 The *dual* norm defined by a Hooke tensor takes the form 22 $$ 23 F^*(x) = \max_{|y|\leq 1} \sqrt{\sum_{ijkl} c_{ijkl} x_i y_j x_k y_l} 24 $$ 25 where c is the Hooke tensor, and y ranges over the unit ball. 26 The primal norm is obtained implicitly, by solving an optimization problem. 27 28 These norms characterize the arrival time of pressure waves in elasticity. 29 They are often encountered in seismic traveltime tomography. 30 31 Member fields and __init__ arguments : 32 - hooke : an array of shape (hdim,hdim,n1,...,nk) where hdim = vdim*(vdim+1)/2 33 and vdim is the ambient space dimension. The array must be symmetric, and encodes the 34 hooke tensor c in Voigt notation. 35 - *args,**kwargs (optional) : passed to ImplicitBase 36 """ 37 def __init__(self,hooke,*args,**kwargs): 38 super(Hooke,self).__init__(*args,**kwargs) 39 self.hooke = hooke 40 self._to_common_field() 41 42 def is_definite(self): 43 return Riemann(self.hooke).is_definite() 44 45 @staticmethod 46 def _vdim(hdim): 47 """Vector dimension from Hooke tensor size""" 48 vdim = int(np.sqrt(2*hdim)) 49 if Hooke._hdim(vdim)!=hdim: 50 raise ValueError("Hooke tensor has inconsistent dimensions.") 51 return vdim 52 53 @staticmethod 54 def _hdim(vdim): 55 """Hooke tensor size from vector dimension""" 56 return (vdim*(vdim+1))//2 57 58 @property 59 def vdim(self): 60 return self._vdim(len(self.hooke)) 61 62 @property 63 def shape(self): return self.hooke.shape[2:] 64 65 def model_HFM(self): 66 d = self.vdim 67 suffix = "" if self.inverse_transformation is None else "Topographic" 68 return f"Seismic{suffix}{d}" 69 70 def flatten(self): 71 hooke = misc.flatten_symmetric_matrix(self.hooke) 72 if self.inverse_transformation is None: 73 return hooke 74 else: 75 inv_trans= self.inverse_transformation.reshape((self.vdim**2,)+self.shape) 76 return np.concatenate((hooke,inv_trans),axis=0) 77 78 @classmethod 79 def expand(cls,arr): 80 return cls(misc.expand_symmetric_matrix(arr)) 81 82 def __iter__(self): 83 yield self.hooke 84 for x in super(Hooke,self).__iter__(): 85 yield x 86 87 def with_cost(self,cost): 88 other = copy.copy(self) 89 hooke,cost = fd.common_field((self.hooke,cost),depths=(2,0)) 90 other.hooke = hooke / cost**2 91 return other 92 93 def _to_common_field(self,*args,**kwargs): 94 self.hooke,self.inverse_transformation = fd.common_field( 95 (self.hooke,self.inverse_transformation),(2,2),*args,**kwargs) 96 97 def _dual_params(self,*args,**kwargs): 98 return fd.common_field((self.hooke,),(2,),*args,**kwargs) 99 100 def _dual_level(self,v,params=None,relax=0.): 101 if params is None: params = self._dual_params(v.shape[1:]) 102 103 # Contract the hooke tensor and covector 104 hooke, = params 105 Voigt,Voigti = self._Voigt,self._Voigti 106 d = self.vdim 107 m = ad.asarray([[ 108 sum(v[j]*v[l] * hooke[Voigt[i,j],Voigt[k,l]] 109 for j in range(d) for l in range(d)) 110 for i in range(d)] for k in range(d)]) 111 112 # Evaluate det 113 s = np.exp(-relax) 114 xp = ad.cupy_generic.get_array_module(m) 115 ident = fd.as_field(xp.eye(d,dtype=m.dtype),m.shape[2:],depth=2) 116 return 1.-s -lp.det(ident - m*s) 117 118 def extract_xz(self): 119 """ 120 Extract a two dimensional Hooke tensor from a three dimensional one, 121 corresponding to a slice through the X and Z axes. 122 """ 123 assert self.vdim==3 124 h=self.hooke 125 return Hooke(ad.asarray([ 126 [h[0,0], h[0,2], h[0,4] ], 127 [h[2,0], h[2,2], h[2,4] ], 128 [h[4,0], h[4,2], h[4,4] ] 129 ])) 130 131 @classmethod 132 def from_VTI_2(cls,Vp,Vs,ε,δ): 133 """ 134 X,Z slice of a Vertical Transverse Isotropic medium 135 based on Thomsen parameters 136 """ 137 c33=Vp**2 138 c44=Vs**2 139 c11=c33*(1+2*ε) 140 c13=-c44+np.sqrt( (c33-c44)**2+2*δ*c33*(c33-c44) ) 141 return cls.from_hexagonal(c11,None,c13,c33,c44,vdim=2) 142 143 @classmethod 144 def from_Ellipse(cls,m): 145 """ 146 Rank deficient Hooke tensor, 147 equivalent, for pressure waves, to the Riemannian metric defined by $m ^ {-2}$. 148 Shear waves are infinitely slow. 149 """ 150 assert(len(m)==2) 151 a,b,c=m[0,0],m[1,1],m[0,1] 152 return Hooke(ad.asarray( [ [a*a, a*b,a*c], [a*b, b*b, b*c], [a*c, b*c, c*c] ] )) 153 154 @classmethod 155 def from_cast(cls,metric): 156 if isinstance(metric,cls): return metric 157 riemann = Riemann.from_cast(metric) 158 159 m = riemann.dual().m 160 assert not ad.is_ad(m) 161 from scipy.linalg import sqrtm 162 return cls.from_Ellipse(sqrtm(m)) 163 164 def _iter_implicit(self): 165 yield self.hooke 166 167 @property 168 def _Voigt(self): 169 """Direct Voigt indices""" 170 if self.vdim==2: return np.array([[0,2],[2,1]]) 171 elif self.vdim==3: return np.array([[0,5,4],[5,1,3],[4,3,2]]) 172 else: raise ValueError("Unsupported dimension") 173 @property 174 def _Voigti(self): 175 """Inverse Voigt indices""" 176 if self.vdim==2: return np.array([[0,0],[1,1],[0,1]]) 177 elif self.vdim==3: return np.array([[0,0],[1,1],[2,2],[1,2],[0,2],[0,1]]) 178 else: raise ValueError("Unsupported dimension") 179 180 def to_depth4(self): 181 """ 182 Produces the full Hooke tensor, of shape 183 (vdim,vdim,vdim,vdim, n1,...,nk) 184 where vdim is the ambient space dimension. 185 """ 186 Voigt = self._Voigt 187 d = self.vdim 188 return ad.array([ [ [ [ self.hooke[Voigt[i,j],Voigt[k,l]] 189 for i in range(d)] for j in range(d)] for k in range(d)] for l in range(d)]) 190 191 def rotate(self,r): 192 other = copy.copy(self) 193 hooke,r = common_field((self.hooke,r),(2,2)) 194 Voigti = self._Voigti 195 R = ad.array([ [ 196 r[i0,i1]*r[j0,j1] if i1==j1 else (r[i0,i1]*r[j0,j1]+r[j0,i1]*r[i0,j1]) 197 for (i0,j0) in Voigti] for (i1,j1) in Voigti]) 198 other.hooke = lp.dot_AA(lp.transpose(R),lp.dot_AA(hooke,R)) 199 return other 200 201 @staticmethod 202 def _Mandel_factors(vdim,shape=tuple(),a=np.sqrt(2)): 203 def f(k): return 1. if k<vdim else a 204 hdim = Hooke._hdim(vdim) 205 factors = ad.array([[f(i)*f(j) for i in range(hdim)] for j in range(hdim)]) 206 return fd.as_field(factors,shape,conditional=False) 207 208 def to_Mandel(self,a=np.sqrt(2)): 209 r"""Introduces the $\sqrt 2$ and $2$ factors involved in Mandel's notation""" 210 return self.hooke*self._Mandel_factors(self.vdim,self.shape) 211 212 @classmethod 213 def from_Mandel(cls,mandel,a=np.sqrt(2)): 214 r"""Removes the $\sqrt 2$ and $2$ factors involved in Mandel's notation""" 215 vdim = cls._vdim(len(mandel)) 216 return Hooke(mandel/cls._Mandel_factors(vdim,mandel.shape[2:],a)) 217 218 @classmethod 219 def from_orthorombic(cls,c11,c12,c13,c22,c23,c33,c44,c55,c66): 220 z=0*c11 # np.zeros_like(c11) raises issue in combination with cupy 221 return cls(ad.array([ 222 [c11,c12,c13, z, z, z], 223 [c12,c22,c23, z, z, z], 224 [c13,c23,c33, z, z, z], 225 [ z, z, z,c44, z, z], 226 [ z, z, z, z,c55, z], 227 [ z, z, z, z, z,c66] 228 ])) 229 230 @classmethod 231 def from_orthorombic2(cls,c11,c21,c22,c31,c32,c33,c44,c55,c66): 232 """Orthorombic medium with a different ordering of the first block coefficients""" 233 c12,c13,c23 = c21,c31,c32 234 return cls.from_orthorombic(c11,c12,c13,c22,c23,c33,c44,c55,c66) 235 236 @classmethod 237 def from_tetragonal(cls,c11,c12,c13,c33,c44,c66): 238 c22,c23,c55 = c11,c13,c44 239 return cls.from_orthorombic(c11,c12,c13,c22,c23,c33,c44,c55,c66) 240 241 @classmethod 242 def from_hexagonal(cls,c11,c12,c13,c33,c44,vdim=3): 243 if vdim==2: 244 z = 0*c11 245 return cls(ad.asarray( [ [c11,c13,z], [c13,c33,z], [z,z,c44] ] )) 246 c66 = (c11-c12)/2. 247 return cls.from_tetragonal(c11,c12,c13,c33,c44,c66) 248 249 @classmethod 250 def from_ThomsenElastic(cls,tem): 251 """Hooke tensor (m/s)^2 and density (g/cm^3)""" 252 if not isinstance(tem,ThomsenElasticMaterial): tem = ThomsenElasticMaterial(*tem) 253 hex,ρ = tem.to_hexagonal() 254 return cls.from_hexagonal(*hex),ρ 255 256 def to_orthorombic(self): 257 """Inverse function of from_orthorombic. No reconstruction check.""" 258 assert self.vdim==3 259 return tuple(self.hooke[i,j] for i,j in 260 ((0,0),(0,1),(0,2),(1,1),(1,2),(2,2),(3,3),(4,4),(5,5))) 261 def to_orthorombic2(self): 262 a,b,c,d,e,f,g,h,i = self.to_orthorombic() 263 return (a,b,d,c,e,f,g,h,i) 264 def to_tetragonal(self): 265 a,b,c,_a,_c,d,e,_e,f = self.to_orthorombic() 266 return (a,b,c,d,e,f) 267 def to_hexagonal(self): 268 a,b,c,d,e,_a_b_2 = self.to_tetragonal() 269 return (a,b,c,d,e) 270 271 def is_TTI(self,tol=None): 272 """ 273 Determine if the metric is in a TTI form. 274 """ 275 # Original code by F. Desquilbet, 2020 276 if tol is None: # small value (acts as zero for floats) 277 tol = max(1e-9, MA(hooke)*1e-12) 278 279 def small(arr): return np.max(np.abs(arr))<tol 280 is_Sym = small(hooke-lp.transpose(hooke)) # symmetrical 281 282 if metric.vdim==2: 283 return is_Sym and small(hooke[2,0]) and small(hooke[2,1]) 284 if metric.vdim==3: 285 return (is_Sym 286 and small((hooke[0,0]-hooke[0,1])/2-hooke[5,5]) 287 and all(small(hooke[i,j]-hooke[k,l]) 288 for ((i,j),(k,l)) in [((0,0),(1,1)), ((2,0),(2,1)), ((3,3),(4,4))]) 289 and all(small(hooke[i,j]) 290 for (i,j) in [(3,0),(4,0),(5,0),(3,1),(4,1),(5,1), 291 (3,2),(4,2),(5,2),(4,3),(5,3),(5,4)]) ) 292 293 def _Voigt_m2v(self,m,sym=True): 294 """ 295 Turns a symmetric matrix into a vector, based on Voigt convention. 296 - sym : True -> use the upper triangular part of m. 297 False -> symmetrize the matrix m (adding its transpose). 298 """ 299 assert(self.inverse_transformation is None) 300 m=ad.asarray(m) 301 vdim = self.vdim 302 assert(m.shape[:2]==(vdim,vdim)) 303 if vdim==1: 304 return m[0] 305 elif vdim==2: 306 if sym: return ad.array((m[0,0],m[1,1],2*m[0,1])) 307 else: return ad.array((m[0,0],m[1,1],m[0,1]+m[1,0])) 308 elif vdim==3: 309 if sym: return ad.array((m[0,0],m[1,1],m[2,2], 310 2*m[1,2],2*m[0,2],2*m[0,1])) 311 else: return ad.array((m1[0,0],m1[1,1],m[2,2], 312 m[1,2]+m[2,1],m[0,2]+m[2,0],m[0,1]+m[1,0])) 313 else: 314 raise ValueError("Unsupported dimension") 315 316 def dot_A(self,m,sym=True): 317 """ 318 Dot product associated with a Hooke tensor, which turns a strain tensor epsilon 319 into a stress tensor sigma. 320 321 Input: 322 - m : the strain tensor. 323 """ 324 v,hooke = fd.common_field((self._Voigt_m2v(m,sym),self.hooke),(1,2)) 325 w = lp.dot_AV(hooke,v) 326 return ad.array( ((w[0],w[2]),(w[2],w[1])) ) 327 328 def dot_AA(self,m1,m2=None,sym=True): 329 """ 330 Inner product associated with a Hooke tensor, on the space of symmetric matrices. 331 332 Inputs: 333 - m1 : first symmetric matrix 334 - m2 : second symmetric matrix. Defaults to m1. 335 """ 336 if m2 is None: 337 v1,hooke = fd.common_field((self._Voigt_m2v(m1,sym),self.hooke),(1,2)) 338 v2=v1 339 else: 340 v1,v2,hooke = fd.common_field( 341 (self._Voigt_m2v(m1,sym),self._Voigt_m2v(m2,sym),self.hooke),(1,1,2)) 342 return lp.dot_VV(v1,lp.dot_AV(hooke,v2)) 343 344 def Selling(self): 345 r""" 346 Returns a decomposition of the hooke tensor in the mathematical form 347 $$ 348 hooke = \sum_i \rho_i m_i m_i^\top, 349 $$ 350 where $\rho_i$ is a non-negative coefficient, $m_i$ is symmetric nonzero and has 351 integer entries, and $\sum_i \rho_i$ is maximal. 352 """ 353 assert(self.inverse_transformation is None) 354 if self.vdim<=2: coefs,offsets = Selling.Decomposition(self.hooke) 355 else: 356 from ... import Eikonal 357 coefs,offsets = Eikonal.VoronoiDecomposition(self.hooke) 358 if self.vdim==1: 359 moffsets = np.expand_dims(offsets,axis=0) 360 elif self.vdim==2: 361 moffsets = ad.array(((offsets[0],offsets[2]),(offsets[2],offsets[1]))) 362 elif self.vdim==3: 363 moffsets = ad.array(( #Voigt notation 364 (offsets[0],offsets[5],offsets[4]), 365 (offsets[5],offsets[1],offsets[3]), 366 (offsets[4],offsets[3],offsets[2]))) 367 else : 368 raise ValueError("Unsupported dimension") 369 return coefs,moffsets 370 371 def apply_transform(self): 372 """ 373 Applies the transformation, if any stored, to the hooke tensor. 374 375 CAUTION : this feature is required for some applications to elasticity, 376 but is incompatible with the eikonal equation solver. 377 """ 378 r = self.inverse_transformation 379 if r is None: return self 380 other = copy.copy(self) 381 other.inverse_transformation = None 382 return other.rotate(lp.transpose(r)) 383 384 @classmethod 385 def from_Lame(cls,λ,μ,vdim=2): 386 """ 387 Constructs a Hooke tensor from the Lame coefficients, in dimension 2 or 3. 388 Positive definite provided μ>0 and 2μ+dλ>0 389 """ 390 z,s = 0*λ,λ+2*μ 391 if vdim==2: return cls(ad.array([ 392 [s,λ,z], 393 [λ,s,z], 394 [z,z,μ]])) 395 elif vdim==3: return cls(ad.array([ 396 [s,λ,λ,z,z,z], 397 [λ,s,λ,z,z,z], 398 [λ,λ,s,z,z,z], 399 [z,z,z,μ,z,z], 400 [z,z,z,z,μ,z], 401 [z,z,z,z,z,μ]])) 402 else: raise ValueError("Unsupported dimension") 403 404 def contract(self,w): 405 r"""Returns the contracted tensor $\sum_{j,l}c_{ijkl} w_j w_l$.""" 406 voi = self._Voigt 407 hooke,w = fd.common_field((self.hooke,w),depths=(2,1)) 408 def c(i,j,k,l): return hooke[voi[i,j],voi[k,l]] 409 d = self.vdim; assert len(w)==d 410 return ad.array([[ 411 sum(c(i,j,k,l)*w[j]*w[l] for j in range(d) for l in range(d)) 412 for i in range(d)] for k in range(d)]) 413 414 def waves(self,k,ρ): 415 """Returns the pulsation and direction of the waves with the given wave vector.""" 416 m = np.moveaxis(self.contract(k),(0,1),(-2,-1)) 417 eVal,eVec = np.linalg.eigh(m) 418 eVec = np.moveaxis(eVec,(-2,-1),(0,1)) 419 return np.sqrt(eVal/ρ),eVec
The dual norm defined by a Hooke tensor takes the form $$ F^*(x) = \max_{|y|\leq 1} \sqrt{\sum_{ijkl} c_{ijkl} x_i y_j x_k y_l} $$ where c is the Hooke tensor, and y ranges over the unit ball. The primal norm is obtained implicitly, by solving an optimization problem.
These norms characterize the arrival time of pressure waves in elasticity. They are often encountered in seismic traveltime tomography.
Member fields and __init__ arguments :
- hooke : an array of shape (hdim,hdim,n1,...,nk) where hdim = vdim*(vdim+1)/2 and vdim is the ambient space dimension. The array must be symmetric, and encodes the hooke tensor c in Voigt notation.
- args,*kwargs (optional) : passed to ImplicitBase
Dimensions of the underlying domain.
Expected to be the empty tuple, or a tuple of length vdim
.
65 def model_HFM(self): 66 d = self.vdim 67 suffix = "" if self.inverse_transformation is None else "Topographic" 68 return f"Seismic{suffix}{d}"
The name of the 'model' for parameter, as input to the HFM library.
70 def flatten(self): 71 hooke = misc.flatten_symmetric_matrix(self.hooke) 72 if self.inverse_transformation is None: 73 return hooke 74 else: 75 inv_trans= self.inverse_transformation.reshape((self.vdim**2,)+self.shape) 76 return np.concatenate((hooke,inv_trans),axis=0)
Flattens and concatenate the member fields into a single array.
Inverse of the flatten member function. Turns a suitable array into a metric.
87 def with_cost(self,cost): 88 other = copy.copy(self) 89 hooke,cost = fd.common_field((self.hooke,cost),depths=(2,0)) 90 other.hooke = hooke / cost**2 91 return other
Produces a norm $N'$ obeying $N'(x) = N(cost*x)$.
118 def extract_xz(self): 119 """ 120 Extract a two dimensional Hooke tensor from a three dimensional one, 121 corresponding to a slice through the X and Z axes. 122 """ 123 assert self.vdim==3 124 h=self.hooke 125 return Hooke(ad.asarray([ 126 [h[0,0], h[0,2], h[0,4] ], 127 [h[2,0], h[2,2], h[2,4] ], 128 [h[4,0], h[4,2], h[4,4] ] 129 ]))
Extract a two dimensional Hooke tensor from a three dimensional one, corresponding to a slice through the X and Z axes.
131 @classmethod 132 def from_VTI_2(cls,Vp,Vs,ε,δ): 133 """ 134 X,Z slice of a Vertical Transverse Isotropic medium 135 based on Thomsen parameters 136 """ 137 c33=Vp**2 138 c44=Vs**2 139 c11=c33*(1+2*ε) 140 c13=-c44+np.sqrt( (c33-c44)**2+2*δ*c33*(c33-c44) ) 141 return cls.from_hexagonal(c11,None,c13,c33,c44,vdim=2)
X,Z slice of a Vertical Transverse Isotropic medium based on Thomsen parameters
143 @classmethod 144 def from_Ellipse(cls,m): 145 """ 146 Rank deficient Hooke tensor, 147 equivalent, for pressure waves, to the Riemannian metric defined by $m ^ {-2}$. 148 Shear waves are infinitely slow. 149 """ 150 assert(len(m)==2) 151 a,b,c=m[0,0],m[1,1],m[0,1] 152 return Hooke(ad.asarray( [ [a*a, a*b,a*c], [a*b, b*b, b*c], [a*c, b*c, c*c] ] ))
Rank deficient Hooke tensor, equivalent, for pressure waves, to the Riemannian metric defined by $m ^ {-2}$. Shear waves are infinitely slow.
154 @classmethod 155 def from_cast(cls,metric): 156 if isinstance(metric,cls): return metric 157 riemann = Riemann.from_cast(metric) 158 159 m = riemann.dual().m 160 assert not ad.is_ad(m) 161 from scipy.linalg import sqrtm 162 return cls.from_Ellipse(sqrtm(m))
Produces a metric by casting another metric of a compatible type.
180 def to_depth4(self): 181 """ 182 Produces the full Hooke tensor, of shape 183 (vdim,vdim,vdim,vdim, n1,...,nk) 184 where vdim is the ambient space dimension. 185 """ 186 Voigt = self._Voigt 187 d = self.vdim 188 return ad.array([ [ [ [ self.hooke[Voigt[i,j],Voigt[k,l]] 189 for i in range(d)] for j in range(d)] for k in range(d)] for l in range(d)])
Produces the full Hooke tensor, of shape (vdim,vdim,vdim,vdim, n1,...,nk) where vdim is the ambient space dimension.
191 def rotate(self,r): 192 other = copy.copy(self) 193 hooke,r = common_field((self.hooke,r),(2,2)) 194 Voigti = self._Voigti 195 R = ad.array([ [ 196 r[i0,i1]*r[j0,j1] if i1==j1 else (r[i0,i1]*r[j0,j1]+r[j0,i1]*r[i0,j1]) 197 for (i0,j0) in Voigti] for (i1,j1) in Voigti]) 198 other.hooke = lp.dot_AA(lp.transpose(R),lp.dot_AA(hooke,R)) 199 return other
Rotation of the norm, by a given rotation matrix. The new unit ball is the direct image of the previous one.
208 def to_Mandel(self,a=np.sqrt(2)): 209 r"""Introduces the $\sqrt 2$ and $2$ factors involved in Mandel's notation""" 210 return self.hooke*self._Mandel_factors(self.vdim,self.shape)
Introduces the $\sqrt 2$ and $2$ factors involved in Mandel's notation
212 @classmethod 213 def from_Mandel(cls,mandel,a=np.sqrt(2)): 214 r"""Removes the $\sqrt 2$ and $2$ factors involved in Mandel's notation""" 215 vdim = cls._vdim(len(mandel)) 216 return Hooke(mandel/cls._Mandel_factors(vdim,mandel.shape[2:],a))
Removes the $\sqrt 2$ and $2$ factors involved in Mandel's notation
218 @classmethod 219 def from_orthorombic(cls,c11,c12,c13,c22,c23,c33,c44,c55,c66): 220 z=0*c11 # np.zeros_like(c11) raises issue in combination with cupy 221 return cls(ad.array([ 222 [c11,c12,c13, z, z, z], 223 [c12,c22,c23, z, z, z], 224 [c13,c23,c33, z, z, z], 225 [ z, z, z,c44, z, z], 226 [ z, z, z, z,c55, z], 227 [ z, z, z, z, z,c66] 228 ]))
230 @classmethod 231 def from_orthorombic2(cls,c11,c21,c22,c31,c32,c33,c44,c55,c66): 232 """Orthorombic medium with a different ordering of the first block coefficients""" 233 c12,c13,c23 = c21,c31,c32 234 return cls.from_orthorombic(c11,c12,c13,c22,c23,c33,c44,c55,c66)
Orthorombic medium with a different ordering of the first block coefficients
249 @classmethod 250 def from_ThomsenElastic(cls,tem): 251 """Hooke tensor (m/s)^2 and density (g/cm^3)""" 252 if not isinstance(tem,ThomsenElasticMaterial): tem = ThomsenElasticMaterial(*tem) 253 hex,ρ = tem.to_hexagonal() 254 return cls.from_hexagonal(*hex),ρ
Hooke tensor (m/s)^2 and density (g/cm^3)
256 def to_orthorombic(self): 257 """Inverse function of from_orthorombic. No reconstruction check.""" 258 assert self.vdim==3 259 return tuple(self.hooke[i,j] for i,j in 260 ((0,0),(0,1),(0,2),(1,1),(1,2),(2,2),(3,3),(4,4),(5,5)))
Inverse function of from_orthorombic. No reconstruction check.
271 def is_TTI(self,tol=None): 272 """ 273 Determine if the metric is in a TTI form. 274 """ 275 # Original code by F. Desquilbet, 2020 276 if tol is None: # small value (acts as zero for floats) 277 tol = max(1e-9, MA(hooke)*1e-12) 278 279 def small(arr): return np.max(np.abs(arr))<tol 280 is_Sym = small(hooke-lp.transpose(hooke)) # symmetrical 281 282 if metric.vdim==2: 283 return is_Sym and small(hooke[2,0]) and small(hooke[2,1]) 284 if metric.vdim==3: 285 return (is_Sym 286 and small((hooke[0,0]-hooke[0,1])/2-hooke[5,5]) 287 and all(small(hooke[i,j]-hooke[k,l]) 288 for ((i,j),(k,l)) in [((0,0),(1,1)), ((2,0),(2,1)), ((3,3),(4,4))]) 289 and all(small(hooke[i,j]) 290 for (i,j) in [(3,0),(4,0),(5,0),(3,1),(4,1),(5,1), 291 (3,2),(4,2),(5,2),(4,3),(5,3),(5,4)]) )
Determine if the metric is in a TTI form.
316 def dot_A(self,m,sym=True): 317 """ 318 Dot product associated with a Hooke tensor, which turns a strain tensor epsilon 319 into a stress tensor sigma. 320 321 Input: 322 - m : the strain tensor. 323 """ 324 v,hooke = fd.common_field((self._Voigt_m2v(m,sym),self.hooke),(1,2)) 325 w = lp.dot_AV(hooke,v) 326 return ad.array( ((w[0],w[2]),(w[2],w[1])) )
Dot product associated with a Hooke tensor, which turns a strain tensor epsilon into a stress tensor sigma.
Input:
- m : the strain tensor.
328 def dot_AA(self,m1,m2=None,sym=True): 329 """ 330 Inner product associated with a Hooke tensor, on the space of symmetric matrices. 331 332 Inputs: 333 - m1 : first symmetric matrix 334 - m2 : second symmetric matrix. Defaults to m1. 335 """ 336 if m2 is None: 337 v1,hooke = fd.common_field((self._Voigt_m2v(m1,sym),self.hooke),(1,2)) 338 v2=v1 339 else: 340 v1,v2,hooke = fd.common_field( 341 (self._Voigt_m2v(m1,sym),self._Voigt_m2v(m2,sym),self.hooke),(1,1,2)) 342 return lp.dot_VV(v1,lp.dot_AV(hooke,v2))
Inner product associated with a Hooke tensor, on the space of symmetric matrices.
Inputs:
- m1 : first symmetric matrix
- m2 : second symmetric matrix. Defaults to m1.
344 def Selling(self): 345 r""" 346 Returns a decomposition of the hooke tensor in the mathematical form 347 $$ 348 hooke = \sum_i \rho_i m_i m_i^\top, 349 $$ 350 where $\rho_i$ is a non-negative coefficient, $m_i$ is symmetric nonzero and has 351 integer entries, and $\sum_i \rho_i$ is maximal. 352 """ 353 assert(self.inverse_transformation is None) 354 if self.vdim<=2: coefs,offsets = Selling.Decomposition(self.hooke) 355 else: 356 from ... import Eikonal 357 coefs,offsets = Eikonal.VoronoiDecomposition(self.hooke) 358 if self.vdim==1: 359 moffsets = np.expand_dims(offsets,axis=0) 360 elif self.vdim==2: 361 moffsets = ad.array(((offsets[0],offsets[2]),(offsets[2],offsets[1]))) 362 elif self.vdim==3: 363 moffsets = ad.array(( #Voigt notation 364 (offsets[0],offsets[5],offsets[4]), 365 (offsets[5],offsets[1],offsets[3]), 366 (offsets[4],offsets[3],offsets[2]))) 367 else : 368 raise ValueError("Unsupported dimension") 369 return coefs,moffsets
Returns a decomposition of the hooke tensor in the mathematical form $$ hooke = \sum_i \rho_i m_i m_i^\top, $$ where $\rho_i$ is a non-negative coefficient, $m_i$ is symmetric nonzero and has integer entries, and $\sum_i \rho_i$ is maximal.
371 def apply_transform(self): 372 """ 373 Applies the transformation, if any stored, to the hooke tensor. 374 375 CAUTION : this feature is required for some applications to elasticity, 376 but is incompatible with the eikonal equation solver. 377 """ 378 r = self.inverse_transformation 379 if r is None: return self 380 other = copy.copy(self) 381 other.inverse_transformation = None 382 return other.rotate(lp.transpose(r))
Applies the transformation, if any stored, to the hooke tensor.
CAUTION : this feature is required for some applications to elasticity, but is incompatible with the eikonal equation solver.
384 @classmethod 385 def from_Lame(cls,λ,μ,vdim=2): 386 """ 387 Constructs a Hooke tensor from the Lame coefficients, in dimension 2 or 3. 388 Positive definite provided μ>0 and 2μ+dλ>0 389 """ 390 z,s = 0*λ,λ+2*μ 391 if vdim==2: return cls(ad.array([ 392 [s,λ,z], 393 [λ,s,z], 394 [z,z,μ]])) 395 elif vdim==3: return cls(ad.array([ 396 [s,λ,λ,z,z,z], 397 [λ,s,λ,z,z,z], 398 [λ,λ,s,z,z,z], 399 [z,z,z,μ,z,z], 400 [z,z,z,z,μ,z], 401 [z,z,z,z,z,μ]])) 402 else: raise ValueError("Unsupported dimension")
Constructs a Hooke tensor from the Lame coefficients, in dimension 2 or 3. Positive definite provided μ>0 and 2μ+dλ>0
404 def contract(self,w): 405 r"""Returns the contracted tensor $\sum_{j,l}c_{ijkl} w_j w_l$.""" 406 voi = self._Voigt 407 hooke,w = fd.common_field((self.hooke,w),depths=(2,1)) 408 def c(i,j,k,l): return hooke[voi[i,j],voi[k,l]] 409 d = self.vdim; assert len(w)==d 410 return ad.array([[ 411 sum(c(i,j,k,l)*w[j]*w[l] for j in range(d) for l in range(d)) 412 for i in range(d)] for k in range(d)])
Returns the contracted tensor $\sum_{j,l}c_{ijkl} w_j w_l$.
414 def waves(self,k,ρ): 415 """Returns the pulsation and direction of the waves with the given wave vector.""" 416 m = np.moveaxis(self.contract(k),(0,1),(-2,-1)) 417 eVal,eVec = np.linalg.eigh(m) 418 eVec = np.moveaxis(eVec,(-2,-1),(0,1)) 419 return np.sqrt(eVal/ρ),eVec
Returns the pulsation and direction of the waves with the given wave vector.