Coverage for /usr/local/lib/python3.11/dist-packages/grond/targets/base.py: 82%
150 statements
« prev ^ index » next coverage.py v6.5.0, created at 2025-04-03 09:31 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2025-04-03 09:31 +0000
1# https://pyrocko.org/grond - GPLv3
2#
3# The Grond Developers, 21st Century
4import copy
5import logging
7import numpy as num
9from pyrocko import gf
10from pyrocko.guts_array import Array
11from pyrocko.guts import Object, Float, String, Dict, List, Choice, load, dump
13from grond.analysers.base import AnalyserResult
14from grond.meta import has_get_plot_classes, GrondError
16logger = logging.getLogger('targets.base')
18guts_prefix = 'grond'
21class TargetGroup(Object):
22 normalisation_family = gf.StringID.T(
23 optional=True,
24 help='Group with common misfit normalisation')
25 path = gf.StringID.T(
26 optional=True,
27 help='Targets.id will be prefixed with this path')
28 weight = Float.T(
29 default=1.0,
30 help='Additional manual weight of the target group')
31 interpolation = gf.InterpolationMethod.T(
32 default='nearest_neighbor',
33 help='Interpolation from pre-calculated GF store.')
34 store_id = gf.StringID.T(
35 optional=True,
36 help='ID of the Green\'s function store for this TargetGroup.')
38 def get_targets(self, ds, event, default_path='none'):
39 if not self._targets:
40 raise NotImplementedError()
42 def is_gf_store_appropriate(self, store, depth_range):
43 return True
45 def _is_gf_store_appropriate_check_extent(self, store, depth_range):
46 gf_mindist = store.config.distance_min
47 gf_maxdist = store.config.distance_max
48 gf_mindepth = store.config.source_depth_min
49 gf_maxdepth = store.config.source_depth_max
51 ok = True
53 if self.distance_min is not None and self.distance_min < gf_mindist:
54 logger.error(
55 'CONFIG: Target min. distance (%s) ' % self.distance_min
56 + '< Greens function min. distance (%s)!' % gf_mindist)
57 ok = False
59 if self.distance_max is not None and self.distance_max > gf_maxdist:
60 logger.error(
61 'CONFIG: Target max. distance (%s) ' % self.distance_max
62 + '> Greens function max. distance (%s)!' % gf_maxdist)
63 ok = False
65 if depth_range.start < gf_mindepth or gf_maxdepth < depth_range.stop:
66 logger.error(
67 'CONFIG: Depth range of problem not covered by GF store! '
68 + 'GF store: %s %s - %s '
69 % (self.store_id, gf_mindepth, gf_maxdepth)
70 + 'Problem depth range: %s - %s'
71 % (depth_range.start, depth_range.stop))
72 ok = False
74 return ok
76 def _is_gf_store_appropriate_check_sample_rate(self, store, depth_range):
77 gf_sampling = store.config.sample_rate
79 if self.misfit_config.fmax > 0.5*gf_sampling:
80 logger.error(
81 'CONFIG: Target max. frequency (%s) ' % self.misfit_config.fmax
82 + ' > Nyquist frequency of GF store %s (sampling %s)!'
83 % (self.store_id, gf_sampling))
85 return False
87 return True
90class MisfitResult(Object):
91 misfits = Array.T(
92 shape=(None, 2),
93 dtype=num.float64)
96class MisfitConfig(Object):
97 pass
100@has_get_plot_classes
101class MisfitTarget(Object):
103 manual_weight = Float.T(
104 default=1.0,
105 help='Relative weight of this target')
106 analyser_results = Dict.T(
107 gf.StringID.T(),
108 AnalyserResult.T(),
109 help='Dictionary of analyser results')
110 normalisation_family = gf.StringID.T(
111 optional=True,
112 help='Normalisation family of this misfit target')
113 path = gf.StringID.T(
114 help='A path identifier used for plotting')
115 misfit_config = MisfitConfig.T(
116 default=MisfitConfig.D(),
117 help='Misfit configuration')
118 bootstrap_weights = Array.T(
119 dtype=num.float64,
120 serialize_as='base64',
121 optional=True)
122 bootstrap_residuals = Array.T(
123 dtype=num.float64,
124 serialize_as='base64',
125 optional=True)
127 can_bootstrap_weights = False
128 can_bootstrap_residuals = False
130 plot_misfits_cumulative = True
132 def __init__(self, **kwargs):
133 Object.__init__(self, **kwargs)
134 self.parameters = []
136 self._ds = None
137 self._result_mode = 'sparse'
139 self._combined_weight = None
140 self._target_parameters = None
141 self._target_ranges = None
143 self._combined_weight = None
145 @classmethod
146 def get_plot_classes(cls):
147 return []
149 def set_dataset(self, ds):
150 self._ds = ds
152 def get_dataset(self):
153 return self._ds
155 def string_id(self):
156 return str(self.path)
158 def misfits_string_ids(self):
159 raise NotImplementedError('%s does not implement misfits_string_id'
160 % self.__class__.__name__)
162 @property
163 def nmisfits(self):
164 return 1
166 def noise_weight_matrix(self):
167 return num.array([[1]])
169 @property
170 def nparameters(self):
171 if self._target_parameters is None:
172 return 0
173 return len(self._target_parameters)
175 @property
176 def target_parameters(self):
177 if self._target_parameters is None:
178 self._target_parameters = copy.deepcopy(self.parameters)
179 for p in self._target_parameters:
180 p.set_groups([self.string_id()])
181 return self._target_parameters
183 @property
184 def target_ranges(self):
185 return {}
187 def set_parameter_values(self, model):
188 for i, p in enumerate(self.parameters):
189 self.parameter_values[p.name_nogroups] = model[i]
191 def set_result_mode(self, result_mode):
192 self._result_mode = result_mode
194 def post_process(self, engine, source, statics):
195 raise NotImplementedError()
197 def get_combined_weight(self):
198 if self._combined_weight is None:
199 w = num.full(self.nmisfits, self.manual_weight, dtype=float)
200 for analyser in self.analyser_results.values():
201 w *= analyser.weight
202 self._combined_weight = w
204 return self._combined_weight
206 def get_correlated_weights(self):
207 pass
209 def set_bootstrap_weights(self, weights):
210 self.bootstrap_weights = weights
212 def get_bootstrap_weights(self):
213 if self.bootstrap_weights is None:
214 raise Exception('Bootstrap weights have not been set!')
215 nbootstraps = self.bootstrap_weights.size // self.nmisfits
216 return self.bootstrap_weights.reshape(nbootstraps, self.nmisfits)
218 def init_bootstrap_residuals(self, nbootstrap, rstate=None):
219 raise NotImplementedError()
221 def set_bootstrap_residuals(self, residuals):
222 self.bootstrap_residuals = residuals
224 def get_bootstrap_residuals(self):
225 if self.bootstrap_residuals is None:
226 raise Exception('Bootstrap residuals have not been set!')
227 nbootstraps = self.bootstrap_residuals.size // self.nmisfits
228 return self.bootstrap_residuals.reshape(nbootstraps, self.nmisfits)
230 def prepare_modelling(self, engine, source, targets):
231 ''' Prepare modelling target
233 This function shall return a list of :class:`pyrocko.gf.Target`
234 for forward modelling in the :class:`pyrocko.gf.LocalEngine`.
235 '''
236 return [self]
238 def finalize_modelling(
239 self, engine, source, modelling_targets, modelling_results):
240 ''' Manipulate modelling before misfit calculation
242 This function can be overloaded interact with the modelling results.
243 '''
244 return modelling_results[0]
247class MisfitResultError(Object):
248 message = String.T()
251class MisfitResultCollection(Object):
252 results = List.T(List.T(
253 Choice.T([MisfitResult.T(), MisfitResultError.T()])))
256def dump_misfit_result_collection(misfit_result_collection, path):
257 dump(misfit_result_collection, filename=path)
260def load_misfit_result_collection(path):
261 try:
262 obj = load(filename=path)
264 except OSError as e:
265 raise GrondError(
266 'Failed to read ensemble misfit results from file "%s" (%s)' % (
267 path, e))
269 if not isinstance(obj, MisfitResultCollection):
270 raise GrondError(
271 'File "%s" does not contain any misfit result collection.' % path)
273 return obj
276__all__ = '''
277 TargetGroup
278 MisfitTarget
279 MisfitResult
280 MisfitResultError
281 dump_misfit_result_collection
282 load_misfit_result_collection
283 MisfitResultCollection
284'''.split()