1# http://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

4# ---|P------/S----------~Lg---------- 

5 

6''' 

7This module provides various moment tensor related utility functions. 

8 

9It can be used to convert between strike-dip-rake and moment tensor 

10representations and provides different options to produce random moment 

11tensors. 

12 

13Moment tensors are represented by :py:class:`MomentTensor` instances. The 

14internal representation uses a north-east-down (NED) coordinate system, but it 

15can convert from/to the conventions used by the Global CMT catalog 

16(up-south-east, USE). 

17 

18If not otherwise noted, scalar moment is interpreted as the Frobenius norm 

19based scalar moment (see :py:meth:`MomentTensor.scalar_moment`. The scalar 

20moment according to the "standard decomposition" can be found in the 

21output of :py:meth:`MomentTensor.standard_decomposition`. 

22''' 

23 

24import math 

25import numpy as num 

26 

27from .guts import Object, Float 

28 

29guts_prefix = 'pf' 

30 

31dynecm = 1e-7 

32 

33 

34def random_axis(rstate=None): 

35 ''' 

36 Get randomly oriented unit vector. 

37 

38 :param rstate: :py:class:`numpy.random.RandomState` object, can be used to 

39 create reproducible pseudo-random sequences 

40 ''' 

41 rstate = rstate or num.random 

42 while True: 

43 axis = rstate.uniform(size=3) * 2.0 - 1.0 

44 uabs = math.sqrt(num.sum(axis**2)) 

45 if 0.001 < uabs < 1.0: 

46 return axis / uabs 

47 

48 

49def rotation_from_angle_and_axis(angle, axis): 

50 ''' 

51 Build rotation matrix based on axis and angle. 

52 

53 :param angle: rotation angle [degrees] 

54 :param axis: orientation of rotation axis, either in spherical 

55 coordinates ``(theta, phi)`` [degrees], or as a unit vector 

56 ``(ux, uy, uz)``. 

57 ''' 

58 

59 if len(axis) == 2: 

60 theta, phi = axis 

61 ux = math.sin(d2r*theta)*math.cos(d2r*phi) 

62 uy = math.sin(d2r*theta)*math.sin(d2r*phi) 

63 uz = math.cos(d2r*theta) 

64 

65 elif len(axis) == 3: 

66 axis = num.asarray(axis) 

67 uabs = math.sqrt(num.sum(axis**2)) 

68 ux, uy, uz = axis / uabs 

69 else: 

70 assert False 

71 

72 ct = math.cos(d2r*angle) 

73 st = math.sin(d2r*angle) 

74 return num.array([ 

75 [ct + ux**2*(1.-ct), ux*uy*(1.-ct)-uz*st, ux*uz*(1.-ct)+uy*st], 

76 [uy*ux*(1.-ct)+uz*st, ct+uy**2*(1.-ct), uy*uz*(1.-ct)-ux*st], 

77 [uz*ux*(1.-ct)-uy*st, uz*uy*(1.-ct)+ux*st, ct+uz**2*(1.-ct)] 

78 ]) 

79 

80 

81def random_rotation(x=None): 

82 ''' 

83 Get random rotation matrix. 

84 

85 A random rotation matrix, drawn from a uniform distrubution in the space 

86 of rotations is returned, after Avro 1992 - "Fast random rotation 

87 matrices". 

88 

89 :param x: three (uniform random) numbers in the range [0, 1[ used as input 

90 to the distribution tranformation. If ``None``, random numbers are 

91 used. Can be used to create grids of random rotations with uniform 

92 density in rotation space. 

93 ''' 

94 

95 if x is not None: 

96 x1, x2, x3 = x 

97 else: 

98 x1, x2, x3 = num.random.random(3) 

99 

100 phi = math.pi*2.0*x1 

101 

102 zrot = num.array([ 

103 [math.cos(phi), math.sin(phi), 0.], 

104 [-math.sin(phi), math.cos(phi), 0.], 

105 [0., 0., 1.]]) 

106 

107 lam = math.pi*2.0*x2 

108 

109 v = num.array([[ 

110 math.cos(lam)*math.sqrt(x3), 

111 math.sin(lam)*math.sqrt(x3), 

112 math.sqrt(1.-x3)]]).T 

113 

114 house = num.identity(3) - 2.0 * num.dot(v, v.T) 

115 return -num.dot(house, zrot) 

116 

117 

118def rand(mi, ma): 

119 return float(num.random.uniform(mi, ma)) 

120 

121 

122def randdip(mi, ma): 

123 mi_ = 0.5*(math.cos(mi * math.pi/180.)+1.) 

124 ma_ = 0.5*(math.cos(ma * math.pi/180.)+1.) 

125 return math.acos(rand(mi_, ma_)*2.-1.)*180./math.pi 

126 

127 

128def random_strike_dip_rake( 

129 strikemin=0., strikemax=360., 

130 dipmin=0., dipmax=90., 

131 rakemin=-180., rakemax=180.): 

132 

133 ''' 

134 Get random strike, dip, rake triplet. 

135 

136 .. note:: 

137 

138 Might not produce a homogeneous distribution of mechanisms. Better use 

139 :py:meth:`MomentTensor.random_dc` which is based on 

140 :py:func:`random_rotation`. 

141 ''' 

142 

143 strike = rand(strikemin, strikemax) 

144 dip = randdip(dipmin, dipmax) 

145 rake = rand(rakemin, rakemax) 

146 

147 return strike, dip, rake 

148 

149 

150def to6(m): 

