Coverage for /usr/local/lib/python3.11/dist-packages/grond/targets/gnss_campaign/target.py: 84%
153 statements
« prev ^ index » next coverage.py v6.5.0, created at 2024-11-27 15:15 +0000
« 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
5import numpy as num
6from scipy import linalg as splinalg
8from pyrocko import gf
9from pyrocko.guts import String, Dict, List, Int
11from ..base import MisfitConfig, MisfitTarget, MisfitResult, TargetGroup
12from grond.meta import has_get_plot_classes
14guts_prefix = 'grond'
15logger = logging.getLogger('grond.target').getChild('gnss_campaign')
18class GNSSCampaignMisfitResult(MisfitResult):
19 """Carries the observations for a target and corresponding synthetics. """
20 statics_syn = Dict.T(optional=True,
21 help='Synthetic gnss surface displacements')
22 statics_obs = Dict.T(optional=True,
23 help='Observed gnss surface displacements')
26class GNSSCampaignMisfitConfig(MisfitConfig):
27 pass
30class GNSSCampaignTargetGroup(TargetGroup):
31 """Handles static displacements from campaign GNSS observations, e.g GPS.
33 Station information, displacements and measurement errors are provided in
34 a `yaml`-file (please find an example in the documentation). The
35 measurement errors may consider correlations between components of a
36 station, but correlations between single stations is not considered.
37 """
38 gnss_campaigns = List.T(
39 optional=True,
40 help='List of individual campaign names'
41 ' (`name` in `gnss.yaml` files).')
42 misfit_config = GNSSCampaignMisfitConfig.T()
44 def get_targets(self, ds, event, default_path='none'):
45 logger.debug('Selecting GNSS targets...')
46 targets = []
48 for camp in ds.get_gnss_campaigns():
49 if camp.name not in self.gnss_campaigns and\
50 '*all' not in self.gnss_campaigns:
51 continue
53 if not isinstance(self.misfit_config,
54 GNSSCampaignMisfitConfig):
55 raise AttributeError('misfit_config must be of type'
56 ' GNSSCampaignMisfitConfig.')
58 lats = num.array([s.lat for s in camp.stations])
59 lons = num.array([s.lon for s in camp.stations])
61 north_shifts = num.array([s.north_shift for s in camp.stations])
62 east_shifts = num.array([s.east_shift for s in camp.stations])
64 gnss_target = GNSSCampaignMisfitTarget(
65 quantity='displacement',
66 campaign_name=camp.name,
67 lats=lats,
68 lons=lons,
69 east_shifts=east_shifts,
70 north_shifts=north_shifts,
71 ncomponents=camp.ncomponents,
72 tsnapshot=None,
73 interpolation=self.interpolation,
74 store_id=self.store_id,
75 normalisation_family=self.normalisation_family,
76 path=self.path or default_path,
77 misfit_config=self.misfit_config)
79 gnss_target.set_dataset(ds)
80 targets.append(gnss_target)
82 return targets
85@has_get_plot_classes
86class GNSSCampaignMisfitTarget(gf.GNSSCampaignTarget, MisfitTarget):
87 """Handles and carries out operations related to the objective functions.
89 The objective function is here the weighted misfit between observed
90 and predicted surface displacements.
91 """
92 campaign_name = String.T()
93 ncomponents = Int.T(optional=True)
94 misfit_config = GNSSCampaignMisfitConfig.T()
96 can_bootstrap_weights = True
97 can_bootstrap_residuals = True
99 plot_misfits_cumulative = False
101 def __init__(self, **kwargs):
102 gf.GNSSCampaignTarget.__init__(self, **kwargs)
103 MisfitTarget.__init__(self, **kwargs)
104 self._obs_data = None
105 self._sigma = None
106 self._weights = None
107 self._correlated_weights = None
108 self._station_component_mask = None
110 @property
111 def id(self):
112 return self.campaign_name
114 def string_id(self):
115 return self.campaign_name
117 def misfits_string_ids(self):
118 id_list = []
119 for station in self.campaign.stations:
120 for name in ('north', 'east', 'up'):
121 component = station.__getattribute__(name)
122 if not component:
123 continue
124 id_list.append('%s.%s.%s' %
125 (self.path, station.code, name[0].upper()))
126 return id_list
128 @property
129 def station_names(self):
130 return ['%s' % (station.code)
131 for station in self.campaign.stations]
133 @property
134 def nmisfits(self):
135 if self.ncomponents is None:
136 try:
137 self.campaign.ncomponents
138 except ValueError:
139 raise ValueError('Set the dataset!')
140 return self.ncomponents
142 @property
143 def nstations(self):
144 return self.lats.size
146 def set_dataset(self, ds):
147 MisfitTarget.set_dataset(self, ds)
149 def get_correlated_weights(self):
150 if self._correlated_weights is None:
151 self._correlated_weights = splinalg.sqrtm(self.weights)
152 return self._correlated_weights
154 @property
155 def campaign(self):
156 return self._ds.get_gnss_campaign(self.campaign_name)
158 @property
159 def obs_data(self):
160 if self._obs_data is None:
161 self._obs_data = num.concatenate(
162 [s.get_displacement_data() for s in self.campaign.stations])
163 return self._obs_data
165 @property
166 def obs_sigma(self):
167 if self._sigma is None:
168 self._sigma = num.array([
169 [s.north.sigma for s in self.campaign.stations],
170 [s.east.sigma for s in self.campaign.stations],
171 [s.up.sigma for s in self.campaign.stations]])\
172 .ravel(order='F')
173 return self._sigma
175 @property
176 def weights(self):
177 """
178 Weights are the square-rooted, inverted data error variance-covariance.
180 The single component variances, and if provided the component
181 covariances, are used to build a data variance matrix or
182 variance-covariance matrix.
184 This matrix has the size for all possible NEU components,
185 but throws zeros for not given components, also recorded in
186 the _station_component_mask.
187 """
188 if self._weights is None:
189 covar = self.campaign.get_covariance_matrix()
191 if not num.any(covar.diagonal()):
192 logger.warning('GNSS Stations have an empty covariance matrix.'
193 ' Weights will be all equal.')
194 num.fill_diagonal(covar, 1.)
196 self._weights = num.asmatrix(covar).I
198 return self._weights
200 @property
201 def station_component_mask(self):
202 if self._station_component_mask is None:
203 self._station_component_mask = self.campaign.get_component_mask()
204 return self._station_component_mask
206 @property
207 def station_weights(self):
208 weights = num.diag(self.weights)
209 return num.mean([weights[0::3], weights[1::3], weights[2::3]], axis=0)
211 def component_weights(self):
212 ws = num.sum(self.weights, axis=0)
213 return ws
215 def post_process(self, engine, source, statics):
216 """Applies the objective function.
218 As a result the weighted misfits are given and the observed and
219 synthetic data.
220 """
221 obs = self.obs_data
223 # All data is ordered in vectors as
224 # S1_n, S1_e, S1_u, ..., Sn_n, Sn_e, Sn_u. Hence (.ravel(order='F'))
225 syn = num.array([
226 statics['displacement.n'],
227 statics['displacement.e'],
228 -statics['displacement.d']])\
229 .ravel(order='F')
231 syn = syn[self.station_component_mask]
232 res = obs - syn
234 misfit_value = res
235 misfit_norm = obs
237 mf = num.vstack((misfit_value, misfit_norm)).T
239 result = GNSSCampaignMisfitResult(
240 misfits=mf)
241 if self._result_mode == 'full':
242 result.statics_syn = statics
243 result.statics_obs = obs
245 return result
247 def get_combined_weight(self):
248 """A given manual weight in the configuration is applied."""
249 if self._combined_weight is None:
250 self._combined_weight = num.full(self.nmisfits, self.manual_weight)
252 return self._combined_weight
254 def init_bootstrap_residuals(self, nbootstraps, rstate=None):
255 logger.info('GNSS campaign %s, bootstrapping residuals'
256 ' from measurement uncertainties ...'
257 % self.campaign.name)
258 if rstate is None:
259 rstate = num.random.RandomState()
261 campaign = self.campaign
262 bootstraps = num.empty((nbootstraps, self.ncomponents))
264 sigmas = num.diag(num.sqrt(campaign.get_covariance_matrix()))
265 if not num.all(sigmas):
266 logger.warning('Bootstrapping GNSS stations is meaningless,'
267 ' all station\'s sigma are 0.0!')
269 for ibs in range(nbootstraps):
270 syn_noise = rstate.normal(scale=sigmas.ravel())
271 bootstraps[ibs, :] = syn_noise
273 self.set_bootstrap_residuals(bootstraps)
275 @classmethod
276 def get_plot_classes(cls):
277 from . import plot
278 plots = super(GNSSCampaignMisfitTarget, cls).get_plot_classes()
279 plots.extend(plot.get_plot_classes())
280 return plots