Coverage for /usr/local/lib/python3.13/dist-packages/pyrocko/gf/seismosizer.py: 83%

2581 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2025-12-04 10:41 +0000

1# http://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

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

5 

6''' 

7High level synthetic seismogram synthesis. 

8 

9.. _coordinate-system-names: 

10 

11Coordinate systems 

12.................. 

13 

14Coordinate system names commonly used in source models. 

15 

16================= ============================================ 

17Name Description 

18================= ============================================ 

19``'xyz'`` northing, easting, depth in [m] 

20``'xy'`` northing, easting in [m] 

21``'latlon'`` latitude, longitude in [deg] 

22``'lonlat'`` longitude, latitude in [deg] 

23``'latlondepth'`` latitude, longitude in [deg], depth in [m] 

24================= ============================================ 

25''' 

26 

27from collections import defaultdict 

28from functools import cmp_to_key 

29import time 

30import math 

31import os 

32import re 

33import logging 

34try: 

35 import resource 

36except ImportError: 

37 resource = None 

38from hashlib import sha1 

39 

40import numpy as num 

41from scipy.interpolate import RegularGridInterpolator 

42 

43from pyrocko.guts import (Object, Float, String, StringChoice, List, 

44 Timestamp, Int, SObject, ArgumentError, Dict, 

45 ValidationError, Bool) 

46from pyrocko.guts_array import Array 

47 

48from pyrocko import moment_tensor as pmt 

49from pyrocko import trace, util, config, model, eikonal_ext 

50from pyrocko.orthodrome import ne_to_latlon 

51from pyrocko.model import Location 

52from pyrocko.modelling import OkadaSource, make_okada_coefficient_matrix, \ 

53 okada_ext, invert_fault_dislocations_bem 

54 

55from . import meta, store, ws 

56from .tractions import TractionField, DirectedTractions 

57from .targets import Target, StaticTarget, SatelliteTarget 

58 

59pjoin = os.path.join 

60 

61guts_prefix = 'pf' 

62 

63d2r = math.pi / 180. 

64r2d = 180. / math.pi 

65km = 1e3 

66 

67logger = logging.getLogger('pyrocko.gf.seismosizer') 

68 

69 

70def cmp_none_aware(a, b): 

71 if isinstance(a, tuple) and isinstance(b, tuple): 

72 for xa, xb in zip(a, b): 

73 rv = cmp_none_aware(xa, xb) 

74 if rv != 0: 

75 return rv 

76 

77 return 0 

78 

79 anone = a is None 

80 bnone = b is None 

81 

82 if anone and bnone: 

83 return 0 

84 

85 if anone: 

86 return -1 

87 

88 if bnone: 

89 return 1 

90 

91 return bool(a > b) - bool(a < b) 

92 

93 

94def xtime(): 

95 return time.time() 

96 

97 

98class SeismosizerError(Exception): 

99 pass 

100 

101 

102class BadRequest(SeismosizerError): 

103 pass 

104 

105 

106class DuplicateStoreId(Exception): 

107 pass 

108 

109 

110class NoDefaultStoreSet(Exception): 

111 ''' 

112 Raised, when a default store would be used but none is set. 

113 ''' 

114 pass 

115 

116 

117class ConversionError(Exception): 

118 pass 

119 

120 

121class STFError(SeismosizerError): 

122 pass 

123 

124 

125class NoSuchStore(BadRequest): 

126 

127 def __init__(self, store_id, store_dirs, store_superdirs, store_ids_avail): 

128 BadRequest.__init__(self) 

129 self.store_id = store_id 

130 self.store_dirs = store_dirs 

131 self.store_superdirs = store_superdirs 

132 self.store_ids_avail = store_ids_avail 

133 

134 def __str__(self): 

135 lines = ['No GF store with id "%s" found.' % self.store_id] 

136 

137 def add_entries(lines, name, entries): 

138 lines.append(' %s:' % name) 

139 if not entries: 

140 lines.append(' *empty*') 

141 else: 

142 for entry in entries: 

143 lines.append(' - %s' % entry) 

144 

145 add_entries(lines, 'store_superdirs searched', self.store_superdirs) 

146 add_entries(lines, 'store_dirs searched', self.store_dirs) 

147 add_entries(lines, 'store IDs available', self.store_ids_avail) 

148 return '\n'.join(lines) 

149 

150 

151def ufloat(s): 

152 units = { 

153 'k': 1e3, 

154 'M': 1e6, 

155 } 

156 

157 factor = 1.0 

158 if s and s[-1] in units: 

159 factor = units[s[-1]] 

160 s = s[:-1] 

161 if not s: 

162 raise ValueError("unit without a number: '%s'" % s) 

163 

164 return float(s) * factor 

165 

166 

167def ufloat_or_none(s): 

168 if s: 

169 return ufloat(s) 

170 else: 

171 return None 

172 

173 

174def int_or_none(s): 

175 if s: 

176 return int(s) 

177 else: 

178 return None 

179 

180 

181def nonzero(x, eps=1e-15): 

182 return abs(x) > eps 

183 

184 

185def permudef(ln, j=0): 

186 if j < len(ln): 

187 k, v = ln[j] 

188 for y in v: 

189 ln[j] = k, y 

190 for s in permudef(ln, j + 1): 

191 yield s 

192 

193 ln[j] = k, v 

194 return 

195 else: 

196 yield ln 

197 

198 

199def arr(x): 

200 return num.atleast_1d(num.asarray(x)) 

201 

202 

203def discretize_rect_source(deltas, deltat, time, north, east, depth, 

204 strike, dip, length, width, 

205 anchor, velocity=None, stf=None, 

206 nucleation_x=None, nucleation_y=None, 

207 decimation_factor=1, pointsonly=False, 

208 plane_coords=False, 

209 aggressive_oversampling=False): 

210 

211 if stf is None: 

212 stf = STF() 

213 

214 if not velocity and not pointsonly: 

215 raise AttributeError('velocity is required in time mode') 

216 

217 mindeltagf = float(num.min(deltas)) 

218 if velocity: 

219 mindeltagf = min(mindeltagf, deltat * velocity) 

220 

221 ln = length 

222 wd = width 

223 

224 if aggressive_oversampling: 

225 nl = int((2. / decimation_factor) * num.ceil(ln / mindeltagf)) + 1 

226 nw = int((2. / decimation_factor) * num.ceil(wd / mindeltagf)) + 1 

227 else: 

228 nl = int((1. / decimation_factor) * num.ceil(ln / mindeltagf)) + 1 

229 nw = int((1. / decimation_factor) * num.ceil(wd / mindeltagf)) + 1 

230 

231 n = int(nl * nw) 

232 

233 dl = ln / nl 

234 dw = wd / nw 

235 

236 xl = num.linspace(-0.5 * (ln - dl), 0.5 * (ln - dl), nl) 

237 xw = num.linspace(-0.5 * (wd - dw), 0.5 * (wd - dw), nw) 

238 

239 points = num.zeros((n, 3)) 

240 points[:, 0] = num.tile(xl, nw) 

241 points[:, 1] = num.repeat(xw, nl) 

242 

243 if nucleation_x is not None: 

244 dist_x = num.abs(nucleation_x - points[:, 0]) 

245 else: 

246 dist_x = num.zeros(n) 

247 

248 if nucleation_y is not None: 

249 dist_y = num.abs(nucleation_y - points[:, 1]) 

250 else: 

251 dist_y = num.zeros(n) 

252 

253 dist = num.sqrt(dist_x**2 + dist_y**2) 

254 times = dist / velocity 

255 

256 anch_x, anch_y = map_anchor[anchor] 

257 

258 points[:, 0] -= anch_x * 0.5 * length 

259 points[:, 1] -= anch_y * 0.5 * width 

260 

261 if plane_coords: 

262 return points, dl, dw, nl, nw 

263 

264 rotmat = pmt.euler_to_matrix(dip * d2r, strike * d2r, 0.0) 

265 points = num.dot(rotmat.T, points.T).T 

266 

267 points[:, 0] += north 

268 points[:, 1] += east 

269 points[:, 2] += depth 

270 

271 if pointsonly: 

272 return points, dl, dw, nl, nw 

273 

274 xtau, amplitudes = stf.discretize_t(deltat, time) 

275 nt = xtau.size 

276 

277 points2 = num.repeat(points, nt, axis=0) 

278 times2 = (times[:, num.newaxis] + xtau[num.newaxis, :]).ravel() 

279 amplitudes2 = num.tile(amplitudes, n) 

280 

281 return points2, times2, amplitudes2, dl, dw, nl, nw 

282 

283 

284def check_rect_source_discretisation(points2, nl, nw, store): 

285 # We assume a non-rotated fault plane 

286 N_CRITICAL = 8 

287 points = points2.T.reshape((3, nl, nw)) 

288 if points.size <= N_CRITICAL: 

289 logger.warning('RectangularSource is defined by only %d sub-sources!' 

290 % points.size) 

291 return True 

292 

293 distances = num.sqrt( 

294 (points[0, 0, :] - points[0, 1, :])**2 + 

295 (points[1, 0, :] - points[1, 1, :])**2 + 

296 (points[2, 0, :] - points[2, 1, :])**2) 

297 

298 depths = points[2, 0, :] 

299 vs_profile = store.config.get_vs( 

300 lat=0., lon=0., 

301 points=num.repeat(depths[:, num.newaxis], 3, axis=1), 

302 interpolation='multilinear') 

303 

304 min_wavelength = vs_profile * (store.config.deltat * 2) 

305 if not num.all(min_wavelength > distances / 2): 

306 return False 

307 return True 

308 

309 

310def outline_rect_source(strike, dip, length, width, anchor): 

311 ln = length 

312 wd = width 

313 points = num.array( 

314 [[-0.5 * ln, -0.5 * wd, 0.], 

315 [0.5 * ln, -0.5 * wd, 0.], 

316 [0.5 * ln, 0.5 * wd, 0.], 

317 [-0.5 * ln, 0.5 * wd, 0.], 

318 [-0.5 * ln, -0.5 * wd, 0.]]) 

319 

320 anch_x, anch_y = map_anchor[anchor] 

321 points[:, 0] -= anch_x * 0.5 * length 

322 points[:, 1] -= anch_y * 0.5 * width 

323 

324 rotmat = pmt.euler_to_matrix(dip * d2r, strike * d2r, 0.0) 

325 

326 return num.dot(rotmat.T, points.T).T 

327 

328 

329def from_plane_coords( 

330 strike, dip, length, width, depth, x_plane_coords, y_plane_coords, 

331 lat=0., lon=0., 

332 north_shift=0, east_shift=0, 

333 anchor='top', cs='xy'): 

334 

335 ln = length 

336 wd = width 

337 x_abs = [] 

338 y_abs = [] 

339 if not isinstance(x_plane_coords, list): 

340 x_plane_coords = [x_plane_coords] 

341 y_plane_coords = [y_plane_coords] 

342 

343 for x_plane, y_plane in zip(x_plane_coords, y_plane_coords): 

344 points = num.array( 

345 [[-0.5 * ln * x_plane, -0.5 * wd * y_plane, 0.], 

346 [0.5 * ln * x_plane, -0.5 * wd * y_plane, 0.], 

347 [0.5 * ln * x_plane, 0.5 * wd * y_plane, 0.], 

348 [-0.5 * ln * x_plane, 0.5 * wd * y_plane, 0.], 

349 [-0.5 * ln * x_plane, -0.5 * wd * y_plane, 0.]]) 

350 

351 anch_x, anch_y = map_anchor[anchor] 

352 points[:, 0] -= anch_x * 0.5 * length 

353 points[:, 1] -= anch_y * 0.5 * width 

354 

355 rotmat = pmt.euler_to_matrix(dip * d2r, strike * d2r, 0.0) 

356 

357 points = num.dot(rotmat.T, points.T).T 

358 points[:, 0] += north_shift 

359 points[:, 1] += east_shift 

360 points[:, 2] += depth 

361 if cs in ('latlon', 'lonlat'): 

362 latlon = ne_to_latlon(lat, lon, 

363 points[:, 0], points[:, 1]) 

364 latlon = num.array(latlon).T 

365 x_abs.append(latlon[1:2, 1]) 

366 y_abs.append(latlon[2:3, 0]) 

367 if cs == 'xy': 

368 x_abs.append(points[1:2, 1]) 

369 y_abs.append(points[2:3, 0]) 

370 

371 if cs == 'lonlat': 

372 return y_abs, x_abs 

373 else: 

374 return x_abs, y_abs 

375 

376 

377def points_on_rect_source( 

378 strike, dip, length, width, anchor, 

379 discretized_basesource=None, points_x=None, points_y=None): 

380 

381 ln = length 

382 wd = width 

383 

384 if isinstance(points_x, list) or isinstance(points_x, float): 

385 points_x = num.array([points_x]) 

386 if isinstance(points_y, list) or isinstance(points_y, float): 

387 points_y = num.array([points_y]) 

388 

389 if discretized_basesource: 

390 ds = discretized_basesource 

391 

392 nl_patches = ds.nl + 1 

393 nw_patches = ds.nw + 1 

394 

395 npoints = nl_patches * nw_patches 

396 points = num.zeros((npoints, 3)) 

397 ln_patches = num.array([il for il in range(nl_patches)]) 

398 wd_patches = num.array([iw for iw in range(nw_patches)]) 

399 

400 points_ln =\ 

401 2 * ((ln_patches - num.min(ln_patches)) / num.ptp(ln_patches)) - 1 

402 points_wd =\ 

403 2 * ((wd_patches - num.min(wd_patches)) / num.ptp(wd_patches)) - 1 

404 

405 for il in range(nl_patches): 

406 for iw in range(nw_patches): 

407 points[il * nw_patches + iw, :] = num.array([ 

408 points_ln[il] * ln * 0.5, 

409 points_wd[iw] * wd * 0.5, 0.0]) 

410 

411 elif points_x.shape[0] > 0 and points_y.shape[0] > 0: 

412 points = num.zeros(shape=((len(points_x), 3))) 

413 for i, (x, y) in enumerate(zip(points_x, points_y)): 

414 points[i, :] = num.array( 

415 [x * 0.5 * ln, y * 0.5 * wd, 0.0]) 

416 

417 anch_x, anch_y = map_anchor[anchor] 

418 

419 points[:, 0] -= anch_x * 0.5 * ln 

420 points[:, 1] -= anch_y * 0.5 * wd 

421 

422 rotmat = pmt.euler_to_matrix(dip * d2r, strike * d2r, 0.0) 

423 

424 return num.dot(rotmat.T, points.T).T 

425 

426 

427class InvalidGridDef(Exception): 

428 pass 

429 

430 

431class Range(SObject): 

432 ''' 

433 Convenient range specification. 

434 

435 Equivalent ways to sepecify the range [ 0., 1000., ... 10000. ]:: 

436 

437 Range('0 .. 10k : 1k') 

438 Range(start=0., stop=10e3, step=1e3) 

439 Range(0, 10e3, 1e3) 

440 Range('0 .. 10k @ 11') 

441 Range(start=0., stop=10*km, n=11) 

442 

443 Range(0, 10e3, n=11) 

444 Range(values=[x*1e3 for x in range(11)]) 

445 

446 Depending on the use context, it can be possible to omit any part of the 

447 specification. E.g. in the context of extracting a subset of an already 

448 existing range, the existing range's specification values would be filled 

449 in where missing. 

450 

451 The values are distributed with equal spacing, unless the ``spacing`` 

452 argument is modified. The values can be created offset or relative to an 

453 external base value with the ``relative`` argument if the use context 

454 supports this. 

455 

456 The range specification can be expressed with a short string 

457 representation:: 

458 

459 'start .. stop @ num | spacing, relative' 

460 'start .. stop : step | spacing, relative' 

461 

462 most parts of the expression can be omitted if not needed. Whitespace is 

463 allowed for readability but can also be omitted. 

464 ''' 

465 

466 start = Float.T(optional=True) 

467 stop = Float.T(optional=True) 

468 step = Float.T(optional=True) 

469 n = Int.T(optional=True) 

470 values = Array.T(optional=True, dtype=float, shape=(None,)) 

471 

472 spacing = StringChoice.T( 

473 choices=['lin', 'log', 'symlog'], 

474 default='lin', 

475 optional=True) 

476 

477 relative = StringChoice.T( 

478 choices=['', 'add', 'mult'], 

479 default='', 

480 optional=True) 

481 

482 pattern = re.compile(r'^((?P<start>.*)\.\.(?P<stop>[^@|:]*))?' 

483 r'(@(?P<n>[^|]+)|:(?P<step>[^|]+))?' 

484 r'(\|(?P<stuff>.+))?$') 

485 

486 def __init__(self, *args, **kwargs): 

487 d = {} 

488 if len(args) == 1: 

489 d = self.parse(args[0]) 

490 elif len(args) in (2, 3): 

491 d['start'], d['stop'] = [float(x) for x in args[:2]] 

492 if len(args) == 3: 

493 d['step'] = float(args[2]) 

494 

495 for k, v in kwargs.items(): 

496 if k in d: 

497 raise ArgumentError('%s specified more than once' % k) 

498 

499 d[k] = v 

500 

501 SObject.__init__(self, **d) 

502 

503 def __str__(self): 

504 def sfloat(x): 

505 if x is not None: 

506 return '%g' % x 

507 else: 

508 return '' 

509 

510 if self.values: 

511 return ','.join('%g' % x for x in self.values) 

512 

513 if self.start is None and self.stop is None: 

514 s0 = '' 

515 else: 

516 s0 = '%s .. %s' % (sfloat(self.start), sfloat(self.stop)) 

517 

518 s1 = '' 

519 if self.step is not None: 

520 s1 = [' : %g', ':%g'][s0 == ''] % self.step 

521 elif self.n is not None: 

522 s1 = [' @ %i', '@%i'][s0 == ''] % self.n 

523 

524 if self.spacing == 'lin' and self.relative == '': 

525 s2 = '' 

526 else: 

527 x = [] 

528 if self.spacing != 'lin': 

529 x.append(self.spacing) 

530 

531 if self.relative != '': 

532 x.append(self.relative) 

533 

534 s2 = ' | %s' % ','.join(x) 

535 

536 return s0 + s1 + s2 

537 

538 @classmethod 

539 def parse(cls, s): 

540 s = re.sub(r'\s+', '', s) 

541 m = cls.pattern.match(s) 

542 if not m: 

543 try: 

544 vals = [ufloat(x) for x in s.split(',')] 

545 except Exception: 

546 raise InvalidGridDef( 

547 '"%s" is not a valid range specification' % s) 

548 

549 return dict(values=num.array(vals, dtype=float)) 

550 

551 d = m.groupdict() 

552 try: 

553 start = ufloat_or_none(d['start']) 

554 stop = ufloat_or_none(d['stop']) 

555 step = ufloat_or_none(d['step']) 

556 n = int_or_none(d['n']) 

557 except Exception: 

558 raise InvalidGridDef( 

559 '"%s" is not a valid range specification' % s) 

560 

561 spacing = 'lin' 

562 relative = '' 

563 

564 if d['stuff'] is not None: 

565 t = d['stuff'].split(',') 

566 for x in t: 

567 if x in cls.spacing.choices: 

568 spacing = x 

569 elif x and x in cls.relative.choices: 

570 relative = x 

571 else: 

572 raise InvalidGridDef( 

573 '"%s" is not a valid range specification' % s) 

574 

575 return dict(start=start, stop=stop, step=step, n=n, spacing=spacing, 

576 relative=relative) 

577 

578 def make(self, mi=None, ma=None, inc=None, base=None, eps=1e-5): 

579 if self.values: 

580 return self.values 

581 

582 start = self.start 

583 stop = self.stop 

584 step = self.step 

585 n = self.n 

586 

587 swap = step is not None and step < 0. 

588 if start is None: 

589 start = [mi, ma][swap] 

590 if stop is None: 

591 stop = [ma, mi][swap] 

592 if step is None and inc is not None: 

593 step = [inc, -inc][ma < mi] 

594 

595 if start is None or stop is None: 

596 raise InvalidGridDef( 

597 'Cannot use range specification "%s" without start ' 

598 'and stop in this context' % self) 

599 

600 if step is None and n is None: 

601 step = stop - start 

602 

603 if n is None: 

604 if (step < 0) != (stop - start < 0): 

605 raise InvalidGridDef( 

606 'Range specification "%s" has inconsistent ordering ' 

607 '(step < 0 => stop > start)' % self) 

608 

609 n = int(round((stop - start) / step)) + 1 

610 stop2 = start + (n - 1) * step 

611 if abs(stop - stop2) > eps: 

612 n = int(math.floor((stop - start) / step)) + 1 

613 stop = start + (n - 1) * step 

614 else: 

615 stop = stop2 

616 

617 if start == stop: 

618 n = 1 

619 

620 if self.spacing == 'lin': 

621 vals = num.linspace(start, stop, n) 

622 

623 elif self.spacing in ('log', 'symlog'): 

624 if start > 0. and stop > 0.: 

625 vals = num.exp(num.linspace(num.log(start), 

626 num.log(stop), n)) 

627 elif start < 0. and stop < 0.: 

628 vals = -num.exp(num.linspace(num.log(-start), 

629 num.log(-stop), n)) 

630 else: 

631 raise InvalidGridDef( 

632 'Log ranges should not include or cross zero ' 

633 '(in range specification "%s").' % self) 

634 

635 if self.spacing == 'symlog': 

636 nvals = - vals 

637 vals = num.concatenate((nvals[::-1], vals)) 

638 

639 if self.relative in ('add', 'mult') and base is None: 

640 raise InvalidGridDef( 

641 'Cannot use relative range specification in this context.') 

642 

643 vals = self.make_relative(base, vals) 

644 

645 return list(map(float, vals)) 

646 

647 def make_relative(self, base, vals): 

648 if self.relative == 'add': 

649 vals += base 

650 

651 if self.relative == 'mult': 

652 vals *= base 

653 

654 return vals 

655 

656 

657class GridDefElement(Object): 

658 

659 param = meta.StringID.T() 

660 rs = Range.T() 

661 

662 def __init__(self, shorthand=None, **kwargs): 

663 if shorthand is not None: 

664 t = shorthand.split('=') 

665 if len(t) != 2: 

666 raise InvalidGridDef( 

667 'Invalid grid specification element: %s' % shorthand) 

668 

669 sp, sr = t[0].strip(), t[1].strip() 

670 

671 kwargs['param'] = sp 

672 kwargs['rs'] = Range(sr) 

673 

674 Object.__init__(self, **kwargs) 

675 

676 def shorthand(self): 

677 return self.param + ' = ' + str(self.rs) 

678 

679 

680class GridDef(Object): 

681 

682 elements = List.T(GridDefElement.T()) 

683 

684 def __init__(self, shorthand=None, **kwargs): 

685 if shorthand is not None: 

686 t = shorthand.splitlines() 

687 tt = [] 

688 for x in t: 

689 x = x.strip() 

690 if x: 

691 tt.extend(x.split(';')) 

692 

693 elements = [] 

694 for se in tt: 

695 elements.append(GridDef(se)) 

696 

697 kwargs['elements'] = elements 

698 

699 Object.__init__(self, **kwargs) 

700 

701 def shorthand(self): 

702 return '; '.join(str(x) for x in self.elements) 

703 

704 

705class Cloneable(object): 

706 ''' 

707 Mix-in class for Guts objects, providing dict-like access and cloning. 

708 ''' 

709 

710 def __iter__(self): 

711 return iter(self.T.propnames) 

712 

713 def __getitem__(self, k): 

714 if k not in self.keys(): 

715 raise KeyError(k) 

716 

717 return getattr(self, k) 

718 

719 def __setitem__(self, k, v): 

720 if k not in self.keys(): 

721 raise KeyError(k) 

722 

723 return setattr(self, k, v) 

724 

725 def clone(self, **kwargs): 

726 ''' 

727 Make a copy of the object. 

728 

729 A new object of the same class is created and initialized with the 

730 parameters of the object on which this method is called on. If 

731 ``kwargs`` are given, these are used to override any of the 

732 initialization parameters. 

733 ''' 

734 

735 d = dict(self) 

736 for k in d: 

737 v = d[k] 

738 if isinstance(v, Cloneable): 

739 d[k] = v.clone() 

740 

741 d.update(kwargs) 

742 return self.__class__(**d) 

743 

744 @classmethod 

745 def keys(cls): 

746 ''' 

747 Get list of the source model's parameter names. 

748 ''' 

749 

750 return cls.T.propnames 

751 

752 

753class STF(Object, Cloneable): 

754 

755 ''' 

756 Base class for source time functions. 

757 ''' 

758 

759 def __init__(self, effective_duration=None, **kwargs): 

760 if effective_duration is not None: 

761 kwargs['duration'] = effective_duration / \ 

762 self.factor_duration_to_effective() 

763 

764 Object.__init__(self, **kwargs) 

765 

766 @classmethod 

767 def factor_duration_to_effective(cls): 

768 return 1.0 

769 

770 def centroid_time(self, tref): 

771 return tref 

772 

773 @property 

774 def effective_duration(self): 

775 return self.duration * self.factor_duration_to_effective() 

776 

777 def discretize_t(self, deltat, tref): 

778 tl = math.floor(tref / deltat) * deltat 

779 th = math.ceil(tref / deltat) * deltat 

780 if tl == th: 

781 return num.array([tl], dtype=float), num.ones(1) 

782 else: 

783 return ( 

784 num.array([tl, th], dtype=float), 

785 num.array([th - tref, tref - tl], dtype=float) / deltat) 

786 

787 def base_key(self): 

788 return (type(self).__name__,) 

789 

790 

791g_unit_pulse = STF() 

792 

793 

794def sshift(times, amplitudes, tshift, deltat): 

795 

796 t0 = math.floor(tshift / deltat) * deltat 

797 t1 = math.ceil(tshift / deltat) * deltat 

798 if t0 == t1: 

799 return times, amplitudes 

800 

801 amplitudes2 = num.zeros(amplitudes.size + 1, dtype=float) 

802 

803 amplitudes2[:-1] += (t1 - tshift) / deltat * amplitudes 

804 amplitudes2[1:] += (tshift - t0) / deltat * amplitudes 

805 

806 times2 = num.arange(times.size + 1, dtype=float) * \ 

807 deltat + times[0] + t0 

808 

809 return times2, amplitudes2 

810 

811 

812class BoxcarSTF(STF): 

813 

814 ''' 

815 Boxcar type source time function. 

816 

817 .. figure :: /static/stf-BoxcarSTF.svg 

818 :width: 40% 

819 :align: center 

820 :alt: boxcar source time function 

821 ''' 

822 

823 duration = Float.T( 

824 default=0.0, 

825 help='duration of the boxcar') 

826 

827 anchor = Float.T( 

828 default=0.0, 

829 help='anchor point with respect to source.time: (' 

830 '-1.0: left -> source duration [0, T] ~ hypocenter time, ' 

831 ' 0.0: center -> source duration [-T/2, T/2] ~ centroid time, ' 

832 '+1.0: right -> source duration [-T, 0] ~ rupture end time)') 

833 

834 @classmethod 

835 def factor_duration_to_effective(cls): 

836 return 1.0 

837 

838 def centroid_time(self, tref): 

839 return tref - 0.5 * self.duration * self.anchor 

840 

841 def discretize_t(self, deltat, tref): 

842 tmin_stf = tref - self.duration * (self.anchor + 1.) * 0.5 

843 tmax_stf = tref + self.duration * (1. - self.anchor) * 0.5 

844 tmin = round(tmin_stf / deltat) * deltat 

845 tmax = round(tmax_stf / deltat) * deltat 

846 nt = int(round((tmax - tmin) / deltat)) + 1 

847 times = num.linspace(tmin, tmax, nt) 

848 amplitudes = num.ones_like(times) 

849 if times.size > 1: 

850 t_edges = num.linspace( 

851 tmin - 0.5 * deltat, tmax + 0.5 * deltat, nt + 1) 

852 t = tmin_stf + self.duration * num.array( 

853 [0.0, 0.0, 1.0, 1.0], dtype=float) 

854 f = num.array([0., 1., 1., 0.], dtype=float) 

855 amplitudes = util.plf_integrate_piecewise(t_edges, t, f) 

856 amplitudes /= num.sum(amplitudes) 

857 

858 tshift = (num.sum(amplitudes * times) - self.centroid_time(tref)) 

859 

860 return sshift(times, amplitudes, -tshift, deltat) 

861 

862 def base_key(self): 

863 return (type(self).__name__, self.duration, self.anchor) 

864 

865 

866class TriangularSTF(STF): 

867 

868 ''' 

869 Triangular type source time function. 

870 

871 .. figure :: /static/stf-TriangularSTF.svg 

872 :width: 40% 

873 :align: center 

874 :alt: triangular source time function 

875 ''' 

876 

877 duration = Float.T( 

878 default=0.0, 

879 help='baseline of the triangle') 

880 

881 peak_ratio = Float.T( 

882 default=0.5, 

883 help='fraction of time compared to duration, ' 

884 'when the maximum amplitude is reached') 

885 

886 anchor = Float.T( 

887 default=0.0, 

888 help='anchor point with respect to source.time: (' 

889 '-1.0: left -> source duration [0, T] ~ hypocenter time, ' 

890 ' 0.0: center -> source duration [-T/2, T/2] ~ centroid time, ' 

891 '+1.0: right -> source duration [-T, 0] ~ rupture end time)') 

