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 

24from __future__ import division, print_function, absolute_import 

25 

26import math 

27import numpy as num 

28 

29from .guts import Object, Float 

30 

31guts_prefix = 'pf' 

32 

33dynecm = 1e-7 

34 

35 

36def random_axis(rstate=None): 

37 ''' 

38 Get randomly oriented unit vector. 

39 

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

41 create reproducible pseudo-random sequences 

42 ''' 

43 rstate = rstate or num.random 

44 while True: 

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

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

47 if 0.001 < uabs < 1.0: 

48 return axis / uabs 

49 

50 

51def rotation_from_angle_and_axis(angle, axis): 

52 ''' 

53 Build rotation matrix based on axis and angle. 

54 

55 :param angle: rotation angle [degrees] 

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

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

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

59 ''' 

60 

61 if len(axis) == 2: 

62 theta, phi = axis 

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

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

65 uz = math.cos(d2r*theta) 

66 

67 elif len(axis) == 3: 

68 axis = num.asarray(axis) 

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

70 ux, uy, uz = axis / uabs 

71 else: 

72 assert False 

73 

74 ct = math.cos(d2r*angle) 

75 st = math.sin(d2r*angle) 

76 return num.array([ 

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

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

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

80 ]) 

81 

82 

83def random_rotation(x=None): 

84 ''' 

85 Get random rotation matrix. 

86 

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

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

89 matrices". 

90 

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

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

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

94 density in rotation space. 

95 ''' 

96 

97 if x is not None: 

98 x1, x2, x3 = x 

99 else: 

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

101 

102 phi = math.pi*2.0*x1 

103 

104 zrot = num.array([ 

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

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

107 [0., 0., 1.]]) 

108 

109 lam = math.pi*2.0*x2 

110 

