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