892 

893 @classmethod 

894 def factor_duration_to_effective(cls, peak_ratio=None): 

895 if peak_ratio is None: 

896 peak_ratio = cls.peak_ratio.default() 

897 

898 return math.sqrt((peak_ratio**2 - peak_ratio + 1.0) * 2.0 / 3.0) 

899 

900 def __init__(self, effective_duration=None, **kwargs): 

901 if effective_duration is not None: 

902 kwargs['duration'] = effective_duration / \ 

903 self.factor_duration_to_effective( 

904 kwargs.get('peak_ratio', None)) 

905 

906 STF.__init__(self, **kwargs) 

907 

908 @property 

909 def centroid_ratio(self): 

910 ra = self.peak_ratio 

911 rb = 1.0 - ra 

912 return self.peak_ratio + (rb**2 / 3. - ra**2 / 3.) / (ra + rb) 

913 

914 def centroid_time(self, tref): 

915 ca = self.centroid_ratio 

916 cb = 1.0 - ca 

917 if self.anchor <= 0.: 

918 return tref - ca * self.duration * self.anchor 

919 else: 

920 return tref - cb * self.duration * self.anchor 

921 

922 @property 

923 def effective_duration(self): 

924 return self.duration * self.factor_duration_to_effective( 

925 self.peak_ratio) 

926 

927 def tminmax_stf(self, tref): 

928 ca = self.centroid_ratio 

929 cb = 1.0 - ca 

930 if self.anchor <= 0.: 

931 tmin_stf = tref - ca * self.duration * (self.anchor + 1.) 

932 tmax_stf = tmin_stf + self.duration 

933 else: 

934 tmax_stf = tref + cb * self.duration * (1. - self.anchor) 

935 tmin_stf = tmax_stf - self.duration 

936 

937 return tmin_stf, tmax_stf 

938 

939 def discretize_t(self, deltat, tref): 

940 tmin_stf, tmax_stf = self.tminmax_stf(tref) 

941 

942 tmin = round(tmin_stf / deltat) * deltat 

943 tmax = round(tmax_stf / deltat) * deltat 

944 nt = int(round((tmax - tmin) / deltat)) + 1 

945 if nt > 1: 

946 t_edges = num.linspace( 

947 tmin - 0.5 * deltat, tmax + 0.5 * deltat, nt + 1) 

948 t = tmin_stf + self.duration * num.array( 

949 [0.0, self.peak_ratio, 1.0], dtype=float) 

950 f = num.array([0., 1., 0.], dtype=float) 

951 amplitudes = util.plf_integrate_piecewise(t_edges, t, f) 

952 amplitudes /= num.sum(amplitudes) 

953 else: 

954 amplitudes = num.ones(1) 

955 

956 times = num.linspace(tmin, tmax, nt) 

957 return times, amplitudes 

958 

959 def base_key(self): 

960 return ( 

961 type(self).__name__, self.duration, self.peak_ratio, self.anchor) 

962 

963 

964class HalfSinusoidSTF(STF): 

965 

966 ''' 

967 Half sinusoid type source time function. 

968 

969 .. figure :: /static/stf-HalfSinusoidSTF.svg 

970 :width: 40% 

971 :align: center 

972 :alt: half-sinusouid source time function 

973 ''' 

974 

975 duration = Float.T( 

976 default=0.0, 

977 help='duration of the half-sinusoid (baseline)') 

978 

979 anchor = Float.T( 

980 default=0.0, 

981 help='anchor point with respect to source.time: (' 

982 '-1.0: left -> source duration [0, T] ~ hypocenter time, ' 

983 ' 0.0: center -> source duration [-T/2, T/2] ~ centroid time, ' 

984 '+1.0: right -> source duration [-T, 0] ~ rupture end time)') 

985 

986 exponent = Int.T( 

987 default=1, 

988 help='set to 2 to use square of the half-period sinusoidal function.') 

989 

990 def __init__(self, effective_duration=None, **kwargs): 

991 if effective_duration is not None: 

992 kwargs['duration'] = effective_duration / \ 

993 self.factor_duration_to_effective( 

994 kwargs.get('exponent', 1)) 

995 

996 STF.__init__(self, **kwargs) 

997 

998 @classmethod 

999 def factor_duration_to_effective(cls, exponent): 

1000 if exponent == 1: 

1001 return math.sqrt(3.0 * math.pi**2 - 24.0) / math.pi 

1002 elif exponent == 2: 

1003 return math.sqrt(math.pi**2 - 6) / math.pi 

1004 else: 

1005 raise ValueError('Exponent for HalfSinusoidSTF must be 1 or 2.') 

1006 

1007 @property 

1008 def effective_duration(self): 

1009 return self.duration * self.factor_duration_to_effective(self.exponent) 

1010 

1011 def centroid_time(self, tref): 

1012 return tref - 0.5 * self.duration * self.anchor 

1013 

1014 def discretize_t(self, deltat, tref): 

1015 tmin_stf = tref - self.duration * (self.anchor + 1.) * 0.5 

1016 tmax_stf = tref + self.duration * (1. - self.anchor) * 0.5 

1017 tmin = round(tmin_stf / deltat) * deltat 

1018 tmax = round(tmax_stf / deltat) * deltat 

1019 nt = int(round((tmax - tmin) / deltat)) + 1 

1020 if nt > 1: 

1021 t_edges = num.maximum(tmin_stf, num.minimum(tmax_stf, num.linspace( 

1022 tmin - 0.5 * deltat, tmax + 0.5 * deltat, nt + 1))) 

1023 

1024 if self.exponent == 1: 

1025 fint = -num.cos( 

1026 (t_edges - tmin_stf) * (math.pi / self.duration)) 

1027 

1028 elif self.exponent == 2: 

1029 fint = (t_edges - tmin_stf) / self.duration \ 

1030 - 1.0 / (2.0 * math.pi) * num.sin( 

1031 (t_edges - tmin_stf) * (2.0 * math.pi / self.duration)) 

1032 else: 

1033 raise ValueError( 

1034 'Exponent for HalfSinusoidSTF must be 1 or 2.') 

1035 

1036 amplitudes = fint[1:] - fint[:-1] 

1037 amplitudes /= num.sum(amplitudes) 

1038 else: 

1039 amplitudes = num.ones(1) 

1040 

1041 times = num.linspace(tmin, tmax, nt) 

1042 return times, amplitudes 

1043 

1044 def base_key(self): 

1045 return (type(self).__name__, self.duration, self.anchor) 

1046 

1047 

1048class SmoothRampSTF(STF): 

1049 ''' 

1050 Smooth-ramp type source time function for near-field displacement. 

1051 Based on moment function of double-couple point source proposed by [1]_. 

1052 

1053 .. [1] W. Bruestle, G. Mueller (1983), Moment and duration of shallow 

1054 earthquakes from Love-wave modelling for regional distances, PEPI 32, 

1055 312-324. 

1056 

1057 .. figure :: /static/stf-SmoothRampSTF.svg 

1058 :width: 40% 

1059 :alt: smooth ramp source time function 

1060 ''' 

1061 duration = Float.T( 

1062 default=0.0, 

1063 help='duration of the ramp (baseline)') 

1064 

1065 rise_ratio = Float.T( 

1066 default=0.5, 

1067 help='fraction of time compared to duration, ' 

1068 'when the maximum amplitude is reached') 

1069 

1070 anchor = Float.T( 

1071 default=0.0, 

1072 help='anchor point with respect to source.time: (' 

1073 '-1.0: left -> source duration ``[0, T]`` ~ hypocenter time, ' 

1074 '0.0: center -> source duration ``[-T/2, T/2]`` ~ centroid time, ' 

1075 '+1.0: right -> source duration ``[-T, 0]`` ~ rupture end time)') 

1076 

1077 def discretize_t(self, deltat, tref): 

1078 tmin_stf = tref - self.duration * (self.anchor + 1.) * 0.5 

1079 tmax_stf = tref + self.duration * (1. - self.anchor) * 0.5 

1080 tmin = round(tmin_stf / deltat) * deltat 

1081 tmax = round(tmax_stf / deltat) * deltat 

1082 D = round((tmax - tmin) / deltat) * deltat 

1083 nt = int(round(D / deltat)) + 1 

1084 times = num.linspace(tmin, tmax, nt) 

1085 if nt > 1: 

1086 rise_time = self.rise_ratio * self.duration 

1087 amplitudes = num.ones_like(times) 

1088 tp = tmin + rise_time 

1089 ii = num.where(times <= tp) 

1090 t_inc = times[ii] 

1091 a = num.cos(num.pi * (t_inc - tmin_stf) / rise_time) 

1092 b = num.cos(3 * num.pi * (t_inc - tmin_stf) / rise_time) - 1.0 

1093 amplitudes[ii] = (9. / 16.) * (1 - a + (1. / 9.) * b) 

1094 

1095 amplitudes /= num.sum(amplitudes) 

1096 else: 

1097 amplitudes = num.ones(1) 

1098 

1099 return times, amplitudes 

1100 

1101 def base_key(self): 

1102 return (type(self).__name__, 

1103 self.duration, self.rise_ratio, self.anchor) 

1104 

1105 

1106class ResonatorSTF(STF): 

1107 ''' 

1108 Simple resonator like source time function. 

1109 

1110 .. math :: 

1111 

1112 f(t) = 0 for t < 0 

1113 f(t) = e^{-t/tau} * sin(2 * pi * f * t) 

1114 

1115 

1116 .. figure :: /static/stf-SmoothRampSTF.svg 

1117 :width: 40% 

1118 :alt: smooth ramp source time function 

1119 

1120 ''' 

1121 

1122 duration = Float.T( 

1123 default=0.0, 

1124 help='decay time') 

1125 

1126 frequency = Float.T( 

1127 default=1.0, 

1128 help='resonance frequency') 

1129 

1130 def discretize_t(self, deltat, tref): 

1131 tmin_stf = tref 

1132 tmax_stf = tref + self.duration * 3 

1133 tmin = math.floor(tmin_stf / deltat) * deltat 

1134 tmax = math.ceil(tmax_stf / deltat) * deltat 

1135 times = util.arange2(tmin, tmax, deltat) 

1136 amplitudes = num.exp(-(times - tref) / self.duration) \ 

1137 * num.sin(2.0 * num.pi * self.frequency * (times - tref)) 

1138 

1139 return times, amplitudes 

1140 

1141 def base_key(self): 

1142 return (type(self).__name__, 

1143 self.duration, self.frequency) 

1144 

1145 

1146class TremorSTF(STF): 

1147 ''' 

1148 Oszillating source time function. 

1149 

1150 .. math :: 

1151 

1152 f(t) = 0 for t < -tau/2 or t > tau/2 

1153 f(t) = cos(pi/tau*t) * sin(2 * pi * f * t) 

1154 

1155 ''' 

1156 

1157 duration = Float.T( 

1158 default=0.0, 

1159 help='Tremor duration [s]') 

1160 

1161 frequency = Float.T( 

1162 default=1.0, 

1163 help='Frequency [Hz]') 

1164 

1165 def discretize_t(self, deltat, tref): 

1166 tmin_stf = tref - 0.5 * self.duration 

1167 tmax_stf = tref + 0.5 * self.duration 

1168 tmin = math.floor(tmin_stf / deltat) * deltat 

1169 tmax = math.ceil(tmax_stf / deltat) * deltat 

1170 times = util.arange2(tmin, tmax, deltat) 

1171 mask = num.logical_and( 

1172 tref - 0.5 * self.duration < times, 

1173 times < tref + 0.5 * self.duration) 

1174 

1175 amplitudes = num.zeros_like(times) 

1176 amplitudes[mask] = num.cos(num.pi/self.duration*(times[mask] - tref)) \ 

1177 * num.sin(2.0 * num.pi * self.frequency * (times[mask] - tref)) 

1178 amplitudes[mask] *= deltat 

1179 

1180 return times, amplitudes 

1181 

1182 def base_key(self): 

1183 return (type(self).__name__, 

1184 self.duration, self.frequency) 

1185 

1186 

1187class SimpleLandslideSTF(STF): 

1188 

1189 ''' 

1190 Doublepulse land-slide STF which respects conservation of momentum. 

1191 ''' 

1192 

1193 duration_acceleration = Float.T( 

1194 default=1.0, 

1195 help='Duratian of the acceleration phase [s].') 

1196 

1197 duration_deceleration = Float.T( 

1198 default=1.0, 

1199 help='Duration of the deceleration phase [s].') 

1200 

1201 mute_acceleration = Bool.T( 

1202 default=False, 

1203 help='set acceleration to zero (for testing)') 

1204 

1205 mute_deceleration = Bool.T( 

1206 default=False, 

1207 help='set acceleration to zero (for testing)') 

1208 

1209 def discretize_t(self, deltat, tref): 

1210 

1211 d_acc = self.duration_acceleration 

1212 d_dec = self.duration_deceleration 

1213 

1214 tmin_stf = tref 

1215 tmax_stf = tref + d_acc + d_dec 

1216 tmin = math.floor(tmin_stf / deltat) * deltat 

1217 tmax = math.ceil(tmax_stf / deltat) * deltat 

1218 times = util.arange2(tmin, tmax, deltat, epsilon=1e-3) 

1219 

1220 mask_acc = num.logical_and( 

1221 tref <= times, 

1222 times < tref + d_acc) 

1223 

1224 mask_dec = num.logical_and( 

1225 tref + d_acc <= times, 

1226 times < tref + d_acc + d_dec) 

1227 

1228 n_acc = num.sum(mask_acc) 

1229 if n_acc < 1: 

1230 raise STFError( 

1231 'SimpleLandslideSTF: `duration_acceleration` must be longer ' 

1232 'than sampling interval.') 

1233 

1234 n_dec = num.sum(mask_dec) 

1235 if n_dec < 1: 

1236 raise STFError( 

1237 'SimpleLandslideSTF: `duration_deceleration` must be longer ' 

1238 'than sampling interval.') 

1239 

1240 amplitudes = num.zeros_like(times) 

1241 

1242 amplitudes[mask_acc] = - num.sin( 

1243 (times[mask_acc] - tref) / d_acc * num.pi) 

1244 

1245 amplitudes[mask_dec] = num.sin( 

1246 (times[mask_dec] - (tref + d_acc)) / d_dec * num.pi) 

1247 

1248 sum_acc = num.abs(num.sum(amplitudes[mask_acc])) 

1249 if sum_acc != 0.0: 

1250 amplitudes[mask_acc] /= sum_acc 

1251 else: 

1252 amplitudes[mask_acc] = 1.0 / n_acc 

1253 

1254 sum_dec = num.abs(num.sum(amplitudes[mask_dec])) 

1255 if sum_dec != 0.0: 

1256 amplitudes[mask_dec] /= sum_dec 

1257 else: 

1258 amplitudes[mask_dec] = 1.0 / n_dec 

1259 

1260 if self.mute_acceleration: 

1261 amplitudes[mask_acc] = 0.0 

1262 

1263 if self.mute_deceleration: 

1264 amplitudes[mask_dec] = 0.0 

1265 

1266 return times, amplitudes 

1267 

1268 

1269class STFMode(StringChoice): 

1270 choices = ['pre', 'post'] 

1271 

1272 

1273class Source(Location, Cloneable): 

1274 ''' 

1275 Base class for all source models. 

1276 ''' 

1277 

1278 name = String.T(optional=True, default='') 

1279 

1280 time = Timestamp.T( 

1281 default=Timestamp.D('1970-01-01 00:00:00'), 

1282 help='source origin time.') 

1283 

1284 stf = STF.T( 

1285 optional=True, 

1286 help='source time function.') 

1287 

1288 stf_mode = STFMode.T( 

1289 default='post', 

1290 help='whether to apply source time function in pre or ' 

1291 'post-processing.') 

1292 

1293 def __init__(self, **kwargs): 

1294 Location.__init__(self, **kwargs) 

1295 

1296 def update(self, **kwargs): 

1297 ''' 

1298 Change some of the source models parameters. 

1299 

1300 Example:: 

1301 

1302 >>> from pyrocko import gf 

1303 >>> s = gf.DCSource() 

1304 >>> s.update(strike=66., dip=33.) 

1305 >>> print(s) 

1306 --- !pf.DCSource 

1307 depth: 0.0 

1308 time: 1970-01-01 00:00:00 

1309 magnitude: 6.0 

1310 strike: 66.0 

1311 dip: 33.0 

1312 rake: 0.0 

1313 

1314 ''' 

1315 

1316 for (k, v) in kwargs.items(): 

1317 self[k] = v 

1318 

1319 def grid(self, **variables): 

1320 ''' 

1321 Create grid of source model variations. 

1322 

1323 :returns: :py:class:`SourceGrid` instance. 

1324 

1325 Example:: 

1326 

1327 >>> from pyrocko import gf 

1328 >>> base = DCSource() 

1329 >>> R = gf.Range 

1330 >>> for s in base.grid(R(' 

1331 

1332 ''' 

1333 return SourceGrid(base=self, variables=variables) 

1334 

1335 def base_key(self): 

1336 ''' 

1337 Get key to decide about source discretization / GF stack sharing. 

1338 

1339 When two source models differ only in amplitude and origin time, the 

1340 discretization and the GF stacking can be done only once for a unit 

1341 amplitude and a zero origin time and the amplitude and origin times of 

1342 the seismograms can be applied during post-processing of the synthetic 

1343 seismogram. 

1344 

1345 For any derived parameterized source model, this method is called to 

1346 decide if discretization and stacking of the source should be shared. 

1347 When two source models return an equal vector of values discretization 

1348 is shared. 

1349 ''' 

1350 return (self.depth, self.lat, self.north_shift, 

1351 self.lon, self.east_shift, self.time, type(self).__name__) + \ 

1352 self.effective_stf_pre().base_key() 

1353 

1354 def get_factor(self): 

1355 ''' 

1356 Get the scaling factor to be applied during post-processing. 

1357 

1358 Discretization of the base seismogram is usually done for a unit 

1359 amplitude, because a common factor can be efficiently multiplied to 

1360 final seismograms. This eliminates to do repeat the stacking when 

1361 creating seismograms for a series of source models only differing in 

1362 amplitude. 

1363 

1364 This method should return the scaling factor to apply in the 

1365 post-processing (often this is simply the scalar moment of the source). 

1366 ''' 

1367 

1368 return 1.0 

1369 

1370 def effective_stf_pre(self): 

1371 ''' 

1372 Return the STF applied before stacking of the Green's functions. 

1373 

1374 This STF is used during discretization of the parameterized source 

1375 models, i.e. to produce a temporal distribution of point sources. 

1376 

1377 Handling of the STF before stacking of the GFs is less efficient but 

1378 allows to use different source time functions for different parts of 

1379 the source. 

1380 ''' 

1381 

1382 if self.stf is not None and self.stf_mode == 'pre': 

1383 return self.stf 

1384 else: 

1385 return g_unit_pulse 

1386 

1387 def effective_stf_post(self): 

1388 ''' 

1389 Return the STF applied after stacking of the Green's fuctions. 

1390 

1391 This STF is used in the post-processing of the synthetic seismograms. 

1392 

1393 Handling of the STF after stacking of the GFs is usually more efficient 

1394 but is only possible when a common STF is used for all subsources. 

1395 ''' 

1396 

1397 if self.stf is not None and self.stf_mode == 'post': 

1398 return self.stf 

1399 else: 

1400 return g_unit_pulse 

1401 

1402 def _dparams_base(self): 

1403 return dict(times=arr(self.time), 

1404 lat=self.lat, lon=self.lon, 

1405 north_shifts=arr(self.north_shift), 

1406 east_shifts=arr(self.east_shift), 

1407 depths=arr(self.depth)) 

1408 

1409 def _hash(self): 

1410 sha = sha1() 

1411 for k in self.base_key(): 

1412 sha.update(str(k).encode()) 

1413 return sha.hexdigest() 

1414 

1415 def _dparams_base_repeated(self, times): 

1416 if times is None: 

1417 return self._dparams_base() 

1418 

1419 nt = times.size 

1420 north_shifts = num.repeat(self.north_shift, nt) 

1421 east_shifts = num.repeat(self.east_shift, nt) 

1422 depths = num.repeat(self.depth, nt) 

1423 return dict(times=times, 

1424 lat=self.lat, lon=self.lon, 

1425 north_shifts=north_shifts, 

1426 east_shifts=east_shifts, 

1427 depths=depths) 

1428 

1429 def pyrocko_event(self, store=None, target=None, **kwargs): 

1430 duration = None 

1431 if self.stf: 

1432 duration = self.stf.effective_duration 

1433 

1434 return model.Event( 

1435 lat=self.lat, 

1436 lon=self.lon, 

1437 north_shift=self.north_shift, 

1438 east_shift=self.east_shift, 

1439 time=self.time, 

1440 name=self.name, 

1441 depth=self.depth, 

1442 duration=duration, 

1443 **kwargs) 

1444 

1445 def geometry(self, **kwargs): 

1446 raise NotImplementedError 

1447 

1448 def outline(self, cs='xyz'): 

1449 points = num.atleast_2d(num.zeros([1, 3])) 

1450 

1451 points[:, 0] += self.north_shift 

1452 points[:, 1] += self.east_shift 

1453 points[:, 2] += self.depth 

1454 if cs == 'xyz': 

1455 return points 

1456 elif cs == 'xy': 

1457 return points[:, :2] 

1458 elif cs in ('latlon', 'lonlat'): 

1459 latlon = ne_to_latlon( 

1460 self.lat, self.lon, points[:, 0], points[:, 1]) 

1461 

1462 latlon = num.array(latlon).T 

1463 if cs == 'latlon': 

1464 return latlon 

1465 else: 

1466 return latlon[:, ::-1] 

1467 

1468 @classmethod 

1469 def from_pyrocko_event(cls, ev, **kwargs): 

1470 if ev.depth is None: 

1471 raise ConversionError( 

1472 'Cannot convert event object to source object: ' 

1473 'no depth information available') 

1474 

1475 stf = None 

1476 if ev.duration is not None: 

1477 stf = HalfSinusoidSTF(effective_duration=ev.duration) 

1478 

1479 d = dict( 

1480 name=ev.name, 

1481 time=ev.time, 

1482 lat=ev.lat, 

1483 lon=ev.lon, 

1484 north_shift=ev.north_shift, 

1485 east_shift=ev.east_shift, 

1486 depth=ev.depth, 

1487 stf=stf) 

1488 d.update(kwargs) 

1489 return cls(**d) 

1490 

1491 def get_magnitude(self): 

1492 raise NotImplementedError( 

1493 '%s does not implement get_magnitude()' 

1494 % self.__class__.__name__) 

1495 

1496 

1497class SourceWithMagnitude(Source): 

1498 ''' 

1499 Base class for sources containing a moment magnitude. 

1500 ''' 

1501 

1502 magnitude = Float.T( 

1503 default=6.0, 

1504 help='Moment magnitude Mw as in [Hanks and Kanamori, 1979]') 

1505 

1506 def __init__(self, **kwargs): 

1507 if 'moment' in kwargs: 

1508 mom = kwargs.pop('moment') 

1509 if 'magnitude' not in kwargs: 

1510 kwargs['magnitude'] = float(pmt.moment_to_magnitude(mom)) 

1511 

1512 Source.__init__(self, **kwargs) 

1513 

1514 @property 

1515 def moment(self): 

1516 return float(pmt.magnitude_to_moment(self.magnitude)) 

1517 

1518 @moment.setter 

1519 def moment(self, value): 

1520 self.magnitude = float(pmt.moment_to_magnitude(value)) 

1521 

1522 def pyrocko_event(self, store=None, target=None, **kwargs): 

1523 return Source.pyrocko_event( 

1524 self, store, target, 

1525 magnitude=self.magnitude, 

1526 **kwargs) 

1527 

1528 @classmethod 

1529 def from_pyrocko_event(cls, ev, **kwargs): 

1530 d = {} 

1531 if ev.magnitude: 

1532 d.update(magnitude=ev.magnitude) 

1533 

1534 d.update(kwargs) 

1535 return super(SourceWithMagnitude, cls).from_pyrocko_event(ev, **d) 

1536 

1537 def get_magnitude(self): 

1538 return self.magnitude 

1539 

1540 

1541class DerivedMagnitudeError(ValidationError): 

1542 ''' 

1543 Raised when conversion between magnitude, moment, volume change or 

1544 displacement failed. 

1545 ''' 

1546 pass 

1547 

1548 

1549class SourceWithDerivedMagnitude(Source): 

1550 

1551 class __T(Source.T): 

1552 

1553 def validate_extra(self, val): 

1554 Source.T.validate_extra(self, val) 

1555 val.check_conflicts() 

1556 

1557 def check_conflicts(self): 

1558 ''' 

1559 Check for parameter conflicts. 

1560 

1561 To be overloaded in subclasses. Raises :py:exc:`DerivedMagnitudeError` 

1562 on conflicts. 

1563 ''' 

1564 pass 

1565 

1566 def get_magnitude(self, store=None, target=None): 

1567 raise DerivedMagnitudeError('No magnitude set.') 

1568 

1569 def get_moment(self, store=None, target=None): 

1570 return float(pmt.magnitude_to_moment( 

1571 self.get_magnitude(store, target))) 

1572 

1573 def pyrocko_moment_tensor(self, store=None, target=None): 

1574 raise NotImplementedError( 

1575 '%s does not implement pyrocko_moment_tensor()' 

1576 % self.__class__.__name__) 

1577 

1578 def pyrocko_event(self, store=None, target=None, **kwargs): 

1579 try: 

1580 mt = self.pyrocko_moment_tensor(store, target) 

1581 magnitude = self.get_magnitude() 

1582 except (DerivedMagnitudeError, NotImplementedError): 

1583 mt = None 

1584 magnitude = None 

1585 

1586 return Source.pyrocko_event( 

1587 self, store, target, 

1588 moment_tensor=mt, 

1589 magnitude=magnitude, 

1590 **kwargs) 

1591 

1592 

1593class ExplosionSource(SourceWithDerivedMagnitude): 

1594 ''' 

1595 An isotropic explosion point source. 

1596 ''' 

1597 

1598 magnitude = Float.T( 

1599 optional=True, 

1600 help='moment magnitude Mw as in [Hanks and Kanamori, 1979]') 

1601 

1602 volume_change = Float.T( 

1603 optional=True, 

1604 help='volume change of the explosion/implosion or ' 

1605 'the contracting/extending magmatic source. [m^3]') 

1606 

1607 discretized_source_class = meta.DiscretizedExplosionSource 

1608 

1609 def __init__(self, **kwargs): 

1610 if 'moment' in kwargs: 

1611 mom = kwargs.pop('moment') 

1612 if 'magnitude' not in kwargs: 

1613 kwargs['magnitude'] = float(pmt.moment_to_magnitude(mom)) 

1614 

1615 SourceWithDerivedMagnitude.__init__(self, **kwargs) 

1616 

1617 def base_key(self): 

1618 return SourceWithDerivedMagnitude.base_key(self) + \ 

1619 (self.volume_change,) 

1620 

1621 def check_conflicts(self): 

1622 if self.magnitude is not None and self.volume_change is not None: 

1623 raise DerivedMagnitudeError( 

1624 'Magnitude and volume_change are both defined.') 

1625 

1626 def get_magnitude(self, store=None, target=None): 

1627 self.check_conflicts() 

1628 

1629 if self.magnitude is not None: 

1630 return self.magnitude 

1631 

1632 elif self.volume_change is not None: 

1633 moment = self.volume_change * \ 

1634 self.get_moment_to_volume_change_ratio(store, target) 

1635 

1636 return float(pmt.moment_to_magnitude(abs(moment))) 

1637 else: 

1638 return float(pmt.moment_to_magnitude(1.0)) 

1639 

1640 def get_volume_change(self, store=None, target=None): 

1641 self.check_conflicts() 

1642 

1643 if self.volume_change is not None: 

1644 return self.volume_change 

1645 

1646 elif self.magnitude is not None: 

1647 moment = float(pmt.magnitude_to_moment(self.magnitude)) 

1648 return moment / self.get_moment_to_volume_change_ratio( 

1649 store, target) 

1650 

1651 else: 

1652 return 1.0 / self.get_moment_to_volume_change_ratio(store) 

1653 

1654 def get_moment_to_volume_change_ratio(self, store, target=None): 

1655 if store is None: 

1656 raise DerivedMagnitudeError( 

1657 'Need earth model to convert between volume change and ' 

1658 'magnitude.') 

1659 

1660 points = num.array( 

1661 [[self.north_shift, self.east_shift, self.depth]], dtype=float) 

1662 

1663 interpolation = target.interpolation if target else 'multilinear' 

1664 try: 

1665 shear_moduli = store.config.get_shear_moduli( 

1666 self.lat, self.lon, 

1667 points=points, 

1668 interpolation=interpolation)[0] 

1669 

1670 bulk_moduli = store.config.get_bulk_moduli( 

1671 self.lat, self.lon, 

1672 points=points, 

1673 interpolation=interpolation)[0] 

1674 except meta.OutOfBounds: 

1675 raise DerivedMagnitudeError( 

1676 'Could not get shear modulus at source position.') 

1677 

1678 return float(2. * shear_moduli + bulk_moduli) 

1679 

1680 def get_factor(self): 

1681 return 1.0 

1682 

1683 def discretize_basesource(self, store, target=None): 

