Coverage for /usr/local/lib/python3.11/dist-packages/grond/meta.py: 55%

286 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-10-26 16:25 +0000

1import logging 

2import math 

3import re 

4import numpy as num 

5import os.path as op 

6from string import Template 

7from pyrocko.guts import Object, String, Float, Unicode, StringPattern, Bool 

8from pyrocko import util, gf 

9 

10guts_prefix = 'grond' 

11 

12 

13try: 

14 newstr = unicode 

15except NameError: 

16 newstr = str 

17 

18 

19logger = logging.getLogger('grond.meta') 

20km = 1e3 

21 

22classes_with_have_get_plot_classes = [] 

23 

24 

25def has_get_plot_classes(cls): 

26 classes_with_have_get_plot_classes.append(cls) 

27 return cls 

28 

29 

30class StringID(StringPattern): 

31 pattern = r'^[A-Za-z][A-Za-z0-9._-]{0,64}$' 

32 

33 

34StringID.regex = re.compile(StringID.pattern) 

35 

36 

37def xjoin(basepath, path): 

38 if path is None and basepath is not None: 

39 return basepath 

40 elif op.isabs(path) or basepath is None: 

41 return path 

42 else: 

43 return op.join(basepath, path) 

44 

45 

46def xrelpath(path, start): 

47 if op.isabs(path): 

48 return path 

49 else: 

50 return op.relpath(path, start) 

51 

52 

53def ordersort(x): 

54 isort = num.argsort(x) 

55 iorder = num.empty(isort.size, dtype=int) 

56 iorder[isort] = num.arange(isort.size) 

57 return iorder 

58 

59 

60def nextpow2(i): 

61 return 2**int(math.ceil(math.log(i) / math.log(2.))) 

62 

63 

64def gather(seq, key, sort=None, filter=None): 

65 d = {} 

66 for x in seq: 

67 if filter is not None and not filter(x): 

68 continue 

69 

70 k = key(x) 

71 if k not in d: 

72 d[k] = [] 

73 

74 d[k].append(x) 

75 

76 if sort is not None: 

77 for v in d.values(): 

78 v.sort(key=sort) 

79 

80 return d 

81 

82 

83def str_dist(dist): 

84 if dist < 10.0: 

85 return '%g m' % dist 

86 elif 10. <= dist < 1. * km: 

87 return '%.0f m' % dist 

88 elif 1. * km <= dist < 10. * km: 

89 return '%.1f km' % (dist / km) 

90 else: 

91 return '%.0f km' % (dist / km) 

92 

93 

94def str_duration(t): 

95 s = '' 

96 if t < 0.: 

97 s = '-' 

98 

99 t = abs(t) 

100 

101 if t < 10.0: 

102 return s + '%.2g s' % t 

103 elif 10.0 <= t < 3600.: 

104 return s + util.time_to_str(t, format='%M:%S min') 

105 elif 3600. <= t < 24 * 3600.: 

106 return s + util.time_to_str(t, format='%H:%M h') 

107 else: 

108 return s + '%.1f d' % (t / (24. * 3600.)) 

109 

110 

111try: 

112 nanmedian = num.nanmedian 

113except AttributeError: 

114 def nanmedian(a, axis=None): 

115 if axis is None: 

116 return num.median(a[num.isfinite(a)]) 

117 else: 

118 shape_out = list(a.shape) 

119 shape_out.pop(axis) 

120 out = num.empty(shape_out, dtype=a.dtype) 

121 out[...] = num.nan 

122 for iout in num.ndindex(tuple(shape_out)): 

123 iin = list(iout) 

124 iin[axis:axis] = [slice(0, a.shape[axis])] 

125 b = a[tuple(iin)] 

126 out[iout] = num.median(b[num.isfinite(b)]) 

127 

128 return out 

129 

130 

131class Forbidden(Exception): 

132 pass 

133 

134 

135class GrondError(Exception): 

136 pass 

137 

138 

139class GrondDeprecationError(GrondError): 

140 pass 

141 

142 

143class PhaseMissContext(Object): 

144 store_id = gf.StringID.T() 

145 timing = gf.Timing.T() 

146 source = gf.Source.T() 

147 target = gf.Target.T() 

148 

149 

150class PhaseMissError(GrondError): 

151 def __init__(self, store_id, timing, source, target): 

152 GrondError.__init__(self) 

153 self._context = PhaseMissContext( 

154 store_id=store_id, 

155 timing=timing, 

156 source=source, 

157 target=target) 

158 

159 def __str__(self): 

160 return 'Phase not available for given geometry. Context:\n%s' \ 

161 % self._context 

162 

163 

164def store_t(store, tdef, source, target): 

165 tt = store.t(tdef, source, target) 

166 if tt is None: 

167 raise PhaseMissError(store.config.id, tdef, source, target) 