151 ''' 

152 Get non-redundant components from symmetric 3x3 matrix. 

153 

154 :returns: 1D NumPy array with entries ordered like 

155 ``(a_xx, a_yy, a_zz, a_xy, a_xz, a_yz)`` 

156 ''' 

157 

158 return num.array([m[0, 0], m[1, 1], m[2, 2], m[0, 1], m[0, 2], m[1, 2]]) 

159 

160 

161def symmat6(a_xx, a_yy, a_zz, a_xy, a_xz, a_yz): 

162 ''' 

163 Create symmetric 3x3 matrix from its 6 non-redundant values. 

164 ''' 

165 

166 return num.array([[a_xx, a_xy, a_xz], 

167 [a_xy, a_yy, a_yz], 

168 [a_xz, a_yz, a_zz]], dtype=float) 

169 

170 

171def values_to_matrix(values): 

172 ''' 

173 Convert anything to moment tensor represented as a NumPy array. 

174 

175 Transforms :py:class:`MomentTensor` objects, tuples, lists and NumPy arrays 

176 with 3x3 or 3, 4, 6, or 7 elements into NumPy 3x3 array objects. 

177 

178 The ``values`` argument is interpreted depending on shape and type as 

179 follows: 

180 

181 * ``(strike, dip, rake)`` 

182 * ``(strike, dip, rake, magnitude)`` 

183 * ``(mnn, mee, mdd, mne, mnd, med)`` 

184 * ``(mnn, mee, mdd, mne, mnd, med, magnitude)`` 

185 * ``((mnn, mne, mnd), (mne, mee, med), (mnd, med, mdd))`` 

186 * :py:class:`MomentTensor` 

187 ''' 

188 

189 if isinstance(values, (tuple, list)): 

190 values = num.asarray(values, dtype=float) 

191 

192 if isinstance(values, MomentTensor): 

193 return values.m() 

194 

195 elif isinstance(values, num.ndarray): 

196 if values.shape == (3,): 

197 strike, dip, rake = values 

198 rotmat1 = euler_to_matrix(d2r*dip, d2r*strike, -d2r*rake) 

199 return num.dot(rotmat1.T, num.dot(MomentTensor._m_unrot, rotmat1)) 

200 

201 elif values.shape == (4,): 

202 strike, dip, rake, magnitude = values 

203 moment = magnitude_to_moment(magnitude) 

204 rotmat1 = euler_to_matrix(d2r*dip, d2r*strike, -d2r*rake) 

205 return moment * num.dot( 

206 rotmat1.T, num.dot(MomentTensor._m_unrot, rotmat1)) 

207 

208 elif values.shape == (6,): 

209 return symmat6(*values) 

210 

211 elif values.shape == (7,): 

212 magnitude = values[6] 

213 moment = magnitude_to_moment(magnitude) 

214 mt = symmat6(*values[:6]) 

215 mt *= moment / (num.linalg.norm(mt) / math.sqrt(2.0)) 

216 return mt 

217 

218 elif values.shape == (3, 3): 

219 return num.asarray(values, dtype=float) 

220 

221 raise Exception('cannot convert object to 3x3 matrix') 

222 

223 

224def moment_to_magnitude(moment): 

225 ''' 

226 Convert scalar moment to moment magnitude Mw. 

227 

228 :param moment: scalar moment [Nm] 

229 :returns: moment magnitude Mw 

230 

231 Moment magnitude is defined as 

232 

233 .. math:: 

234 

235 M_\\mathrm{w} = {\\frac{2}{3}}\\log_{10}(M_0 \\cdot 10^7) - 10.7 

236 

237 where :math:`M_0` is the scalar moment given in [Nm]. 

238 

239 .. note:: 

240 

241 Global CMT uses 10.7333333 instead of 10.7, based on [Kanamori 1977], 

242 10.7 is from [Hanks and Kanamori 1979]. 

243 ''' 

244 

245 return num.log10(moment*1.0e7) / 1.5 - 10.7 

246 

247 

248def magnitude_to_moment(magnitude): 

249 ''' 

250 Convert moment magnitude Mw to scalar moment. 

251 

252 :param magnitude: moment magnitude 

253 :returns: scalar moment [Nm] 

254 

255 See :py:func:`moment_to_magnitude`. 

256 ''' 

257 

258 return 10.0**(1.5*(magnitude+10.7))*1.0e-7 

259 

260 

261magnitude_1Nm = moment_to_magnitude(1.0) 

262 

263 

264def euler_to_matrix(alpha, beta, gamma): 

265 ''' 

266 Given euler angle triplet, create rotation matrix 

267 

268 Given coordinate system `(x,y,z)` and rotated system `(xs,ys,zs)` 

269 the line of nodes is the intersection between the `x,y` and the `xs,ys` 

270 planes. 

271 

272 :param alpha: is the angle between the `z`-axis and the `zs`-axis [rad] 

273 :param beta: is the angle between the `x`-axis and the line of nodes [rad] 

274 :param gamma: is the angle between the line of nodes and the `xs`-axis 

275 [rad] 

276 

277 Usage for moment tensors:: 

278 

279 m_unrot = numpy.array([[0,0,-1],[0,0,0],[-1,0,0]]) 

280 euler_to_matrix(dip,strike,-rake, rotmat) 

281 m = num.dot(rotmat.T, num.dot(m_unrot, rotmat)) 

282 

283 ''' 

284 

285 ca = math.cos(alpha) 

286 cb = math.cos(beta) 

287 cg = math.cos(gamma) 

288 sa = math.sin(alpha) 

289 sb = math.sin(beta) 

290 sg = math.sin(gamma) 

291 