1684 times, amplitudes = self.effective_stf_pre().discretize_t( 

1685 store.config.deltat, self.time) 

1686 

1687 amplitudes *= self.get_moment(store, target) * math.sqrt(2. / 3.) 

1688 

1689 if self.volume_change is not None: 

1690 if self.volume_change < 0.: 

1691 amplitudes *= -1 

1692 

1693 return meta.DiscretizedExplosionSource( 

1694 m0s=amplitudes, 

1695 **self._dparams_base_repeated(times)) 

1696 

1697 def pyrocko_moment_tensor(self, store=None, target=None): 

1698 a = self.get_moment(store, target) * math.sqrt(2. / 3.) 

1699 return pmt.MomentTensor(m=pmt.symmat6(a, a, a, 0., 0., 0.)) 

1700 

1701 

1702class RectangularExplosionSource(ExplosionSource): 

1703 ''' 

1704 Rectangular or line explosion source. 

1705 ''' 

1706 

1707 discretized_source_class = meta.DiscretizedExplosionSource 

1708 

1709 strike = Float.T( 

1710 default=0.0, 

1711 help='strike direction in [deg], measured clockwise from north') 

1712 

1713 dip = Float.T( 

1714 default=90.0, 

1715 help='dip angle in [deg], measured downward from horizontal') 

1716 

1717 length = Float.T( 

1718 default=0., 

1719 help='length of rectangular source area [m]') 

1720 

1721 width = Float.T( 

1722 default=0., 

1723 help='width of rectangular source area [m]') 

1724 

1725 anchor = StringChoice.T( 

1726 choices=['top', 'top_left', 'top_right', 'center', 'bottom', 

1727 'bottom_left', 'bottom_right'], 

1728 default='center', 

1729 optional=True, 

1730 help='Anchor point for positioning the plane, can be: top, center or' 

1731 'bottom and also top_left, top_right,bottom_left,' 

1732 'bottom_right, center_left and center right') 

1733 

1734 nucleation_x = Float.T( 

1735 optional=True, 

1736 help='horizontal position of rupture nucleation in normalized fault ' 

1737 'plane coordinates (-1 = left edge, +1 = right edge)') 

1738 

1739 nucleation_y = Float.T( 

1740 optional=True, 

1741 help='down-dip position of rupture nucleation in normalized fault ' 

1742 'plane coordinates (-1 = upper edge, +1 = lower edge)') 

1743 

1744 velocity = Float.T( 

1745 default=3500., 

1746 help='speed of explosion front [m/s]') 

1747 

1748 aggressive_oversampling = Bool.T( 

1749 default=False, 

1750 help='Aggressive oversampling for basesource discretization. ' 

1751 "When using 'multilinear' interpolation oversampling has" 

1752 ' practically no effect.') 

1753 

1754 def base_key(self): 

1755 return Source.base_key(self) + (self.strike, self.dip, self.length, 

1756 self.width, self.nucleation_x, 

1757 self.nucleation_y, self.velocity, 

1758 self.anchor) 

1759 

1760 def discretize_basesource(self, store, target=None): 

1761 

1762 if self.nucleation_x is not None: 

1763 nucx = self.nucleation_x * 0.5 * self.length 

1764 else: 

1765 nucx = None 

1766 

1767 if self.nucleation_y is not None: 

1768 nucy = self.nucleation_y * 0.5 * self.width 

1769 else: 

1770 nucy = None 

1771 

1772 stf = self.effective_stf_pre() 

1773 

1774 points, times, amplitudes, dl, dw, nl, nw = discretize_rect_source( 

1775 store.config.deltas, store.config.deltat, 

1776 self.time, self.north_shift, self.east_shift, self.depth, 

1777 self.strike, self.dip, self.length, self.width, self.anchor, 

1778 self.velocity, stf=stf, nucleation_x=nucx, nucleation_y=nucy) 

1779 

1780 amplitudes /= num.sum(amplitudes) 

1781 amplitudes *= self.get_moment(store, target) 

1782 

1783 return meta.DiscretizedExplosionSource( 

1784 lat=self.lat, 

1785 lon=self.lon, 

1786 times=times, 

1787 north_shifts=points[:, 0], 

1788 east_shifts=points[:, 1], 

1789 depths=points[:, 2], 

1790 m0s=amplitudes) 

1791 

1792 def outline(self, cs='xyz'): 

1793 points = outline_rect_source(self.strike, self.dip, self.length, 

1794 self.width, self.anchor) 

1795 

1796 points[:, 0] += self.north_shift 

1797 points[:, 1] += self.east_shift 

1798 points[:, 2] += self.depth 

1799 if cs == 'xyz': 

1800 return points 

1801 elif cs == 'xy': 

1802 return points[:, :2] 

1803 elif cs in ('latlon', 'lonlat'): 

1804 latlon = ne_to_latlon( 

1805 self.lat, self.lon, points[:, 0], points[:, 1]) 

1806 

1807 latlon = num.array(latlon).T 

1808 if cs == 'latlon': 

1809 return latlon 

1810 else: 

1811 return latlon[:, ::-1] 

1812 

1813 def get_nucleation_abs_coord(self, cs='xy'): 

1814 

1815 if self.nucleation_x is None: 

1816 return None, None 

1817 

1818 coords = from_plane_coords(self.strike, self.dip, self.length, 

1819 self.width, self.depth, self.nucleation_x, 

1820 self.nucleation_y, lat=self.lat, 

1821 lon=self.lon, north_shift=self.north_shift, 

1822 east_shift=self.east_shift, cs=cs) 

1823 return coords 

1824 

1825 

1826class DCSource(SourceWithMagnitude): 

1827 ''' 

1828 A double-couple point source. 

1829 ''' 

1830 

1831 strike = Float.T( 

1832 default=0.0, 

1833 help='strike direction in [deg], measured clockwise from north') 

1834 

1835 dip = Float.T( 

1836 default=90.0, 

1837 help='dip angle in [deg], measured downward from horizontal') 

1838 

1839 rake = Float.T( 

1840 default=0.0, 

1841 help='rake angle in [deg], ' 

1842 'measured counter-clockwise from right-horizontal ' 

1843 'in on-plane view') 

1844 

1845 discretized_source_class = meta.DiscretizedMTSource 

1846 

1847 def base_key(self): 

1848 return Source.base_key(self) + (self.strike, self.dip, self.rake) 

1849 

1850 def get_factor(self): 

1851 return float(pmt.magnitude_to_moment(self.magnitude)) 

1852 

1853 def discretize_basesource(self, store, target=None): 

1854 mot = pmt.MomentTensor( 

1855 strike=self.strike, dip=self.dip, rake=self.rake) 

1856 

1857 times, amplitudes = self.effective_stf_pre().discretize_t( 

1858 store.config.deltat, self.time) 

1859 return meta.DiscretizedMTSource( 

1860 m6s=mot.m6()[num.newaxis, :] * amplitudes[:, num.newaxis], 

1861 **self._dparams_base_repeated(times)) 

1862 

1863 def pyrocko_moment_tensor(self, store=None, target=None): 

1864 return pmt.MomentTensor( 

1865 strike=self.strike, 

1866 dip=self.dip, 

1867 rake=self.rake, 

1868 scalar_moment=self.moment) 

1869 

1870 def pyrocko_event(self, store=None, target=None, **kwargs): 

1871 return SourceWithMagnitude.pyrocko_event( 

1872 self, store, target, 

1873 moment_tensor=self.pyrocko_moment_tensor(store, target), 

1874 **kwargs) 

1875 

1876 @classmethod 

1877 def from_pyrocko_event(cls, ev, **kwargs): 

1878 d = {} 

1879 mt = ev.moment_tensor 

1880 if mt: 

1881 (strike, dip, rake), _ = mt.both_strike_dip_rake() 

1882 d.update( 

1883 strike=float(strike), 

1884 dip=float(dip), 

1885 rake=float(rake), 

1886 magnitude=float(mt.moment_magnitude())) 

1887 

1888 d.update(kwargs) 

1889 return super(DCSource, cls).from_pyrocko_event(ev, **d) 

1890 

1891 

1892class CLVDSource(SourceWithMagnitude): 

1893 ''' 

1894 A pure CLVD point source. 

1895 ''' 

1896 

1897 discretized_source_class = meta.DiscretizedMTSource 

1898 

1899 azimuth = Float.T( 

1900 default=0.0, 

1901 help='azimuth direction of largest dipole, clockwise from north [deg]') 

1902 

1903 dip = Float.T( 

1904 default=90., 

1905 help='dip direction of largest dipole, downward from horizontal [deg]') 

1906 

1907 def base_key(self): 

1908 return Source.base_key(self) + (self.azimuth, self.dip) 

1909 

1910 def get_factor(self): 

1911 return float(pmt.magnitude_to_moment(self.magnitude)) 

1912 

1913 @property 

1914 def m6(self): 

1915 a = math.sqrt(4. / 3.) * self.get_factor() 

1916 m = pmt.symmat6(-0.5 * a, -0.5 * a, a, 0., 0., 0.) 

1917 rotmat1 = pmt.euler_to_matrix( 

1918 d2r * (self.dip - 90.), 

1919 d2r * (self.azimuth - 90.), 

1920 0.) 

1921 m = num.dot(rotmat1.T, num.dot(m, rotmat1)) 

1922 return pmt.to6(m) 

1923 

1924 @property 

1925 def m6_astuple(self): 

1926 return tuple(self.m6.tolist()) 

1927 

1928 def discretize_basesource(self, store, target=None): 

1929 factor = self.get_factor() 

1930 times, amplitudes = self.effective_stf_pre().discretize_t( 

1931 store.config.deltat, self.time) 

1932 return meta.DiscretizedMTSource( 

1933 m6s=self.m6[num.newaxis, :] * amplitudes[:, num.newaxis] / factor, 

1934 **self._dparams_base_repeated(times)) 

1935 

1936 def pyrocko_moment_tensor(self, store=None, target=None): 

1937 return pmt.MomentTensor(m=pmt.symmat6(*self.m6_astuple)) 

1938 

1939 def pyrocko_event(self, store=None, target=None, **kwargs): 

1940 mt = self.pyrocko_moment_tensor(store, target) 

1941 return Source.pyrocko_event( 

1942 self, store, target, 

1943 moment_tensor=self.pyrocko_moment_tensor(store, target), 

1944 magnitude=float(mt.moment_magnitude()), 

1945 **kwargs) 

1946 

1947 

1948class VLVDSource(SourceWithDerivedMagnitude): 

1949 ''' 

1950 Volumetric linear vector dipole source. 

1951 

1952 This source is a parameterization for a restricted moment tensor point 

1953 source, useful to represent dyke or sill like inflation or deflation 

1954 sources. The restriction is such that the moment tensor is rotational 

1955 symmetric. It can be represented by a superposition of a linear vector 

1956 dipole (here we use a CLVD for convenience) and an isotropic component. The 

1957 restricted moment tensor has 4 degrees of freedom: 2 independent 

1958 eigenvalues and 2 rotation angles orienting the the symmetry axis. 

1959 

1960 In this parameterization, the isotropic component is controlled by 

1961 ``volume_change``. To define the moment tensor, it must be converted to the 

1962 scalar moment of the the MT's isotropic component. For the conversion, the 

1963 shear modulus at the source's position must be known. This value is 

1964 extracted from the earth model defined in the GF store in use. 

1965 

1966 The CLVD part by controlled by its scalar moment :math:`M_0`: 

1967 ``clvd_moment``. The sign of ``clvd_moment`` is used to switch between a 

1968 positiv or negativ CLVD (the sign of the largest eigenvalue). 

1969 ''' 

1970 

1971 discretized_source_class = meta.DiscretizedMTSource 

1972 

1973 azimuth = Float.T( 

1974 default=0.0, 

1975 help='azimuth direction of symmetry axis, clockwise from north [deg].') 

1976 

1977 dip = Float.T( 

1978 default=90., 

1979 help='dip direction of symmetry axis, downward from horizontal [deg].') 

1980 

1981 volume_change = Float.T( 

1982 default=0., 

1983 help='volume change of the inflation/deflation [m^3].') 

1984 

1985 clvd_moment = Float.T( 

1986 default=0., 

1987 help='scalar moment :math:`M_0` of the CLVD component [Nm]. The sign ' 

1988 'controls the sign of the CLVD (the sign of its largest ' 

1989 'eigenvalue).') 

1990 

1991 def get_moment_to_volume_change_ratio(self, store, target): 

1992 if store is None or target is None: 

1993 raise DerivedMagnitudeError( 

1994 'Need earth model to convert between volume change and ' 

1995 'magnitude.') 

1996 

1997 points = num.array( 

1998 [[self.north_shift, self.east_shift, self.depth]], dtype=float) 

1999 

2000 try: 

2001 shear_moduli = store.config.get_shear_moduli( 

2002 self.lat, self.lon, 

2003 points=points, 

2004 interpolation=target.interpolation)[0] 

2005 

2006 bulk_moduli = store.config.get_bulk_moduli( 

2007 self.lat, self.lon, 

2008 points=points, 

2009 interpolation=target.interpolation)[0] 

2010 except meta.OutOfBounds: 

2011 raise DerivedMagnitudeError( 

2012 'Could not get shear modulus at source position.') 

2013 

2014 return float(2. * shear_moduli + bulk_moduli) 

2015 

2016 def base_key(self): 

2017 return Source.base_key(self) + \ 

2018 (self.azimuth, self.dip, self.volume_change, self.clvd_moment) 

2019 

2020 def get_magnitude(self, store=None, target=None): 

2021 mt = self.pyrocko_moment_tensor(store, target) 

2022 return float(pmt.moment_to_magnitude(mt.moment)) 

2023 

2024 def get_m6(self, store, target): 

2025 a = math.sqrt(4. / 3.) * self.clvd_moment 

2026 m_clvd = pmt.symmat6(-0.5 * a, -0.5 * a, a, 0., 0., 0.) 

2027 

2028 rotmat1 = pmt.euler_to_matrix( 

2029 d2r * (self.dip - 90.), 

2030 d2r * (self.azimuth - 90.), 

2031 0.) 

2032 m_clvd = num.dot(rotmat1.T, num.dot(m_clvd, rotmat1)) 

2033 

2034 m_iso = self.volume_change * \ 

2035 self.get_moment_to_volume_change_ratio(store, target) 

2036 

2037 m_iso = pmt.symmat6(m_iso, m_iso, m_iso, 0., 

2038 0., 0.,) * math.sqrt(2. / 3) 

2039 

2040 m = pmt.to6(m_clvd) + pmt.to6(m_iso) 

2041 return m 

2042 

2043 def get_moment(self, store=None, target=None): 

2044 return float(pmt.magnitude_to_moment( 

2045 self.get_magnitude(store, target))) 

2046 

2047 def get_m6_astuple(self, store, target): 

2048 m6 = self.get_m6(store, target) 

2049 return tuple(m6.tolist()) 

2050 

2051 def discretize_basesource(self, store, target=None): 

2052 times, amplitudes = self.effective_stf_pre().discretize_t( 

2053 store.config.deltat, self.time) 

2054 

2055 m6 = self.get_m6(store, target) 

2056 m6 *= amplitudes / self.get_factor() 

2057 

2058 return meta.DiscretizedMTSource( 

2059 m6s=m6[num.newaxis, :], 

2060 **self._dparams_base_repeated(times)) 

2061 

2062 def pyrocko_moment_tensor(self, store=None, target=None): 

2063 m6_astuple = self.get_m6_astuple(store, target) 

2064 return pmt.MomentTensor(m=pmt.symmat6(*m6_astuple)) 

2065 

2066 

2067class MTSource(Source): 

2068 ''' 

2069 A moment tensor point source. 

2070 ''' 

2071 

2072 discretized_source_class = meta.DiscretizedMTSource 

2073 

2074 mnn = Float.T( 

2075 default=1., 

2076 help='north-north component of moment tensor in [Nm]') 

2077 

2078 mee = Float.T( 

2079 default=1., 

2080 help='east-east component of moment tensor in [Nm]') 

2081 

2082 mdd = Float.T( 

2083 default=1., 

2084 help='down-down component of moment tensor in [Nm]') 

2085 

2086 mne = Float.T( 

2087 default=0., 

2088 help='north-east component of moment tensor in [Nm]') 

2089 

2090 mnd = Float.T( 

2091 default=0., 

2092 help='north-down component of moment tensor in [Nm]') 

2093 

2094 med = Float.T( 

2095 default=0., 

2096 help='east-down component of moment tensor in [Nm]') 

2097 

2098 def __init__(self, **kwargs): 

2099 if 'm6' in kwargs: 

2100 for (k, v) in zip('mnn mee mdd mne mnd med'.split(), 

2101 kwargs.pop('m6')): 

2102 kwargs[k] = float(v) 

2103 

2104 Source.__init__(self, **kwargs) 

2105 

2106 @property 

2107 def m6(self): 

2108 return num.array(self.m6_astuple) 

2109 

2110 @property 

2111 def m6_astuple(self): 

2112 return (self.mnn, self.mee, self.mdd, self.mne, self.mnd, self.med) 

2113 

2114 @m6.setter 

2115 def m6(self, value): 

2116 self.mnn, self.mee, self.mdd, self.mne, self.mnd, self.med = value 

2117 

2118 def base_key(self): 

2119 return Source.base_key(self) + self.m6_astuple 

2120 

2121 def discretize_basesource(self, store, target=None): 

2122 times, amplitudes = self.effective_stf_pre().discretize_t( 

2123 store.config.deltat, self.time) 

2124 return meta.DiscretizedMTSource( 

2125 m6s=self.m6[num.newaxis, :] * amplitudes[:, num.newaxis], 

2126 **self._dparams_base_repeated(times)) 

2127 

2128 def get_magnitude(self, store=None, target=None): 

2129 m6 = self.m6 

2130 return pmt.moment_to_magnitude( 

2131 math.sqrt(num.sum(m6[0:3]**2) + 2.0 * num.sum(m6[3:6]**2)) / 

2132 math.sqrt(2.)) 

2133 

2134 def pyrocko_moment_tensor(self, store=None, target=None): 

2135 return pmt.MomentTensor(m=pmt.symmat6(*self.m6_astuple)) 

2136 

2137 def pyrocko_event(self, store=None, target=None, **kwargs): 

2138 mt = self.pyrocko_moment_tensor(store, target) 

2139 return Source.pyrocko_event( 

2140 self, store, target, 

2141 moment_tensor=self.pyrocko_moment_tensor(store, target), 

2142 magnitude=float(mt.moment_magnitude()), 

2143 **kwargs) 

2144 

2145 @classmethod 

2146 def from_pyrocko_event(cls, ev, **kwargs): 

2147 d = {} 

2148 mt = ev.moment_tensor 

2149 if mt: 

2150 d.update(m6=tuple(map(float, mt.m6()))) 

2151 else: 

2152 if ev.magnitude is not None: 

2153 mom = pmt.magnitude_to_moment(ev.magnitude) 

2154 v = math.sqrt(2. / 3.) * mom 

2155 d.update(m6=(v, v, v, 0., 0., 0.)) 

2156 

2157 d.update(kwargs) 

2158 return super(MTSource, cls).from_pyrocko_event(ev, **d) 

2159 

2160 

2161map_anchor = { 

2162 'center': (0.0, 0.0), 

2163 'center_left': (-1.0, 0.0), 

2164 'center_right': (1.0, 0.0), 

2165 'top': (0.0, -1.0), 

2166 'top_left': (-1.0, -1.0), 

2167 'top_right': (1.0, -1.0), 

2168 'bottom': (0.0, 1.0), 

2169 'bottom_left': (-1.0, 1.0), 

2170 'bottom_right': (1.0, 1.0)} 

2171 

2172 

2173class RectangularSource(SourceWithDerivedMagnitude): 

2174 ''' 

2175 Classical Haskell source model modified for bilateral rupture. 

2176 ''' 

2177 

2178 discretized_source_class = meta.DiscretizedMTSource 

2179 

2180 magnitude = Float.T( 

2181 optional=True, 

2182 help='moment magnitude Mw as in [Hanks and Kanamori, 1979]') 

2183 

2184 strike = Float.T( 

2185 default=0.0, 

2186 help='strike direction in [deg], measured clockwise from north') 

2187 

2188 dip = Float.T( 

2189 default=90.0, 

2190 help='dip angle in [deg], measured downward from horizontal') 

2191 

2192 rake = Float.T( 

2193 default=0.0, 

2194 help='rake angle in [deg], ' 

2195 'measured counter-clockwise from right-horizontal ' 

2196 'in on-plane view') 

2197 

2198 length = Float.T( 

2199 default=0., 

2200 help='length of rectangular source area [m]') 

2201 

2202 width = Float.T( 

2203 default=0., 

2204 help='width of rectangular source area [m]') 

2205 

2206 anchor = StringChoice.T( 

2207 choices=['top', 'top_left', 'top_right', 'center', 'bottom', 

2208 'bottom_left', 'bottom_right'], 

2209 default='center', 

2210 optional=True, 

2211 help='Anchor point for positioning the plane, can be: ``top, center ' 

2212 'bottom, top_left, top_right,bottom_left,' 

2213 'bottom_right, center_left, center right``.') 

2214 

2215 nucleation_x = Float.T( 

2216 optional=True, 

2217 help='horizontal position of rupture nucleation in normalized fault ' 

2218 'plane coordinates (``-1.`` = left edge, ``+1.`` = right edge)') 

2219 

2220 nucleation_y = Float.T( 

2221 optional=True, 

2222 help='down-dip position of rupture nucleation in normalized fault ' 

2223 'plane coordinates (``-1.`` = upper edge, ``+1.`` = lower edge)') 

2224 

2225 velocity = Float.T( 

2226 default=3500., 

2227 help='speed of rupture front [m/s]') 

2228 

2229 slip = Float.T( 

2230 optional=True, 

2231 help='Slip on the rectangular source area [m]') 

2232 

2233 opening_fraction = Float.T( 

2234 default=0., 

2235 help='Determines fraction of slip related to opening. ' 

2236 '(``-1``: pure tensile closing, ' 

2237 '``0``: pure shear, ' 

2238 '``1``: pure tensile opening)') 

2239 

2240 decimation_factor = Int.T( 

2241 optional=True, 

2242 default=1, 

2243 help='Sub-source decimation factor, a larger decimation will' 

2244 ' make the result inaccurate but shorten the necessary' 

2245 ' computation time (use for testing puposes only).') 

2246 

2247 aggressive_oversampling = Bool.T( 

2248 default=False, 

2249 help='Aggressive oversampling for basesource discretization. ' 

2250 "When using 'multilinear' interpolation oversampling has" 

2251 ' practically no effect.') 

2252 

2253 def __init__(self, **kwargs): 

2254 if 'moment' in kwargs: 

2255 mom = kwargs.pop('moment') 

2256 if 'magnitude' not in kwargs: 

2257 kwargs['magnitude'] = float(pmt.moment_to_magnitude(mom)) 

2258 

2259 SourceWithDerivedMagnitude.__init__(self, **kwargs) 

2260 

2261 def base_key(self): 

2262 return SourceWithDerivedMagnitude.base_key(self) + ( 

2263 self.magnitude, 

2264 self.slip, 

2265 self.strike, 

2266 self.dip, 

2267 self.rake, 

2268 self.length, 

2269 self.width, 

2270 self.nucleation_x, 

2271 self.nucleation_y, 

2272 self.velocity, 

2273 self.decimation_factor, 

2274 self.anchor) 

2275 

2276 def check_conflicts(self): 

2277 if self.magnitude is not None and self.slip is not None: 

2278 raise DerivedMagnitudeError( 

2279 'Magnitude and slip are both defined.') 

2280 

2281 def get_magnitude(self, store=None, target=None): 

2282 self.check_conflicts() 

2283 if self.magnitude is not None: 

2284 return self.magnitude 

2285 

2286 elif self.slip is not None: 

2287 if None in (store, target): 

2288 raise DerivedMagnitudeError( 

2289 'Magnitude for a rectangular source with slip defined ' 

2290 'can only be derived when earth model and target ' 

2291 'interpolation method are available.') 

2292 

2293 amplitudes = self._discretize(store, target)[2] 

2294 if amplitudes.ndim == 2: 

2295 # CLVD component has no net moment, leave out 

2296 return float(pmt.moment_to_magnitude( 

2297 num.sum(num.abs(amplitudes[0:2, :]).sum()))) 

2298 else: 

2299 return float(pmt.moment_to_magnitude(num.sum(amplitudes))) 

2300 

2301 else: 

2302 return float(pmt.moment_to_magnitude(1.0)) 

2303 

2304 def get_factor(self): 

2305 return 1.0 

2306 

2307 def get_slip_tensile(self): 

2308 return self.slip * self.opening_fraction 

2309 

2310 def get_slip_shear(self): 

2311 return self.slip - abs(self.get_slip_tensile) 

2312 

2313 def _discretize(self, store, target): 

2314 if self.nucleation_x is not None: 

2315 nucx = self.nucleation_x * 0.5 * self.length 

2316 else: 

2317 nucx = None 

2318 

2319 if self.nucleation_y is not None: 

2320 nucy = self.nucleation_y * 0.5 * self.width 

2321 else: 

2322 nucy = None 

2323 

2324 stf = self.effective_stf_pre() 

2325 

2326 points, times, amplitudes, dl, dw, nl, nw = discretize_rect_source( 

2327 store.config.deltas, store.config.deltat, 

2328 self.time, self.north_shift, self.east_shift, self.depth, 

2329 self.strike, self.dip, self.length, self.width, self.anchor, 

2330 self.velocity, stf=stf, nucleation_x=nucx, nucleation_y=nucy, 

2331 decimation_factor=self.decimation_factor, 

2332 aggressive_oversampling=self.aggressive_oversampling) 

2333 

2334 if self.slip is not None: 

2335 if target is not None: 

2336 interpolation = target.interpolation 

2337 else: 

2338 interpolation = 'nearest_neighbor' 

2339 logger.warning( 

2340 'no target information available, will use ' 

2341 '"nearest_neighbor" interpolation when extracting shear ' 

2342 'modulus from earth model') 

2343 

2344 shear_moduli = store.config.get_shear_moduli( 

2345 self.lat, self.lon, 

2346 points=points, 

2347 interpolation=interpolation) 

2348 

2349 tensile_slip = self.get_slip_tensile() 

2350 shear_slip = self.slip - abs(tensile_slip) 

2351 

2352 amplitudes_total = [shear_moduli * shear_slip] 

2353 if tensile_slip != 0: 

2354 bulk_moduli = store.config.get_bulk_moduli( 

2355 self.lat, self.lon, 

2356 points=points, 

2357 interpolation=interpolation) 

2358 

2359 tensile_iso = bulk_moduli * tensile_slip 

2360 tensile_clvd = (2. / 3.) * shear_moduli * tensile_slip 

2361 

2362 amplitudes_total.extend([tensile_iso, tensile_clvd]) 

2363 

2364 amplitudes_total = num.vstack(amplitudes_total).squeeze() * \ 

2365 amplitudes * dl * dw 

2366 

2367 else: 

2368 # normalization to retain total moment 

2369 amplitudes_norm = amplitudes / num.sum(amplitudes) 

2370 moment = self.get_moment(store, target) 

2371 

2372 amplitudes_total = [ 

2373 amplitudes_norm * moment * (1 - abs(self.opening_fraction))] 

2374 if self.opening_fraction != 0.: 

2375 amplitudes_total.append( 

2376 amplitudes_norm * self.opening_fraction * moment) 

2377 

2378 amplitudes_total = num.vstack(amplitudes_total).squeeze() 

2379 

2380 return points, times, num.atleast_1d(amplitudes_total), dl, dw, nl, nw 

2381 

2382 def discretize_basesource(self, store, target=None): 

2383 

2384 points, times, amplitudes, dl, dw, nl, nw = self._discretize( 

2385 store, target) 

2386 

2387 mot = pmt.MomentTensor( 

2388 strike=self.strike, dip=self.dip, rake=self.rake) 

2389 m6s = num.repeat(mot.m6()[num.newaxis, :], times.size, axis=0) 

2390 

2391 if amplitudes.ndim == 1: 

2392 m6s[:, :] *= amplitudes[:, num.newaxis] 

2393 elif amplitudes.ndim == 2: 

2394 # shear MT components 

2395 rotmat1 = pmt.euler_to_matrix( 

2396 d2r * self.dip, d2r * self.strike, d2r * -self.rake) 

2397 m6s[:, :] *= amplitudes[0, :][:, num.newaxis] 

2398 

2399 if amplitudes.shape[0] == 2: 

2400 # tensile MT components - moment/magnitude input 

2401 tensile = pmt.symmat6(1., 1., 3., 0., 0., 0.) 

2402 rot_tensile = pmt.to6( 

2403 num.dot(rotmat1.T, num.dot(tensile, rotmat1))) 

2404 

2405 m6s_tensile = rot_tensile[ 

2406 num.newaxis, :] * amplitudes[1, :][:, num.newaxis] 

2407 m6s += m6s_tensile 

2408 

2409 elif amplitudes.shape[0] == 3: 

2410 # tensile MT components - slip input 

2411 iso = pmt.symmat6(1., 1., 1., 0., 0., 0.) 

2412 clvd = pmt.symmat6(-1., -1., 2., 0., 0., 0.) 

2413 

2414 rot_iso = pmt.to6( 

2415 num.dot(rotmat1.T, num.dot(iso, rotmat1))) 