168 

169 return tt 

170 

171 

172def expand_template(template, d): 

173 try: 

174 return Template(template).substitute(d) 

175 except KeyError as e: 

176 raise GrondError( 

177 'invalid placeholder "%s" in template: "%s"' % (str(e), template)) 

178 except ValueError: 

179 raise GrondError( 

180 'malformed placeholder in template: "%s"' % template) 

181 

182 

183class ADict(dict): 

184 def __getattr__(self, k): 

185 return self[k] 

186 

187 def __setattr__(self, k, v): 

188 self[k] = v 

189 

190 

191class Parameter(Object): 

192 name__ = String.T() 

193 unit = Unicode.T(optional=True) 

194 scale_factor = Float.T(default=1., optional=True) 

195 scale_unit = Unicode.T(optional=True) 

196 label = Unicode.T(optional=True) 

197 optional = Bool.T(default=True, optional=True) 

198 

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

200 if len(args) >= 1: 

201 kwargs['name'] = args[0] 

202 if len(args) >= 2: 

203 kwargs['unit'] = newstr(args[1]) 

204 

205 self.groups = [None] 

206 self._name = None 

207 

208 Object.__init__(self, **kwargs) 

209 

210 def get_label(self, with_unit=True): 

211 lbl = [self.label or self.name] 

212 if with_unit: 

213 unit = self.get_unit_label() 

214 if unit: 

215 lbl.append('[%s]' % unit) 

216 

217 return ' '.join(lbl) 

218 

219 def set_groups(self, groups): 

220 if not isinstance(groups, list): 

221 raise AttributeError('Groups must be a list of strings.') 

222 self.groups = groups 

223 

224 def _get_name(self): 

225 if None not in self.groups: 

226 return '%s.%s' % ('.'.join(self.groups), self._name) 

227 return self._name 

228 

229 def _set_name(self, value): 

230 self._name = value 

231 

232 name = property(_get_name, _set_name) 

233 

234 @property 

235 def name_nogroups(self): 

236 return self._name 

237 

238 def get_value_label(self, value, format='%(value)g%(unit)s'): 

239 value = self.scaled(value) 

240 unit = self.get_unit_suffix() 

241 return format % dict(value=value, unit=unit) 

242 

243 def get_unit_label(self): 

244 if self.scale_unit is not None: 

245 return self.scale_unit 

246 elif self.unit: 

247 return self.unit 

248 else: 

249 return None 

250 

251 def get_unit_suffix(self): 

252 unit = self.get_unit_label() 

253 if not unit: 

254 return '' 

255 else: 

256 return ' %s' % unit 

257 

258 def scaled(self, x): 

259 if isinstance(x, tuple): 

260 return tuple(v/self.scale_factor for v in x) 

261 if isinstance(x, list): 

262 return list(v/self.scale_factor for v in x) 

263 else: 

264 return x/self.scale_factor 

265 

266 def inv_scaled(self, x): 

267 if isinstance(x, tuple): 

268 return tuple(v*self.scale_factor for v in x) 

269 if isinstance(x, list): 

270 return list(v*self.scale_factor for v in x) 

271 else: 

272 return x*self.scale_factor 

273 

274 

275class Path(String): 

276 pass 

277 

278 

279class HasPaths(Object): 

280 path_prefix = Path.T(optional=True) 

281 

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

283 Object.__init__(self, *args, **kwargs) 

284 self._basepath = None 

285 self._parent_path_prefix = None 

286 

287 def set_basepath(self, basepath, parent_path_prefix=None): 

288 self._basepath = basepath 

289 self._parent_path_prefix = parent_path_prefix 

290 for (prop, val) in self.T.ipropvals(self): 

291 if isinstance(val, HasPaths): 

292 val.set_basepath( 

293 basepath, self.path_prefix or self._parent_path_prefix) 

294 

295 def get_basepath(self): 

296 assert self._basepath is not None 

297 return self._basepath 

298 

299 def change_basepath(self, new_basepath, parent_path_prefix=None): 

300 assert self._basepath is not None 

301 

302 self._parent_path_prefix = parent_path_prefix 

303 if self.path_prefix or not self._parent_path_prefix: 

304 

305 self.path_prefix = op.normpath(xjoin(xrelpath( 

306 self._basepath, new_basepath), self.path_prefix)) 

307 

308 for val in self.T.ivals(self): 

309 if isinstance(val, HasPaths): 

310 val.change_basepath( 

311 new_basepath, self.path_prefix or self._parent_path_prefix) 

312 

313 self._basepath = new_basepath 

314 

315 def expand_path(self, path, extra=None): 

316 assert self._basepath is not None 

317 

318 if extra is None: 

319 def extra(path): 

320 return path 

321 

322 path_prefix = self.path_prefix or self._parent_path_prefix 

323 