292 mat = num.array([[cb*cg-ca*sb*sg, sb*cg+ca*cb*sg, sa*sg], 

293 [-cb*sg-ca*sb*cg, -sb*sg+ca*cb*cg, sa*cg], 

294 [sa*sb, -sa*cb, ca]], dtype=float) 

295 return mat 

296 

297 

298def matrix_to_euler(rotmat): 

299 ''' 

300 Get eulerian angle triplet from rotation matrix. 

301 ''' 

302 

303 ex = cvec(1., 0., 0.) 

304 ez = cvec(0., 0., 1.) 

305 exs = num.dot(rotmat.T, ex) 

306 ezs = num.dot(rotmat.T, ez) 

307 enodes = num.cross(ez.T, ezs.T).T 

308 if num.linalg.norm(enodes) < 1e-10: 

309 enodes = exs 

310 enodess = num.dot(rotmat, enodes) 

311 cos_alpha = float((num.dot(ez.T, ezs))) 

312 if cos_alpha > 1.: 

313 cos_alpha = 1. 

314 

315 if cos_alpha < -1.: 

316 cos_alpha = -1. 

317 

318 alpha = math.acos(cos_alpha) 

319 beta = num.mod(math.atan2(enodes[1, 0], enodes[0, 0]), math.pi*2.) 

320 gamma = num.mod(-math.atan2(enodess[1, 0], enodess[0, 0]), math.pi*2.) 

321 

322 return unique_euler(alpha, beta, gamma) 

323 

324 

325def unique_euler(alpha, beta, gamma): 

326 ''' 

327 Uniquify eulerian angle triplet. 

328 

329 Put eulerian angle triplet into ranges compatible with 

330 ``(dip, strike, -rake)`` conventions in seismology:: 

331 

332 alpha (dip) : [0, pi/2] 

333 beta (strike) : [0, 2*pi) 

334 gamma (-rake) : [-pi, pi) 

335 

336 If ``alpha1`` is near to zero, ``beta`` is replaced by ``beta+gamma`` and 

337 ``gamma`` is set to zero, to prevent this additional ambiguity. 

338 

339 If ``alpha`` is near to ``pi/2``, ``beta`` is put into the range 

340 ``[0,pi)``. 

341 ''' 

342 

343 pi = math.pi 

344 

345 alpha = num.mod(alpha, 2.0*pi) 

346 

347 if 0.5*pi < alpha and alpha <= pi: 

348 alpha = pi - alpha 

349 beta = beta + pi 

350 gamma = 2.0*pi - gamma 

351 elif pi < alpha and alpha <= 1.5*pi: 

352 alpha = alpha - pi 

353 gamma = pi - gamma 

354 elif 1.5*pi < alpha and alpha <= 2.0*pi: 

355 alpha = 2.0*pi - alpha 

356 beta = beta + pi 

357 gamma = pi + gamma 

358 

359 alpha = num.mod(alpha, 2.0*pi) 

360 beta = num.mod(beta, 2.0*pi) 

361 gamma = num.mod(gamma+pi, 2.0*pi)-pi 

362 

363 # If dip is exactly 90 degrees, one is still 

364 # free to choose between looking at the plane from either side. 

365 # Choose to look at such that beta is in the range [0,180) 

366 

367 # This should prevent some problems, when dip is close to 90 degrees: 

368 if abs(alpha - 0.5*pi) < 1e-10: 

369 alpha = 0.5*pi 

370 

371 if abs(beta - pi) < 1e-10: 

372 beta = pi 

373 

374 if abs(beta - 2.*pi) < 1e-10: 

375 beta = 0. 

376 

377 if abs(beta) < 1e-10: 

378 beta = 0. 

379 

380 if alpha == 0.5*pi and beta >= pi: 

381 gamma = - gamma 

382 beta = num.mod(beta-pi, 2.0*pi) 

383 gamma = num.mod(gamma+pi, 2.0*pi)-pi 

384 assert 0. <= beta < pi 

385 assert -pi <= gamma < pi 

386 

387 if alpha < 1e-7: 

388 beta = num.mod(beta + gamma, 2.0*pi) 

389 gamma = 0. 

390 

391 return (alpha, beta, gamma) 

392 

393 

394def cvec(x, y, z): 

395 return num.array([[x, y, z]], dtype=float).T 

396 

397 

398def rvec(x, y, z): 

399 return num.array([[x, y, z]], dtype=float) 

400 

401 

402def eigh_check(a): 

403 evals, evecs = num.linalg.eigh(a) 

404 assert evals[0] <= evals[1] <= evals[2] 

405 return evals, evecs 

406 

407 

408r2d = 180. / math.pi 

409d2r = 1. / r2d 

410 

411 

412def random_mt(x=None, scalar_moment=1.0, magnitude=None): 

413 

414 if magnitude is not None: 

415 scalar_moment = magnitude_to_moment(magnitude) 

416 

417 if x is None: 

418 x = num.random.random(6) 

419 

420 evals = x[:3] * 2. - 1.0 

421 evals /= num.sqrt(num.sum(evals**2)) / math.sqrt(2.0) 

422 rotmat = random_rotation(x[3:]) 

423 return scalar_moment * num.dot( 

424 rotmat, num.dot(num.array(num.diag(evals)), rotmat.T)) 

425 

426 

427def random_m6(*args, **kwargs): 

428 return to6(random_mt(*args, **kwargs)) 

429 

430 

431def random_dc(x=None, scalar_moment=1.0, magnitude=None): 

432 if magnitude is not None: 

433 scalar_moment = magnitude_to_moment(magnitude) 

434 

435 rotmat = random_rotation(x) 