2416 rot_clvd = pmt.to6( 

2417 num.dot(rotmat1.T, num.dot(clvd, rotmat1))) 

2418 

2419 m6s_iso = rot_iso[ 

2420 num.newaxis, :] * amplitudes[1, :][:, num.newaxis] 

2421 m6s_clvd = rot_clvd[ 

2422 num.newaxis, :] * amplitudes[2, :][:, num.newaxis] 

2423 m6s += m6s_iso + m6s_clvd 

2424 else: 

2425 raise ValueError('Unknwown amplitudes shape!') 

2426 else: 

2427 raise ValueError( 

2428 'Unexpected dimension of {}'.format(amplitudes.ndim)) 

2429 

2430 ds = meta.DiscretizedMTSource( 

2431 lat=self.lat, 

2432 lon=self.lon, 

2433 times=times, 

2434 north_shifts=points[:, 0], 

2435 east_shifts=points[:, 1], 

2436 depths=points[:, 2], 

2437 m6s=m6s, 

2438 dl=dl, 

2439 dw=dw, 

2440 nl=nl, 

2441 nw=nw) 

2442 

2443 return ds 

2444 

2445 def xy_to_coord(self, x, y, cs='xyz'): 

2446 ln, wd = self.length, self.width 

2447 strike, dip = self.strike, self.dip 

2448 

2449 def array_check(variable): 

2450 if not isinstance(variable, num.ndarray): 

2451 return num.array(variable) 

2452 else: 

2453 return variable 

2454 

2455 x, y = array_check(x), array_check(y) 

2456 

2457 if x.shape[0] != y.shape[0]: 

2458 raise ValueError('Shapes of x and y mismatch') 

2459 

2460 x, y = x * 0.5 * ln, y * 0.5 * wd 

2461 

2462 points = num.hstack(( 

2463 x.reshape(-1, 1), y.reshape(-1, 1), num.zeros((x.shape[0], 1)))) 

2464 

2465 anch_x, anch_y = map_anchor[self.anchor] 

2466 points[:, 0] -= anch_x * 0.5 * ln 

2467 points[:, 1] -= anch_y * 0.5 * wd 

2468 

2469 rotmat = num.asarray( 

2470 pmt.euler_to_matrix(dip * d2r, strike * d2r, 0.0)) 

2471 

2472 points_rot = num.dot(rotmat.T, points.T).T 

2473 

2474 points_rot[:, 0] += self.north_shift 

2475 points_rot[:, 1] += self.east_shift 

2476 points_rot[:, 2] += self.depth 

2477 

2478 if cs == 'xyz': 

2479 return points_rot 

2480 elif cs == 'xy': 

2481 return points_rot[:, :2] 

2482 elif cs in ('latlon', 'lonlat', 'latlondepth'): 

2483 latlon = ne_to_latlon( 

2484 self.lat, self.lon, points_rot[:, 0], points_rot[:, 1]) 

2485 latlon = num.array(latlon).T 

2486 if cs == 'latlon': 

2487 return latlon 

2488 elif cs == 'lonlat': 

2489 return latlon[:, ::-1] 

2490 else: 

2491 return num.concatenate( 

2492 (latlon, points_rot[:, 2].reshape((len(points_rot), 1))), 

2493 axis=1) 

2494 

2495 def outline(self, cs='xyz'): 

2496 x = num.array([-1., 1., 1., -1., -1.]) 

2497 y = num.array([-1., -1., 1., 1., -1.]) 

2498 

2499 return self.xy_to_coord(x, y, cs=cs) 

2500 

2501 def points_on_source(self, cs='xyz', **kwargs): 

2502 

2503 points = points_on_rect_source( 

2504 self.strike, self.dip, self.length, self.width, 

2505 self.anchor, **kwargs) 

2506 

2507 points[:, 0] += self.north_shift 

2508 points[:, 1] += self.east_shift 

2509 points[:, 2] += self.depth 

2510 if cs == 'xyz': 

2511 return points 

2512 elif cs == 'xy': 

2513 return points[:, :2] 

2514 elif cs in ('latlon', 'lonlat', 'latlondepth'): 

2515 latlon = ne_to_latlon( 

2516 self.lat, self.lon, points[:, 0], points[:, 1]) 

2517 

2518 latlon = num.array(latlon).T 

2519 if cs == 'latlon': 

2520 return latlon 

2521 elif cs == 'lonlat': 

2522 return latlon[:, ::-1] 

2523 else: 

2524 return num.concatenate( 

2525 (latlon, points[:, 2].reshape((len(points), 1))), 

2526 axis=1) 

2527 

2528 def geometry(self, *args, **kwargs): 

2529 from pyrocko.model import Geometry 

2530 

2531 ds = self.discretize_basesource(*args, **kwargs) 

2532 nx, ny = ds.nl, ds.nw 

2533 

2534 def patch_outlines_xy(nx, ny): 

2535 points = num.zeros((nx * ny, 2)) 

2536 points[:, 0] = num.tile(num.linspace(-1., 1., nx), ny) 

2537 points[:, 1] = num.repeat(num.linspace(-1., 1., ny), nx) 

2538 

2539 return points 

2540 

2541 points_ds = patch_outlines_xy(nx + 1, ny + 1) 

2542 npoints = (nx + 1) * (ny + 1) 

2543 

2544 vertices = num.hstack(( 

2545 num.ones((npoints, 1)) * self.lat, 

2546 num.ones((npoints, 1)) * self.lon, 

2547 self.xy_to_coord(points_ds[:, 0], points_ds[:, 1], cs='xyz'))) 

2548 

2549 faces = num.array([[ 

2550 iy * (nx + 1) + ix, 

2551 iy * (nx + 1) + ix + 1, 

2552 (iy + 1) * (nx + 1) + ix + 1, 

2553 (iy + 1) * (nx + 1) + ix, 

2554 iy * (nx + 1) + ix] 

2555 for iy in range(ny) for ix in range(nx)]) 

2556 

2557 xyz = self.outline('xyz') 

2558 latlon = num.ones((5, 2)) * num.array([self.lat, self.lon]) 

2559 patchverts = num.hstack((latlon, xyz)) 

2560 

2561 geom = Geometry() 

2562 geom.setup(vertices, faces) 

2563 geom.set_outlines([patchverts]) 

2564 

2565 if self.stf: 

2566 geom.times = num.unique(ds.times) 

2567 

2568 if self.nucleation_x is not None and self.nucleation_y is not None: 

2569 geom.add_property('t_arrival', ds.times) 

2570 

2571 geom.add_property( 

2572 'moment', ds.moments().reshape(ds.nl*ds.nw, -1)) 

2573 

2574 geom.add_property( 

2575 'slip', num.ones_like(ds.times) * self.slip) 

2576 

2577 return geom 

2578 

2579 def get_nucleation_abs_coord(self, cs='xy'): 

2580 

2581 if self.nucleation_x is None: 

2582 return None, None 

2583 

2584 coords = from_plane_coords(self.strike, self.dip, self.length, 

2585 self.width, self.depth, self.nucleation_x, 

2586 self.nucleation_y, lat=self.lat, 

2587 lon=self.lon, north_shift=self.north_shift, 

2588 east_shift=self.east_shift, cs=cs) 

2589 return coords 

2590 

2591 def pyrocko_moment_tensor(self, store=None, target=None): 

2592 return pmt.MomentTensor( 

2593 strike=self.strike, 

2594 dip=self.dip, 

2595 rake=self.rake, 

2596 scalar_moment=self.get_moment(store, target)) 

2597 

2598 def pyrocko_event(self, store=None, target=None, **kwargs): 

2599 return SourceWithDerivedMagnitude.pyrocko_event( 

2600 self, store, target, 

2601 **kwargs) 

2602 

2603 @classmethod 

2604 def from_pyrocko_event(cls, ev, **kwargs): 

2605 d = {} 

2606 mt = ev.moment_tensor 

2607 if mt: 

2608 (strike, dip, rake), _ = mt.both_strike_dip_rake() 

2609 d.update( 

2610 strike=float(strike), 

2611 dip=float(dip), 

2612 rake=float(rake), 

2613 magnitude=float(mt.moment_magnitude())) 

2614 

2615 d.update(kwargs) 

2616 return super(RectangularSource, cls).from_pyrocko_event(ev, **d) 

2617 

2618 

2619class PseudoDynamicRupture(SourceWithDerivedMagnitude): 

2620 ''' 

2621 Combined Eikonal and Okada quasi-dynamic rupture model. 

2622 

2623 Details are described in :doc:`/topics/pseudo-dynamic-rupture`. 

2624 Note: attribute `stf` is not used so far, but kept for future applications. 

2625 ''' 

2626 

2627 discretized_source_class = meta.DiscretizedMTSource 

2628 

2629 strike = Float.T( 

2630 default=0.0, 

2631 help='Strike direction in [deg], measured clockwise from north.') 

2632 

2633 dip = Float.T( 

2634 default=0.0, 

2635 help='Dip angle in [deg], measured downward from horizontal.') 

2636 

2637 length = Float.T( 

2638 default=10. * km, 

2639 help='Length of rectangular source area in [m].') 

2640 

2641 width = Float.T( 

2642 default=5. * km, 

2643 help='Width of rectangular source area in [m].') 

2644 

2645 anchor = StringChoice.T( 

2646 choices=['top', 'top_left', 'top_right', 'center', 'bottom', 

2647 'bottom_left', 'bottom_right'], 

2648 default='center', 

2649 optional=True, 

2650 help='Anchor point for positioning the plane, can be: ``top, center, ' 

2651 'bottom, top_left, top_right, bottom_left, ' 

2652 'bottom_right, center_left, center_right``.') 

2653 

2654 nucleation_x__ = Array.T( 

2655 default=num.array([0.]), 

2656 dtype=num.float64, 

2657 serialize_as='list', 

2658 help='Horizontal position of rupture nucleation in normalized fault ' 

2659 'plane coordinates (``-1.`` = left edge, ``+1.`` = right edge).') 

2660 

2661 nucleation_y__ = Array.T( 

2662 default=num.array([0.]), 

2663 dtype=num.float64, 

2664 serialize_as='list', 

2665 help='Down-dip position of rupture nucleation in normalized fault ' 

2666 'plane coordinates (``-1.`` = upper edge, ``+1.`` = lower edge).') 

2667 

2668 nucleation_time__ = Array.T( 

2669 optional=True, 

2670 help='Time in [s] after origin, when nucleation points defined by ' 

2671 '``nucleation_x`` and ``nucleation_y`` rupture.', 

2672 dtype=num.float64, 

2673 serialize_as='list') 

2674 

2675 gamma = Float.T( 

2676 default=0.8, 

2677 help='Scaling factor between rupture velocity and S-wave velocity: ' 

2678 r':math:`v_r = \gamma * v_s`.') 

2679 

2680 nx = Int.T( 

2681 default=2, 

2682 help='Number of discrete source patches in x direction (along ' 

2683 'strike).') 

2684 

2685 ny = Int.T( 

2686 default=2, 

2687 help='Number of discrete source patches in y direction (down dip).') 

2688 

2689 slip = Float.T( 

2690 optional=True, 

2691 help='Maximum slip of the rectangular source [m]. ' 

2692 'Setting the slip the tractions/stress field ' 

2693 'will be normalized to accomodate the desired maximum slip.') 

2694 

2695 rake = Float.T( 

2696 optional=True, 

2697 help='Rake angle in [deg], ' 

2698 'measured counter-clockwise from right-horizontal ' 

2699 'in on-plane view. Rake is translated into homogenous tractions ' 

2700 'in strike and up-dip direction. ``rake`` is mutually exclusive ' 

2701 'with tractions parameter.') 

2702 

2703 patches = List.T( 

2704 OkadaSource.T(), 

2705 optional=True, 

2706 help='List of all boundary elements/sub faults/fault patches.') 

2707 

2708 patch_mask__ = Array.T( 

2709 dtype=bool, 

2710 serialize_as='list', 

2711 shape=(None,), 

2712 optional=True, 

2713 help='Mask for all boundary elements/sub faults/fault patches. True ' 

2714 'leaves the patch in the calculation, False excludes the patch.') 

2715 

2716 tractions = TractionField.T( 

2717 optional=True, 

2718 help='Traction field the rupture plane is exposed to. See the ' 

2719 ':py:mod:`pyrocko.gf.tractions` module for more details. ' 

2720 'If ``tractions=None`` and ``rake`` is given' 

2721 ' :py:class:`~pyrocko.gf.tractions.DirectedTractions` will' 

2722 ' be used.') 

2723 

2724 coef_mat = Array.T( 

2725 optional=True, 

2726 help='Coefficient matrix linking traction and dislocation field.', 

2727 dtype=num.float64, 

2728 shape=(None, None)) 

2729 

2730 eikonal_decimation = Int.T( 

2731 optional=True, 

2732 default=1, 

2733 help='Sub-source eikonal factor, a smaller eikonal factor will' 

2734 ' increase the accuracy of rupture front calculation but' 

2735 ' increases also the computation time.') 

2736 

2737 decimation_factor = Int.T( 

2738 optional=True, 

2739 default=1, 

2740 help='Sub-source decimation factor, a larger decimation will' 

2741 ' make the result inaccurate but shorten the necessary' 

2742 ' computation time (use for testing puposes only).') 

2743 

2744 nthreads = Int.T( 

2745 optional=True, 

2746 default=1, 

2747 help='Number of threads for Okada forward modelling, ' 

2748 'matrix inversion and calculation of point subsources. ' 

2749 'Note: for small/medium matrices 1 thread is most efficient.') 

2750 

2751 pure_shear = Bool.T( 

2752 optional=True, 

2753 default=False, 

2754 help='Calculate only shear tractions and omit tensile tractions.') 

2755 

2756 smooth_rupture = Bool.T( 

2757 default=True, 

2758 help='Smooth the tractions by weighting partially ruptured' 

2759 ' fault patches.') 

2760 

2761 aggressive_oversampling = Bool.T( 

2762 default=False, 

2763 help='Aggressive oversampling for basesource discretization. ' 

2764 "When using 'multilinear' interpolation oversampling has" 

2765 ' practically no effect.') 

2766 

2767 def __init__(self, **kwargs): 

2768 SourceWithDerivedMagnitude.__init__(self, **kwargs) 

2769 self._interpolators = {} 

2770 self.check_conflicts() 

2771 

2772 @property 

2773 def nucleation_x(self): 

2774 return self.nucleation_x__ 

2775 

2776 @nucleation_x.setter 

2777 def nucleation_x(self, nucleation_x): 

2778 if isinstance(nucleation_x, list): 

2779 nucleation_x = num.array(nucleation_x) 

2780 

2781 elif not isinstance( 

2782 nucleation_x, num.ndarray) and nucleation_x is not None: 

2783 

2784 nucleation_x = num.array([nucleation_x]) 

2785 self.nucleation_x__ = nucleation_x 

2786 

2787 @property 

2788 def nucleation_y(self): 

2789 return self.nucleation_y__ 

2790 

2791 @nucleation_y.setter 

2792 def nucleation_y(self, nucleation_y): 

2793 if isinstance(nucleation_y, list): 

2794 nucleation_y = num.array(nucleation_y) 

2795 

2796 elif not isinstance(nucleation_y, num.ndarray) \ 

2797 and nucleation_y is not None: 

2798 nucleation_y = num.array([nucleation_y]) 

2799 

2800 self.nucleation_y__ = nucleation_y 

2801 

2802 @property 

2803 def nucleation(self): 

2804 nucl_x, nucl_y = self.nucleation_x, self.nucleation_y 

2805 

2806 if (nucl_x is None) or (nucl_y is None): 

2807 return None 

2808 

2809 assert nucl_x.shape[0] == nucl_y.shape[0] 

2810 

2811 return num.concatenate( 

2812 (nucl_x[:, num.newaxis], nucl_y[:, num.newaxis]), axis=1) 

2813 

2814 @nucleation.setter 

2815 def nucleation(self, nucleation): 

2816 if isinstance(nucleation, list): 

2817 nucleation = num.array(nucleation) 

2818 

2819 assert nucleation.shape[1] == 2 

2820 

2821 self.nucleation_x = nucleation[:, 0] 

2822 self.nucleation_y = nucleation[:, 1] 

2823 

2824 @property 

2825 def nucleation_time(self): 

2826 return self.nucleation_time__ 

2827 

2828 @nucleation_time.setter 

2829 def nucleation_time(self, nucleation_time): 

2830 if not isinstance(nucleation_time, num.ndarray) \ 

2831 and nucleation_time is not None: 

2832 nucleation_time = num.array([nucleation_time]) 

2833 

2834 self.nucleation_time__ = nucleation_time 

2835 

2836 @property 

2837 def patch_mask(self): 

2838 if (self.patch_mask__ is not None and 

2839 self.patch_mask__.shape == (self.nx * self.ny,)): 

2840 

2841 return self.patch_mask__ 

2842 else: 

2843 return num.ones(self.nx * self.ny, dtype=bool) 

2844 

2845 @patch_mask.setter 

2846 def patch_mask(self, patch_mask): 

2847 if isinstance(patch_mask, list): 

2848 patch_mask = num.array(patch_mask) 

2849 

2850 self.patch_mask__ = patch_mask 

2851 

2852 def get_tractions(self): 

2853 ''' 

2854 Get source traction vectors. 

2855 

2856 If :py:attr:`rake` is given, unit length directed traction vectors 

2857 (:py:class:`~pyrocko.gf.tractions.DirectedTractions`) are returned, 

2858 else the given :py:attr:`tractions` are used. 

2859 

2860 :returns: 

2861 Traction vectors per patch. 

2862 :rtype: 

2863 :py:class:`~numpy.ndarray`: ``(n_patches, 3)``. 

2864 ''' 

2865 

2866 if self.rake is not None: 

2867 if num.isnan(self.rake): 

2868 raise ValueError('Rake must be a real number, not NaN.') 

2869 

2870 logger.warning( 

2871 'Tractions are derived based on the given source rake.') 

2872 tractions = DirectedTractions(rake=self.rake) 

2873 else: 

2874 tractions = self.tractions 

2875 return tractions.get_tractions(self.nx, self.ny, self.patches) 

2876 

2877 def get_scaled_tractions(self, store): 

2878 ''' 

2879 Get traction vectors rescaled to given slip. 

2880 

2881 Opposing to :py:meth:`get_tractions` traction vectors 

2882 (:py:class:`~pyrocko.gf.tractions.DirectedTractions`) are rescaled to 

2883 the given :py:attr:`slip` before returning. If no :py:attr:`slip` and 

2884 :py:attr:`rake` are provided, the given :py:attr:`tractions` are 

2885 returned without scaling. 

2886 

2887 :param store: 

2888 Green's function database (needs to cover whole region of the 

2889 source). 

2890 :type store: 

2891 :py:class:`~pyrocko.gf.store.Store` 

2892 

2893 :returns: 

2894 Traction vectors per patch. 

2895 :rtype: 

2896 :py:class:`~numpy.ndarray`: ``(n_patches, 3)``. 

2897 ''' 

2898 tractions = self.tractions 

2899 factor = 1. 

2900 

2901 if self.rake is not None and self.slip is not None: 

2902 if num.isnan(self.rake): 

2903 raise ValueError('Rake must be a real number, not NaN.') 

2904 

2905 self.discretize_patches(store) 

2906 slip_0t = max(num.linalg.norm( 

2907 self.get_slip(scale_slip=False), 

2908 axis=1)) 

2909 

2910 factor = self.slip / slip_0t 

2911 tractions = DirectedTractions(rake=self.rake) 

2912 

2913 return tractions.get_tractions(self.nx, self.ny, self.patches) * factor 

2914 

2915 def base_key(self): 

2916 return SourceWithDerivedMagnitude.base_key(self) + ( 

2917 self.slip, 

2918 self.strike, 

2919 self.dip, 

2920 self.rake, 

2921 self.length, 

2922 self.width, 

2923 float(self.nucleation_x.mean()), 

2924 float(self.nucleation_y.mean()), 

2925 self.decimation_factor, 

2926 self.anchor, 

2927 self.pure_shear, 

2928 self.gamma, 

2929 tuple(self.patch_mask)) 

2930 

2931 def check_conflicts(self): 

2932 if self.tractions and self.rake: 

2933 raise AttributeError( 

2934 'Tractions and rake are mutually exclusive.') 

2935 if self.tractions is None and self.rake is None: 

2936 self.rake = 0. 

2937 

2938 def get_magnitude(self, store=None, target=None): 

2939 ''' 

2940 Get total seismic moment magnitude Mw. 

2941 

2942 :param store: 

2943 GF store to guide the discretization and providing the earthmodel 

2944 which is needed to calculate moment from slip. 

2945 :type store: 

2946 :py:class:`~pyrocko.gf.store.Store` 

2947 

2948 :param target: 

2949 Target, used to get GF interpolation settings. 

2950 :type target: 

2951 :py:class:`pyrocko.gf.targets.Target` 

2952 

2953 :returns: 

2954 Moment magnitude 

2955 :rtype: 

2956 float 

2957 ''' 

2958 self.check_conflicts() 

2959 if self.slip is not None or self.tractions is not None: 

2960 if store is None: 

2961 raise DerivedMagnitudeError( 

2962 'Magnitude for a rectangular source with slip or ' 

2963 'tractions defined can only be derived when earth model ' 

2964 'is set.') 

2965 

2966 moment_rate, calc_times = self.discretize_basesource( 

2967 store, target=target).get_moment_rate(store.config.deltat) 

2968 

2969 deltat = num.concatenate(( 

2970 (num.diff(calc_times)[0],), 

2971 num.diff(calc_times))) 

2972 

2973 return float(pmt.moment_to_magnitude( 

2974 num.sum(moment_rate * deltat))) 

2975 

2976 else: 

2977 return float(pmt.moment_to_magnitude(1.0)) 

2978 

2979 def get_factor(self): 

2980 return 1.0 

2981 

2982 def outline(self, cs='xyz'): 

2983 ''' 

2984 Get source outline corner coordinates. 

2985 

2986 :param cs: 

2987 :ref:`Output coordinate system <coordinate-system-names>`. 

2988 :type cs: 

2989 str 

2990 

2991 :returns: 

2992 Corner points in desired coordinate system. 

2993 :rtype: 

2994 :py:class:`~numpy.ndarray`: ``(5, [2, 3])``. 

2995 ''' 

2996 points = outline_rect_source(self.strike, self.dip, self.length, 

2997 self.width, self.anchor) 

2998 

2999 points[:, 0] += self.north_shift 

3000 points[:, 1] += self.east_shift 

3001 points[:, 2] += self.depth 

3002 if cs == 'xyz': 

3003 return points 

3004 elif cs == 'xy': 

3005 return points[:, :2] 

3006 elif cs in ('latlon', 'lonlat', 'latlondepth'): 

3007 latlon = ne_to_latlon( 

3008 self.lat, self.lon, points[:, 0], points[:, 1]) 

3009 

3010 latlon = num.array(latlon).T 

3011 if cs == 'latlon': 

3012 return latlon 

3013 elif cs == 'lonlat': 

3014 return latlon[:, ::-1] 

3015 else: 

3016 return num.concatenate( 

3017 (latlon, points[:, 2].reshape((len(points), 1))), 

3018 axis=1) 

3019 

3020 def points_on_source(self, cs='xyz', **kwargs): 

3021 ''' 

3022 Convert relative plane coordinates to geographical coordinates. 

3023 

3024 Given x and y coordinates (relative source coordinates between -1. 

3025 and 1.) are converted to desired geographical coordinates. Coordinates 

3026 need to be given as :py:class:`~numpy.ndarray` arguments ``points_x`` 

3027 and ``points_y``. 

3028 

3029 :param cs: 

3030 :ref:`Output coordinate system <coordinate-system-names>`. 

3031 :type cs: 

3032 str 

3033 

3034 :returns: 

3035 Point coordinates in desired coordinate system. 

3036 :rtype: 

3037 :py:class:`~numpy.ndarray`: ``(n_points, [2, 3])``. 

3038 ''' 

3039 points = points_on_rect_source( 

3040 self.strike, self.dip, self.length, self.width, 

3041 self.anchor, **kwargs) 

3042 

3043 points[:, 0] += self.north_shift 

3044 points[:, 1] += self.east_shift 

3045 points[:, 2] += self.depth 

3046 if cs == 'xyz': 

3047 return points 

3048 elif cs == 'xy': 

3049 return points[:, :2] 

3050 elif cs in ('latlon', 'lonlat', 'latlondepth'): 

3051 latlon = ne_to_latlon( 

3052 self.lat, self.lon, points[:, 0], points[:, 1]) 

3053 

3054 latlon = num.array(latlon).T 

3055 if cs == 'latlon': 

3056 return latlon 

3057 elif cs == 'lonlat': 

3058 return latlon[:, ::-1] 

3059 else: 

3060 return num.concatenate( 

3061 (latlon, points[:, 2].reshape((len(points), 1))), 

3062 axis=1) 

3063 

3064 def pyrocko_moment_tensor(self, store=None, target=None): 

3065 ''' 

3066 Get overall moment tensor of the rupture. 

3067 

3068 :param store: 

3069 GF store to guide the discretization and providing the earthmodel 

3070 which is needed to calculate moment from slip. 

3071 :type store: 

3072 :py:class:`~pyrocko.gf.store.Store` 

3073 

3074 :param target: 

3075 Target, used to get GF interpolation settings. 

3076 :type target: 

3077 :py:class:`pyrocko.gf.targets.Target` 

3078 

3079 :returns: 

3080 Moment tensor. 

3081 :rtype: 

3082 :py:class:`~pyrocko.moment_tensor.MomentTensor` 

3083 ''' 

3084 if store is not None: 

3085 if not self.patches: 

3086 self.discretize_patches(store) 

3087 

3088 data = self.get_slip() 

3089 else: 

3090 data = self.get_tractions() 

3091 

3092 weights = num.linalg.norm(data, axis=1) 

3093 weights /= weights.sum() 

3094 

3095 rakes = num.arctan2(data[:, 1], data[:, 0]) * r2d 

3096 rake = num.average(rakes, weights=weights) 

3097 

3098 return pmt.MomentTensor( 

3099 strike=self.strike, 

3100 dip=self.dip, 

3101 rake=rake, 

3102 scalar_moment=self.get_moment(store, target)) 

3103 

3104 def pyrocko_event(self, store=None, target=None, **kwargs): 

3105 return SourceWithDerivedMagnitude.pyrocko_event( 

3106 self, store, target, 

3107 **kwargs) 

3108 

3109 @classmethod 

3110 def from_pyrocko_event(cls, ev, **kwargs): 

3111 d = {} 

3112 mt = ev.moment_tensor 

3113 if mt: 

3114 (strike, dip, rake), _ = mt.both_strike_dip_rake() 

3115 d.update( 

3116 strike=float(strike), 

3117 dip=float(dip), 

3118 rake=float(rake)) 

3119 

3120 d.update(kwargs) 

3121 return super(PseudoDynamicRupture, cls).from_pyrocko_event(ev, **d) 

3122 

3123 def _discretize_points(self, store, *args, **kwargs): 

3124 ''' 

3125 Discretize source plane with equal vertical and horizontal spacing. 

3126 

3127 Additional ``*args`` and ``**kwargs`` are passed to 

3128 :py:meth:`points_on_source`. 

3129 

3130 :param store: 

3131 Green's function database (needs to cover whole region of the 

3132 source). 

3133 :type store: 

3134 :py:class:`~pyrocko.gf.store.Store` 

3135 

3136 :returns: 

3137 Number of points in strike and dip direction, distance 

3138 between adjacent points, coordinates (latlondepth) and coordinates 

3139 (xy on fault) for discrete points. 

3140 :rtype: 

3141 (int, int, float, :py:class:`~numpy.ndarray`, 

3142 :py:class:`~numpy.ndarray`). 

3143 ''' 

3144 anch_x, anch_y = map_anchor[self.anchor] 

3145 

