Coverage for /usr/local/lib/python3.11/dist-packages/grond/targets/phase_pick/target.py: 80%

153 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2024-11-27 15:15 +0000

1# https://pyrocko.org/grond - GPLv3 

2# 

3# The Grond Developers, 21st Century 

4import logging 

5 

6import numpy as num 

7 

8from pyrocko.guts import Float, Tuple, String, Int, List 

9from pyrocko import gf, util 

10from pyrocko.plot import beachball 

11 

12from ..base import MisfitTarget, TargetGroup, MisfitResult 

13from grond.meta import has_get_plot_classes, GrondError, nslcs_to_patterns 

14 

15guts_prefix = 'grond' 

16logger = logging.getLogger('grond.targets.phase_pick.target') 

17 

18 

19def log_exclude(target, reason): 

20 logger.debug('Excluding potential target %s: %s' % ( 

21 target.string_id(), reason)) 

22 

23 

24class PhasePickTargetGroup(TargetGroup): 

25 

26 ''' 

27 Generate targets to fit phase arrival times. 

28 ''' 

29 

30 distance_min = Float.T(optional=True) 

31 distance_max = Float.T(optional=True) 

32 distance_3d_min = Float.T(optional=True) 

33 distance_3d_max = Float.T(optional=True) 

34 depth_min = Float.T(optional=True) 

35 depth_max = Float.T(optional=True) 

36 store_id = gf.StringID.T(optional=True) 

37 include = List.T( 

38 String.T(), 

39 optional=True, 

40 help='If not None, list of stations/components to include according ' 

41 'to their STA, NET.STA, NET.STA.LOC, or NET.STA.LOC.CHA codes.') 

42 exclude = List.T( 

43 String.T(), 

44 help='Stations/components to be excluded according to their STA, ' 

45 'NET.STA, NET.STA.LOC, or NET.STA.LOC.CHA codes.') 

46 limit = Int.T(optional=True) 

47 

48 pick_synthetic_traveltime = gf.Timing.T( 

49 help='Synthetic phase arrival definition.') 

50 pick_phasename = String.T( 

51 help='Name of phase in pick file.') 

52 

53 weight_traveltime = Float.T(default=1.0) 

54 weight_polarity = Float.T(default=1.0) 

55 

56 def get_targets(self, ds, event, default_path='none'): 

57 logger.debug('Selecting phase pick targets...') 

58 origin = event 

59 targets = [] 

60 

61 stations = ds.get_stations() 

62 if len(stations) == 0: 

63 logger.warning( 

64 'No stations found to create waveform target group.') 

65 

66 for st in stations: 

67 nslc = st.nsl() + ('',) 

68 target = PhasePickTarget( 

69 codes=nslc[:3], 

70 lat=st.lat, 

71 lon=st.lon, 

72 north_shift=st.north_shift, 

73 east_shift=st.east_shift, 

74 depth=st.depth, 

75 store_id=self.store_id, 

76 manual_weight=self.weight, 

77 normalisation_family=self.normalisation_family, 

78 path=self.path or default_path, 

79 pick_synthetic_traveltime=self.pick_synthetic_traveltime, 

80 pick_phasename=self.pick_phasename) 

81 

82 if util.match_nslc( 

83 nslcs_to_patterns(self.exclude), nslc): 

84 log_exclude(target, 'excluded by target group') 

85 continue 

86 

87 if self.include is not None and not util.match_nslc( 

88 nslcs_to_patterns(self.include), nslc): 

89 log_exclude(target, 'excluded by target group') 

90 continue 

91 

92 if self.distance_min is not None and \ 

93 target.distance_to(origin) < self.distance_min: 

94 log_exclude(target, 'distance < distance_min') 

95 continue 

96 

97 if self.distance_max is not None and \ 

98 target.distance_to(origin) > self.distance_max: 

99 log_exclude(target, 'distance > distance_max') 

100 continue 

101 

102 if self.distance_3d_min is not None and \ 

103 target.distance_3d_to(origin) < self.distance_3d_min: 

104 log_exclude(target, 'distance_3d < distance_3d_min') 

105 continue 

106 

107 if self.distance_3d_max is not None and \ 

108 target.distance_3d_to(origin) > self.distance_3d_max: 

109 log_exclude(target, 'distance_3d > distance_3d_max') 

110 continue 

111 

112 if self.depth_min is not None and \ 

113 target.depth < self.depth_min: 

114 log_exclude(target, 'depth < depth_min') 

115 continue 

116 

117 if self.depth_max is not None and \ 

118 target.depth > self.depth_max: 

119 log_exclude(target, 'depth > depth_max') 

120 continue 

121 

122 marker = ds.get_pick( 

123 event.name, 

124 target.codes[:3], 

125 target.pick_phasename) 

126 

127 if not marker: 

128 log_exclude(target, 'no pick available') 

129 continue 

130 

131 target.set_dataset(ds) 

132 targets.append(target) 

133 

134 return targets 

135 

136 def is_gf_store_appropriate(self, store, depth_range): 

137 ok = TargetGroup.is_gf_store_appropriate( 

138 self, store, depth_range) 

139 ok &= self._is_gf_store_appropriate_check_extent( 

140 store, depth_range) 

141 return ok 

142 

143 

144class PhasePickResult(MisfitResult): 

145 tobs = Float.T(optional=True) 