436 return scalar_moment * num.dot( 

437 rotmat, num.dot(MomentTensor._m_unrot, rotmat.T)) 

438 

439 

440def sm(m): 

441 return '/ %5.2F %5.2F %5.2F \\\n' % (m[0, 0], m[0, 1], m[0, 2]) + \ 

442 '| %5.2F %5.2F %5.2F |\n' % (m[1, 0], m[1, 1], m[1, 2]) + \ 

443 '\\ %5.2F %5.2F %5.2F /\n' % (m[2, 0], m[2, 1], m[2, 2]) 

444 

445 

446def as_mt(mt): 

447 ''' 

448 Convenience function to convert various objects to moment tensor object. 

449 

450 Like :py:meth:`MomentTensor.from_values`, but does not create a new 

451 :py:class:`MomentTensor` object when ``mt`` already is one. 

452 ''' 

453 

454 if isinstance(mt, MomentTensor): 

455 return mt 

456 else: 

457 return MomentTensor.from_values(mt) 

458 

459 

460def pt_axes_to_strike_dip_rake(p_axis, t_axis): 

461 n_axis = num.cross(p_axis, t_axis) 

462 m_evecs = num.vstack([p_axis, n_axis, t_axis]).T 

463 if num.linalg.det(m_evecs) < 0.: 

464 m_evecs *= -1. 

465 rotmat = num.dot(m_evecs, MomentTensor._u_evecs.T).T 

466 if num.linalg.det(rotmat) < 0.: 

467 rotmat *= -1. 

468 alpha, beta, gamma = [r2d*x for x in matrix_to_euler(rotmat)] 

469 return (beta, alpha, -gamma) 

470 

471 

472class MomentTensor(Object): 

473 

474 ''' 

475 Moment tensor object 

476 

477 :param m: NumPy array in north-east-down convention 

478 :param m_up_south_east: NumPy array in up-south-east convention 

479 :param m_east_north_up: NumPy array in east-north-up convention 

480 :param strike,dip,rake: fault plane angles in [degrees] 

481 :param p_axis,t_axis: initialize double-couple from p and t axes 

482 :param scalar_moment: scalar moment in [Nm] 

483 :param magnitude: moment magnitude Mw 

484 

485 Global CMT catalog moment tensors use the up-south-east (USE) coordinate 

486 system convention with :math:`r` (up), :math:`\\theta` (south), and 

487 :math:`\\phi` (east). 

488 

489 .. math:: 

490 :nowrap: 

491 

492 \\begin{align*} 

493 M_{rr} &= M_{dd}, & M_{ r\\theta} &= M_{nd},\\\\ 

494 M_{\\theta\\theta} &= M_{ nn}, & M_{r\\phi} &= -M_{ed},\\\\ 

495 M_{\\phi\\phi} &= M_{ee}, & M_{\\theta\\phi} &= -M_{ne} 

496 \\end{align*} 

497 

498 ''' 

499 

500 mnn__ = Float.T(default=0.0) 

501 mee__ = Float.T(default=0.0) 

502 mdd__ = Float.T(default=0.0) 

503 mne__ = Float.T(default=0.0) 

504 mnd__ = Float.T(default=-1.0) 

505 med__ = Float.T(default=0.0) 

506 strike1__ = Float.T(default=None, optional=True) # read-only 

507 dip1__ = Float.T(default=None, optional=True) # read-only 

508 rake1__ = Float.T(default=None, optional=True) # read-only 

509 strike2__ = Float.T(default=None, optional=True) # read-only 

510 dip2__ = Float.T(default=None, optional=True) # read-only 

511 rake2__ = Float.T(default=None, optional=True) # read-only 

512 moment__ = Float.T(default=None, optional=True) # read-only 

513 magnitude__ = Float.T(default=None, optional=True) # read-only 

514 

515 _flip_dc = num.array( 

516 [[0., 0., -1.], [0., -1., 0.], [-1., 0., 0.]], dtype=float) 

517 _to_up_south_east = num.array( 

518 [[0., 0., -1.], [-1., 0., 0.], [0., 1., 0.]], dtype=float).T 

519 _to_east_north_up = num.array( 

520 [[0., 1., 0.], [1., 0., 0.], [0., 0., -1.]], dtype=float) 

521 _m_unrot = num.array( 

522 [[0., 0., -1.], [0., 0., 0.], [-1., 0., 0.]], dtype=float) 

523 

524 _u_evals, _u_evecs = eigh_check(_m_unrot) 

525 

526 @classmethod 

527 def random_dc(cls, x=None, scalar_moment=1.0, magnitude=None): 

528 ''' 

529 Create random oriented double-couple moment tensor 

530 

531 The rotations used are uniformly distributed in the space of rotations. 

532 ''' 

533 return MomentTensor( 

534 m=random_dc(x=x, scalar_moment=scalar_moment, magnitude=magnitude)) 

535 

536 @classmethod 

537 def random_mt(cls, x=None, scalar_moment=1.0, magnitude=None): 

538 ''' 

539 Create random moment tensor 

540 

541 Moment tensors produced by this function appear uniformly distributed 

542 when shown in a Hudson's diagram. The rotations used are unifomly 

543 distributed in the space of rotations. 

544 ''' 

545 return MomentTensor( 

546 m=random_mt(x=x, scalar_moment=scalar_moment, magnitude=magnitude)) 

547 

548 @classmethod 

549 def from_values(cls, values): 