324 if path is None: 

325 return None 

326 elif isinstance(path, str): 

327 return extra( 

328 op.normpath(xjoin(self._basepath, xjoin(path_prefix, path)))) 

329 else: 

330 return [ 

331 extra( 

332 op.normpath(xjoin(self._basepath, xjoin(path_prefix, p)))) 

333 for p in path] 

334 

335 def rel_path(self, path): 

336 return xrelpath(path, self.get_basepath()) 

337 

338 

339def nslc_to_pattern(s): 

340 toks = s.split('.') 

341 if len(toks) == 1: 

342 return '*.%s.*.*' % s 

343 elif len(toks) == 2: 

344 return '%s.*.*' % s 

345 elif len(toks) == 3: 

346 return '%s.*' % s 

347 elif len(toks) == 4: 

348 return s 

349 else: 

350 raise GrondError('Invalid net.sta.loc.cha pattern: %s' % s) 

351 

352 

353def nslcs_to_patterns(seq): 

354 return [nslc_to_pattern(s) for s in seq] 

355 

356 

357class SelectionError(GrondError): 

358 pass 

359 

360 

361# --select="magnitude_min:5 tag_contains:a,b " 

362 

363g_conditions = {} 

364 

365selected_operators_1_1 = { 

366 'min': lambda data, key, value: data[key] >= value, 

367 'max': lambda data, key, value: data[key] <= value, 

368 'is': lambda data, key, value: data[key] == value} 

369 

370selected_operators_1_n = { 

371 'in': lambda data, key, values: 

372 data[key] in values} 

373 

374selected_operators_n_1 = {} 

375 

376selected_operators_n_n = { 

377 'contains': lambda data, key, values: 

378 any(d in values for d in data[key]), 

379 'notcontains': lambda data, key, values: 

380 not any(d in values for d in data[key])} 

381 

382 

383selected_operators = set() 

384 

385for s in (selected_operators_1_1, selected_operators_1_n, 

386 selected_operators_n_1, selected_operators_n_n): 

387 for k in s: 

388 selected_operators.add(k) 

389 

390 

391def _parse_selected_expression(expression): 

392 for condition in expression.split(): 

393 condition = condition.strip() 

394 if condition not in g_conditions: 

395 try: 

396 argument, value = condition.split(':', 1) 

397 except ValueError: 

398 raise SelectionError( 

399 'Invalid condition in selection expression: ' 

400 '"%s", must be "ARGUMENT:VALUE"' % condition) 

401 

402 argument = argument.strip() 

403 

404 try: 

405 key, operator = argument.rsplit('_', 1) 

406 except ValueError: 

407 raise SelectionError( 

408 'Invalid argument in selection expression: ' 

409 '"%s", must be "KEY_OPERATOR"' % argument) 

410 

411 if operator not in selected_operators: 

412 raise SelectionError( 

413 'Invalid operator in selection expression: ' 

414 '"%s", available: %s' % ( 

415 operator, ', '.join('"%s"' % s for s in sorted( 

416 list(selected_operators))))) 

417 

418 g_conditions[condition] = key, operator, value 

419 

420 yield g_conditions[condition] 

421 

422 

423def selected(expression, data, types): 

424 results = [] 

425 for (key, operator, value) in _parse_selected_expression(expression): 

426 if key not in data: 

427 raise SelectionError( 

428 'Invalid key in selection expression: ' 

429 '"%s", available:\n %s' % ( 

430 key, '\n '.join(sorted(data.keys())))) 

431 

432 typ = types[key] 

433 if not isinstance(typ, tuple): 

434 if operator in selected_operators_1_1: 

435 results.append( 

436 selected_operators_1_1[operator](data, key, typ(value))) 

437 elif operator in selected_operators_1_n: 

438 values = list(typ(v) for v in value.split(',')) 

439 results.append( 

440 selected_operators_1_n[operator](data, key, values)) 

441 else: 

442 raise SelectionError( 

443 'Cannot use operator "%s" with argument "%s".' % ( 

444 operator, 

445 key)) 

446 

447 else: 

448 if operator in selected_operators_n_1: 

449 results.append( 

450 selected_operators_n_1[operator](data, key, typ[1](value))) 

451 elif operator in selected_operators_n_n: 

452 values = typ[0](typ[1](v) for v in value.split(',')) 

453 results.append( 

454 selected_operators_n_n[operator](data, key, values)) 

455 else: 

456 raise SelectionError( 

457 'Cannot use operator "%s" with argument "%s".' % ( 

458 operator, 

459 key)) 

460 

461 return all(results) 

462 

463 

464__all__ = ''' 

465 Forbidden 

466 GrondError 

467 PhaseMissContext 

468 PhaseMissError 

469 store_t 

470 Path 

471 HasPaths 

472 Parameter 

473 StringID 

474'''.split()