3146 npoints = int(self.width // km) + 1 

3147 points = num.zeros((npoints, 3)) 

3148 points[:, 1] = num.linspace(-1., 1., npoints) 

3149 points[:, 1] = (points[:, 1] - anch_y) * self.width/2 

3150 

3151 rotmat = pmt.euler_to_matrix(self.dip*d2r, self.strike*d2r, 0.0) 

3152 points = num.dot(rotmat.T, points.T).T 

3153 points[:, 2] += self.depth 

3154 

3155 vs_min = store.config.get_vs( 

3156 self.lat, self.lon, points, 

3157 interpolation='nearest_neighbor') 

3158 vr_min = max(vs_min.min(), .5*km) * self.gamma 

3159 

3160 oversampling = 10. 

3161 delta_l = self.length / (self.nx * oversampling) 

3162 delta_w = self.width / (self.ny * oversampling) 

3163 

3164 delta = self.eikonal_decimation * num.min([ 

3165 store.config.deltat * vr_min / oversampling, 

3166 delta_l, delta_w] + [ 

3167 deltas for deltas in store.config.deltas]) 

3168 

3169 delta = delta_w / num.ceil(delta_w / delta) 

3170 

3171 nx = int(num.ceil(self.length / delta)) + 1 

3172 ny = int(num.ceil(self.width / delta)) + 1 

3173 

3174 rem_l = (nx-1)*delta - self.length 

3175 lim_x = rem_l / self.length 

3176 

3177 points_xy = num.zeros((nx * ny, 2)) 

3178 points_xy[:, 0] = num.repeat( 

3179 num.linspace(-1.-lim_x, 1.+lim_x, nx), ny) 

3180 points_xy[:, 1] = num.tile( 

3181 num.linspace(-1., 1., ny), nx) 

3182 

3183 points = self.points_on_source( 

3184 points_x=points_xy[:, 0], 

3185 points_y=points_xy[:, 1], 

3186 **kwargs) 

3187 

3188 return nx, ny, delta, points, points_xy 

3189 

3190 def _discretize_rupture_v(self, store, interpolation='nearest_neighbor', 

3191 points=None): 

3192 ''' 

3193 Get rupture velocity for discrete points on source plane. 

3194 

3195 :param store: 

3196 Green's function database (needs to cover the whole region of the 

3197 source) 

3198 :type store: 

3199 :py:class:`~pyrocko.gf.store.Store` 

3200 

3201 :param interpolation: 

3202 Interpolation method to use (choose between ``'nearest_neighbor'`` 

3203 and ``'multilinear'``). 

3204 :type interpolation: 

3205 str 

3206 

3207 :param points: 

3208 Coordinates on fault (-1.:1.) of discrete points. 

3209 :type points: 

3210 :py:class:`~numpy.ndarray`: ``(n_points, 2)`` 

3211 

3212 :returns: 

3213 Rupture velocity assumed as :math:`v_s * \\gamma` for discrete 

3214 points. 

3215 :rtype: 

3216 :py:class:`~numpy.ndarray`: ``(n_points, )``. 

3217 ''' 

3218 

3219 if points is None: 

3220 _, _, _, points, _ = self._discretize_points(store, cs='xyz') 

3221 

3222 return store.config.get_vs( 

3223 self.lat, self.lon, 

3224 points=points, 

3225 interpolation=interpolation) * self.gamma 

3226 

3227 def discretize_time( 

3228 self, store, interpolation='nearest_neighbor', 

3229 vr=None, times=None, *args, **kwargs): 

3230 ''' 

3231 Get rupture start time for discrete points on source plane. 

3232 

3233 :param store: 

3234 Green's function database (needs to cover whole region of the 

3235 source) 

3236 :type store: 

3237 :py:class:`~pyrocko.gf.store.Store` 

3238 

3239 :param interpolation: 

3240 Interpolation method to use (choose between ``'nearest_neighbor'`` 

3241 and ``'multilinear'``). 

3242 :type interpolation: 

3243 str 

3244 

3245 :param vr: 

3246 Array, containing rupture user defined rupture velocity values. 

3247 :type vr: 

3248 :py:class:`~numpy.ndarray` 

3249 

3250 :param times: 

3251 Array, containing zeros, where rupture is starting, real positive 

3252 numbers at later secondary nucleation points and -1, where time 

3253 will be calculated. If not given, rupture starts at nucleation_x, 

3254 nucleation_y. Times are given for discrete points with equal 

3255 horizontal and vertical spacing. 

3256 :type times: 

3257 :py:class:`~numpy.ndarray` 

3258 

3259 :returns: 

3260 Coordinates (latlondepth), coordinates (xy), rupture velocity, 

3261 rupture propagation time of discrete points. 

3262 :rtype: 

3263 :py:class:`~numpy.ndarray`: ``(n_points, 3)``, 

3264 :py:class:`~numpy.ndarray`: ``(n_points, 2)``, 

3265 :py:class:`~numpy.ndarray`: ``(n_points_dip, n_points_strike)``, 

3266 :py:class:`~numpy.ndarray`: ``(n_points_dip, n_points_strike)``. 

3267 ''' 

3268 nx, ny, delta, points, points_xy = self._discretize_points( 

3269 store, cs='xyz') 

3270 

3271 if vr is None or vr.shape != tuple((nx, ny)): 

3272 if vr: 

3273 logger.warning( 

3274 'Given rupture velocities are not in right shape: ' 

3275 '(%i, %i), but needed is (%i, %i).', *vr.shape + (nx, ny)) 

3276 vr = self._discretize_rupture_v(store, interpolation, points)\ 

3277 .reshape(nx, ny) 

3278 

3279 if vr.shape != tuple((nx, ny)): 

3280 logger.warning( 

3281 'Given rupture velocities are not in right shape. Therefore' 

3282 ' standard rupture velocity array is used.') 

3283 

3284 def initialize_times(): 

3285 nucl_x, nucl_y = self.nucleation_x, self.nucleation_y 

3286 

3287 if nucl_x.shape != nucl_y.shape: 

3288 raise ValueError( 

3289 'Nucleation coordinates have different shape.') 

3290 

3291 dist_points = num.array([ 

3292 num.linalg.norm(points_xy - num.array([x, y]).ravel(), axis=1) 

3293 for x, y in zip(nucl_x, nucl_y)]) 

3294 nucl_indices = num.argmin(dist_points, axis=1) 

3295 

3296 if self.nucleation_time is None: 

3297 nucl_times = num.zeros_like(nucl_indices) 

3298 else: 

3299 if self.nucleation_time.shape == nucl_x.shape: 

3300 nucl_times = self.nucleation_time 

3301 else: 

3302 raise ValueError( 

3303 'Nucleation coordinates and times have different ' 

3304 'shapes') 

3305 

3306 t = num.full(nx * ny, -1.) 

3307 t[nucl_indices] = nucl_times 

3308 return t.reshape(nx, ny) 

3309 

3310 if times is None: 

3311 times = initialize_times() 

3312 elif times.shape != tuple((nx, ny)): 

3313 times = initialize_times() 

3314 logger.warning( 

3315 'Given times are not in right shape. Therefore standard time' 

3316 ' array is used.') 

3317 

3318 eikonal_ext.eikonal_solver_fmm_cartesian( 

3319 speeds=vr, times=times, delta=delta) 

3320 

3321 return points, points_xy, vr, times 

3322 

3323 def get_vr_time_interpolators( 

3324 self, store, interpolation='nearest_neighbor', force=False, 

3325 **kwargs): 

3326 ''' 

3327 Get interpolators for rupture velocity and rupture time. 

3328 

3329 Additional ``**kwargs`` are passed to :py:meth:`discretize_time`. 

3330 

3331 :param store: 

3332 Green's function database (needs to cover whole region of the 

3333 source). 

3334 :type store: 

3335 :py:class:`~pyrocko.gf.store.Store` 

3336 

3337 :param interpolation: 

3338 Interpolation method to use (choose between ``'nearest_neighbor'`` 

3339 and ``'multilinear'``). 

3340 :type interpolation: 

3341 str 

3342 

3343 :param force: 

3344 Force recalculation of the interpolators (e.g. after change of 

3345 nucleation point locations/times). Default is ``False``. 

3346 :type force: 

3347 bool 

3348 ''' 

3349 interp_map = {'multilinear': 'linear', 'nearest_neighbor': 'nearest'} 

3350 if interpolation not in interp_map: 

3351 raise TypeError( 

3352 'Interpolation method %s not available' % interpolation) 

3353 

3354 if not self._interpolators.get(interpolation, False) or force: 

3355 _, points_xy, vr, times = self.discretize_time( 

3356 store, **kwargs) 

3357 

3358 if self.length <= 0.: 

3359 raise ValueError( 

3360 'length must be larger then 0. not %g' % self.length) 

3361 

3362 if self.width <= 0.: 

3363 raise ValueError( 

3364 'width must be larger then 0. not %g' % self.width) 

3365 

3366 nx, ny = times.shape 

3367 anch_x, anch_y = map_anchor[self.anchor] 

3368 

3369 points_xy[:, 0] = (points_xy[:, 0] - anch_x) * self.length / 2. 

3370 points_xy[:, 1] = (points_xy[:, 1] - anch_y) * self.width / 2. 

3371 

3372 ascont = num.ascontiguousarray 

3373 

3374 self._interpolators[interpolation] = ( 

3375 nx, ny, times, vr, 

3376 RegularGridInterpolator( 

3377 (ascont(points_xy[::ny, 0]), ascont(points_xy[:ny, 1])), 

3378 times, 

3379 method=interp_map[interpolation]), 

3380 RegularGridInterpolator( 

3381 (ascont(points_xy[::ny, 0]), ascont(points_xy[:ny, 1])), 

3382 vr, 

3383 method=interp_map[interpolation])) 

3384 

3385 return self._interpolators[interpolation] 

3386 

3387 def discretize_patches( 

3388 self, store, interpolation='nearest_neighbor', force=False, 

3389 grid_shape=(), 

3390 **kwargs): 

3391 ''' 

3392 Get rupture start time and OkadaSource elements for points on rupture. 

3393 

3394 All source elements and their corresponding center points are 

3395 calculated and stored in the :py:attr:`patches` attribute. 

3396 

3397 Additional ``**kwargs`` are passed to :py:meth:`discretize_time`. 

3398 

3399 :param store: 

3400 Green's function database (needs to cover whole region of the 

3401 source). 

3402 :type store: 

3403 :py:class:`~pyrocko.gf.store.Store` 

3404 

3405 :param interpolation: 

3406 Interpolation method to use (choose between ``'nearest_neighbor'`` 

3407 and ``'multilinear'``). 

3408 :type interpolation: 

3409 str 

3410 

3411 :param force: 

3412 Force recalculation of the vr and time interpolators ( e.g. after 

3413 change of nucleation point locations/times). Default is ``False``. 

3414 :type force: 

3415 bool 

3416 

3417 :param grid_shape: 

3418 Desired sub fault patch grid size (nlength, nwidth). Either factor 

3419 or grid_shape should be set. 

3420 :type grid_shape: 

3421 :py:class:`tuple` of :py:class:`int` 

3422 ''' 

3423 nx, ny, times, vr, time_interpolator, vr_interpolator = \ 

3424 self.get_vr_time_interpolators( 

3425 store, 

3426 interpolation=interpolation, force=force, **kwargs) 

3427 anch_x, anch_y = map_anchor[self.anchor] 

3428 

3429 al = self.length / 2. 

3430 aw = self.width / 2. 

3431 al1 = -(al + anch_x * al) 

3432 al2 = al - anch_x * al 

3433 aw1 = -aw + anch_y * aw 

3434 aw2 = aw + anch_y * aw 

3435 assert num.abs([al1, al2]).sum() == self.length 

3436 assert num.abs([aw1, aw2]).sum() == self.width 

3437 

3438 def get_lame(*a, **kw): 

3439 shear_mod = store.config.get_shear_moduli(*a, **kw) 

3440 lamb = store.config.get_vp(*a, **kw)**2 \ 

3441 * store.config.get_rho(*a, **kw) - 2. * shear_mod 

3442 return shear_mod, lamb / (2. * (lamb + shear_mod)) 

3443 

3444 shear_mod, poisson = get_lame( 

3445 self.lat, self.lon, 

3446 num.array([[self.north_shift, self.east_shift, self.depth]]), 

3447 interpolation=interpolation) 

3448 

3449 okada_src = OkadaSource( 

3450 lat=self.lat, lon=self.lon, 

3451 strike=self.strike, dip=self.dip, 

3452 north_shift=self.north_shift, east_shift=self.east_shift, 

3453 depth=self.depth, 

3454 al1=al1, al2=al2, aw1=aw1, aw2=aw2, 

3455 poisson=poisson.mean(), 

3456 shearmod=shear_mod.mean(), 

3457 opening=kwargs.get('opening', 0.)) 

3458 

3459 if not (self.nx and self.ny): 

3460 if grid_shape: 

3461 self.nx, self.ny = grid_shape 

3462 else: 

3463 self.nx = nx 

3464 self.ny = ny 

3465 

3466 source_disc, source_points = okada_src.discretize(self.nx, self.ny) 

3467 

3468 shear_mod, poisson = get_lame( 

3469 self.lat, self.lon, 

3470 num.array([src.source_patch()[:3] for src in source_disc]), 

3471 interpolation=interpolation) 

3472 

3473 if (self.nx, self.ny) != (nx, ny): 

3474 times_interp = time_interpolator( 

3475 num.ascontiguousarray(source_points[:, :2])) 

3476 vr_interp = vr_interpolator( 

3477 num.ascontiguousarray(source_points[:, :2])) 

3478 else: 

3479 times_interp = times.T.ravel() 

3480 vr_interp = vr.T.ravel() 

3481 

3482 for isrc, src in enumerate(source_disc): 

3483 src.vr = vr_interp[isrc] 

3484 src.time = times_interp[isrc] + self.time 

3485 

3486 self.patches = source_disc 

3487 

3488 def discretize_basesource(self, store, target=None): 

3489 ''' 

3490 Prepare source for synthetic waveform calculation. 

3491 

3492 :param store: 

3493 Green's function database (needs to cover whole region of the 

3494 source). 

3495 :type store: 

3496 :py:class:`~pyrocko.gf.store.Store` 

3497 

3498 :param target: 

3499 Target information. 

3500 :type target: 

3501 :py:class:`~pyrocko.gf.targets.Target` 

3502 

3503 :returns: 

3504 Source discretized by a set of moment tensors and times. 

3505 :rtype: 

3506 :py:class:`~pyrocko.gf.meta.DiscretizedMTSource` 

3507 ''' 

3508 if not target: 

3509 interpolation = 'nearest_neighbor' 

3510 else: 

3511 interpolation = target.interpolation 

3512 

3513 if not self.patches: 

3514 self.discretize_patches(store, interpolation) 

3515 

3516 if self.coef_mat is None: 

3517 self.calc_coef_mat() 

3518 

3519 delta_slip, slip_times = self.get_delta_slip(store) 

3520 npatches = self.nx * self.ny 

3521 ntimes = slip_times.size 

3522 

3523 anch_x, anch_y = map_anchor[self.anchor] 

3524 

3525 pln = self.length / self.nx 

3526 pwd = self.width / self.ny 

3527 

3528 patch_coords = num.array([ 

3529 (p.ix, p.iy) 

3530 for p in self.patches]).reshape(self.nx, self.ny, 2) 

3531 

3532 # boundary condition is zero-slip 

3533 # is not valid to avoid unwished interpolation effects 

3534 slip_grid = num.zeros((self.nx + 2, self.ny + 2, ntimes, 3)) 

3535 slip_grid[1:-1, 1:-1, :, :] = \ 

3536 delta_slip.reshape(self.nx, self.ny, ntimes, 3) 

3537 

3538 slip_grid[0, 0, :, :] = slip_grid[1, 1, :, :] 

3539 slip_grid[0, -1, :, :] = slip_grid[1, -2, :, :] 

3540 slip_grid[-1, 0, :, :] = slip_grid[-2, 1, :, :] 

3541 slip_grid[-1, -1, :, :] = slip_grid[-2, -2, :, :] 

3542 

3543 slip_grid[1:-1, 0, :, :] = slip_grid[1:-1, 1, :, :] 

3544 slip_grid[1:-1, -1, :, :] = slip_grid[1:-1, -2, :, :] 

3545 slip_grid[0, 1:-1, :, :] = slip_grid[1, 1:-1, :, :] 

3546 slip_grid[-1, 1:-1, :, :] = slip_grid[-2, 1:-1, :, :] 

3547 

3548 def make_grid(patch_parameter): 

3549 grid = num.zeros((self.nx + 2, self.ny + 2)) 

3550 grid[1:-1, 1:-1] = patch_parameter.reshape(self.nx, self.ny) 

3551 

3552 grid[0, 0] = grid[1, 1] 

3553 grid[0, -1] = grid[1, -2] 

3554 grid[-1, 0] = grid[-2, 1] 

3555 grid[-1, -1] = grid[-2, -2] 

3556 

3557 grid[1:-1, 0] = grid[1:-1, 1] 

3558 grid[1:-1, -1] = grid[1:-1, -2] 

3559 grid[0, 1:-1] = grid[1, 1:-1] 

3560 grid[-1, 1:-1] = grid[-2, 1:-1] 

3561 

3562 return grid 

3563 

3564 lamb = self.get_patch_attribute('lamb') 

3565 mu = self.get_patch_attribute('shearmod') 

3566 

3567 lamb_grid = make_grid(lamb) 

3568 mu_grid = make_grid(mu) 

3569 

3570 coords_x = num.zeros(self.nx + 2) 

3571 coords_x[1:-1] = patch_coords[:, 0, 0] 

3572 coords_x[0] = coords_x[1] - pln / 2 

3573 coords_x[-1] = coords_x[-2] + pln / 2 

3574 

3575 coords_y = num.zeros(self.ny + 2) 

3576 coords_y[1:-1] = patch_coords[0, :, 1] 

3577 coords_y[0] = coords_y[1] - pwd / 2 

3578 coords_y[-1] = coords_y[-2] + pwd / 2 

3579 

3580 slip_interp = RegularGridInterpolator( 

3581 (coords_x, coords_y, slip_times), 

3582 slip_grid, method='nearest') 

3583 

3584 lamb_interp = RegularGridInterpolator( 

3585 (coords_x, coords_y), 

3586 lamb_grid, method='nearest') 

3587 

3588 mu_interp = RegularGridInterpolator( 

3589 (coords_x, coords_y), 

3590 mu_grid, method='nearest') 

3591 

3592 # discretize basesources 

3593 mindeltagf = min(tuple( 

3594 (self.length / self.nx, self.width / self.ny) + 

3595 tuple(store.config.deltas))) 

3596 

3597 nl = int((1. / self.decimation_factor) * 

3598 num.ceil(pln / mindeltagf)) + 1 

3599 nw = int((1. / self.decimation_factor) * 

3600 num.ceil(pwd / mindeltagf)) + 1 

3601 nsrc_patch = int(nl * nw) 

3602 dl = pln / nl 

3603 dw = pwd / nw 

3604 

3605 patch_area = dl * dw 

3606 

3607 xl = num.linspace(-0.5 * (pln - dl), 0.5 * (pln - dl), nl) 

3608 xw = num.linspace(-0.5 * (pwd - dw), 0.5 * (pwd - dw), nw) 

3609 

3610 base_coords = num.zeros((nsrc_patch, 3)) 

3611 base_coords[:, 0] = num.tile(xl, nw) 

3612 base_coords[:, 1] = num.repeat(xw, nl) 

3613 base_coords = num.tile(base_coords, (npatches, 1)) 

3614 

3615 center_coords = num.zeros((npatches, 3)) 

3616 center_coords[:, 0] = num.repeat( 

3617 num.arange(self.nx) * pln + pln / 2, self.ny) - self.length / 2 

3618 center_coords[:, 1] = num.tile( 

3619 num.arange(self.ny) * pwd + pwd / 2, self.nx) - self.width / 2 

3620 

3621 base_coords -= center_coords.repeat(nsrc_patch, axis=0) 

3622 nbaselocs = base_coords.shape[0] 

3623 

3624 base_interp = base_coords.repeat(ntimes, axis=0) 

3625 

3626 base_times = num.tile(slip_times, nbaselocs) 

3627 base_interp[:, 0] -= anch_x * self.length / 2 

3628 base_interp[:, 1] -= anch_y * self.width / 2 

3629 base_interp[:, 2] = base_times 

3630 

3631 _, _, _, _, time_interpolator, _ = self.get_vr_time_interpolators( 

3632 store, interpolation=interpolation) 

3633 

3634 time_eikonal_max = time_interpolator.values.max() 

3635 

3636 nbasesrcs = base_interp.shape[0] 

3637 delta_slip = slip_interp(base_interp).reshape(nbaselocs, ntimes, 3) 

3638 lamb = lamb_interp(base_interp[:, :2]).ravel() 

3639 mu = mu_interp(base_interp[:, :2]).ravel() 

3640 

3641 if False: 

3642 try: 

3643 import matplotlib.pyplot as plt 

3644 coords = base_coords.copy() 

3645 norm = num.sum(num.linalg.norm(delta_slip, axis=2), axis=1) 

3646 plt.scatter(coords[:, 0], coords[:, 1], c=norm) 

3647 plt.show() 

3648 except AttributeError: 

3649 pass 

3650 

3651 base_interp[:, 2] = 0. 

3652 rotmat = pmt.euler_to_matrix(self.dip * d2r, self.strike * d2r, 0.0) 

3653 base_interp = num.dot(rotmat.T, base_interp.T).T 

3654 base_interp[:, 0] += self.north_shift 

3655 base_interp[:, 1] += self.east_shift 

3656 base_interp[:, 2] += self.depth 

3657 

3658 slip_strike = delta_slip[:, :, 0].ravel() 

3659 slip_dip = delta_slip[:, :, 1].ravel() 

3660 slip_norm = delta_slip[:, :, 2].ravel() 

3661 

3662 slip_shear = num.linalg.norm([slip_strike, slip_dip], axis=0) 

3663 slip_rake = r2d * num.arctan2(slip_dip, slip_strike) 

3664 

3665 m6s = okada_ext.patch2m6( 

3666 strikes=num.full(nbasesrcs, self.strike, dtype=float), 

3667 dips=num.full(nbasesrcs, self.dip, dtype=float), 

3668 rakes=slip_rake, 

3669 disl_shear=slip_shear, 

3670 disl_norm=slip_norm, 

3671 lamb=lamb, 

3672 mu=mu, 

3673 nthreads=self.nthreads) 

3674 

3675 m6s *= patch_area 

3676 

3677 dl = -self.patches[0].al1 + self.patches[0].al2 

3678 dw = -self.patches[0].aw1 + self.patches[0].aw2 

3679 

3680 base_times[base_times > time_eikonal_max] = time_eikonal_max 

3681 

3682 ds = meta.DiscretizedMTSource( 

3683 lat=self.lat, 

3684 lon=self.lon, 

3685 times=base_times + self.time, 

3686 north_shifts=base_interp[:, 0], 

3687 east_shifts=base_interp[:, 1], 

3688 depths=base_interp[:, 2], 

3689 m6s=m6s, 

3690 dl=dl, 

3691 dw=dw, 

3692 nl=self.nx, 

3693 nw=self.ny) 

3694 

3695 return ds 

3696 

3697 def calc_coef_mat(self): 

3698 ''' 

3699 Calculate coefficients connecting tractions and dislocations. 

3700 ''' 

3701 if not self.patches: 

3702 raise ValueError( 

3703 'Patches are needed. Please calculate them first.') 

3704 

3705 self.coef_mat = make_okada_coefficient_matrix( 

3706 self.patches, nthreads=self.nthreads, pure_shear=self.pure_shear) 

3707 

3708 def get_patch_attribute(self, attr): 

3709 ''' 

3710 Get patch attributes. 

3711 

3712 :param attr: 

3713 Name of selected attribute (see 

3714 :py:class`pyrocko.modelling.okada.OkadaSource`). 

3715 :type attr: 

3716 str 

3717 

3718 :returns: 

3719 Array with attribute value for each fault patch. 

3720 :rtype: 

3721 :py:class:`~numpy.ndarray` 

3722 

3723 ''' 

3724 if not self.patches: 

3725 raise ValueError( 

3726 'Patches are needed. Please calculate them first.') 

3727 return num.array([getattr(p, attr) for p in self.patches]) 

3728 

3729 def get_slip( 

3730 self, 

3731 time=None, 

3732 scale_slip=True, 

3733 interpolation='nearest_neighbor', 

3734 **kwargs): 

3735 ''' 

3736 Get slip per subfault patch for given time after rupture start. 

3737 

3738 :param time: 

3739 Time after origin [s], for which slip is computed. If not 

3740 given, final static slip is returned. 

3741 :type time: 

3742 float 

3743 

3744 :param scale_slip: 

3745 If ``True`` and :py:attr:`slip` given, all slip values are scaled 

3746 to fit the given maximum slip. 

3747 :type scale_slip: 

3748 bool 

3749 

3750 :param interpolation: 

3751 Interpolation method to use (choose between ``'nearest_neighbor'`` 

3752 and ``'multilinear'``). 

3753 :type interpolation: 

3754 str 

3755 

3756 :returns: 

3757 Inverted dislocations (:math:`u_{strike}, u_{dip}, u_{tensile}`) 

3758 for each source patch. 

3759 :rtype: 

3760 :py:class:`~numpy.ndarray`: ``(n_sources, 3)`` 

3761 ''' 

3762 

3763 if self.patches is None: 

3764 raise ValueError( 

3765 'Please discretize the source first (discretize_patches())') 

3766 npatches = len(self.patches) 

3767 tractions = self.get_tractions() 

3768 time_patch_max = self.get_patch_attribute('time').max() - self.time 

3769 

3770 time_patch = time 

3771 if time is None: 

3772 time_patch = time_patch_max 

3773 

3774 if self.coef_mat is None: 

3775 self.calc_coef_mat() 

3776 

3777 if tractions.shape != (npatches, 3): 

3778 raise AttributeError( 

3779 'The traction vector is of invalid shape.' 

3780 ' Required shape is (npatches, 3)') 

3781 

3782 patch_mask = num.ones(npatches, dtype=bool) 

3783 if self.patch_mask is not None: 

3784 patch_mask = self.patch_mask 

3785 

3786 times = self.get_patch_attribute('time') - self.time 

3787 times[~patch_mask] = time_patch + 1. # exlcude unmasked patches 

3788 relevant_sources = num.nonzero(times <= time_patch)[0] 

3789 disloc_est = num.zeros_like(tractions) 

3790 

3791 if self.smooth_rupture: 

3792 patch_activation = num.zeros(npatches) 

3793 

3794 nx, ny, times, vr, time_interpolator, vr_interpolator = \ 

3795 self.get_vr_time_interpolators( 

3796 store, interpolation=interpolation) 

3797 

3798 # Getting the native Eikonal grid, bit hackish 

3799 points_x = num.round(time_interpolator.grid[0], decimals=2) 

3800 points_y = num.round(time_interpolator.grid[1], decimals=2) 

3801 times_eikonal = time_interpolator.values 

3802 

3803 time_max = time 

3804 if time is None: 

3805 time_max = times_eikonal.max() 

3806 

3807 for ip, p in enumerate(self.patches): 

3808 ul = num.round((p.ix + p.al1, p.iy + p.aw1), decimals=2) 

3809 lr = num.round((p.ix + p.al2, p.iy + p.aw2), decimals=2) 

3810 

3811 idx_length = num.logical_and( 

3812 points_x >= ul[0], points_x <= lr[0]) 

3813 idx_width = num.logical_and( 

3814 points_y >= ul[1], points_y <= lr[1]) 

3815 

3816 times_patch = times_eikonal[num.ix_(idx_length, idx_width)] 

3817 if times_patch.size == 0: 

3818 raise AttributeError('could not use smooth_rupture') 

3819 

3820 patch_activation[ip] = \ 

3821 (times_patch <= time_max).sum() / times_patch.size 

3822 

3823 if time_patch == 0 and time_patch != time_patch_max: 

3824 patch_activation[ip] = 0. 

3825 

3826 patch_activation[~patch_mask] = 0. # exlcude unmasked patches 

3827 

3828 relevant_sources = num.nonzero(patch_activation > 0.)[0] 

3829 

3830 if relevant_sources.size == 0: 

3831 return disloc_est 

3832 

3833 indices_disl = num.repeat(relevant_sources * 3, 3) 

3834 indices_disl[1::3] += 1 

3835 indices_disl[2::3] += 2 

3836 

3837 disloc_est[relevant_sources] = invert_fault_dislocations_bem( 

3838 stress_field=tractions[relevant_sources, :].ravel(), 

3839 coef_mat=self.coef_mat[indices_disl, :][:, indices_disl], 

3840 pure_shear=self.pure_shear, nthreads=self.nthreads, 

3841 epsilon=None, 

3842 **kwargs) 

3843 

3844 if self.smooth_rupture: 

3845 disloc_est *= patch_activation[:, num.newaxis] 

3846 

3847 if scale_slip and self.slip is not None: 

3848 disloc_tmax = num.zeros(npatches) 

3849 

3850 indices_disl = num.repeat(num.nonzero(patch_mask)[0] * 3, 3) 

3851 indices_disl[1::3] += 1 

3852 indices_disl[2::3] += 2 

3853 

3854 disloc_tmax[patch_mask] = num.linalg.norm( 

3855 invert_fault_dislocations_bem( 

3856 stress_field=tractions[patch_mask, :].ravel(), 

3857 coef_mat=self.coef_mat[indices_disl, :][:, indices_disl], 

3858 pure_shear=self.pure_shear, nthreads=self.nthreads, 

3859 epsilon=None, 

3860 **kwargs), axis=1) 

3861 

3862 disloc_tmax_max = disloc_tmax.max() 

3863 if disloc_tmax_max == 0.: 

3864 logger.warning( 

3865 'slip scaling not performed. Maximum slip is 0.') 

3866 

3867 disloc_est *= self.slip / disloc_tmax_max 

3868 

3869 return disloc_est 

3870 

3871 def get_delta_slip( 

3872 self, 

3873 store=None, 

3874 deltat=None, 

3875 delta=True, 

3876 interpolation='nearest_neighbor', 

3877 **kwargs): 

3878 ''' 

3879 Get slip change snapshots. 

3880 

3881 The time interval, within which the slip changes are computed is 

3882 determined by the sampling rate of the Green's function database or 

3883 ``deltat``. Additional ``**kwargs`` are passed to :py:meth:`get_slip`. 

3884 

3885 :param store: 

3886 Green's function database (needs to cover whole region of of the 

3887 source). Its sampling interval is used as time increment for slip 

3888 difference calculation. Either ``deltat`` or ``store`` should be 

3889 given. 

3890 :type store: 

3891 :py:class:`~pyrocko.gf.store.Store` 

3892 

3893 :param deltat: 

3894 Time interval for slip difference calculation [s]. Either 

3895 ``deltat`` or ``store`` should be given. 

3896 :type deltat: 

3897 float 

3898 

3899 :param delta: 

3900 If ``True``, slip differences between two time steps are given. If 

3901 ``False``, cumulative slip for all time steps. 

3902 :type delta: 

3903 bool 

3904 

3905 :param interpolation: 

3906 Interpolation method to use (choose between ``'nearest_neighbor'`` 

3907 and ``'multilinear'``). 

3908 :type interpolation: 

3909 str 

3910 

3911 :returns: 

3912 Displacement changes(:math:`\\Delta u_{strike}, 

3913 \\Delta u_{dip} , \\Delta u_{tensile}`) for each source patch and 

3914 time; corner times, for which delta slip is computed. The order of 

3915 displacement changes array is: 

3916 

3917 .. math:: 

3918 

3919 &[[\\\\ 

3920 &[\\Delta u_{strike, patch1, t1}, 

3921 \\Delta u_{dip, patch1, t1}, 

3922 \\Delta u_{tensile, patch1, t1}],\\\\ 

3923 &[\\Delta u_{strike, patch1, t2}, 

3924 \\Delta u_{dip, patch1, t2}, 

3925 \\Delta u_{tensile, patch1, t2}]\\\\ 

3926 &], [\\\\ 

3927 &[\\Delta u_{strike, patch2, t1}, ...],\\\\ 

3928 &[\\Delta u_{strike, patch2, t2}, ...]]]\\\\ 

3929 

3930 :rtype: :py:class:`~numpy.ndarray`: ``(n_sources, n_times, 3)``, 

3931 :py:class:`~numpy.ndarray`: ``(n_times, )`` 

3932 ''' 

3933 if store and deltat: 

3934 raise AttributeError( 

3935 'Argument collision. ' 

3936 'Please define only the store or the deltat argument.') 

3937 

3938 if store: 

3939 deltat = store.config.deltat 

3940 

3941 if not deltat: 

3942 raise AttributeError('Please give a GF store or set deltat.') 

3943 

3944 npatches = len(self.patches) 

3945 

3946 _, _, _, _, time_interpolator, _ = self.get_vr_time_interpolators( 

3947 store, interpolation=interpolation) 

3948 tmax = time_interpolator.values.max() 

3949 

3950 calc_times = num.arange(0., tmax + deltat, deltat) 

3951 calc_times[calc_times > tmax] = tmax 

3952 

3953 disloc_est = num.zeros((npatches, calc_times.size, 3)) 

3954 

3955 for itime, t in enumerate(calc_times): 

3956 disloc_est[:, itime, :] = self.get_slip( 

3957 time=t, scale_slip=False, **kwargs) 

3958 

3959 if self.slip: 

3960 disloc_tmax = num.linalg.norm( 

3961 self.get_slip(scale_slip=False, time=tmax), 

3962 axis=1) 

3963 

3964 disloc_tmax_max = disloc_tmax.max() 

3965 if disloc_tmax_max == 0.: 

3966 logger.warning( 

3967 'Slip scaling not performed. Maximum slip is 0.') 

3968 else: 

3969 disloc_est *= self.slip / disloc_tmax_max 

3970 

3971 if not delta: 

3972 return disloc_est, calc_times 

3973 

3974 # if we have only one timestep there is no gradient 

3975 if calc_times.size > 1: 

3976 disloc_init = disloc_est[:, 0, :] 

3977 disloc_est = num.diff(disloc_est, axis=1) 

3978 disloc_est = num.concatenate(( 

3979 disloc_init[:, num.newaxis, :], disloc_est), axis=1) 

3980 

3981 calc_times = calc_times 

3982 

3983 return disloc_est, calc_times 

3984 

3985 def get_slip_rate(self, *args, **kwargs): 

3986 ''' 

3987 Get slip rate inverted from patches. 

3988 

3989 The time interval, within which the slip rates are computed is 

3990 determined by the sampling rate of the Green's function database or 

3991 ``deltat``. Additional ``*args`` and ``**kwargs`` are passed to 

3992 :py:meth:`get_delta_slip`. 

3993 

3994 :returns: 

3995 Slip rates (:math:`\\Delta u_{strike}/\\Delta t`, 

3996 :math:`\\Delta u_{dip}/\\Delta t, \\Delta u_{tensile}/\\Delta t`) 

3997 for each source patch and time; corner times, for which slip rate 

3998 is computed. The order of sliprate array is: 

3999 

4000 .. math:: 

4001 

4002 &[[\\\\ 

4003 &[\\Delta u_{strike, patch1, t1}/\\Delta t, 

4004 \\Delta u_{dip, patch1, t1}/\\Delta t, 

4005 \\Delta u_{tensile, patch1, t1}/\\Delta t],\\\\ 

4006 &[\\Delta u_{strike, patch1, t2}/\\Delta t, 

4007 \\Delta u_{dip, patch1, t2}/\\Delta t, 

4008 \\Delta u_{tensile, patch1, t2}/\\Delta t]], [\\\\ 

4009 &[\\Delta u_{strike, patch2, t1}/\\Delta t, ...],\\\\ 

4010 &[\\Delta u_{strike, patch2, t2}/\\Delta t, ...]]]\\\\ 

4011 

4012 :rtype: :py:class:`~numpy.ndarray`: ``(n_sources, n_times, 3)``, 

4013 :py:class:`~numpy.ndarray`: ``(n_times, )`` 

4014 ''' 

4015 ddisloc_est, calc_times = self.get_delta_slip( 

4016 *args, delta=True, **kwargs) 

4017 

4018 dt = num.concatenate( 

4019 [(num.diff(calc_times)[0], ), num.diff(calc_times)]) 

4020 slip_rate = num.linalg.norm(ddisloc_est, axis=2) / dt 

4021 

4022 return slip_rate, calc_times 

4023 

4024 def get_moment_rate_patches(self, *args, **kwargs): 

4025 ''' 

4026 Get scalar seismic moment rate for each patch individually. 

4027 

4028 Additional ``*args`` and ``**kwargs`` are passed to 

4029 :py:meth:`get_slip_rate`. 

4030 

4031 :returns: 

4032 Seismic moment rate for each source patch and time; corner times, 

4033 for which patch moment rate is computed based on slip rate. The 

4034 order of the moment rate array is: 

4035 

4036 .. math:: 

4037 

4038 &[\\\\ 

4039 &[(\\Delta M / \\Delta t)_{patch1, t1}, 

4040 (\\Delta M / \\Delta t)_{patch1, t2}, ...],\\\\ 

4041 &[(\\Delta M / \\Delta t)_{patch2, t1}, 

4042 (\\Delta M / \\Delta t)_{patch, t2}, ...],\\\\ 

4043 &[...]]\\\\ 

4044 

4045 :rtype: :py:class:`~numpy.ndarray`: ``(n_sources, n_times)``, 

4046 :py:class:`~numpy.ndarray`: ``(n_times, )`` 

4047 ''' 

4048 slip_rate, calc_times = self.get_slip_rate(*args, **kwargs) 

4049 

4050 shear_mod = self.get_patch_attribute('shearmod') 

4051 p_length = self.get_patch_attribute('length') 

4052 p_width = self.get_patch_attribute('width') 

4053 

4054 dA = p_length * p_width 

4055 

4056 mom_rate = shear_mod[:, num.newaxis] * slip_rate * dA[:, num.newaxis] 

4057 

4058 return mom_rate, calc_times 

4059 

4060 def get_moment_rate(self, store, target=None, deltat=None): 

4061 ''' 

4062 Get seismic source moment rate for the total source (STF). 

4063 

4064 :param store: 

4065 Green's function database (needs to cover whole region of of the 

4066 source). Its ``deltat`` [s] is used as time increment for slip 

4067 difference calculation. Either ``deltat`` or ``store`` should be 

4068 given. 

4069 :type store: 

4070 :py:class:`~pyrocko.gf.store.Store` 

4071 

4072 :param target: 

4073 Target information, needed for interpolation method. 

4074 :type target: 

4075 :py:class:`~pyrocko.gf.targets.Target` 

4076 

4077 :param deltat: 

4078 Time increment for slip difference calculation [s]. If not given 

4079 ``store.deltat`` is used. 

4080 :type deltat: 

4081 float 

4082 

4083 :return: 

4084 Seismic moment rate [Nm/s] for each time; corner times, for which 

4085 moment rate is computed. The order of the moment rate array is: 

4086 

4087 .. math:: 

4088 

4089 &[\\\\ 

4090 &(\\Delta M / \\Delta t)_{t1},\\\\ 

4091 &(\\Delta M / \\Delta t)_{t2},\\\\ 

4092 &...]\\\\ 

4093 

4094 :rtype: 

4095 :py:class:`~numpy.ndarray`: ``(n_times, )``, 

4096 :py:class:`~numpy.ndarray`: ``(n_times, )`` 

4097 ''' 

4098 if not deltat: 

4099 deltat = store.config.deltat 

4100 return self.discretize_basesource( 

4101 store, target=target).get_moment_rate(deltat) 

4102 

4103 def get_moment(self, *args, **kwargs): 

4104 ''' 

4105 Get cumulative seismic moment. 

4106 

4107 Additional ``*args`` and ``**kwargs`` are passed to 

4108 :py:meth:`get_magnitude`. 

4109 

4110 :returns: 

4111 Cumulative seismic moment in [Nm]. 

4112 :rtype: 

4113 float 

4114 ''' 

4115 return float(pmt.magnitude_to_moment(self.get_magnitude( 

4116 *args, **kwargs))) 

4117 

4118 def rescale_slip(self, magnitude=None, moment=None, **kwargs): 

4119 ''' 

4120 Rescale source slip based on given target magnitude or seismic moment. 

4121 

4122 Rescale the maximum source slip to fit the source moment magnitude or 

4123 seismic moment to the given target values. Either ``magnitude`` or 

4124 ``moment`` need to be given. Additional ``**kwargs`` are passed to 

4125 :py:meth:`get_moment`. 

4126 

4127 :param magnitude: 

4128 Target moment magnitude :math:`M_\\mathrm{w}` as in 

4129 [Hanks and Kanamori, 1979] 

4130 :type magnitude: 

4131 float 

4132 

4133 :param moment: 

4134 Target seismic moment :math:`M_0` [Nm]. 

4135 :type moment: 

4136 float 

4137 ''' 

4138 if self.slip is None: 

4139 self.slip = 1. 

4140 logger.warning('No slip found for rescaling. ' 

4141 'An initial slip of 1 m is assumed.') 

4142 

4143 if magnitude is None and moment is None: 

4144 raise ValueError( 

4145 'Either target magnitude or moment need to be given.') 

4146 

4147 moment_init = self.get_moment(**kwargs) 

4148 

4149 if magnitude is not None: 

4150 moment = pmt.magnitude_to_moment(magnitude) 

4151 

4152 self.slip *= moment / moment_init 

4153 

4154 def get_centroid(self, store, *args, **kwargs): 

4155 ''' 

4156 Centroid of the pseudo dynamic rupture model. 

4157 

4158 The centroid location and time are derived from the locations and times 

4159 of the individual patches weighted with their moment contribution. 

4160 Additional ``**kwargs`` are passed to :py:meth:`pyrocko_moment_tensor`. 

4161 

4162 :param store: 

4163 Green's function database (needs to cover whole region of of the 

4164 source). Its ``deltat`` [s] is used as time increment for slip 

4165 difference calculation. Either ``deltat`` or ``store`` should be 

4166 given. 

4167 :type store: 

4168 :py:class:`~pyrocko.gf.store.Store` 

4169 

4170 :returns: 

4171 The centroid location and associated moment tensor. 

4172 :rtype: 

4173 :py:class:`pyrocko.model.event.Event` 

4174 ''' 

4175 _, _, _, _, time, _ = self.get_vr_time_interpolators(store) 

4176 t_max = time.values.max() 

4177 

4178 moment_rate, times = self.get_moment_rate_patches(deltat=t_max) 

4179 

4180 moment = num.sum(moment_rate * times, axis=1) 

4181 weights = moment / moment.sum() 

4182 

4183 norths = self.get_patch_attribute('north_shift') 

4184 easts = self.get_patch_attribute('east_shift') 

4185 depths = self.get_patch_attribute('depth') 

4186 

4187 centroid_n = num.sum(weights * norths) 

4188 centroid_e = num.sum(weights * easts) 

4189 centroid_d = num.sum(weights * depths) 

4190 

4191 centroid_lat, centroid_lon = ne_to_latlon( 

4192 self.lat, self.lon, centroid_n, centroid_e) 

4193 

4194 moment_rate_, times = self.get_moment_rate(store) 

4195 delta_times = num.concatenate(( 

4196 [times[1] - times[0]], 

4197 num.diff(times))) 

4198 moment_src = delta_times * moment_rate 

4199 

4200 centroid_t = num.sum( 

4201 moment_src / num.sum(moment_src) * times) + self.time 

4202 

4203 mt = self.pyrocko_moment_tensor(store, *args, **kwargs) 

4204 

4205 return model.Event( 

4206 lat=centroid_lat, 

4207 lon=centroid_lon, 

4208 depth=centroid_d, 

4209 time=centroid_t, 

4210 moment_tensor=mt, 

4211 magnitude=mt.magnitude, 

4212 duration=t_max) 

4213 

4214 def get_coulomb_failure_stress( 

4215 self, 

4216 receiver_points, 

4217 friction, 

4218 pressure, 

4219 strike, 

4220 dip, 

4221 rake, 

4222 time=None, 

4223 *args, 

4224 **kwargs): 

4225 ''' 

4226 Calculate Coulomb failure stress change CFS. 

4227 

4228 The function obtains the Coulomb failure stress change :math:`\\Delta 

4229 \\sigma_C` at arbitrary receiver points with a commonly oriented 

4230 receiver plane assuming: 

4231 

4232 .. math:: 

4233 

4234 \\Delta \\sigma_C = \\sigma_S - \\mu (\\sigma_N - \\Delta p) 

4235 

4236 with the shear stress :math:`\\sigma_S`, the coefficient of friction 

4237 :math:`\\mu`, the normal stress :math:`\\sigma_N`, and the pore fluid 

4238 pressure change :math:`\\Delta p`. Each receiver point is characterized 

4239 by its geographical coordinates, and depth. The required receiver plane 

4240 orientation is defined by ``strike``, ``dip``, and ``rake``. The 

4241 Coulomb failure stress change is calculated for a given time after 

4242 rupture origin time. 

4243 

4244 :param receiver_points: 

4245 Location of the receiver points in Northing, Easting, and depth in 

4246 [m]. 

4247 :type receiver_points: 

4248 :py:class:`~numpy.ndarray`: ``(n_receiver, 3)`` 

4249 

4250 :param friction: 

4251 Coefficient of friction. 

4252 :type friction: 

4253 float 

4254 

4255 :param pressure: 

4256 Pore pressure change in [Pa]. 

4257 :type pressure: 

4258 float 

4259 

4260 :param strike: 

4261 Strike of the receiver plane in [deg]. 

4262 :type strike: 

4263 float 

4264 

4265 :param dip: 

4266 Dip of the receiver plane in [deg]. 

4267 :type dip: 

4268 float 

4269 

4270 :param rake: 

4271 Rake of the receiver plane in [deg]. 

4272 :type rake: 

4273 float 

4274 

4275 :param time: 

4276 Time after origin [s], for which the resulting :math:`\\Delta 

4277 \\Sigma_c` is computed. If not given, :math:`\\Delta \\Sigma_c` is 

4278 derived based on the final static slip. 

4279 :type time: 

4280 float 

4281 

4282 :returns: 

4283 The Coulomb failure stress change :math:`\\Delta \\Sigma_c` at each 

4284 receiver point in [Pa]. 

4285 :rtype: 

4286 :py:class:`~numpy.ndarray`: ``(n_receiver,)`` 

4287 ''' 

4288 # dislocation at given time 

4289 source_slip = self.get_slip(time=time, scale_slip=True) 

4290 

4291 # source planes 

4292 source_patches = num.array([ 

4293 src.source_patch() for src in self.patches]) 

4294 

4295 # earth model 

4296 lambda_mean = num.mean([src.lamb for src in self.patches]) 

4297 mu_mean = num.mean([src.shearmod for src in self.patches]) 

4298 

4299 # Dislocation and spatial derivatives from okada in NED 

4300 results = okada_ext.okada( 

4301 source_patches, 

4302 source_slip, 

4303 receiver_points, 

4304 lambda_mean, 

4305 mu_mean, 

4306 rotate_sdn=False, # TODO Check 

4307 stack_sources=0, # TODO Check 

4308 *args, **kwargs) 

4309 

4310 # resolve stress tensor (sum!) 

4311 diag_ind = [0, 4, 8] 

4312 kron = num.zeros(9) 

4313 kron[diag_ind] = 1. 

4314 kron = kron[num.newaxis, num.newaxis, :] 

4315 

4316 eps = 0.5 * ( 

4317 results[:, :, 3:] + 

4318 results[:, :, (3, 6, 9, 4, 7, 10, 5, 8, 11)]) 

4319 

4320 dilatation \ 

4321 = eps[:, :, diag_ind].sum(axis=-1)[:, :, num.newaxis] 

4322 

4323 stress = kron*lambda_mean*dilatation + 2.*mu_mean*eps 

4324 

4325 # superposed stress of all sources at receiver locations 

4326 stress_sum = num.sum(stress, axis=0) 

4327 

4328 # get shear and normal stress from stress tensor 

4329 strike_rad = d2r * strike 

4330 dip_rad = d2r * dip 

4331 rake_rad = d2r * rake 

4332 

4333 n_rec = receiver_points.shape[0] 

4334 stress_normal = num.zeros(n_rec) 

4335 tau = num.zeros(n_rec) 

4336 

4337 # Get vectors in receiver fault normal (ns), strike (rst) and 

4338 # dip (rdi) direction 

4339 ns = num.zeros(3) 

4340 rst = num.zeros(3) 

4341 rdi = num.zeros(3) 

4342 

4343 ns[0] = num.sin(dip_rad) * num.cos(strike_rad + 0.5 * num.pi) 

4344 ns[1] = num.sin(dip_rad) * num.sin(strike_rad + 0.5 * num.pi) 

4345 ns[2] = -num.cos(dip_rad) 

4346 

4347 rst[0] = num.cos(strike_rad) 

4348 rst[1] = num.sin(strike_rad) 

4349 rst[2] = 0.0 

4350 

4351 rdi[0] = num.cos(dip_rad) * num.cos(strike_rad + 0.5 * num.pi) 

4352 rdi[1] = num.cos(dip_rad) * num.sin(strike_rad + 0.5 * num.pi) 

4353 rdi[2] = num.sin(dip_rad) 

4354 

4355 ts = rst * num.cos(rake_rad) - rdi * num.sin(rake_rad) 

4356 

4357 stress_normal = num.sum( 

4358 num.tile(ns, 3) * stress_sum * num.repeat(ns, 3), axis=1) 

4359 

4360 tau = num.sum( 

4361 num.tile(ts, 3) * stress_sum * num.repeat(ns, 3), axis=1) 

4362 

4363 # calculate cfs using formula above and return 

4364 return tau + friction * (stress_normal + pressure) 

4365 

4366 

4367class DoubleDCSource(SourceWithMagnitude): 

4368 ''' 

4369 Two double-couple point sources separated in space and time. 

4370 Moment share between the sub-sources is controlled by the 

4371 parameter mix. 

4372 The position of the subsources is dependent on the moment 

4373 distribution between the two sources. Depth, east and north 

4374 shift are given for the centroid between the two double-couples. 

4375 The subsources will positioned according to their moment shares 

4376 around this centroid position. 

4377 This is done according to their delta parameters, which are 

4378 therefore in relation to that centroid. 

4379 Note that depth of the subsources therefore can be 

4380 depth+/-delta_depth. For shallow earthquakes therefore 

4381 the depth has to be chosen deeper to avoid sampling 

4382 above surface. 

4383 ''' 

4384 

4385 strike1 = Float.T( 

4386 default=0.0, 

4387 help='strike direction in [deg], measured clockwise from north') 

4388 

4389 dip1 = Float.T( 

4390 default=90.0, 

4391 help='dip angle in [deg], measured downward from horizontal') 

4392 

4393 azimuth = Float.T( 

4394 default=0.0, 

4395 help='azimuth to second double-couple [deg], ' 

4396 'measured at first, clockwise from north') 

4397 

4398 rake1 = Float.T( 

4399 default=0.0, 

4400 help='rake angle in [deg], ' 

4401 'measured counter-clockwise from right-horizontal ' 

4402 'in on-plane view') 

4403 

4404 strike2 = Float.T( 

4405 default=0.0, 

4406 help='strike direction in [deg], measured clockwise from north') 

4407 

4408 dip2 = Float.T( 

4409 default=90.0, 

4410 help='dip angle in [deg], measured downward from horizontal') 

4411 

4412 rake2 = Float.T( 

4413 default=0.0, 

4414 help='rake angle in [deg], ' 

4415 'measured counter-clockwise from right-horizontal ' 

4416 'in on-plane view') 

4417 

4418 delta_time = Float.T( 

4419 default=0.0, 

4420 help='separation of double-couples in time (t2-t1) [s]') 

4421 

4422 delta_depth = Float.T( 

4423 default=0.0, 

4424 help='difference in depth (z2-z1) [m]') 

4425 

4426 distance = Float.T( 

4427 default=0.0, 

4428 help='distance between the two double-couples [m]') 

4429 

4430 mix = Float.T( 

4431 default=0.5, 

4432 help='how to distribute the moment to the two doublecouples ' 

4433 'mix=0 -> m1=1 and m2=0; mix=1 -> m1=0, m2=1') 

4434 

4435 stf1 = STF.T( 

4436 optional=True, 

4437 help='Source time function of subsource 1 ' 

4438 '(if given, overrides STF from attribute :py:gattr:`Source.stf`)') 

4439 

4440 stf2 = STF.T( 

4441 optional=True, 

4442 help='Source time function of subsource 2 ' 

4443 '(if given, overrides STF from attribute :py:gattr:`Source.stf`)') 

4444 

4445 discretized_source_class = meta.DiscretizedMTSource 

4446 

4447 def base_key(self): 

4448 return ( 

4449 self.time, self.depth, self.lat, self.north_shift, 

4450 self.lon, self.east_shift, type(self).__name__) + \ 

4451 self.effective_stf1_pre().base_key() + \ 

4452 self.effective_stf2_pre().base_key() + ( 

4453 self.strike1, self.dip1, self.rake1, 

4454 self.strike2, self.dip2, self.rake2, 

4455 self.delta_time, self.delta_depth, 

4456 self.azimuth, self.distance, self.mix) 

4457 

4458 def get_factor(self): 

4459 return self.moment 

4460 

4461 def effective_stf1_pre(self): 

4462 return self.stf1 or self.stf or g_unit_pulse 

4463 

4464 def effective_stf2_pre(self): 

4465 return self.stf2 or self.stf or g_unit_pulse 

4466 

4467 def effective_stf_post(self): 

4468 return g_unit_pulse 

4469 

4470 def split(self): 

4471 a1 = 1.0 - self.mix 

4472 a2 = self.mix 

4473 delta_north = math.cos(self.azimuth * d2r) * self.distance 

4474 delta_east = math.sin(self.azimuth * d2r) * self.distance 

4475 

4476 dc1 = DCSource( 

4477 lat=self.lat, 

4478 lon=self.lon, 

4479 time=self.time - self.delta_time * a2, 

4480 north_shift=self.north_shift - delta_north * a2, 

4481 east_shift=self.east_shift - delta_east * a2, 

4482 depth=self.depth - self.delta_depth * a2, 

4483 moment=self.moment * a1, 

4484 strike=self.strike1, 

4485 dip=self.dip1, 

4486 rake=self.rake1, 

4487 stf=self.stf1 or self.stf) 

4488 

4489 dc2 = DCSource( 

4490 lat=self.lat, 

4491 lon=self.lon, 

4492 time=self.time + self.delta_time * a1, 

4493 north_shift=self.north_shift + delta_north * a1, 

4494 east_shift=self.east_shift + delta_east * a1, 

4495 depth=self.depth + self.delta_depth * a1, 

4496 moment=self.moment * a2, 

4497 strike=self.strike2, 

4498 dip=self.dip2, 

4499 rake=self.rake2, 

4500 stf=self.stf2 or self.stf) 

4501 

4502 return [dc1, dc2] 

4503 

4504 def discretize_basesource(self, store, target=None): 

4505 a1 = 1.0 - self.mix 

4506 a2 = self.mix 

4507 mot1 = pmt.MomentTensor(strike=self.strike1, dip=self.dip1, 

4508 rake=self.rake1, scalar_moment=a1) 

4509 mot2 = pmt.MomentTensor(strike=self.strike2, dip=self.dip2, 

4510 rake=self.rake2, scalar_moment=a2) 

4511 

4512 delta_north = math.cos(self.azimuth * d2r) * self.distance 

4513 delta_east = math.sin(self.azimuth * d2r) * self.distance 

4514 

4515 times1, amplitudes1 = self.effective_stf1_pre().discretize_t( 

4516 store.config.deltat, self.time - self.delta_time * a2) 

4517 

4518 times2, amplitudes2 = self.effective_stf2_pre().discretize_t( 

4519 store.config.deltat, self.time + self.delta_time * a1) 

4520 

4521 nt1 = times1.size 

4522 nt2 = times2.size 

4523 

4524 ds = meta.DiscretizedMTSource( 

4525 lat=self.lat, 

4526 lon=self.lon, 

4527 times=num.concatenate((times1, times2)), 

4528 north_shifts=num.concatenate(( 

4529 num.repeat(self.north_shift - delta_north * a2, nt1), 

4530 num.repeat(self.north_shift + delta_north * a1, nt2))), 

4531 east_shifts=num.concatenate(( 

4532 num.repeat(self.east_shift - delta_east * a2, nt1), 

4533 num.repeat(self.east_shift + delta_east * a1, nt2))), 

4534 depths=num.concatenate(( 

4535 num.repeat(self.depth - self.delta_depth * a2, nt1), 

4536 num.repeat(self.depth + self.delta_depth * a1, nt2))), 

4537 m6s=num.vstack(( 

4538 mot1.m6()[num.newaxis, :] * amplitudes1[:, num.newaxis], 

4539 mot2.m6()[num.newaxis, :] * amplitudes2[:, num.newaxis]))) 

4540 

4541 return ds 

4542 

4543 def pyrocko_moment_tensor(self, store=None, target=None): 

4544 a1 = 1.0 - self.mix 

4545 a2 = self.mix 

4546 mot1 = pmt.MomentTensor(strike=self.strike1, dip=self.dip1, 

4547 rake=self.rake1, 

4548 scalar_moment=a1 * self.moment) 

4549 mot2 = pmt.MomentTensor(strike=self.strike2, dip=self.dip2, 

4550 rake=self.rake2, 

4551 scalar_moment=a2 * self.moment) 

4552 return pmt.MomentTensor(m=mot1.m() + mot2.m()) 

4553 

4554 def pyrocko_event(self, store=None, target=None, **kwargs): 

4555 return SourceWithMagnitude.pyrocko_event( 

4556 self, store, target, 

4557 moment_tensor=self.pyrocko_moment_tensor(store, target), 

4558 **kwargs) 

4559 

4560 @classmethod 

4561 def from_pyrocko_event(cls, ev, **kwargs): 

4562 d = {} 

4563 mt = ev.moment_tensor 

4564 if mt: 

4565 (strike, dip, rake), _ = mt.both_strike_dip_rake() 

4566 d.update( 

4567 strike1=float(strike), 

4568 dip1=float(dip), 

4569 rake1=float(rake), 

4570 strike2=float(strike), 

4571 dip2=float(dip), 

4572 rake2=float(rake), 

4573 mix=0.0, 

4574 magnitude=float(mt.moment_magnitude())) 

4575 

4576 d.update(kwargs) 

4577 source = super(DoubleDCSource, cls).from_pyrocko_event(ev, **d) 

4578 source.stf1 = source.stf 

4579 source.stf2 = HalfSinusoidSTF(effective_duration=0.) 

4580 source.stf = None 

4581 return source 

4582 

4583 

4584class RingfaultSource(SourceWithMagnitude): 

4585 ''' 

4586 A ring fault with vertical doublecouples. 

4587 ''' 

4588 

4589 diameter = Float.T( 

4590 default=1.0, 

4591 help='diameter of the ring in [m]') 

4592 

4593 sign = Float.T( 

4594 default=1.0, 

4595 help='inside of the ring moves up (+1) or down (-1)') 

4596 

4597 strike = Float.T( 

4598 default=0.0, 

4599 help='strike direction of the ring plane, clockwise from north,' 

4600 ' in [deg]') 

4601 

4602 dip = Float.T( 

4603 default=0.0, 

4604 help='dip angle of the ring plane from horizontal in [deg]') 

4605 

4606 npointsources = Int.T( 

4607 default=360, 

4608 help='number of point sources to use') 

4609 

4610 discretized_source_class = meta.DiscretizedMTSource 

4611 

4612 def base_key(self): 

4613 return Source.base_key(self) + ( 

4614 self.strike, self.dip, self.diameter, self.npointsources) 

4615 

4616 def get_factor(self): 

4617 return self.sign * self.moment 

4618 

4619 def discretize_basesource(self, store=None, target=None): 

4620 n = self.npointsources 

4621 phi = num.linspace(0, 2.0 * num.pi, n, endpoint=False) 

4622 

4623 points = num.zeros((n, 3)) 

4624 points[:, 0] = num.cos(phi) * 0.5 * self.diameter 

4625 points[:, 1] = num.sin(phi) * 0.5 * self.diameter 

4626 

4627 rotmat = pmt.euler_to_matrix(self.dip * d2r, self.strike * d2r, 0.0) 

4628 points = num.dot(rotmat.T, points.T).T # !!! ? 

4629 

4630 points[:, 0] += self.north_shift 

4631 points[:, 1] += self.east_shift 

4632 points[:, 2] += self.depth 

4633 

4634 m = num.array(pmt.MomentTensor(strike=90., dip=90., rake=-90., 

4635 scalar_moment=1.0 / n).m()) 

4636 

4637 rotmats = num.transpose( 

4638 [[num.cos(phi), num.sin(phi), num.zeros(n)], 

4639 [-num.sin(phi), num.cos(phi), num.zeros(n)], 

4640 [num.zeros(n), num.zeros(n), num.ones(n)]], (2, 0, 1)) 

4641 

4642 ms = num.zeros((n, 3, 3)) 

4643 for i in range(n): 

4644 mtemp = num.dot(rotmats[i].T, num.dot(m, rotmats[i])) 

4645 ms[i, :, :] = num.dot(rotmat.T, num.dot(mtemp, rotmat)) 

4646 

4647 m6s = num.vstack((ms[:, 0, 0], ms[:, 1, 1], ms[:, 2, 2], 

4648 ms[:, 0, 1], ms[:, 0, 2], ms[:, 1, 2])).T 

4649 

4650 times, amplitudes = self.effective_stf_pre().discretize_t( 

4651 store.config.deltat, self.time) 

4652 

4653 nt = times.size 

4654 

4655 return meta.DiscretizedMTSource( 

4656 times=num.tile(times, n), 

4657 lat=self.lat, 

4658 lon=self.lon, 

4659 north_shifts=num.repeat(points[:, 0], nt), 

4660 east_shifts=num.repeat(points[:, 1], nt), 

4661 depths=num.repeat(points[:, 2], nt), 

4662 m6s=num.repeat(m6s, nt, axis=0) * num.tile( 

4663 amplitudes, n)[:, num.newaxis]) 

4664 

4665 

4666class CombiSource(Source): 

4667 ''' 

4668 Composite source model. 

4669 ''' 

4670 

4671 discretized_source_class = meta.DiscretizedMTSource 

4672 

4673 subsources = List.T(Source.T()) 

4674 

4675 def __init__(self, subsources=[], **kwargs): 

4676 if not subsources: 

4677 raise BadRequest( 

4678 'Need at least one sub-source to create a CombiSource object.') 

4679 

4680 lats = num.array( 

4681 [subsource.lat for subsource in subsources], dtype=float) 

4682 lons = num.array( 

4683 [subsource.lon for subsource in subsources], dtype=float) 

4684 

4685 lat, lon = lats[0], lons[0] 

4686 if not num.all(lats == lat) and num.all(lons == lon): 

4687 subsources = [s.clone() for s in subsources] 

4688 for subsource in subsources[1:]: 

4689 subsource.set_origin(lat, lon) 

4690 

4691 depth = float(num.mean([p.depth for p in subsources])) 

4692 time = float(num.mean([p.time for p in subsources])) 

4693 north_shift = float(num.mean([p.north_shift for p in subsources])) 

4694 east_shift = float(num.mean([p.east_shift for p in subsources])) 

4695 kwargs.update( 

4696 time=time, 

4697 lat=float(lat), 

4698 lon=float(lon), 

4699 north_shift=north_shift, 

4700 east_shift=east_shift, 

4701 depth=depth) 

4702 

4703 Source.__init__(self, subsources=subsources, **kwargs) 

4704 

4705 def get_factor(self): 

4706 return 1.0 

4707 

4708 def discretize_basesource(self, store, target=None): 

4709 dsources = [] 

4710 for sf in self.subsources: 

4711 ds = sf.discretize_basesource(store, target) 

4712 ds.m6s *= sf.get_factor() 

4713 dsources.append(ds) 

4714 

4715 return meta.DiscretizedMTSource.combine(dsources) 

4716 

4717 

4718class CombiSFSource(Source): 

4719 ''' 

4720 Composite source model. 

4721 ''' 

4722 

4723 discretized_source_class = meta.DiscretizedSFSource 

4724 

4725 subsources = List.T(Source.T()) 

4726 

4727 def __init__(self, subsources=[], **kwargs): 

4728 if not subsources: 

4729 raise BadRequest( 

4730 'Need at least one sub-source to create a CombiSFSource ' 

4731 'object.') 

4732 

4733 lats = num.array( 

4734 [subsource.lat for subsource in subsources], dtype=float) 

4735 lons = num.array( 

4736 [subsource.lon for subsource in subsources], dtype=float) 

4737 

4738 lat, lon = lats[0], lons[0] 

4739 if not num.all(lats == lat) and num.all(lons == lon): 

4740 subsources = [s.clone() for s in subsources] 

4741 for subsource in subsources[1:]: 

4742 subsource.set_origin(lat, lon) 

4743 

4744 depth = float(num.mean([p.depth for p in subsources])) 

4745 time = float(num.mean([p.time for p in subsources])) 

4746 north_shift = float(num.mean([p.north_shift for p in subsources])) 

4747 east_shift = float(num.mean([p.east_shift for p in subsources])) 

4748 kwargs.update( 

4749 time=time, 

4750 lat=float(lat), 

4751 lon=float(lon), 

4752 north_shift=north_shift, 

4753 east_shift=east_shift, 

4754 depth=depth) 

4755 

4756 Source.__init__(self, subsources=subsources, **kwargs) 

4757 

4758 def get_factor(self): 

4759 return 1.0 

4760 

4761 def discretize_basesource(self, store, target=None): 

4762 dsources = [] 

4763 for sf in self.subsources: 

4764 ds = sf.discretize_basesource(store, target) 

4765 ds.forces *= sf.get_factor() 

4766 dsources.append(ds) 

4767 

4768 return meta.DiscretizedSFSource.combine(dsources) 

4769 

4770 

4771class SFSource(Source): 

4772 ''' 

4773 A single force point source. 

4774 

4775 Supported GF schemes: `'elastic5'`. 

4776 ''' 

4777 

4778 discretized_source_class = meta.DiscretizedSFSource 

4779 

4780 fn = Float.T( 

4781 default=0., 

4782 help='northward component of single force [N]') 

4783 

4784 fe = Float.T( 

4785 default=0., 

4786 help='eastward component of single force [N]') 

4787 

4788 fd = Float.T( 

4789 default=0., 

4790 help='downward component of single force [N]') 

4791 

4792 def __init__(self, **kwargs): 

4793 Source.__init__(self, **kwargs) 

4794 

4795 def base_key(self): 

4796 return Source.base_key(self) + (self.fn, self.fe, self.fd) 

4797 

4798 def get_factor(self): 

4799 return 1.0 

4800 

4801 @property 

4802 def force(self): 

4803 return math.sqrt(self.fn**2 + self.fe**2 + self.fd**2) 

4804 

4805 def discretize_basesource(self, store, target=None): 

4806 times, amplitudes = self.effective_stf_pre().discretize_t( 

4807 store.config.deltat, self.time) 

4808 forces = amplitudes[:, num.newaxis] * num.array( 

4809 [[self.fn, self.fe, self.fd]], dtype=float) 

4810 

4811 return meta.DiscretizedSFSource(forces=forces, 

4812 **self._dparams_base_repeated(times)) 

4813 

4814 def pyrocko_event(self, store=None, target=None, **kwargs): 

4815 return Source.pyrocko_event( 

4816 self, store, target, 

4817 **kwargs) 

4818 

4819 @classmethod 

4820 def from_pyrocko_event(cls, ev, **kwargs): 

4821 d = {} 

4822 d.update(kwargs) 

4823 return super(SFSource, cls).from_pyrocko_event(ev, **d) 

4824 

4825 

4826class SimpleLandslideSource(Source): 

4827 ''' 

4828 A single force landslide source respecting conservation of momentum. 

4829 

4830 The landslide is modelled point-like in space but with individual source 

4831 time functions for each force component. The source time functions 

4832 :py:class:`SimpleLandslideSTF` impose the constraint that everything is at 

4833 rest before and after the event but are allowed to have different 

4834 acceleration and deceleration durations. It should thus be suitable as a 

4835 first order approximation of a rock fall or landslide. 

4836 For realistic landslides, the horizontal accelerations and decelerations 

4837 can be delayed with respect to the vertical ones but cannot precede them. 

4838 

4839 Supported GF schemes: `'elastic5'`. 

4840 ''' 

4841 

4842 discretized_source_class = meta.DiscretizedSFSource 

4843 

4844 stf_mode = STFMode.T( 

4845 default='pre', 

4846 help='SimpleLandslideSource only works with `stf_mode == "pre"`.') 

4847 

4848 impulse_n = Float.T( 

4849 default=0., 

4850 help='northward component of impulse [Ns]') 

4851 

4852 impulse_e = Float.T( 

4853 default=0., 

4854 help='eastward component of impulse [Ns]') 

4855 

4856 impulse_d = Float.T( 

4857 default=0., 

4858 help='downward component of impulse [Ns]') 

4859 

4860 azimuth = Float.T( 

4861 default=0., 

4862 help='azimuth direction of the mass movement [deg]') 

4863 

4864 stf_v = SimpleLandslideSTF.T( 

4865 default=SimpleLandslideSTF.D(), 

4866 help='source time function for vertical force component') 

4867 

4868 stf_h = SimpleLandslideSTF.T( 

4869 default=SimpleLandslideSTF.D(), 

4870 help='source time function for horizontal force component') 

4871 

4872 anchor_stf = StringChoice.T( 

4873 choices=['onset', 'centroid'], 

4874 default='onset', 

4875 help='``"onset"``: STFs start at origin time ``"centroid"``: STFs all ' 

4876 'start at the same time but so that the centroid is at the given ' 

4877 'origin time.') 

4878 

4879 def __init__(self, **kwargs): 

4880 Source.__init__(self, **kwargs) 

4881 

4882 def base_key(self): 

4883 return Source.base_key(self) + ( 

4884 self.impulse_n, self.impulse_e, self.impulse_d) \ 

4885 + self.stf_v.base_key() + self.stf_h.base_key() 

4886 

4887 def get_factor(self): 

4888 return 1.0 

4889 

4890 def discretize_basesource(self, store, target=None): 

4891 if self.stf_mode != 'pre': 

4892 raise Exception( 

4893 'SimpleLandslideSource: ' 

4894 'Only works with `stf_mode == "pre"`.') 

4895 

4896 if self.stf is not None: 

4897 raise Exception( 

4898 'SimpleLandslideSource: ' 

4899 'Setting `stf` is not supported: use `stf_v` and `stf_h`.') 

4900 

4901 if self.anchor_stf == 'centroid': 

4902 duration_acc = num.array([ 

4903 self.stf_h.duration_acceleration, 

4904 self.stf_h.duration_acceleration, 

4905 self.stf_v.duration_acceleration], dtype=float) 

4906 

4907 impulse = num.array([ 

4908 self.impulse_n, 

4909 self.impulse_e, 

4910 self.impulse_d], dtype=float) 

4911 

4912 tshift_centroid = \ 

4913 - num.sum(duration_acc * impulse**2) \ 

4914 / num.sum(impulse**2) 

4915 

4916 elif self.anchor_stf == 'onset': 

4917 tshift_centroid = 0.0 

4918 

4919 times, amplitudes = self.stf_v.discretize_t( 

4920 store.config.deltat, 

4921 self.time + tshift_centroid) 

4922 

4923 forces_v = num.zeros((times.size, 3)) 

4924 forces_v[:, 2] = amplitudes * self.impulse_d 

4925 

4926 dsource_v = meta.DiscretizedSFSource( 

4927 forces=forces_v, 

4928 **self._dparams_base_repeated(times)) 

4929 

4930 times, amplitudes = self.stf_h.discretize_t( 

4931 store.config.deltat, 

4932 self.time + tshift_centroid) 

4933 

4934 forces_h = num.zeros((times.size, 3)) 

4935 forces_h[:, 0] = \ 

4936 amplitudes * self.impulse_n 

4937 forces_h[:, 1] = \ 

4938 amplitudes * self.impulse_e 

4939 

4940 dsource_h = meta.DiscretizedSFSource( 

4941 forces=forces_h, 

4942 **self._dparams_base_repeated(times)) 

4943 

4944 return meta.DiscretizedSFSource.combine([dsource_v, dsource_h]) 

4945 

4946 def pyrocko_event(self, store=None, target=None, **kwargs): 

4947 return Source.pyrocko_event( 

4948 self, store, target, 

4949 **kwargs) 

4950 

4951 @classmethod 

4952 def from_pyrocko_event(cls, ev, **kwargs): 

4953 d = {} 

4954 d.update(kwargs) 

4955 return super(SimpleLandslideSource, cls).from_pyrocko_event(ev, **d) 

4956 

4957 

4958class PorePressurePointSource(Source): 

4959 ''' 

4960 Excess pore pressure point source. 

4961 

4962 For poro-elastic initial value problem where an excess pore pressure is 

4963 brought into a small source volume. 

4964 ''' 

4965 

4966 discretized_source_class = meta.DiscretizedPorePressureSource 

4967 

4968 pp = Float.T( 

4969 default=1.0, 

4970 help='initial excess pore pressure in [Pa]') 

4971 

4972 def base_key(self): 

4973 return Source.base_key(self) 

4974 

4975 def get_factor(self): 

4976 return self.pp 

4977 

4978 def discretize_basesource(self, store, target=None): 

4979 return meta.DiscretizedPorePressureSource(pp=arr(1.0), 

4980 **self._dparams_base()) 

4981 

4982 

4983class PorePressureLineSource(Source): 

4984 ''' 

4985 Excess pore pressure line source. 

4986 

4987 The line source is centered at (north_shift, east_shift, depth). 

4988 ''' 

4989 

4990 discretized_source_class = meta.DiscretizedPorePressureSource 

4991 

4992 pp = Float.T( 

4993 default=1.0, 

4994 help='initial excess pore pressure in [Pa]') 

4995 

4996 length = Float.T( 

4997 default=0.0, 

4998 help='length of the line source [m]') 

4999 

5000 azimuth = Float.T( 

5001 default=0.0, 

5002 help='azimuth direction, clockwise from north [deg]') 

5003 

5004 dip = Float.T( 

5005 default=90., 

5006 help='dip direction, downward from horizontal [deg]') 

5007 

5008 def base_key(self): 

5009 return Source.base_key(self) + (self.azimuth, self.dip, self.length) 

5010 

5011 def get_factor(self): 

5012 return self.pp 

5013 

5014 def discretize_basesource(self, store, target=None): 

5015 

5016 n = 2 * int(math.ceil(self.length / num.min(store.config.deltas))) + 1 

5017 

5018 a = num.linspace(-0.5 * self.length, 0.5 * self.length, n) 

5019 

5020 sa = math.sin(self.azimuth * d2r) 

5021 ca = math.cos(self.azimuth * d2r) 

5022 sd = math.sin(self.dip * d2r) 

5023 cd = math.cos(self.dip * d2r) 

5024 

5025 points = num.zeros((n, 3)) 

5026 points[:, 0] = self.north_shift + a * ca * cd 

5027 points[:, 1] = self.east_shift + a * sa * cd 

5028 points[:, 2] = self.depth + a * sd 

5029 

5030 return meta.DiscretizedPorePressureSource( 

5031 times=num.full(n, self.time), 

5032 lat=self.lat, 

5033 lon=self.lon, 

5034 north_shifts=points[:, 0], 

5035 east_shifts=points[:, 1], 

5036 depths=points[:, 2], 

5037 pp=num.ones(n) / n) 

5038 

5039 

5040class Request(Object): 

5041 ''' 

5042 Synthetic seismogram computation request. 

5043 

5044 :: 

5045 

5046 Request(**kwargs) 

5047 Request(sources, targets, **kwargs) 

5048 ''' 

5049 

5050 sources = List.T( 

5051 Source.T(), 

5052 help='list of sources for which to produce synthetics.') 

5053 

5054 targets = List.T( 

5055 Target.T(), 

5056 help='list of targets for which to produce synthetics.') 

5057 

5058 @classmethod 

5059 def args2kwargs(cls, args): 

5060 if len(args) not in (0, 2, 3): 

5061 raise BadRequest('Invalid arguments.') 

5062 

5063 if len(args) == 2: 

5064 return dict(sources=args[0], targets=args[1]) 

5065 else: 

5066 return {} 

5067 

5068 def __init__(self, *args, **kwargs): 

5069 kwargs.update(self.args2kwargs(args)) 

5070 sources = kwargs.pop('sources', []) 

5071 targets = kwargs.pop('targets', []) 

5072 

5073 if isinstance(sources, Source): 

5074 sources = [sources] 

5075 

5076 if isinstance(targets, Target) or isinstance(targets, StaticTarget): 

5077 targets = [targets] 

5078 

5079 Object.__init__(self, sources=sources, targets=targets, **kwargs) 

5080 

5081 @property 

5082 def targets_dynamic(self): 

5083 return [t for t in self.targets if isinstance(t, Target)] 

5084 

5085 @property 

5086 def targets_static(self): 

5087 return [t for t in self.targets if isinstance(t, StaticTarget)] 

5088 

5089 @property 

5090 def has_dynamic(self): 

5091 return True if len(self.targets_dynamic) > 0 else False 

5092 

5093 @property 

5094 def has_statics(self): 

5095 return True if len(self.targets_static) > 0 else False 

5096 

5097 def subsources_map(self): 

5098 m = defaultdict(list) 

5099 for source in self.sources: 

5100 m[source.base_key()].append(source) 

5101 

5102 return m 

5103 

5104 def subtargets_map(self): 

5105 m = defaultdict(list) 

5106 for target in self.targets: 

5107 m[target.base_key()].append(target) 

5108 

5109 return m 

5110 

5111 def subrequest_map(self): 

5112 ms = self.subsources_map() 

5113 mt = self.subtargets_map() 

5114 m = {} 

5115 for (ks, ls) in ms.items(): 

5116 for (kt, lt) in mt.items(): 

5117 m[ks, kt] = (ls, lt) 

5118 

5119 return m 

5120 

5121 

5122class ProcessingStats(Object): 

5123 t_perc_get_store_and_receiver = Float.T(default=0.) 

5124 t_perc_discretize_source = Float.T(default=0.) 

5125 t_perc_make_base_seismogram = Float.T(default=0.) 

5126 t_perc_make_same_span = Float.T(default=0.) 

5127 t_perc_post_process = Float.T(default=0.) 

5128 t_perc_optimize = Float.T(default=0.) 

5129 t_perc_stack = Float.T(default=0.) 

5130 t_perc_static_get_store = Float.T(default=0.) 

5131 t_perc_static_discretize_basesource = Float.T(default=0.) 

5132 t_perc_static_sum_statics = Float.T(default=0.) 

5133 t_perc_static_post_process = Float.T(default=0.) 

5134 t_wallclock = Float.T(default=0.) 

5135 t_cpu = Float.T(default=0.) 

5136 n_read_blocks = Int.T(default=0) 

5137 n_results = Int.T(default=0) 

5138 n_subrequests = Int.T(default=0) 

5139 n_stores = Int.T(default=0) 

5140 n_records_stacked = Int.T(default=0) 

5141 

5142 

5143class Response(Object): 

5144 ''' 

5145 Resonse object to a synthetic seismogram computation request. 

5146 ''' 

5147 

5148 request = Request.T() 

5149 results_list = List.T(List.T(meta.SeismosizerResult.T())) 

5150 stats = ProcessingStats.T() 

5151 

5152 def pyrocko_traces(self): 

5153 ''' 

5154 Return a list of requested 

5155 :class:`~pyrocko.trace.Trace` instances. 

5156 ''' 

5157 

5158 traces = [] 

5159 for results in self.results_list: 

5160 for result in results: 

5161 if not isinstance(result, meta.Result): 

5162 continue 

5163 traces.append(result.trace.pyrocko_trace()) 

5164 

5165 return traces 

5166 

5167 def kite_scenes(self): 

5168 ''' 

5169 Return a list of requested 

5170 :class:`kite.Scene` instances. 

5171 ''' 

5172 kite_scenes = [] 

5173 for results in self.results_list: 

5174 for result in results: 

5175 if isinstance(result, meta.KiteSceneResult): 

5176 sc = result.get_scene() 

5177 kite_scenes.append(sc) 

5178 

5179 return kite_scenes 

5180 

5181 def static_results(self): 

5182 ''' 

5183 Return a list of requested 

5184 :class:`~pyrocko.gf.meta.StaticResult` instances. 

5185 ''' 

5186 statics = [] 

5187 for results in self.results_list: 

5188 for result in results: 

5189 if not isinstance(result, meta.StaticResult): 

5190 continue 

5191 statics.append(result) 

5192 

5193 return statics 

5194 

5195 def iter_results(self, get='pyrocko_traces'): 

5196 ''' 

5197 Generator function to iterate over results of request. 

5198 

5199 Yields associated :py:class:`Source`, 

5200 :class:`~pyrocko.gf.targets.Target`, 

5201 :class:`~pyrocko.trace.Trace` instances in each iteration. 

5202 ''' 

5203 

5204 for isource, source in enumerate(self.request.sources): 

5205 for itarget, target in enumerate(self.request.targets): 

5206 result = self.results_list[isource][itarget] 

5207 if get == 'pyrocko_traces': 

5208 yield source, target, result.trace.pyrocko_trace() 

5209 elif get == 'results': 

5210 yield source, target, result 

5211 

5212 def snuffle(self, **kwargs): 

5213 ''' 

5214 Open *snuffler* with requested traces. 

5215 ''' 

5216 

5217 trace.snuffle(self.pyrocko_traces(), **kwargs) 

5218 

5219 

5220class Engine(Object): 

5221 ''' 

5222 Base class for synthetic seismogram calculators. 

5223 ''' 

5224 

5225 def get_store_ids(self): 

5226 ''' 

5227 Get list of available GF store IDs 

5228 ''' 

5229 

5230 return [] 

5231 

5232 

5233class Rule(object): 

5234 pass 

5235 

5236 

5237class VectorRule(Rule): 

5238 

5239 def __init__(self, quantity, differentiate=0, integrate=0): 

5240 self.components = [quantity + '.' + c for c in 'ned'] 

5241 self.differentiate = differentiate 

5242 self.integrate = integrate 

5243 

5244 def required_components(self, target): 

5245 n, e, d = self.components 

5246 sa, ca, sd, cd = target.get_sin_cos_factors() 

5247 

5248 comps = [] 

5249 if nonzero(ca * cd): 

5250 comps.append(n) 

5251 

5252 if nonzero(sa * cd): 

5253 comps.append(e) 

5254 

5255 if nonzero(sd): 

5256 comps.append(d) 

5257 

5258 return tuple(comps) 

5259 

5260 def apply_(self, target, base_seismogram): 

5261 n, e, d = self.components 

5262 sa, ca, sd, cd = target.get_sin_cos_factors() 

5263 

5264 if nonzero(ca * cd): 

5265 data = base_seismogram[n].data * (ca * cd) 

5266 deltat = base_seismogram[n].deltat 

5267 else: 

5268 data = 0.0 

5269 

5270 if nonzero(sa * cd): 

5271 data = data + base_seismogram[e].data * (sa * cd) 

5272 deltat = base_seismogram[e].deltat 

5273 

5274 if nonzero(sd): 

5275 data = data + base_seismogram[d].data * sd 

5276 deltat = base_seismogram[d].deltat 

5277 

5278 if self.differentiate: 

5279 data = util.diff_fd(self.differentiate, 4, deltat, data) 

5280 

5281 if self.integrate: 

5282 raise NotImplementedError('Integration is not implemented yet.') 

5283 

5284 return data 

5285 

5286 

5287class HorizontalVectorRule(Rule): 

5288 

5289 def __init__(self, quantity, differentiate=0, integrate=0): 

5290 self.components = [quantity + '.' + c for c in 'ne'] 

5291 self.differentiate = differentiate 

5292 self.integrate = integrate 

5293 

5294 def required_components(self, target): 

5295 n, e = self.components 

5296 sa, ca, _, _ = target.get_sin_cos_factors() 

5297 

5298 comps = [] 

5299 if nonzero(ca): 

5300 comps.append(n) 

5301 

5302 if nonzero(sa): 

5303 comps.append(e) 

5304 

5305 return tuple(comps) 

5306 

5307 def apply_(self, target, base_seismogram): 

5308 n, e = self.components 

5309 sa, ca, _, _ = target.get_sin_cos_factors() 

5310 

5311 if nonzero(ca): 

5312 data = base_seismogram[n].data * ca 

5313 else: 

5314 data = 0.0 

5315 

5316 if nonzero(sa): 

5317 data = data + base_seismogram[e].data * sa 

5318 

5319 if self.differentiate: 

5320 deltat = base_seismogram[e].deltat 

5321 data = util.diff_fd(self.differentiate, 4, deltat, data) 

5322 

5323 if self.integrate: 

5324 raise NotImplementedError('Integration is not implemented yet.') 

5325 

5326 return data 

5327 

5328 

5329class ScalarRule(Rule): 

5330 

5331 def __init__(self, quantity, differentiate=0): 

5332 self.c = quantity 

5333 

5334 def required_components(self, target): 

5335 return (self.c, ) 

5336 

5337 def apply_(self, target, base_seismogram): 

5338 data = base_seismogram[self.c].data.copy() 

5339 deltat = base_seismogram[self.c].deltat 

5340 if self.differentiate: 

5341 data = util.diff_fd(self.differentiate, 4, deltat, data) 

5342 

5343 return data 

5344 

5345 

5346class StaticDisplacement(Rule): 

5347 

5348 def required_components(self, target): 

5349 return tuple(['displacement.%s' % c for c in list('ned')]) 

5350 

5351 def apply_(self, target, base_statics): 

5352 if isinstance(target, SatelliteTarget): 

5353 los_fac = target.get_los_factors() 

5354 base_statics['displacement.los'] =\ 

5355 (los_fac[:, 0] * -base_statics['displacement.d'] + 

5356 los_fac[:, 1] * base_statics['displacement.e'] + 

5357 los_fac[:, 2] * base_statics['displacement.n']) 

5358 return base_statics 

5359 

5360 

5361channel_rules = { 

5362 'displacement': [VectorRule('displacement')], 

5363 'rotation_displacement': [VectorRule('rotation_displacement')], 

5364 'velocity': [ 

5365 VectorRule('velocity'), 

5366 VectorRule('displacement', differentiate=1)], 

5367 'acceleration': [ 

5368 VectorRule('acceleration'), 

5369 VectorRule('velocity', differentiate=1), 

5370 VectorRule('displacement', differentiate=2)], 

5371 'pore_pressure': [ScalarRule('pore_pressure')], 

5372 'vertical_tilt': [HorizontalVectorRule('vertical_tilt')], 

5373 'darcy_velocity': [VectorRule('darcy_velocity')], 

5374} 

5375 

5376static_rules = { 

5377 'displacement': [StaticDisplacement()] 

5378} 

5379 

5380 

5381class OutOfBoundsContext(Object): 

5382 source = Source.T() 

5383 target = Target.T() 

5384 distance = Float.T() 

5385 components = List.T(String.T()) 

5386 

5387 

5388def process_dynamic_timeseries(work, psources, ptargets, engine, nthreads=0): 

5389 dsource_cache = {} 

5390 tcounters = list(range(6)) 

5391 

5392 store_ids = set() 

5393 sources = set() 

5394 targets = set() 

5395 

5396 for itarget, target in enumerate(ptargets): 

5397 target._id = itarget 

5398 

5399 for w in work: 

5400 _, _, isources, itargets = w 

5401 

5402 sources.update([psources[isource] for isource in isources]) 

5403 targets.update([ptargets[itarget] for itarget in itargets]) 

5404 

5405 store_ids = set([t.store_id for t in targets]) 

5406 

5407 for isource, source in enumerate(psources): 

5408 

5409 components = set() 

5410 for itarget, target in enumerate(targets): 

5411 rule = engine.get_rule(source, target) 

5412 components.update(rule.required_components(target)) 

5413 

5414 for store_id in store_ids: 

5415 store_targets = [t for t in targets if t.store_id == store_id] 

5416 

5417 sample_rates = set([t.sample_rate for t in store_targets]) 

5418 interpolations = set([t.interpolation for t in store_targets]) 

5419 

5420 base_seismograms = [] 

5421 store_targets_out = [] 

5422 

5423 for samp_rate in sample_rates: 

5424 for interp in interpolations: 

5425 engine_targets = [ 

5426 t for t in store_targets if t.sample_rate == samp_rate 

5427 and t.interpolation == interp] 

5428 

5429 if not engine_targets: 

5430 continue 

5431 

5432 store_targets_out += engine_targets 

5433 

5434 base_seismograms += engine.base_seismograms( 

5435 source, 

5436 engine_targets, 

5437 components, 

5438 dsource_cache, 

5439 nthreads) 

5440 

5441 for iseis, seismogram in enumerate(base_seismograms): 

5442 for tr in seismogram.values(): 

5443 if tr.err != store.SeismosizerErrorEnum.SUCCESS: 

5444 e = SeismosizerError( 

5445 'Seismosizer failed with return code %i\n%s' % ( 

5446 tr.err, str( 

5447 OutOfBoundsContext( 

5448 source=source, 

5449 target=store_targets[iseis], 

5450 distance=source.distance_to( 

5451 store_targets[iseis]), 

5452 components=components)))) 

5453 raise e 

5454 

5455 for seismogram, target in zip(base_seismograms, store_targets_out): 

5456 

5457 try: 

5458 result = engine._post_process_dynamic( 

5459 seismogram, source, target) 

5460 except SeismosizerError as e: 

5461 result = e 

5462 

5463 yield (isource, target._id, result), tcounters 

5464 

5465 

5466def process_dynamic(work, psources, ptargets, engine, nthreads=0): 

5467 dsource_cache = {} 

5468 

5469 for w in work: 

5470 _, _, isources, itargets = w 

5471 

5472 sources = [psources[isource] for isource in isources] 

5473 targets = [ptargets[itarget] for itarget in itargets] 

5474 

5475 components = set() 

5476 for target in targets: 

5477 rule = engine.get_rule(sources[0], target) 

5478 components.update(rule.required_components(target)) 

5479 

5480 for isource, source in zip(isources, sources): 

5481 for itarget, target in zip(itargets, targets): 

5482 

5483 try: 

5484 base_seismogram, tcounters = engine.base_seismogram( 

5485 source, target, components, dsource_cache, nthreads) 

5486 except meta.OutOfBounds as e: 

5487 e.context = OutOfBoundsContext( 

5488 source=sources[0], 

5489 target=targets[0], 

5490 distance=sources[0].distance_to(targets[0]), 

5491 components=components) 

5492 raise 

5493 

5494 n_records_stacked = 0 

5495 t_optimize = 0.0 

5496 t_stack = 0.0 

5497 

5498 for _, tr in base_seismogram.items(): 

5499 n_records_stacked += tr.n_records_stacked 

5500 t_optimize += tr.t_optimize 

5501 t_stack += tr.t_stack 

5502 

5503 try: 

5504 result = engine._post_process_dynamic( 

5505 base_seismogram, source, target) 

5506 result.n_records_stacked = n_records_stacked 

5507 result.n_shared_stacking = len(sources) *\ 

5508 len(targets) 

5509 result.t_optimize = t_optimize 

5510 result.t_stack = t_stack 

5511 except SeismosizerError as e: 

5512 result = e 

5513 

5514 tcounters.append(xtime()) 

5515 yield (isource, itarget, result), tcounters 

5516 

5517 

5518def process_static(work, psources, ptargets, engine, nthreads=0): 

5519 for w in work: 

5520 _, _, isources, itargets = w 

5521 

5522 sources = [psources[isource] for isource in isources] 

5523 targets = [ptargets[itarget] for itarget in itargets] 

5524 

5525 for isource, source in zip(isources, sources): 

5526 for itarget, target in zip(itargets, targets): 

5527 components = engine.get_rule(source, target)\ 

5528 .required_components(target) 

5529 

5530 try: 

5531 base_statics, tcounters = engine.base_statics( 

5532 source, target, components, nthreads) 

5533 except meta.OutOfBounds as e: 

5534 e.context = OutOfBoundsContext( 

5535 source=sources[0], 

5536 target=targets[0], 

5537 distance=float('nan'), 

5538 components=components) 

5539 raise 

5540 result = engine._post_process_statics( 

5541 base_statics, source, target) 

5542 tcounters.append(xtime()) 

5543 

5544 yield (isource, itarget, result), tcounters 

5545 

5546 

5547class LocalEngine(Engine): 

5548 ''' 

5549 Offline synthetic seismogram calculator. 

5550 

5551 :param use_env: if ``True``, fill :py:attr:`store_superdirs` and 

5552 :py:attr:`store_dirs` with paths set in environment variables 

5553 GF_STORE_SUPERDIRS and GF_STORE_DIRS. 

5554 :param use_config: if ``True``, fill :py:attr:`store_superdirs` and 

5555 :py:attr:`store_dirs` with paths set in the user's config file. 

5556 

5557 The config file can be found at :file:`~/.pyrocko/config.pf` 

5558 

5559 .. code-block :: python 

5560 

5561 gf_store_dirs: ['/home/pyrocko/gf_stores/ak135/'] 

5562 gf_store_superdirs: ['/home/pyrocko/gf_stores/'] 

5563 ''' 

5564 

5565 store_superdirs = List.T( 

5566 String.T(), 

5567 help="directories which are searched for Green's function stores") 

5568 

5569 store_dirs = List.T( 

5570 String.T(), 

5571 help="additional individual Green's function store directories") 

5572 

5573 default_store_id = String.T( 

5574 optional=True, 

5575 help='default store ID to be used when a request does not provide ' 

5576 'one') 

5577 

5578 nthreads = Int.T( 

5579 default=1, 

5580 help='default number of threads to utilize') 

5581 

5582 def __init__(self, **kwargs): 

5583 use_env = kwargs.pop('use_env', False) 

5584 use_config = kwargs.pop('use_config', False) 

5585 Engine.__init__(self, **kwargs) 

5586 if use_env: 

5587 env_store_superdirs = os.environ.get('GF_STORE_SUPERDIRS', '') 

5588 env_store_dirs = os.environ.get('GF_STORE_DIRS', '') 

5589 if env_store_superdirs: 

5590 self.store_superdirs.extend(env_store_superdirs.split(':')) 

5591 

5592 if env_store_dirs: 

5593 self.store_dirs.extend(env_store_dirs.split(':')) 

5594 

5595 if use_config: 

5596 c = config.config() 

5597 self.store_superdirs.extend(c.gf_store_superdirs) 

5598 self.store_dirs.extend(c.gf_store_dirs) 

5599 

5600 self._check_store_dirs_type() 

5601 self._id_to_store_dir = {} 

5602 self._open_stores = {} 

5603 self._effective_default_store_id = None 

5604 

5605 def _check_store_dirs_type(self): 

5606 for sdir in ['store_dirs', 'store_superdirs']: 

5607 if not isinstance(self.__getattribute__(sdir), list): 

5608 raise TypeError('{} of {} is not of type list'.format( 

5609 sdir, self.__class__.__name__)) 

5610 

5611 def _get_store_id(self, store_dir): 

5612 store_ = store.Store(store_dir) 

5613 store_id = store_.config.id 

5614 store_.close() 

5615 return store_id 

5616 

5617 def _looks_like_store_dir(self, store_dir): 

5618 return os.path.isdir(store_dir) and \ 

5619 all(os.path.isfile(pjoin(store_dir, x)) for x in 

5620 ('index', 'traces', 'config')) 

5621 

5622 def iter_store_dirs(self): 

5623 store_dirs = set() 

5624 for d in self.store_superdirs: 

5625 if not os.path.exists(d): 

5626 logger.warning('store_superdir not available: %s' % d) 

5627 continue 

5628 

5629 for entry in os.listdir(d): 

5630 store_dir = os.path.realpath(pjoin(d, entry)) 

5631 if self._looks_like_store_dir(store_dir): 

5632 store_dirs.add(store_dir) 

5633 

5634 for store_dir in self.store_dirs: 

5635 store_dirs.add(os.path.realpath(store_dir)) 

5636 

5637 return store_dirs 

5638 

5639 def _scan_stores(self): 

5640 for store_dir in self.iter_store_dirs(): 

5641 store_id = self._get_store_id(store_dir) 

5642 if store_id not in self._id_to_store_dir: 

5643 self._id_to_store_dir[store_id] = store_dir 

5644 else: 

5645 if store_dir != self._id_to_store_dir[store_id]: 

5646 raise DuplicateStoreId( 

5647 'GF store ID %s is used in (at least) two ' 

5648 'different stores. Locations are: %s and %s' % 

5649 (store_id, self._id_to_store_dir[store_id], store_dir)) 

5650 

5651 def get_store_dir(self, store_id): 

5652 ''' 

5653 Lookup directory given a GF store ID. 

5654 ''' 

5655 

5656 if store_id not in self._id_to_store_dir: 

5657 self._scan_stores() 

5658 

5659 if store_id not in self._id_to_store_dir: 

5660 raise NoSuchStore( 

5661 store_id, 

5662 self.store_dirs, 

5663 self.store_superdirs, 

5664 sorted(self._id_to_store_dir.keys())) 

5665 

5666 return self._id_to_store_dir[store_id] 

5667 

5668 def get_store_ids(self): 

5669 ''' 

5670 Get list of available store IDs. 

5671 ''' 

5672 

5673 self._scan_stores() 

5674 return sorted(self._id_to_store_dir.keys()) 

5675 

5676 def effective_default_store_id(self): 

5677 if self._effective_default_store_id is None: 

5678 if self.default_store_id is None: 

5679 store_ids = self.get_store_ids() 

5680 if len(store_ids) == 1: 

5681 self._effective_default_store_id = self.get_store_ids()[0] 

5682 else: 

5683 raise NoDefaultStoreSet() 

5684 else: 

5685 self._effective_default_store_id = self.default_store_id 

5686 

5687 return self._effective_default_store_id 

5688 

5689 def get_store(self, store_id=None): 

5690 ''' 

5691 Get a store from the engine. 

5692 

5693 :param store_id: identifier of the store (optional) 

5694 :returns: :py:class:`~pyrocko.gf.store.Store` object 

5695 

5696 If no ``store_id`` is provided the store 

5697 associated with the :py:gattr:`default_store_id` is returned. 

5698 Raises :py:exc:`NoDefaultStoreSet` if :py:gattr:`default_store_id` is 

5699 undefined. 

5700 ''' 

5701 

5702 if store_id is None: 

5703 store_id = self.effective_default_store_id() 

5704 

5705 if store_id not in self._open_stores: 

5706 store_dir = self.get_store_dir(store_id) 

5707 self._open_stores[store_id] = store.Store(store_dir) 

5708 

5709 return self._open_stores[store_id] 

5710 

5711 def get_store_config(self, store_id): 

5712 store = self.get_store(store_id) 

5713 return store.config 

5714 

5715 def get_store_extra(self, store_id, key): 

5716 store = self.get_store(store_id) 

5717 return store.get_extra(key) 

5718 

5719 def close_cashed_stores(self): 

5720 ''' 

5721 Close and remove ids from cashed stores. 

5722 ''' 

5723 store_ids = [] 

5724 for store_id, store_ in self._open_stores.items(): 

5725 store_.close() 

5726 store_ids.append(store_id) 

5727 

5728 for store_id in store_ids: 

5729 self._open_stores.pop(store_id) 

5730 

5731 def get_rule(self, source, target): 

5732 cprovided = self.get_store(target.store_id).get_provided_components() 

5733 

5734 if isinstance(target, StaticTarget): 

5735 quantity = target.quantity 

5736 available_rules = static_rules 

5737 elif isinstance(target, Target): 

5738 quantity = target.effective_quantity() 

5739 available_rules = channel_rules 

5740 

5741 try: 

5742 for rule in available_rules[quantity]: 

5743 cneeded = rule.required_components(target) 

5744 if all(c in cprovided for c in cneeded): 

5745 return rule 

5746 

5747 except KeyError: 

5748 pass 

5749 

5750 raise BadRequest( 

5751 'No rule to calculate "%s" with GFs from store "%s" ' 

5752 'for source model "%s".' % ( 

5753 target.effective_quantity(), 

5754 target.store_id, 

5755 source.__class__.__name__)) 

5756 

5757 def _cached_discretize_basesource(self, source, store, cache, target): 

5758 if (source, store) not in cache: 

5759 cache[source, store] = source.discretize_basesource(store, target) 

5760 

5761 return cache[source, store] 

5762 

5763 def base_seismograms(self, source, targets, components, dsource_cache, 

5764 nthreads=None): 

5765 

5766 target = targets[0] 

5767 

5768 interp = set([t.interpolation for t in targets]) 

5769 if len(interp) > 1: 

5770 raise BadRequest('Targets have different interpolation schemes.') 

5771 

5772 rates = set([t.sample_rate for t in targets]) 

5773 if len(rates) > 1: 

5774 raise BadRequest('Targets have different sample rates.') 

5775 

5776 store_ = self.get_store(target.store_id) 

5777 receivers = [t.receiver(store_) for t in targets] 

5778 

5779 if target.sample_rate is not None: 

5780 deltat = 1. / target.sample_rate 

5781 rate = target.sample_rate 

5782 else: 

5783 deltat = None 

5784 rate = store_.config.sample_rate 

5785 

5786 tmin = num.fromiter( 

5787 (t.tmin for t in targets), dtype=float, count=len(targets)) 

5788 tmax = num.fromiter( 

5789 (t.tmax for t in targets), dtype=float, count=len(targets)) 

5790 

5791 mask = num.logical_and(num.isfinite(tmin), num.isfinite(tmax)) 

5792 

5793 itmin = num.zeros_like(tmin, dtype=num.int64) 

5794 itmax = num.zeros_like(tmin, dtype=num.int64) 

5795 nsamples = num.full_like(tmin, -1, dtype=num.int64) 

5796 

5797 itmin[mask] = num.floor(tmin[mask] * rate).astype(num.int64) 

5798 itmax[mask] = num.ceil(tmax[mask] * rate).astype(num.int64) 

5799 nsamples = itmax - itmin + 1 

5800 nsamples[num.logical_not(mask)] = -1 

5801 

5802 base_source = self._cached_discretize_basesource( 

5803 source, store_, dsource_cache, target) 

5804 

5805 base_seismograms = store_.calc_seismograms( 

5806 base_source, receivers, components, 

5807 deltat=deltat, 

5808 itmin=itmin, nsamples=nsamples, 

5809 interpolation=target.interpolation, 

5810 optimization=target.optimization, 

5811 nthreads=nthreads if nthreads is not None else 1) 

5812 

5813 for i, base_seismogram in enumerate(base_seismograms): 

5814 base_seismograms[i] = store.make_same_span(base_seismogram) 

5815 

5816 return base_seismograms 

5817 

5818 def base_seismogram(self, source, target, components, dsource_cache, 

5819 nthreads): 

5820 

5821 tcounters = [xtime()] 

5822 

5823 store_ = self.get_store(target.store_id) 

5824 receiver = target.receiver(store_) 

5825 

5826 if target.tmin and target.tmax is not None: 

5827 rate = store_.config.sample_rate 

5828 itmin = int(num.floor(target.tmin * rate)) 

5829 itmax = int(num.ceil(target.tmax * rate)) 

5830 nsamples = itmax - itmin + 1 

5831 else: 

5832 itmin = None 

5833 nsamples = None 

5834 

5835 tcounters.append(xtime()) 

5836 base_source = self._cached_discretize_basesource( 

5837 source, store_, dsource_cache, target) 

5838 

5839 tcounters.append(xtime()) 

5840 

5841 if target.sample_rate is not None: 

5842 deltat = 1. / target.sample_rate 

5843 else: 

5844 deltat = None 

5845 

5846 base_seismogram = store_.seismogram( 

5847 base_source, receiver, components, 

5848 deltat=deltat, 

5849 itmin=itmin, nsamples=nsamples, 

5850 interpolation=target.interpolation, 

5851 optimization=target.optimization, 

5852 nthreads=nthreads) 

5853 

5854 tcounters.append(xtime()) 

5855 

5856 base_seismogram = store.make_same_span(base_seismogram) 

5857 

5858 tcounters.append(xtime()) 

5859 

5860 return base_seismogram, tcounters 

5861 

5862 def base_statics(self, source, target, components, nthreads): 

5863 tcounters = [xtime()] 

5864 store_ = self.get_store(target.store_id) 

5865 

5866 if target.tsnapshot is not None: 

5867 rate = store_.config.sample_rate 

5868 itsnapshot = int(num.floor(target.tsnapshot * rate)) 

5869 else: 

5870 itsnapshot = None 

5871 tcounters.append(xtime()) 

5872 

5873 base_source = source.discretize_basesource(store_, target=target) 

5874 

5875 tcounters.append(xtime()) 

5876 

5877 base_statics = store_.statics( 

5878 base_source, 

5879 target, 

5880 itsnapshot, 

5881 components, 

5882 target.interpolation, 

5883 nthreads) 

5884 

5885 tcounters.append(xtime()) 

5886 

5887 return base_statics, tcounters 

5888 

5889 def _post_process_dynamic(self, base_seismogram, source, target): 

5890 base_any = next(iter(base_seismogram.values())) 

5891 deltat = base_any.deltat 

5892 itmin = base_any.itmin 

5893 

5894 rule = self.get_rule(source, target) 

5895 data = rule.apply_(target, base_seismogram) 

5896 

5897 factor = source.get_factor() * target.get_factor() 

5898 if factor != 1.0: 

5899 data = data * factor 

5900 

5901 stf = source.effective_stf_post() 

5902 

5903 times, amplitudes = stf.discretize_t( 

5904 deltat, 0.0) 

5905 

5906 # repeat end point to prevent boundary effects 

5907 padded_data = num.empty(data.size + amplitudes.size, dtype=float) 

5908 padded_data[:data.size] = data 

5909 padded_data[data.size:] = data[-1] 

5910 data = num.convolve(amplitudes, padded_data) 

5911 

5912 tmin = itmin * deltat + times[0] 

5913 

5914 tr = meta.SeismosizerTrace( 

5915 codes=target.codes, 

5916 data=data[:-amplitudes.size], 

5917 deltat=deltat, 

5918 tmin=tmin) 

5919 

5920 return target.post_process(self, source, tr) 

5921 

5922 def _post_process_statics(self, base_statics, source, starget): 

5923 rule = self.get_rule(source, starget) 

5924 data = rule.apply_(starget, base_statics) 

5925 

5926 factor = source.get_factor() 

5927 if factor != 1.0: 

5928 for v in data.values(): 

5929 v *= factor 

5930 

5931 return starget.post_process(self, source, base_statics) 

5932 

5933 def process(self, *args, **kwargs): 

5934 ''' 

5935 Process a request. 

5936 

5937 :: 

5938 

5939 process(**kwargs) 

5940 process(request, **kwargs) 

5941 process(sources, targets, **kwargs) 

5942 

5943 The request can be given a a :py:class:`Request` object, or such an 

5944 object is created using ``Request(**kwargs)`` for convenience. 

5945 

5946 :returns: :py:class:`Response` object 

5947 ''' 

5948 

5949 if len(args) not in (0, 1, 2): 

5950 raise BadRequest('Invalid arguments.') 

5951 

5952 if len(args) == 1: 

5953 kwargs['request'] = args[0] 

5954 

5955 elif len(args) == 2: 

5956 kwargs.update(Request.args2kwargs(args)) 

5957 

5958 request = kwargs.pop('request', None) 

5959 status_callback = kwargs.pop('status_callback', None) 

5960 calc_timeseries = kwargs.pop('calc_timeseries', True) 

5961 

5962 nprocs = kwargs.pop('nprocs', None) 

5963 if nprocs is not None: 

5964 raise BadRequest( 

5965 'The `nprocs` keyword argument to process is no longer ' 

5966 'available. Please use `nthreads`.') 

5967 

5968 nthreads = kwargs.pop('nthreads', self.nthreads) 

5969 

5970 if request is None: 

5971 request = Request(**kwargs) 

5972 

5973 if resource: 

5974 rs0 = resource.getrusage(resource.RUSAGE_SELF) 

5975 rc0 = resource.getrusage(resource.RUSAGE_CHILDREN) 

5976 tt0 = xtime() 

5977 

5978 # make sure stores are open before fork() 

5979 store_ids = set(target.store_id for target in request.targets) 

5980 for store_id in store_ids: 

5981 self.get_store(store_id) 

5982 

5983 source_index = dict((x, i) for (i, x) in 

5984 enumerate(request.sources)) 

5985 target_index = dict((x, i) for (i, x) in 

5986 enumerate(request.targets)) 

5987 

5988 m = request.subrequest_map() 

5989 

5990 skeys = sorted(m.keys(), key=cmp_to_key(cmp_none_aware)) 

5991 results_list = [] 

5992 

5993 for i in range(len(request.sources)): 

5994 results_list.append([None] * len(request.targets)) 

5995 

5996 tcounters_dyn_list = [] 

5997 tcounters_static_list = [] 

5998 nsub = len(skeys) 

5999 isub = 0 

6000 

6001 # Processing dynamic targets through 

6002 # parimap(process_subrequest_dynamic) 

6003 

6004 if calc_timeseries: 

6005 _process_dynamic = process_dynamic_timeseries 

6006 else: 

6007 _process_dynamic = process_dynamic 

6008 

6009 if request.has_dynamic: 

6010 work_dynamic = [ 

6011 (i, nsub, 

6012 [source_index[source] for source in m[k][0]], 

6013 [target_index[target] for target in m[k][1] 

6014 if not isinstance(target, StaticTarget)]) 

6015 for (i, k) in enumerate(skeys)] 

6016 

6017 for ii_results, tcounters_dyn in _process_dynamic( 

6018 work_dynamic, request.sources, request.targets, self, 

6019 nthreads): 

6020 

6021 tcounters_dyn_list.append(num.diff(tcounters_dyn)) 

6022 isource, itarget, result = ii_results 

6023 results_list[isource][itarget] = result 

6024 

6025 if status_callback: 

6026 status_callback(isub, nsub) 

6027 

6028 isub += 1 

6029 

6030 # Processing static targets through process_static 

6031 if request.has_statics: 

6032 work_static = [ 

6033 (i, nsub, 

6034 [source_index[source] for source in m[k][0]], 

6035 [target_index[target] for target in m[k][1] 

6036 if isinstance(target, StaticTarget)]) 

6037 for (i, k) in enumerate(skeys)] 

6038 

6039 for ii_results, tcounters_static in process_static( 

6040 work_static, request.sources, request.targets, self, 

6041 nthreads=nthreads): 

6042 

6043 tcounters_static_list.append(num.diff(tcounters_static)) 

6044 isource, itarget, result = ii_results 

6045 results_list[isource][itarget] = result 

6046 

6047 if status_callback: 

6048 status_callback(isub, nsub) 

6049 

6050 isub += 1 

6051 

6052 if status_callback: 

6053 status_callback(nsub, nsub) 

6054 

6055 tt1 = time.time() 

6056 if resource: 

6057 rs1 = resource.getrusage(resource.RUSAGE_SELF) 

6058 rc1 = resource.getrusage(resource.RUSAGE_CHILDREN) 

6059 

6060 s = ProcessingStats() 

6061 

6062 if request.has_dynamic: 

6063 tcumu_dyn = num.sum(num.vstack(tcounters_dyn_list), axis=0) 

6064 t_dyn = float(num.sum(tcumu_dyn)) 

6065 perc_dyn = map(float, tcumu_dyn / t_dyn * 100.) 

6066 (s.t_perc_get_store_and_receiver, 

6067 s.t_perc_discretize_source, 

6068 s.t_perc_make_base_seismogram, 

6069 s.t_perc_make_same_span, 

6070 s.t_perc_post_process) = perc_dyn 

6071 else: 

6072 t_dyn = 0. 

6073 

6074 if request.has_statics: 

6075 tcumu_static = num.sum(num.vstack(tcounters_static_list), axis=0) 

6076 t_static = num.sum(tcumu_static) 

6077 perc_static = map(float, tcumu_static / t_static * 100.) 

6078 (s.t_perc_static_get_store, 

6079 s.t_perc_static_discretize_basesource, 

6080 s.t_perc_static_sum_statics, 

6081 s.t_perc_static_post_process) = perc_static 

6082 

6083 s.t_wallclock = tt1 - tt0 

6084 if resource: 

6085 s.t_cpu = ( 

6086 (rs1.ru_utime + rs1.ru_stime + rc1.ru_utime + rc1.ru_stime) - 

6087 (rs0.ru_utime + rs0.ru_stime + rc0.ru_utime + rc0.ru_stime)) 

6088 s.n_read_blocks = ( 

6089 (rs1.ru_inblock + rc1.ru_inblock) - 

6090 (rs0.ru_inblock + rc0.ru_inblock)) 

6091 

6092 n_records_stacked = 0. 

6093 for results in results_list: 

6094 for result in results: 

6095 if not isinstance(result, meta.Result): 

6096 continue 

6097 shr = float(result.n_shared_stacking) 

6098 n_records_stacked += result.n_records_stacked / shr 

6099 s.t_perc_optimize += result.t_optimize / shr 

6100 s.t_perc_stack += result.t_stack / shr 

6101 s.n_records_stacked = int(n_records_stacked) 

6102 if t_dyn != 0.: 

6103 s.t_perc_optimize /= t_dyn * 100 

6104 s.t_perc_stack /= t_dyn * 100 

6105 

6106 return Response( 

6107 request=request, 

6108 results_list=results_list, 

6109 stats=s) 

6110 

6111 

6112class RemoteEngine(Engine): 

6113 ''' 

6114 Client for remote synthetic seismogram calculator. 

6115 ''' 

6116 

6117 site = String.T(default=ws.g_default_site, optional=True) 

6118 url = String.T(default=ws.g_url, optional=True) 

6119 

6120 def process(self, request=None, status_callback=None, **kwargs): 

6121 

6122 if request is None: 

6123 request = Request(**kwargs) 

6124 

6125 return ws.seismosizer(url=self.url, site=self.site, request=request) 

6126 

6127 

6128g_engine = None 

6129 

6130 

6131def get_engine(store_superdirs=[]): 

6132 global g_engine 

6133 if g_engine is None: 

6134 g_engine = LocalEngine(use_env=True, use_config=True) 

6135 

6136 for d in store_superdirs: 

6137 if d not in g_engine.store_superdirs: 

6138 g_engine.store_superdirs.append(d) 

6139 

6140 return g_engine 

6141 

6142 

6143class SourceGroup(Object): 

6144 

6145 def __getattr__(self, k): 

6146 return num.fromiter((getattr(s, k) for s in self), 

6147 dtype=float) 

6148 

6149 def __iter__(self): 

6150 raise NotImplementedError( 

6151 'This method should be implemented in subclass.') 

6152 

6153 def __len__(self): 

6154 raise NotImplementedError( 

6155 'This method should be implemented in subclass.') 

6156 

6157 

6158class SourceList(SourceGroup): 

6159 sources = List.T(Source.T()) 

6160 

6161 def append(self, s): 

6162 self.sources.append(s) 

6163 

6164 def __iter__(self): 

6165 return iter(self.sources) 

6166 

6167 def __len__(self): 

6168 return len(self.sources) 

6169 

6170 

6171class SourceGrid(SourceGroup): 

6172 

6173 base = Source.T() 

6174 variables = Dict.T(String.T(), Range.T()) 

6175 order = List.T(String.T()) 

6176 

6177 def __len__(self): 

6178 n = 1 

6179 for (k, v) in self.make_coords(self.base): 

6180 n *= len(list(v)) 

6181 

6182 return n 

6183 

6184 def __iter__(self): 

6185 for items in permudef(self.make_coords(self.base)): 

6186 s = self.base.clone(**{k: v for (k, v) in items}) 

6187 s.regularize() 

6188 yield s 

6189 

6190 def ordered_params(self): 

6191 ks = list(self.variables.keys()) 

6192 for k in self.order + list(self.base.keys()): 

6193 if k in ks: 

6194 yield k 

6195 ks.remove(k) 

6196 if ks: 

6197 raise Exception('Invalid parameter "%s" for source type "%s".' % 

6198 (ks[0], self.base.__class__.__name__)) 

6199 

6200 def make_coords(self, base): 

6201 return [(param, self.variables[param].make(base=base[param])) 

6202 for param in self.ordered_params()] 

6203 

6204 

6205source_classes = [ 

6206 Source, 

6207 SourceWithMagnitude, 

6208 SourceWithDerivedMagnitude, 

6209 ExplosionSource, 

6210 RectangularExplosionSource, 

6211 DCSource, 

6212 CLVDSource, 

6213 VLVDSource, 

6214 MTSource, 

6215 RectangularSource, 

6216 PseudoDynamicRupture, 

6217 DoubleDCSource, 

6218 RingfaultSource, 

6219 CombiSource, 

6220 CombiSFSource, 

6221 SFSource, 

6222 SimpleLandslideSource, 

6223 PorePressurePointSource, 

6224 PorePressureLineSource, 

6225] 

6226 

6227stf_classes = [ 

6228 STF, 

6229 BoxcarSTF, 

6230 TriangularSTF, 

6231 HalfSinusoidSTF, 

6232 ResonatorSTF, 

6233 TremorSTF, 

6234 SimpleLandslideSTF, 

6235] 

6236 

6237__all__ = ''' 

6238Cloneable 

6239NoDefaultStoreSet 

6240SeismosizerError 

6241STFError 

6242BadRequest 

6243NoSuchStore 

6244DerivedMagnitudeError 

6245STFMode 

6246'''.split() + [S.__name__ for S in source_classes + stf_classes] + ''' 

6247Request 

6248ProcessingStats 

6249Response 

6250Engine 

6251LocalEngine 

6252RemoteEngine 

6253source_classes 

6254get_engine 

6255Range 

6256SourceGroup 

6257SourceList 

6258SourceGrid 

6259map_anchor 

6260'''.split()