550 ''' 

551 Alternative constructor for moment tensor objects 

552 

553 This constructor takes a :py:class:`MomentTensor` object, a tuple, list 

554 or NumPy array with 3x3 or 3, 4, 6, or 7 elements to build a Moment 

555 tensor object. 

556 

557 The ``values`` argument is interpreted depending on shape and type as 

558 follows: 

559 

560 * ``(strike, dip, rake)`` 

561 * ``(strike, dip, rake, magnitude)`` 

562 * ``(mnn, mee, mdd, mne, mnd, med)`` 

563 * ``(mnn, mee, mdd, mne, mnd, med, magnitude)`` 

564 * ``((mnn, mne, mnd), (mne, mee, med), (mnd, med, mdd))`` 

565 * :py:class:`MomentTensor` object 

566 ''' 

567 

568 m = values_to_matrix(values) 

569 return MomentTensor(m=m) 

570 

571 def __init__( 

572 self, m=None, m_up_south_east=None, m_east_north_up=None, 

573 strike=0., dip=0., rake=0., scalar_moment=1., 

574 mnn=None, mee=None, mdd=None, mne=None, mnd=None, med=None, 

575 strike1=None, dip1=None, rake1=None, 

576 strike2=None, dip2=None, rake2=None, 

577 p_axis=None, t_axis=None, 

578 magnitude=None, moment=None): 

579 

580 Object.__init__(self, init_props=False) 

581 

582 if any(mxx is not None for mxx in (mnn, mee, mdd, mne, mnd, med)): 

583 m = symmat6(mnn, mee, mdd, mne, mnd, med) 

584 

585 if p_axis is not None and t_axis is not None: 

586 strike, dip, rake = pt_axes_to_strike_dip_rake(p_axis, t_axis) 

587 

588 strike = d2r*strike 

589 dip = d2r*dip 

590 rake = d2r*rake 

591 

592 if isinstance(m, num.matrix): 

593 m = m.A 

594 

595 if isinstance(m_up_south_east, num.matrix): 

596 m_up_south_east = m_up_south_east.A 

597 

598 if isinstance(m_east_north_up, num.matrix): 

599 m_east_north_up = m_east_north_up.A 

600 

601 if m_up_south_east is not None: 

602 m = num.dot( 

603 self._to_up_south_east, 

604 num.dot(m_up_south_east, self._to_up_south_east.T)) 

605 

606 if m_east_north_up is not None: 

607 m = num.dot( 

608 self._to_east_north_up, 

609 num.dot(m_east_north_up, self._to_east_north_up.T)) 

610 

611 if m is None: 

612 if any(x is not None for x in ( 

613 strike1, dip1, rake1, strike2, dip2, rake2)): 

614 raise Exception( 

615 'strike1, dip1, rake1, strike2, dip2, rake2 are read-only ' 

616 'properties') 

617 

618 if moment is not None: 

619 scalar_moment = moment 

620 

621 if magnitude is not None: 

622 scalar_moment = magnitude_to_moment(magnitude) 

623 

624 rotmat1 = euler_to_matrix(dip, strike, -rake) 

625 m = scalar_moment * num.dot( 

626 rotmat1.T, 

627 num.dot(MomentTensor._m_unrot, rotmat1)) 

628 

629 self._m = m 

630 self._update() 

631 

632 def _update(self): 

633 m_evals, m_evecs = eigh_check(self._m) 

634 if num.linalg.det(m_evecs) < 0.: 

635 m_evecs *= -1. 

636 

637 rotmat1 = num.dot(m_evecs, MomentTensor._u_evecs.T).T 

638 if num.linalg.det(rotmat1) < 0.: 

639 rotmat1 *= -1. 

640 

641 self._m_eigenvals = m_evals 

642 self._m_eigenvecs = m_evecs 

643 

644 self._rotmats = sorted( 

645 [rotmat1, num.dot(MomentTensor._flip_dc, rotmat1)], 

646 key=lambda m: num.abs(m.flat).tolist()) 

647 

648 @property 

649 def mnn(self): 

650 return float(self._m[0, 0]) 

651 

652 @mnn.setter 

653 def mnn(self, value): 

654 self._m[0, 0] = value 

655 self._update() 

656 

657 @property 

658 def mee(self): 

659 return float(self._m[1, 1]) 

660 

661 @mee.setter 

662 def mee(self, value): 

663 self._m[1, 1] = value 

664 self._update() 

665 

666 @property 

667 def mdd(self): 

668 return float(self._m[2, 2]) 

669 

670 @mdd.setter 

671 def mdd(self, value): 

672 self._m[2, 2] = value 

673 self._update() 

674 

675 @property 

676 def mne(self): 

677 return float(self._m[0, 1]) 

678 

679 @mne.setter 

680 def mne(self, value): 

681 self._m[0, 1] = value 

682 self._m[1, 0] = value 

683 self._update() 

684 

685 @property 

686 def mnd(self): 

687 return float(self._m[0, 2]) 

688 

689 @mnd.setter 

690 def mnd(self, value): 

691 self._m[0, 2] = value 

692 self._m[2, 0] = value 

693 self._update() 

694 

695 @property 

696 def med(self): 

697 return float(self._m[1, 2]) 

698 

699 @med.setter 

700 def med(self, value): 

701 self._m[1, 2] = value 

702 self._m[2, 1] = value 

703 self._update() 

704 

705 @property 

706 def strike1(self): 

707 return float(self.both_strike_dip_rake()[0][0]) 

708 

709 @property 

710 def dip1(self): 

711 return float(self.both_strike_dip_rake()[0][1]) 

712 

713 @property 

714 def rake1(self): 

715 return float(self.both_strike_dip_rake()[0][2]) 

716 

717 @property 

718 def strike2(self): 

