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

149 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2024-06-12 12:25 +0000

1import logging 

2 

3import numpy as num 

4 

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

6from pyrocko import gf, util 

7from pyrocko.plot import beachball 

8 

9from ..base import MisfitTarget, TargetGroup, MisfitResult 

10from grond.meta import has_get_plot_classes, GrondError, nslcs_to_patterns 

11 

12guts_prefix = 'grond' 

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

14 

15 

16def log_exclude(target, reason): 

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

18 target.string_id(), reason)) 

19 

20 

21class PhasePickTargetGroup(TargetGroup): 

22 

23 ''' 

24 Generate targets to fit phase arrival times. 

25 ''' 

26 

27 distance_min = Float.T(optional=True) 

28 distance_max = Float.T(optional=True) 

29 distance_3d_min = Float.T(optional=True) 

30 distance_3d_max = Float.T(optional=True) 

31 depth_min = Float.T(optional=True) 

32 depth_max = Float.T(optional=True) 

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

34 include = List.T( 

35 String.T(), 

36 optional=True, 

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

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

39 exclude = List.T( 

40 String.T(), 

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

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

43 limit = Int.T(optional=True) 

44 

45 pick_synthetic_traveltime = gf.Timing.T( 

46 help='Synthetic phase arrival definition.') 

47 pick_phasename = String.T( 

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

49 

50 weight_traveltime = Float.T(default=1.0) 

51 weight_polarity = Float.T(default=1.0) 

52 

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

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

55 origin = event 

56 targets = [] 

57 

58 stations = ds.get_stations() 

59 if len(stations) == 0: 

60 logger.warning( 

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

62 

63 for st in stations: 

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

65 target = PhasePickTarget( 

66 codes=nslc[:3], 

67 lat=st.lat, 

68 lon=st.lon, 

69 north_shift=st.north_shift, 

70 east_shift=st.east_shift, 

71 depth=st.depth, 

72 store_id=self.store_id, 

73 manual_weight=self.weight, 

74 normalisation_family=self.normalisation_family, 

75 path=self.path or default_path, 

76 pick_synthetic_traveltime=self.pick_synthetic_traveltime, 

77 pick_phasename=self.pick_phasename) 

78 

79 if util.match_nslc( 

80 nslcs_to_patterns(self.exclude), nslc): 

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

82 continue 

83 

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

85 nslcs_to_patterns(self.include), nslc): 

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

87 continue 

88 

89 if self.distance_min is not None and \ 

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

91 log_exclude(target, 'distance < distance_min') 

92 continue 

93 

94 if self.distance_max is not None and \ 

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

96 log_exclude(target, 'distance > distance_max') 

97 continue 

98 

99 if self.distance_3d_min is not None and \ 

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

101 log_exclude(target, 'distance_3d < distance_3d_min') 

102 continue 

103 

104 if self.distance_3d_max is not None and \ 

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

106 log_exclude(target, 'distance_3d > distance_3d_max') 

107 continue 

108 

109 if self.depth_min is not None and \ 

110 target.depth < self.depth_min: 

111 log_exclude(target, 'depth < depth_min') 

112 continue 

113 

114 if self.depth_max is not None and \ 

115 target.depth > self.depth_max: 

116 log_exclude(target, 'depth > depth_max') 

117 continue 

118 

119 marker = ds.get_pick( 

120 event.name, 

121 target.codes[:3], 

122 target.pick_phasename) 

123 

124 if not marker: 

125 log_exclude(target, 'no pick available') 

126 continue 

127 

128 target.set_dataset(ds) 

129 targets.append(target) 

130 

131 return targets 

132 

133 

134class PhasePickResult(MisfitResult): 

135 tobs = Float.T(optional=True) 

136 tsyn = Float.T(optional=True) 

137 polarity_obs = Float.T(optional=True) 

138 polarity_syn = Float.T(optional=True) 

139 azimuth = Float.T(optional=True) 

140 takeoff_angle = Float.T(optional=True) 

141 

142 

143@has_get_plot_classes 

144class PhasePickTarget(gf.Location, MisfitTarget): 

145 

146 ''' 

147 Target to fit phase arrival times. 

148 ''' 

149 

150 codes = Tuple.T( 

151 3, String.T(), 

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

153 

154 store_id = gf.StringID.T( 

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

156 

157 pick_synthetic_traveltime = gf.Timing.T( 

158 help='Synthetic phase arrival definition.') 

159 

160 pick_phasename = String.T( 

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

162 

163 can_bootstrap_weights = True 

164 

165 weight_traveltime = Float.T(default=1.0) 

166 weight_polarity = Float.T(default=1.0) 

167 

168 def __init__(self, **kwargs): 

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

170 MisfitTarget.__init__(self, **kwargs) 

171 self._tobs_cache = {} 

172 

173 @property 

174 def nmisfits(self): 

175 return 2 

176 

177 @classmethod 

178 def get_plot_classes(cls): 

179 from . import plot 

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

181 plots.extend(plot.get_plot_classes()) 

182 return plots 

183 

184 def string_id(self): 

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

186 

187 def set_dataset(self, ds): 

188 MisfitTarget.set_dataset(self, ds) 

189 self._obs_cache = {} 

190 

191 def get_plain_targets(self, engine, source): 

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

193 

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

195 return [] 

196 

197 def get_times_polarities(self, engine, source): 

198 tsyn = None 

199 

200 k = source.name 

201 if k not in self._obs_cache: 

202 ds = self.get_dataset() 

203 tobs = None 

204 marker = ds.get_pick( 

205 source.name, 

206 self.codes[:3], 

207 self.pick_phasename) 

208 

209 if marker: 

210 polarity_obs = marker.get_polarity() 

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

212 raise GrondError( 

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

214 % marker.one_nslc()) 

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

216 else: 

217 self._obs_cache[k] = None 

218 

219 tobs, polarity_obs = self._obs_cache[k] 

220 

221 store = engine.get_store(self.store_id) 

222 

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

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

225 

226 if use_polarities: 

227 traveltime, takeoff_angle = store.t( 

228 self.pick_synthetic_traveltime, source, self, 

229 attributes=['takeoff_angle']) 

230 else: 

231 traveltime = store.t( 

232 self.pick_synthetic_traveltime, source, self) 

233 takeoff_angle = None 

234 

235 if use_traveltimes: 

236 tsyn = source.time + traveltime 

237 else: 

238 tsyn = None 

239 

240 if use_polarities: 

241 mt = source.pyrocko_moment_tensor() 

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

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

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

245 else: 

246 polarity_syn = None 

247 azimuth = None 

248 

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

250 

251 def finalize_modelling( 

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

253 

254 ds = self.get_dataset() # noqa 

255 

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

257 self.get_times_polarities(engine, source) 

258 

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

260 misfits[:, 1] = 1.0 

261 

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

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

264 

265 if self.weight_polarity > 0. \ 

266 and None not in (polarity_obs, polarity_syn): 

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

268 * 0.5 * abs(polarity_obs - polarity_syn) 

269 

270 result = PhasePickResult( 

271 tobs=tobs, 

272 tsyn=tsyn, 

273 polarity_obs=polarity_obs, 

274 polarity_syn=polarity_syn, 

275 azimuth=azimuth, 

276 takeoff_angle=takeoff_angle, 

277 misfits=misfits) 

278 

279 return result 

280 

281 

282__all__ = ''' 

283 PhasePickTargetGroup 

284 PhasePickTarget 

285 PhasePickResult 

286'''.split()