111 v = num.array([[ 

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

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

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

115 

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

117 return -num.dot(house, zrot) 

118 

119 

120def rand(mi, ma): 

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

122 

123 

124def randdip(mi, ma): 

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

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

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

128 

129 

130def random_strike_dip_rake( 

131 strikemin=0., strikemax=360., 

132 dipmin=0., dipmax=90., 

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

134 

135 ''' 

136 Get random strike, dip, rake triplet. 

137 

138 .. note:: 

139 

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

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

142 :py:func:`random_rotation`. 

143 ''' 

144 

145 strike = rand(strikemin, strikemax) 

146 dip = randdip(dipmin, dipmax) 

147 rake = rand(rakemin, rakemax) 

148 

149 return strike, dip, rake 

150 

151 

152def to6(m): 

153 ''' 

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

155 

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

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

158 ''' 

159 

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

161 

162 

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

164 ''' 

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

166 ''' 

167 

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

169 [a_xy, a_yy, a_yz], 

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

171 

172 

173def values_to_matrix(values): 

174 ''' 

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

176 

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

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

179 

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

181 follows: 

182 

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

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

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

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

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

188 * :py:class:`MomentTensor` 

189 ''' 

190 

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

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

193 

194 if isinstance(values, MomentTensor): 

195 return values.m() 

196 

197 elif isinstance(values, num.ndarray): 

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

199 strike, dip, rake = values 

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

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

202 

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

204 strike, dip, rake, magnitude = values 

205 moment = magnitude_to_moment(magnitude) 

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

207 return moment * num.dot( 

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

209 

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

211 return symmat6(*values) 

212 

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

214 magnitude = values[6] 

215 moment = magnitude_to_moment(magnitude) 

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

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

218 return mt 

219 

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

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

222 

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

224 

225 

226def moment_to_magnitude(moment): 

227 ''' 

228 Convert scalar moment to moment magnitude Mw. 

229 

230 :param moment: scalar moment [Nm] 

231 :returns: moment magnitude Mw 

232 

233 Moment magnitude is defined as 

234 

235 .. math:: 

236 

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

238 

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

240 

241 .. note:: 

242 

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

244 10.7 is from [Hanks and Kanamori 1979]. 

245 ''' 

246 

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

248 

249 

250def magnitude_to_moment(magnitude): 

251 ''' 

252 Convert moment magnitude Mw to scalar moment. 

253 

254 :param magnitude: moment magnitude 

255 :returns: scalar moment [Nm] 

256 

257 See :py:func:`moment_to_magnitude`. 

258 ''' 

259 

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

261 

262 

263magnitude_1Nm = moment_to_magnitude(1.0) 

264 

265 

266def euler_to_matrix(alpha, beta, gamma): 

267 ''' 

268 Given euler angle triplet, create rotation matrix 

269 

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

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

272 planes. 

273 

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

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

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

277 [rad] 

278 

279 Usage for moment tensors:: 

280 

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

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

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

284 

285 ''' 

286 

287 ca = math.cos(alpha) 

288 cb = math.cos(beta) 

289 cg = math.cos(gamma) 

290 sa = math.sin(alpha) 

291 sb = math.sin(beta) 

292 sg = math.sin(gamma) 

293 

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

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

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

297 return mat 

298 

299 

300def matrix_to_euler(rotmat): 

301 ''' 

302 Get eulerian angle triplet from rotation matrix. 

303 ''' 

304 

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

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

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

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

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

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

311 enodes = exs 

312 enodess = num.dot(rotmat, enodes) 

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

314 if cos_alpha > 1.: 

315 cos_alpha = 1. 

316 

317 if cos_alpha < -1.: 

318 cos_alpha = -1. 

319 

320 alpha = math.acos(cos_alpha) 

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

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

323 

324 return unique_euler(alpha, beta, gamma) 

325 

326 

327def unique_euler(alpha, beta, gamma): 

328 ''' 

329 Uniquify eulerian angle triplet. 

330 

331 Put eulerian angle triplet into ranges compatible with 

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

333 

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

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

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

337 

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

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

340 

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

342 ``[0,pi)``. 

343 ''' 

344 

345 pi = math.pi 

346 

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

348 

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

350 alpha = pi - alpha 

351 beta = beta + pi 

352 gamma = 2.0*pi - gamma 

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

354 alpha = alpha - pi 

355 gamma = pi - gamma 

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

357 alpha = 2.0*pi - alpha 

358 beta = beta + pi 

359 gamma = pi + gamma 

360 

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

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

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

364 

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

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

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

368 

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

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

371 alpha = 0.5*pi 

372 

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

374 beta = pi 

375 

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

377 beta = 0. 

378 

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

380 beta = 0. 

381 

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

383 gamma = - gamma 

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

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

386 assert 0. <= beta < pi 

387 assert -pi <= gamma < pi 

388 

389 if alpha < 1e-7: 

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

391 gamma = 0. 

392 

393 return (alpha, beta, gamma) 

394 

395 

396def cvec(x, y, z): 

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

398 

399 

400def rvec(x, y, z): 

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

402 

403 

404def eigh_check(a): 

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

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

407 return evals, evecs 

408 

409 

410r2d = 180. / math.pi 

411d2r = 1. / r2d 

412 

413 

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

415 

416 if magnitude is not None: 

417 scalar_moment = magnitude_to_moment(magnitude) 

418 

419 if x is None: 

420 x = num.random.random(6) 

421 

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

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

424 rotmat = random_rotation(x[3:]) 

425 return scalar_moment * num.dot( 

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

427 

428 

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

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

431 

432 

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

434 if magnitude is not None: 

435 scalar_moment = magnitude_to_moment(magnitude) 

436 

437 rotmat = random_rotation(x) 

438 return scalar_moment * num.dot( 

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

440 

441 

442def sm(m): 

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

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

445 "\\ %5.2F %5.2F %5.2F /\n" % (m[2, 0], m[2, 1], m[2, 2]) 

446 

447 

448def as_mt(mt): 

449 ''' 

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

451 

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

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

454 ''' 

455 

456 if isinstance(mt, MomentTensor): 

457 return mt 

458 else: 

459 return MomentTensor.from_values(mt) 

460 

461 

462def pt_axes_to_strike_dip_rake(p_axis, t_axis): 

463 n_axis = num.cross(p_axis, t_axis) 

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

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

466 m_evecs *= -1. 

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

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

469 rotmat *= -1. 

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

471 return (beta, alpha, -gamma) 

472 

473 

474class MomentTensor(Object): 

475 

476 ''' 

477 Moment tensor object 

478 

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

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

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

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

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

484 :param scalar_moment: scalar moment in [Nm] 

485 :param magnitude: moment magnitude Mw 

486 

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

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

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

490 

491 .. math:: 

492 :nowrap: 

493 

494 \\begin{align*} 

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

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

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

498 \\end{align*} 

499 

500 ''' 

501 

502 mnn__ = Float.T(default=0.0) 

503 mee__ = Float.T(default=0.0) 

504 mdd__ = Float.T(default=0.0) 

505 mne__ = Float.T(default=0.0) 

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

507 med__ = Float.T(default=0.0) 

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

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

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

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

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

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

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

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

516 

517 _flip_dc = num.array( 

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

519 _to_up_south_east = num.array( 

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

521 _to_east_north_up = num.array( 

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

523 _m_unrot = num.array( 

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

525 

526 _u_evals, _u_evecs = eigh_check(_m_unrot) 

527 

528 @classmethod 

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

530 ''' 

531 Create random oriented double-couple moment tensor 

532 

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

534 ''' 

535 return MomentTensor( 

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

537 

538 @classmethod 

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

540 ''' 

541 Create random moment tensor 

542 

543 Moment tensors produced by this function appear uniformly distributed 

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

545 distributed in the space of rotations. 

546 ''' 

547 return MomentTensor( 

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

549 

550 @classmethod 

551 def from_values(cls, values): 

552 ''' 

553 Alternative constructor for moment tensor objects 

554 

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

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

557 tensor object. 

558 

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

560 follows: 

561 

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

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

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

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

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

567 * :py:class:`MomentTensor` object 

568 ''' 

569 

570 m = values_to_matrix(values) 

571 return MomentTensor(m=m) 

572 

573 def __init__( 

574 self, m=None, m_up_south_east=None, m_east_north_up=None, 

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

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

577 strike1=None, dip1=None, rake1=None, 

578 strike2=None, dip2=None, rake2=None, 

579 p_axis=None, t_axis=None, 

580 magnitude=None, moment=None): 

581 

582 Object.__init__(self, init_props=False) 

583 

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

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

586 

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

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

589 

590 strike = d2r*strike 

591 dip = d2r*dip 

592 rake = d2r*rake 

593 

594 if isinstance(m, num.matrix): 

595 m = m.A 

596 

597 if isinstance(m_up_south_east, num.matrix): 

598 m_up_south_east = m_up_south_east.A 

599 

600 if isinstance(m_east_north_up, num.matrix): 

601 m_east_north_up = m_east_north_up.A 

602 

603 if m_up_south_east is not None: 

604 m = num.dot( 

605 self._to_up_south_east, 

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

607 

608 if m_east_north_up is not None: 

609 m = num.dot( 

610 self._to_east_north_up, 

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

612 

613 if m is None: 

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

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

616 raise Exception( 

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

618 'properties') 

619 

620 if moment is not None: 

621 scalar_moment = moment 

622 

623 if magnitude is not None: 

624 scalar_moment = magnitude_to_moment(magnitude) 

625 

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

627 m = scalar_moment * num.dot( 

628 rotmat1.T, 

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

630 

631 self._m = m 

632 self._update() 

633 

634 def _update(self): 

635 m_evals, m_evecs = eigh_check(self._m) 

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

637 m_evecs *= -1. 

638 

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

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

641 rotmat1 *= -1. 

642 

643 self._m_eigenvals = m_evals 

644 self._m_eigenvecs = m_evecs 

645 

646 self._rotmats = sorted( 

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

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

649 

650 @property 

651 def mnn(self): 

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

653 

654 @mnn.setter 

655 def mnn(self, value): 

656 self._m[0, 0] = value 

657 self._update() 

658 

659 @property 

660 def mee(self): 

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

662 

663 @mee.setter 

664 def mee(self, value): 

665 self._m[1, 1] = value 

666 self._update() 

667 

668 @property 

669 def mdd(self): 

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

671 

672 @mdd.setter 

673 def mdd(self, value): 

674 self._m[2, 2] = value 

675 self._update() 

676 

677 @property 

678 def mne(self): 

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

680 

681 @mne.setter 

682 def mne(self, value): 

683 self._m[0, 1] = value 

684 self._m[1, 0] = value 

685 self._update() 

686 

687 @property 

688 def mnd(self): 

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

690 

691 @mnd.setter 

692 def mnd(self, value): 

693 self._m[0, 2] = value 

694 self._m[2, 0] = value 

695 self._update() 

696 

697 @property 

698 def med(self): 

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

700 

701 @med.setter 

702 def med(self, value): 

703 self._m[1, 2] = value 

704 self._m[2, 1] = value 

705 self._update() 

706 

707 @property 

708 def strike1(self): 

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

710 

711 @property 

712 def dip1(self): 

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

714 

715 @property 

716 def rake1(self): 

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

718 

719 @property 

720 def strike2(self): 

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

722 

723 @property 

724 def dip2(self): 

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

726 

727 @property 

728 def rake2(self): 

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

730 

731 def both_strike_dip_rake(self): 

732 ''' 

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

734 ''' 

735 results = [] 

736 for rotmat in self._rotmats: 

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

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

739 

740 return results 

741 

742 def p_axis(self): 

743 ''' 

744 Get direction of p axis. 

745 ''' 

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

747 

748 def t_axis(self): 

749 ''' 

750 Get direction of t axis. 

751 ''' 

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

753 

754 def null_axis(self): 

755 ''' 

756 Get diretion of the null axis. 

757 ''' 

758 return self._m_eigenvecs.T[1] 

759 

760 def eigenvals(self): 

761 ''' 

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

763 

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

765 ''' 

766 

767 return self._m_eigenvals 

768 

769 def eigensystem(self): 

770 ''' 

771 Get the eigenvalues and eigenvectors of the moment tensor. 

772 

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

774 ''' 

775 

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

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

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

779 ep, en, et = self._m_eigenvals 

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

781 

782 def both_slip_vectors(self): 

783 ''' 

784 Get both possible slip directions. 

785 ''' 

786 return [ 

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

788 

789 def m(self): 

790 ''' 

791 Get plain moment tensor as 3x3 matrix. 

792 ''' 

793 return self._m.copy() 

794 

795 def m6(self): 

796 ''' 

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

798 

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

800 ''' 

801 return to6(self._m) 

802 

803 def m_up_south_east(self): 

804 ''' 

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

806 

807 .. math:: 

808 :nowrap: 

809 

810 \\begin{align*} 

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

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

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

814 \\end{align*} 

815 ''' 

816 

817 return num.dot( 

818 self._to_up_south_east.T, 

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

820 

821 def m_east_north_up(self): 

822 ''' 

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

824 ''' 

825 

826 return num.dot( 

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

828 

829 def m6_up_south_east(self): 

830 ''' 

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

832 

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

834 ''' 

835 return to6(self.m_up_south_east()) 

836 

837 def m6_east_north_up(self): 

838 ''' 

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

840 

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

842 ''' 

843 return to6(self.m_east_north_up()) 

844 

845 def m_plain_double_couple(self): 

846 ''' 

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

848 ''' 

849 rotmat1 = self._rotmats[0] 

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

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

852 return m 

853 

854 def moment_magnitude(self): 

855 ''' 

856 Get moment magnitude of moment tensor. 

857 ''' 

858 return moment_to_magnitude(self.scalar_moment()) 

859 

860 def scalar_moment(self): 

861 ''' 

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

863 

864 .. math:: 

865 

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

867 

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

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

870 differs from the standard decomposition based definition of the scalar 

871 moment for non-double-couple moment tensors. 

872 ''' 

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

874 

875 @property 

876 def moment(self): 

877 return float(self.scalar_moment()) 

878 

879 @moment.setter 

880 def moment(self, value): 

881 self._m *= value / self.moment 

882 self._update() 

883 

884 @property 

885 def magnitude(self): 

886 return float(self.moment_magnitude()) 

887 

888 @magnitude.setter 

889 def magnitude(self, value): 

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

891 self._update() 

892 

893 def __str__(self): 

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

895 m = self._m / mexp 

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

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

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

899''' % ( 

900 self.scalar_moment(), 

901 self.moment_magnitude(), 

902 m[0, 0], m[1, 1], m[2, 2], m[0, 1], m[0, 2], m[1, 2], 

903 mexp) 

904 

905 s += self.str_fault_planes() 

906 return s 

907 

908 def str_fault_planes(self): 

909 s = '' 

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

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

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

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

914 

915 return s 

916 

917 def deviatoric(self): 

918 ''' 

919 Get deviatoric part of moment tensor. 

920 

921 Returns a new moment tensor object with zero trace. 

922 ''' 

923 

924 m = self.m() 

925 

926 trace_m = num.trace(m) 

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

928 m_devi = m - m_iso 

929 mt = MomentTensor(m=m_devi) 

930 return mt 

931 

932 def standard_decomposition(self): 

933 ''' 

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

935 

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

937 returned as:: 

938 

939 [ 

940 (moment_iso, ratio_iso, m_iso), 

941 (moment_dc, ratio_dc, m_dc), 

942 (moment_clvd, ratio_clvd, m_clvd), 

943 (moment_devi, ratio_devi, m_devi), 

944 (moment, 1.0, m) 

945 ] 

946 ''' 

947 

948 epsilon = 1e-6 

949 

950 m = self.m() 

951 

952 trace_m = num.trace(m) 

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

954 moment_iso = abs(trace_m / 3.) 

955 

956 m_devi = m - m_iso 

957 

958 evals, evecs = eigh_check(m_devi) 

959 

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

961 moment = moment_iso + moment_devi 

962 

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

964 evals_sorted = evals[iorder] 

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

966 

967 if moment_devi < epsilon * moment_iso: 

968 signed_moment_dc = 0. 

969 else: 

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

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

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

973 

974 moment_dc = abs(signed_moment_dc) 

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

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

977 

978 m_clvd = m_devi - m_dc 

979 

980 moment_clvd = moment_devi - moment_dc 

981 

982 ratio_dc = moment_dc / moment 

983 ratio_clvd = moment_clvd / moment 

984 ratio_iso = moment_iso / moment 

985 ratio_devi = moment_devi / moment 

986 

987 return [ 

988 (moment_iso, ratio_iso, m_iso), 

989 (moment_dc, ratio_dc, m_dc), 

990 (moment_clvd, ratio_clvd, m_clvd), 

991 (moment_devi, ratio_devi, m_devi), 

992 (moment, 1.0, m)] 

993 

994 def rotated(self, rot): 

995 ''' 

996 Get rotated moment tensor. 

997 

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

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

1000 ''' 

1001 

1002 rotmat = num.array(rot) 

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

1004 

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

1006 ''' 

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

1008 

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

1010 zero mean and given standard deviation [degrees] 

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

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

1013 used to create reproducible pseudo-random sequences 

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

1015 ''' 

1016 

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

1018 'either angle or angle_std must be given' 

1019 

1020 if angle_std is not None: 

1021 rstate = rstate or num.random 

1022 angle = rstate.normal(scale=angle_std) 

1023 

1024 axis = random_axis(rstate=rstate) 

1025 rot = rotation_from_angle_and_axis(angle, axis) 

1026 return self.rotated(rot) 

1027 

1028 

1029def other_plane(strike, dip, rake): 

1030 ''' 

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

1032 ''' 

1033 

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

1035 both_sdr = mt.both_strike_dip_rake() 

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

1037 for i in (0, 1)] 

1038 

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

1040 return both_sdr[1] 

1041 else: 

1042 return both_sdr[0] 

1043 

1044 

1045def dsdr(sdr1, sdr2): 

1046 s1, d1, r1 = sdr1 

1047 s2, d2, r2 = sdr2 

1048 

1049 s1 = s1 % 360. 

1050 s2 = s2 % 360. 

1051 r1 = r1 % 360. 

1052 r2 = r2 % 360. 

1053 

1054 ds = abs(s1 - s2) 

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

1056 

1057 dr = abs(r1 - r2) 

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

1059 

1060 dd = abs(d1 - d2) 

1061 

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

1063 

1064 

1065def order_like(sdrs, sdrs_ref): 

1066 ''' 

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

1068 

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

1070 :param sdrs_ref: as above but with reference pair 

1071 ''' 

1072 

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

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

1075 if d1 < d2: 

1076 return sdrs 

1077 else: 

1078 return sdrs[::-1] 

1079 

1080 

1081def _tpb2q(t, p, b): 

1082 eps = 0.001 

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

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

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

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

1087 

1088 q = num.zeros(4) 

1089 if tqw > eps: 

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

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

1092 elif tqx > eps: 

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

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

1095 elif tqy > eps: 

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

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

1098 elif tqz > eps: 

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

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

1101 else: 

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

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

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

1105 

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

1107 

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

1109 

1110 return q 

1111 

1112 

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

1114 

1115 

1116def kagan_angle(mt1, mt2): 

1117 ''' 

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

1119 

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

1121 ''' 

1122 

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

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

1125 

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

1127 

1128 tk, pk, bk = u 

1129 

1130 qk = _tpb2q(tk, pk, bk) 

1131 

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

1133 

1134 

1135def rand_to_gutenberg_richter(rand, b_value, magnitude_min): 

1136 ''' 

1137 Draw magnitude from Gutenberg Richter distribution. 

1138 ''' 

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

1140 

1141 

1142def magnitude_to_duration_gcmt(magnitudes): 

1143 ''' 

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

1145 ''' 

1146 

1147 mom = magnitude_to_moment(magnitudes) 

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

1149 

1150 

1151if __name__ == '__main__': 

1152 

1153 import sys 

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

1155 mt = MomentTensor.from_values(v) 

1156 print(mt)