719 return float(self.both_strike_dip_rake()[1][0]) 

720 

721 @property 

722 def dip2(self): 

723 return float(self.both_strike_dip_rake()[1][1]) 

724 

725 @property 

726 def rake2(self): 

727 return float(self.both_strike_dip_rake()[1][2]) 

728 

729 def both_strike_dip_rake(self): 

730 ''' 

731 Get both possible (strike,dip,rake) triplets. 

732 ''' 

733 results = [] 

734 for rotmat in self._rotmats: 

735 alpha, beta, gamma = [r2d*x for x in matrix_to_euler(rotmat)] 

736 results.append((beta, alpha, -gamma)) 

737 

738 return results 

739 

740 def p_axis(self): 

741 ''' 

742 Get direction of p axis. 

743 ''' 

744 return (self._m_eigenvecs.T)[0] 

745 

746 def t_axis(self): 

747 ''' 

748 Get direction of t axis. 

749 ''' 

750 return (self._m_eigenvecs.T)[2] 

751 

752 def null_axis(self): 

753 ''' 

754 Get diretion of the null axis. 

755 ''' 

756 return self._m_eigenvecs.T[1] 

757 

758 def eigenvals(self): 

759 ''' 

760 Get the eigenvalues of the moment tensor in accending order. 

761 

762 :returns: ``(ep, en, et)`` 

763 ''' 

764 

765 return self._m_eigenvals 

766 

767 def eigensystem(self): 

768 ''' 

769 Get the eigenvalues and eigenvectors of the moment tensor. 

770 

771 :returns: ``(ep, en, et, vp, vn, vt)`` 

772 ''' 

773 

774 vp = self.p_axis().flatten() 

775 vn = self.null_axis().flatten() 

776 vt = self.t_axis().flatten() 

777 ep, en, et = self._m_eigenvals 

778 return ep, en, et, vp, vn, vt 

779 

780 def both_slip_vectors(self): 

781 ''' 

782 Get both possible slip directions. 

783 ''' 

784 return [ 

785 num.dot(rotmat.T, cvec(1., 0., 0.)) for rotmat in self._rotmats] 

786 

787 def m(self): 

788 ''' 

789 Get plain moment tensor as 3x3 matrix. 

790 ''' 

791 return self._m.copy() 

792 

793 def m6(self): 

794 ''' 

795 Get the moment tensor as a six-element array. 

796 

797 :returns: ``(mnn, mee, mdd, mne, mnd, med)`` 

798 ''' 

799 return to6(self._m) 

800 

801 def m_up_south_east(self): 

802 ''' 

803 Get moment tensor in up-south-east convention as 3x3 matrix. 

804 

805 .. math:: 

806 :nowrap: 

807 

808 \\begin{align*} 

809 M_{rr} &= M_{dd}, & M_{ r\\theta} &= M_{nd},\\\\ 

810 M_{\\theta\\theta} &= M_{ nn}, & M_{r\\phi} &= -M_{ed},\\\\ 

811 M_{\\phi\\phi} &= M_{ee}, & M_{\\theta\\phi} &= -M_{ne} 

812 \\end{align*} 

813 ''' 

814 

815 return num.dot( 

816 self._to_up_south_east.T, 

817 num.dot(self._m, self._to_up_south_east)) 

818 

819 def m_east_north_up(self): 

820 ''' 

821 Get moment tensor in east-north-up convention as 3x3 matrix. 

822 ''' 

823 

824 return num.dot( 

825 self._to_east_north_up.T, num.dot(self._m, self._to_east_north_up)) 

826 

827 def m6_up_south_east(self): 

828 ''' 

829 Get moment tensor in up-south-east convention as a six-element array. 

830 

831 :returns: ``(muu, mss, mee, mus, mue, mse)`` 

832 ''' 

833 return to6(self.m_up_south_east()) 

834 

835 def m6_east_north_up(self): 

836 ''' 

837 Get moment tensor in east-north-up convention as a six-element array. 

838 

839 :returns: ``(mee, mnn, muu, men, meu, mnu)`` 

840 ''' 

841 return to6(self.m_east_north_up()) 

842 

843 def m_plain_double_couple(self): 

844 ''' 

845 Get plain double couple with same scalar moment as moment tensor. 

846 ''' 

847 rotmat1 = self._rotmats[0] 

848 m = self.scalar_moment() * num.dot( 

849 rotmat1.T, num.dot(MomentTensor._m_unrot, rotmat1)) 

850 return m 

851 

852 def moment_magnitude(self): 

853 ''' 

854 Get moment magnitude of moment tensor. 

855 ''' 

856 return moment_to_magnitude(self.scalar_moment()) 

857 

858 def scalar_moment(self): 

859 ''' 

860 Get the scalar moment of the moment tensor (Frobenius norm based) 

861 

862 .. math:: 

863 

864 M_0 = \\frac{1}{\\sqrt{2}}\\sqrt{\\sum_{i,j} |M_{ij}|^2} 

865 

866 The scalar moment is calculated based on the Euclidean (Frobenius) norm 

867 (Silver and Jordan, 1982). The scalar moment returned by this function 

868 differs from the standard decomposition based definition of the scalar 

869 moment for non-double-couple moment tensors. 

870 ''' 

871 return num.linalg.norm(self._m_eigenvals) / math.sqrt(2.) 

872 

873 @property 

874 def moment(self): 

875 return float(self.scalar_moment()) 

876 

877 @moment.setter 

878 def moment(self, value): 

879 self._m *= value / self.moment 

880 self._update() 

881 

882 @property 

883 def magnitude(self): 