146 tsyn = Float.T(optional=True) 

147 polarity_obs = Float.T(optional=True) 

148 polarity_syn = Float.T(optional=True) 

149 azimuth = Float.T(optional=True) 

150 takeoff_angle = Float.T(optional=True) 

151 

152 

153@has_get_plot_classes 

154class PhasePickTarget(gf.Location, MisfitTarget): 

155 

156 ''' 

157 Target to fit phase arrival times. 

158 ''' 

159 

160 codes = Tuple.T( 

161 3, String.T(), 

162 help='network, station, location codes.') 

163 

164 store_id = gf.StringID.T( 

165 help='ID of Green\'s function store (only used for earth model).') 

166 

167 pick_synthetic_traveltime = gf.Timing.T( 

168 help='Synthetic phase arrival definition.') 

169 

170 pick_phasename = String.T( 

171 help='Name of phase in pick file.') 

172 

173 can_bootstrap_weights = True 

174 

175 weight_traveltime = Float.T(default=1.0) 

176 weight_polarity = Float.T(default=1.0) 

177 

178 def __init__(self, **kwargs): 

179 gf.Location.__init__(self, **kwargs) 

180 MisfitTarget.__init__(self, **kwargs) 

181 self._tobs_cache = {} 

182 

183 @property 

184 def nmisfits(self): 

185 return 2 

186 

187 @classmethod 

188 def get_plot_classes(cls): 

189 from . import plot 

190 plots = super(PhasePickTarget, cls).get_plot_classes() 

191 plots.extend(plot.get_plot_classes()) 

192 return plots 

193 

194 def string_id(self): 

195 return '.'.join(x for x in (self.path,) + self.codes) 

196 

197 def set_dataset(self, ds): 

198 MisfitTarget.set_dataset(self, ds) 

199 self._obs_cache = {} 

200 

201 def get_plain_targets(self, engine, source): 

202 return self.prepare_modelling(engine, source, None) 

203 

204 def prepare_modelling(self, engine, source, targets): 

205 return [] 

206 

207 def get_times_polarities(self, engine, source): 

208 tsyn = None 

209 

210 k = source.name 

211 if k not in self._obs_cache: 

212 ds = self.get_dataset() 

213 tobs = None 

214 marker = ds.get_pick( 

215 source.name, 

216 self.codes[:3], 

217 self.pick_phasename) 

218 

219 if marker: 

220 polarity_obs = marker.get_polarity() 

221 if polarity_obs not in (1, -1, None): 

222 raise GrondError( 

223 'Polarity of pick %s.%s.%s.%s must be 1 or -1 or None' 

224 % marker.one_nslc()) 

225 self._obs_cache[k] = marker.tmin, polarity_obs 

226 else: 

227 self._obs_cache[k] = None 

228 

229 tobs, polarity_obs = self._obs_cache[k] 

230 

231 store = engine.get_store(self.store_id) 

232 

233 use_polarities = self.weight_polarity > 0. and polarity_obs is not None 

234 use_traveltimes = self.weight_traveltime > 0. and tobs is not None 

235 

236 if use_polarities: 

237 traveltime, takeoff_angle = store.t( 

238 self.pick_synthetic_traveltime, source, self, 

239 attributes=['takeoff_angle']) 

240 else: 

241 traveltime = store.t( 

242 self.pick_synthetic_traveltime, source, self) 

243 takeoff_angle = None 

244 

245 if use_traveltimes: 

246 tsyn = source.time + traveltime 

247 else: 

248 tsyn = None 

249 

250 if use_polarities: 

251 mt = source.pyrocko_moment_tensor() 

252 azimuth = source.azibazi_to(self)[0] 

253 amp = beachball.amplitudes(mt, [azimuth], [takeoff_angle])[0] 

254 polarity_syn = -1.0 if amp < 0. else 1.0 

255 else: 

256 polarity_syn = None 

257 azimuth = None 

258 

259 return tobs, tsyn, polarity_obs, polarity_syn, azimuth, takeoff_angle 

260 

261 def finalize_modelling( 

262 self, engine, source, modelling_targets, modelling_results): 

263 

264 ds = self.get_dataset() # noqa 

265 

266 tobs, tsyn, polarity_obs, polarity_syn, azimuth, takeoff_angle = \ 

267 self.get_times_polarities(engine, source) 

268 

269 misfits = num.full((2, 2), num.nan) 

270 misfits[:, 1] = 1.0 

271 

272 if self.weight_traveltime > 0. and None not in (tobs, tsyn): 

273 misfits[0, 0] = self.weight_traveltime * abs(tobs - tsyn) 

274 

275 if self.weight_polarity > 0. \ 

276 and None not in (polarity_obs, polarity_syn): 

277 misfits[1, 0] = self.weight_polarity \ 

278 * 0.5 * abs(polarity_obs - polarity_syn) 

279 

280 result = PhasePickResult( 

281 tobs=tobs, 

282 tsyn=tsyn, 

283 polarity_obs=polarity_obs, 

284 polarity_syn=polarity_syn, 

285 azimuth=azimuth, 

286 takeoff_angle=takeoff_angle, 

287 misfits=misfits) 

288 

289 return result 

290 

291 

292__all__ = ''' 

293 PhasePickTargetGroup 

294 PhasePickTarget 

295 PhasePickResult 

296'''.split()