884 return float(self.moment_magnitude()) 

885 

886 @magnitude.setter 

887 def magnitude(self, value): 

888 self._m *= magnitude_to_moment(value) / self.moment 

889 self._update() 

890 

891 def __str__(self): 

892 mexp = pow(10, math.ceil(num.log10(num.max(num.abs(self._m))))) 

893 m = self._m / mexp 

894 s = '''Scalar Moment [Nm]: M0 = %g (Mw = %3.1f) 

895Moment Tensor [Nm]: Mnn = %6.3f, Mee = %6.3f, Mdd = %6.3f, 

896 Mne = %6.3f, Mnd = %6.3f, Med = %6.3f [ x %g ] 

897''' % ( 

898 self.scalar_moment(), 

899 self.moment_magnitude(), 

900 m[0, 0], m[1, 1], m[2, 2], m[0, 1], m[0, 2], m[1, 2], 

901 mexp) 

902 

903 s += self.str_fault_planes() 

904 return s 

905 

906 def str_fault_planes(self): 

907 s = '' 

908 for i, sdr in enumerate(self.both_strike_dip_rake()): 

909 s += 'Fault plane %i [deg]: ' \ 

910 'strike = %3.0f, dip = %3.0f, slip-rake = %4.0f\n' \ 

911 % (i+1, sdr[0], sdr[1], sdr[2]) 

912 

913 return s 

914 

915 def deviatoric(self): 

916 ''' 

917 Get deviatoric part of moment tensor. 

918 

919 Returns a new moment tensor object with zero trace. 

920 ''' 

921 

922 m = self.m() 

923 

924 trace_m = num.trace(m) 

925 m_iso = num.diag([trace_m / 3., trace_m / 3., trace_m / 3.]) 

926 m_devi = m - m_iso 

927 mt = MomentTensor(m=m_devi) 

928 return mt 

929 

930 def standard_decomposition(self): 

931 ''' 

932 Decompose moment tensor into isotropic, DC and CLVD components. 

933 

934 Standard decomposition according to e.g. Jost and Herrmann 1989 is 

935 returned as:: 

936 

937 [ 

938 (moment_iso, ratio_iso, m_iso), 

939 (moment_dc, ratio_dc, m_dc), 

940 (moment_clvd, ratio_clvd, m_clvd), 

941 (moment_devi, ratio_devi, m_devi), 

942 (moment, 1.0, m) 

943 ] 

944 ''' 

945 

946 epsilon = 1e-6 

947 

948 m = self.m() 

949 

950 trace_m = num.trace(m) 

951 m_iso = num.diag([trace_m / 3., trace_m / 3., trace_m / 3.]) 

952 moment_iso = abs(trace_m / 3.) 

953 

954 m_devi = m - m_iso 

955 

956 evals, evecs = eigh_check(m_devi) 

957 

958 moment_devi = num.max(num.abs(evals)) 

959 moment = moment_iso + moment_devi 

960 

961 iorder = num.argsort(num.abs(evals)) 

962 evals_sorted = evals[iorder] 

963 evecs_sorted = (evecs.T[iorder]).T 

964 

965 if moment_devi < epsilon * moment_iso: 

966 signed_moment_dc = 0. 

967 else: 

968 assert -epsilon <= -evals_sorted[0] / evals_sorted[2] <= 0.5 

969 signed_moment_dc = evals_sorted[2] * (1.0 + 2.0 * ( 

970 min(0.0, evals_sorted[0] / evals_sorted[2]))) 

971 

972 moment_dc = abs(signed_moment_dc) 

973 m_dc_es = num.dot(signed_moment_dc, num.diag([0., -1.0, 1.0])) 

974 m_dc = num.dot(evecs_sorted, num.dot(m_dc_es, evecs_sorted.T)) 

975 

976 m_clvd = m_devi - m_dc 

977 

978 moment_clvd = moment_devi - moment_dc 

979 

980 ratio_dc = moment_dc / moment 

981 ratio_clvd = moment_clvd / moment 

982 ratio_iso = moment_iso / moment 

983 ratio_devi = moment_devi / moment 

984 

985 return [ 

986 (moment_iso, ratio_iso, m_iso), 

987 (moment_dc, ratio_dc, m_dc), 

988 (moment_clvd, ratio_clvd, m_clvd), 

989 (moment_devi, ratio_devi, m_devi), 

990 (moment, 1.0, m)] 

991 

992 def rotated(self, rot): 

993 ''' 

994 Get rotated moment tensor. 

995 

996 :param rot: ratation matrix, coordinate system is NED 

997 :returns: new :py:class:`MomentTensor` object 

998 ''' 

999 

1000 rotmat = num.array(rot) 

1001 return MomentTensor(m=num.dot(rotmat, num.dot(self.m(), rotmat.T))) 

1002 

1003 def random_rotated(self, angle_std=None, angle=None, rstate=None): 

1004 ''' 

1005 Get distorted MT by rotation around random axis and angle. 

1006 

1007 :param angle_std: angles are drawn from a normal distribution with 

1008 zero mean and given standard deviation [degrees] 

1009 :param angle: set angle [degrees], only axis will be random 

1010 :param rstate: :py:class:`numpy.random.RandomState` object, can be 

1011 used to create reproducible pseudo-random sequences 

1012 :returns: new :py:class:`MomentTensor` object 

1013 ''' 

1014 

1015 assert (angle_std is None) != (angle is None), \ 

1016 'either angle or angle_std must be given' 

1017 

1018 if angle_std is not None: 

1019 rstate = rstate or num.random 

1020 angle = rstate.normal(scale=angle_std) 

1021 

1022 axis = random_axis(rstate=rstate) 

1023 rot = rotation_from_angle_and_axis(angle, axis) 

1024 return self.rotated(rot) 

1025 

1026 

1027def other_plane(strike, dip, rake): 

1028 ''' 

1029 Get the respectively other plane in the double-couple ambiguity. 

1030 ''' 

1031 

1032 mt = MomentTensor(strike=strike, dip=dip, rake=rake) 

1033 both_sdr = mt.both_strike_dip_rake() 

1034 w = [sum([abs(x-y) for x, y in zip(both_sdr[i], (strike, dip, rake))]) 

1035 for i in (0, 1)] 

1036 

1037 if w[0] < w[1]: 

1038 return both_sdr[1] 

1039 else: 

1040 return both_sdr[0] 

1041 

1042 

1043def dsdr(sdr1, sdr2): 

1044 s1, d1, r1 = sdr1 

1045 s2, d2, r2 = sdr2 

1046 

1047 s1 = s1 % 360. 

1048 s2 = s2 % 360. 

1049 r1 = r1 % 360. 

1050 r2 = r2 % 360. 

1051 

1052 ds = abs(s1 - s2) 

1053 ds = ds if ds <= 180. else 360. - ds 

1054 

1055 dr = abs(r1 - r2) 

1056 dr = dr if dr <= 180. else 360. - dr 

1057 

1058 dd = abs(d1 - d2) 

1059 

1060 return math.sqrt(ds**2 + dr**2 + dd**2) 

1061 

1062 

1063def order_like(sdrs, sdrs_ref): 

1064 ''' 

1065 Order strike-dip-rake pair post closely to a given reference pair. 

1066 

1067 :param sdrs: tuple, ``((strike1, dip1, rake1), (strike2, dip2, rake2))`` 

1068 :param sdrs_ref: as above but with reference pair 

1069 ''' 

1070 

1071 d1 = min(dsdr(sdrs[0], sdrs_ref[0]), dsdr(sdrs[1], sdrs_ref[1])) 

1072 d2 = min(dsdr(sdrs[0], sdrs_ref[1]), dsdr(sdrs[1], sdrs_ref[0])) 

1073 if d1 < d2: 

1074 return sdrs 

1075 else: 

1076 return sdrs[::-1] 

1077 

1078 

1079def _tpb2q(t, p, b): 

1080 eps = 0.001 

1081 tqw = 1. + t[0] + p[1] + b[2] 

1082 tqx = 1. + t[0] - p[1] - b[2] 

1083 tqy = 1. - t[0] + p[1] - b[2] 

1084 tqz = 1. - t[0] - p[1] + b[2] 

1085 

1086 q = num.zeros(4) 

1087 if tqw > eps: 

1088 q[0] = 0.5 * math.sqrt(tqw) 

1089 q[1:] = p[2] - b[1], b[0] - t[2], t[1] - p[0] 

1090 elif tqx > eps: 

1091 q[0] = 0.5 * math.sqrt(tqx) 

1092 q[1:] = p[2] - b[1], p[0] + t[1], b[0] + t[2] 

1093 elif tqy > eps: 

1094 q[0] = 0.5 * math.sqrt(tqy) 

1095 q[1:] = b[0] - t[2], p[0] + t[1], b[1] + p[2] 

1096 elif tqz > eps: 

1097 q[0] = 0.5 * math.sqrt(tqz) 

1098 q[1:] = t[1] - p[0], b[0] + t[2], b[1] + p[2] 

1099 else: 

1100 raise Exception('should not reach this line, check theory!') 

1101 # q[0] = max(0.5 * math.sqrt(tqx), eps) 

1102 # q[1:] = p[2] - b[1], p[0] + t[1], b[0] + t[2] 

1103 

1104 q[1:] /= 4.0 * q[0] 

1105 

1106 q /= math.sqrt(num.sum(q**2)) 

1107 

1108 return q 

1109 

1110 

1111_pbt2tpb = num.array(((0., 0., 1.), (1., 0., 0.), (0., 1., 0.))) 

1112 

1113 

1114def kagan_angle(mt1, mt2): 

1115 ''' 

1116 Given two moment tensors, return the Kagan angle in degrees. 

1117 

1118 After Kagan (1991) and Tape & Tape (2012). 

1119 ''' 

1120 

1121 ai = num.dot(_pbt2tpb, mt1._m_eigenvecs.T) 

1122 aj = num.dot(_pbt2tpb, mt2._m_eigenvecs.T) 

1123 

1124 u = num.dot(ai, aj.T) 

1125 

1126 tk, pk, bk = u 

1127 

1128 qk = _tpb2q(tk, pk, bk) 

1129 

1130 return 2. * r2d * math.acos(num.max(num.abs(qk))) 

1131 

1132 

1133def rand_to_gutenberg_richter(rand, b_value, magnitude_min): 

1134 ''' 

1135 Draw magnitude from Gutenberg Richter distribution. 

1136 ''' 

1137 return magnitude_min + num.log10(1.-rand) / -b_value 

1138 

1139 

1140def magnitude_to_duration_gcmt(magnitudes): 

1141 ''' 

1142 Scaling relation used by Global CMT catalog for most of its events. 

1143 ''' 

1144 

1145 mom = magnitude_to_moment(magnitudes) 

1146 return (mom / 1.1e16)**(1./3.) 

1147 

1148 

1149if __name__ == '__main__': 

1150 

1151 import sys 

1152 v = list(map(float, sys.argv[1:])) 

1153 mt = MomentTensor.from_values(v) 

1154 print(mt)