class VLVDProblem(Problem): problem_parameters = [ Parameter('north_shift', 'm', label='Northing', **as_km), Parameter('east_shift', 'm', label='Easting', **as_km), Parameter('depth', 'm', label='Depth', **as_km), Parameter('dip', 'deg', label='Dip'), Parameter('azimuth', 'deg', label='Azimuth'), Parameter('clvd_moment', 'Nm', label='CLVD Moment'), Parameter('volume_change', 'm^3', label='Volume Change', **as_km3), ] problem_waveform_parameters = [ Parameter('time', 's', label='Time'), Parameter('duration', 's', label='Duration') ] distance_min = Float.T(default=0.0) def pack(self, source): arr = self.get_parameter_array(source) for ip, p in enumerate(self.parameters): if p.name == 'time': arr[ip] -= self.base_source.time return arr def get_source(self, x): d = self.get_parameter_dict(x) p = { k: float(self.ranges[k].make_relative(self.base_source[k], d[k])) for k in self.base_source.keys() if k in d } stf = None if self.has_waveforms: stf = gf.HalfSinusoidSTF(duration=float(d.duration)) source = self.base_source.clone(stf=stf, **p) return source @classmethod def get_plot_classes(cls): from . import plot plots = super(VLVDProblem, cls).get_plot_classes() plots.extend([plot.VLVDLocationPlot]) return plots
class Problem(Object): ''' Base class for objective function setup. Defines the *problem* to be solved by the optimiser. ''' name = String.T() ranges = Dict.T(String.T(), gf.Range.T()) dependants = List.T(Parameter.T()) norm_exponent = Int.T(default=2) base_source = gf.Source.T(optional=True) targets = List.T(MisfitTarget.T()) target_groups = List.T(TargetGroup.T()) grond_version = String.T(optional=True) nthreads = Int.T(default=1) def __init__(self, **kwargs): Object.__init__(self, **kwargs) if self.grond_version is None: self.grond_version = __version__ self._target_weights = None self._engine = None self._family_mask = None if hasattr(self, 'problem_waveform_parameters') and self.has_waveforms: self.problem_parameters =\ self.problem_parameters + self.problem_waveform_parameters self.check() @classmethod def get_plot_classes(cls): from . import plot return plot.get_plot_classes() def check(self): paths = set() for grp in self.target_groups: if grp.path == 'all': continue if grp.path in paths: raise ValueError('Path %s defined more than once! In %s' % (grp.path, grp.__class__.__name__)) paths.add(grp.path) logger.debug('TargetGroup check OK.') def copy(self): o = copy.copy(self) o._target_weights = None return o def set_target_parameter_values(self, x): nprob = len(self.problem_parameters) for target in self.targets: target.set_parameter_values(x[nprob:nprob + target.nparameters]) nprob += target.nparameters def get_parameter_dict(self, model, group=None): params = [] for ip, p in enumerate(self.parameters): if group in p.groups or group is None: params.append((p.name, model[ip])) return ADict(params) def get_parameter_array(self, d): arr = num.zeros(self.nparameters, dtype=num.float) for ip, p in enumerate(self.parameters): if p.name in d.keys(): arr[ip] = d[p.name] return arr def dump_problem_info(self, dirname): fn = op.join(dirname, 'problem.yaml') util.ensuredirs(fn) guts.dump(self, filename=fn) def dump_problem_data(self, dirname, x, misfits, bootstraps=None, sampler_context=None): fn = op.join(dirname, 'models') if not isinstance(x, num.ndarray): x = num.array(x) with open(fn, 'ab') as f: x.astype('<f8').tofile(f) fn = op.join(dirname, 'misfits') with open(fn, 'ab') as f: misfits.astype('<f8').tofile(f) if bootstraps is not None: fn = op.join(dirname, 'bootstraps') with open(fn, 'ab') as f: bootstraps.astype('<f8').tofile(f) if sampler_context is not None: fn = op.join(dirname, 'choices') with open(fn, 'ab') as f: num.array(sampler_context, dtype='<i8').tofile(f) def name_to_index(self, name): pnames = [p.name for p in self.combined] return pnames.index(name) @property def parameters(self): target_parameters = [] for target in self.targets: target_parameters.extend(target.target_parameters) return self.problem_parameters + target_parameters @property def parameter_names(self): return [p.name for p in self.combined] @property def dependant_names(self): return [p.name for p in self.dependants] @property def nparameters(self): return len(self.parameters) @property def ntargets(self): return len(self.targets) @property def nwaveform_targets(self): return len(self.waveform_targets) @property def nsatellite_targets(self): return len(self.satellite_targets) @property def ngnss_targets(self): return len(self.gnss_targets) @property def nmisfits(self): nmisfits = 0 for target in self.targets: nmisfits += target.nmisfits return nmisfits @property def ndependants(self): return len(self.dependants) @property def ncombined(self): return len(self.parameters) + len(self.dependants) @property def combined(self): return self.parameters + self.dependants @property def satellite_targets(self): return [ t for t in self.targets if isinstance(t, SatelliteMisfitTarget) ] @property def gnss_targets(self): return [ t for t in self.targets if isinstance(t, GNSSCampaignMisfitTarget) ] @property def waveform_targets(self): return [t for t in self.targets if isinstance(t, WaveformMisfitTarget)] @property def has_satellite(self): if self.satellite_targets: return True return False @property def has_waveforms(self): if self.waveform_targets: return True return False def set_engine(self, engine): self._engine = engine def get_engine(self): return self._engine def get_gf_store(self, target): if self.get_engine() is None: raise GrondError('Cannot get GF Store, modelling is not set up!') return self.get_engine().get_store(target.store_id) def random_uniform(self, xbounds, rstate): x = rstate.uniform(0., 1., self.nparameters) x *= (xbounds[:, 1] - xbounds[:, 0]) x += xbounds[:, 0] return x def preconstrain(self, x): return x def extract(self, xs, i): if xs.ndim == 1: return self.extract(xs[num.newaxis, :], i)[0] if i < self.nparameters: return xs[:, i] else: return self.make_dependant( xs, self.dependants[i - self.nparameters].name) def get_target_weights(self): if self._target_weights is None: self._target_weights = num.concatenate( [target.get_combined_weight() for target in self.targets]) return self._target_weights def get_target_residuals(self): pass def inter_family_weights(self, ns): exp, root = self.get_norm_functions() family, nfamilies = self.get_family_mask() ws = num.zeros(self.nmisfits) for ifamily in range(nfamilies): mask = family == ifamily ws[mask] = 1.0 / root(num.nansum(exp(ns[mask]))) return ws def inter_family_weights2(self, ns): ''' :param ns: 2D array with normalization factors ``ns[imodel, itarget]`` :returns: 2D array ``weights[imodel, itarget]`` ''' exp, root = self.get_norm_functions() family, nfamilies = self.get_family_mask() ws = num.zeros(ns.shape) for ifamily in range(nfamilies): mask = family == ifamily ws[:, mask] = ( 1.0 / root(num.nansum(exp(ns[:, mask]), axis=1)))[:, num.newaxis] return ws def get_reference_model(self, expand=False): if expand: src_params = self.pack(self.base_source) ref = num.zeros(self.nparameters) ref[:src_params.size] = src_params else: ref = self.pack(self.base_source) return ref def get_parameter_bounds(self): out = [] for p in self.problem_parameters: r = self.ranges[p.name] out.append((r.start, r.stop)) for target in self.targets: for p in target.target_parameters: r = target.target_ranges[p.name_nogroups] out.append((r.start, r.stop)) return num.array(out, dtype=num.float) def get_dependant_bounds(self): return num.zeros((0, 2)) def get_combined_bounds(self): return num.vstack( (self.get_parameter_bounds(), self.get_dependant_bounds())) def raise_invalid_norm_exponent(self): raise GrondError('Invalid norm exponent: %f' % self.norm_exponent) def get_norm_functions(self): if self.norm_exponent == 2: def sqr(x): return x**2 return sqr, num.sqrt elif self.norm_exponent == 1: def noop(x): return x return noop, num.abs else: self.raise_invalid_norm_exponent() def combine_misfits(self, misfits, extra_weights=None, extra_residuals=None, get_contributions=False): ''' Combine misfit contributions (residuals) to global or bootstrap misfits :param misfits: 3D array ``misfits[imodel, iresidual, 0]`` are the misfit contributions (residuals) ``misfits[imodel, iresidual, 1]`` are the normalisation contributions. It is also possible to give the misfit and normalisation contributions for a single model as ``misfits[iresidual, 0]`` and misfits[iresidual, 1]`` in which case, the first dimension (imodel) of the result will be stipped off. :param extra_weights: if given, 2D array of extra weights to be applied to the contributions, indexed as ``extra_weights[ibootstrap, iresidual]``. :param extra_residuals: if given, 2D array of perturbations to be added to the residuals, indexed as ``extra_residuals[ibootstrap, iresidual]``. :param get_contributions: get the weighted and perturbed contributions (don't do the sum). :returns: if no *extra_weights* or *extra_residuals* are given, a 1D array indexed as ``misfits[imodel]`` containing the global misfit for each model is returned, otherwise a 2D array ``misfits[imodel, ibootstrap]`` with the misfit for every model and weighting/residual set is returned. ''' exp, root = self.get_norm_functions() if misfits.ndim == 2: misfits = misfits[num.newaxis, :, :] return self.combine_misfits(misfits, extra_weights, extra_residuals, get_contributions)[0, ...] assert misfits.ndim == 3 assert extra_weights is None or extra_weights.ndim == 2 assert extra_residuals is None or extra_residuals.ndim == 2 if extra_weights is not None or extra_residuals is not None: if extra_weights is not None: w = extra_weights[num.newaxis, :, :] \ * self.get_target_weights()[num.newaxis, num.newaxis, :] \ * self.inter_family_weights2( misfits[:, :, 1])[:, num.newaxis, :] else: w = 1.0 if extra_residuals is not None: r = extra_residuals[num.newaxis, :, :] else: r = 0.0 if get_contributions: return exp(w*(misfits[:, num.newaxis, :, 0]+r)) \ / num.nansum( exp(w*misfits[:, num.newaxis, :, 1]), axis=2)[:, :, num.newaxis] res = root( num.nansum(exp(w * (misfits[:, num.newaxis, :, 0] + r)), axis=2) / num.nansum(exp(w * (misfits[:, num.newaxis, :, 1])), axis=2)) assert res[res < 0].size == 0 return res else: w = self.get_target_weights()[num.newaxis, :] \ * self.inter_family_weights2(misfits[:, :, 1]) r = self.get_target_weights()[num.newaxis, :] \ * self.inter_family_weights2(misfits[:, :, 1]) if get_contributions: return exp(w*misfits[:, :, 0]) \ / num.nansum( exp(w*misfits[:, :, 1]), axis=1)[:, num.newaxis] return root( num.nansum(exp(w * misfits[:, :, 0]), axis=1) / num.nansum(exp(w * misfits[:, :, 1]), axis=1)) def make_family_mask(self): family_names = set() families = num.zeros(self.nmisfits, dtype=num.int) idx = 0 for itarget, target in enumerate(self.targets): family_names.add(target.normalisation_family) families[idx:idx + target.nmisfits] = len(family_names) - 1 idx += target.nmisfits return families, len(family_names) def get_family_mask(self): if self._family_mask is None: self._family_mask = self.make_family_mask() return self._family_mask def evaluate(self, x, mask=None, result_mode='full', targets=None): source = self.get_source(x) engine = self.get_engine() self.set_target_parameter_values(x) if mask is not None and targets is not None: raise ValueError('Mask cannot be defined with targets set.') targets = targets if targets is not None else self.targets for target in targets: target.set_result_mode(result_mode) modelling_targets = [] t2m_map = {} for itarget, target in enumerate(targets): t2m_map[target] = target.prepare_modelling(engine, source, targets) if mask is None or mask[itarget]: modelling_targets.extend(t2m_map[target]) u2m_map = {} for imtarget, mtarget in enumerate(modelling_targets): if mtarget not in u2m_map: u2m_map[mtarget] = [] u2m_map[mtarget].append(imtarget) modelling_targets_unique = list(u2m_map.keys()) resp = engine.process(source, modelling_targets_unique, nthreads=self.nthreads) modelling_results_unique = list(resp.results_list[0]) modelling_results = [None] * len(modelling_targets) for mtarget, mresult in zip(modelling_targets_unique, modelling_results_unique): for itarget in u2m_map[mtarget]: modelling_results[itarget] = mresult imt = 0 results = [] for itarget, target in enumerate(targets): nmt_this = len(t2m_map[target]) if mask is None or mask[itarget]: result = target.finalize_modelling( engine, source, t2m_map[target], modelling_results[imt:imt + nmt_this]) imt += nmt_this else: result = gf.SeismosizerError( 'target was excluded from modelling') results.append(result) return results def misfits(self, x, mask=None): results = self.evaluate(x, mask=mask, result_mode='sparse') misfits = num.full((self.nmisfits, 2), num.nan) imisfit = 0 for target, result in zip(self.targets, results): if isinstance(result, MisfitResult): misfits[imisfit:imisfit + target.nmisfits, :] = result.misfits imisfit += target.nmisfits return misfits def forward(self, x): source = self.get_source(x) engine = self.get_engine() plain_targets = [] for target in self.targets: plain_targets.extend(target.get_plain_targets(engine, source)) resp = engine.process(source, plain_targets) results = [] for target, result in zip(plain_targets, resp.results_list[0]): if isinstance(result, gf.SeismosizerError): logger.debug('%s.%s.%s.%s: %s' % (target.codes + (str(result), ))) else: results.append(result) return results def get_random_model(self, ntries_limit=100): xbounds = self.get_parameter_bounds() for _ in range(ntries_limit): x = self.random_uniform(xbounds, rstate=g_rstate) try: return self.preconstrain(x) except Forbidden: pass raise GrondError( 'Could not find any suitable candidate sample within %i tries' % (ntries_limit))
class RectangularProblem(Problem): # nucleation_x # nucleation_y # time # stf problem_parameters = [ Parameter('east_shift', 'm', label='Easting', **as_km), Parameter('north_shift', 'm', label='Northing', **as_km), Parameter('depth', 'm', label='Depth', **as_km), Parameter('length', 'm', label='Length', **as_km), Parameter('width', 'm', label='Width', **as_km), Parameter('slip', 'm', label='Slip'), Parameter('strike', 'deg', label='Strike'), Parameter('dip', 'deg', label='Dip'), Parameter('rake', 'deg', label='Rake') ] problem_waveform_parameters = [ Parameter('nucleation_x', 'offset', label='Nucleation X'), Parameter('nucleation_y', 'offset', label='Nucleation Y'), Parameter('time', 's', label='Time'), ] dependants = [] distance_min = Float.T(default=0.0) def pack(self, source): arr = self.get_parameter_array(source) for ip, p in enumerate(self.parameters): if p.name == 'time': arr[ip] -= self.base_source.time return arr def get_source(self, x): d = self.get_parameter_dict(x) p = {} for k in self.base_source.keys(): if k in d: p[k] = float(self.ranges[k].make_relative( self.base_source[k], d[k])) source = self.base_source.clone(**p) return source def random_uniform(self, xbounds, rstate): x = num.zeros(self.nparameters) for i in range(self.nparameters): x[i] = rstate.uniform(xbounds[i, 0], xbounds[i, 1]) return x def preconstrain(self, x): # source = self.get_source(x) # if any(self.distance_min > source.distance_to(t) # for t in self.targets): # raise Forbidden() return x @classmethod def get_plot_classes(cls): plots = super(RectangularProblem, cls).get_plot_classes() return plots
class SatelliteMisfitTarget(gf.SatelliteTarget, MisfitTarget): """Handles and carries out operations related to the objective functions. Standard operations are the calculation of the weighted misfit between observed and predicted (synthetic) data. If enabled in the misfit configuration, orbital ramps are optimized for. """ scene_id = String.T(help='UID string each Kite displacemente scene.' ' Corresponds to Kite scene_id.') misfit_config = SatelliteMisfitConfig.T( help='Configuration of the ``SatelliteTarget``') can_bootstrap_residuals = True available_parameters = [ Parameter('offset', 'm'), Parameter('ramp_north', 'm/m'), Parameter('ramp_east', 'm/m') ] def __init__(self, *args, **kwargs): gf.SatelliteTarget.__init__(self, *args, **kwargs) MisfitTarget.__init__(self, **kwargs) if not self.misfit_config.optimise_orbital_ramp: self.parameters = [] else: self.parameters = self.available_parameters self.parameter_values = {} @property def target_ranges(self): return self.misfit_config.ranges def string_id(self): return '.'.join([self.path, self.scene_id]) def set_dataset(self, ds): MisfitTarget.set_dataset(self, ds) @property def nmisfits(self): return self.lats.size @property def scene(self): return self._ds.get_kite_scene(self.scene_id) def post_process(self, engine, source, statics): """Applies the objective function. As a result the weighted misfits are given and the observed and synthetic data. For the satellite target the orbital ramp is calculated and applied here.""" scene = self.scene quadtree = scene.quadtree obs = quadtree.leaf_medians if self.misfit_config.optimise_orbital_ramp: stat_level = num.full_like(obs, self.parameter_values['offset']) stat_level += (quadtree.leaf_center_distance[:, 0] * self.parameter_values['ramp_east']) stat_level += (quadtree.leaf_center_distance[:, 1] * self.parameter_values['ramp_north']) statics['displacement.los'] += stat_level stat_syn = statics['displacement.los'] res = obs - stat_syn misfit_value = res misfit_norm = obs mf = num.vstack([misfit_value, misfit_norm]).T result = SatelliteMisfitResult(misfits=mf) if self._result_mode == 'full': result.statics_syn = statics result.statics_obs = quadtree.leaf_medians return result def get_combined_weight(self): if self._combined_weight is None: self._combined_weight = num.full(self.nmisfits, self.manual_weight) return self._combined_weight def prepare_modelling(self, engine, source, targets): return [self] def finalize_modelling(self, engine, source, modelling_targets, modelling_results): return modelling_results[0] def init_bootstrap_residuals(self, nbootstraps, rstate=None): logger.info( 'Scene "%s", initializing bootstrapping residuals from noise ' 'pertubations...' % self.scene_id) if rstate is None: rstate = num.random.RandomState() scene = self.scene qt = scene.quadtree cov = scene.covariance bootstraps = num.empty((nbootstraps, qt.nleaves)) for ibs in range(nbootstraps): if not (ibs + 1) % 5: logger.info('Calculating noise realisation %d/%d.' % (ibs + 1, nbootstraps)) bootstraps[ibs, :] = cov.getQuadtreeNoise(rstate=rstate) self.set_bootstrap_residuals(bootstraps) @classmethod def get_plot_classes(cls): from . import plot plots = super(SatelliteMisfitTarget, cls).get_plot_classes() plots.extend(plot.get_plot_classes()) return plots
class SatelliteMisfitTarget(gf.SatelliteTarget, MisfitTarget): """Handles and carries out operations related to the objective functions. Standard operations are the calculation of the weighted misfit between observed and predicted (synthetic) data. If enabled in the misfit configuration, orbital ramps are optimized for. """ scene_id = String.T( help='UID string each Kite displacemente scene.' ' Corresponds to Kite scene_id.') misfit_config = SatelliteMisfitConfig.T( help='Configuration of the ``SatelliteTarget``') can_bootstrap_residuals = True available_parameters = [ Parameter('offset', 'm'), Parameter('ramp_north', 'm/m'), Parameter('ramp_east', 'm/m')] def __init__(self, *args, **kwargs): gf.SatelliteTarget.__init__(self, *args, **kwargs) MisfitTarget.__init__(self, **kwargs) if not self.misfit_config.optimise_orbital_ramp: self.parameters = [] else: self.parameters = self.available_parameters self.parameter_values = {} self._noise_weight_matrix = None @property def target_ranges(self): return self.misfit_config.ranges def string_id(self): return '.'.join([self.path, self.scene_id]) @property def id(self): return self.scene_id def set_dataset(self, ds): MisfitTarget.set_dataset(self, ds) @property def nmisfits(self): return self.lats.size def get_correlated_weights(self, nthreads=0): ''' is for L2-norm weighting, the square-rooted, inverse covar ''' if self._noise_weight_matrix is None: logger.info( 'Inverting scene covariance matrix (nthreads=%i)...' % nthreads) cov = self.scene.covariance cov.nthreads = nthreads self._noise_weight_matrix = splinalg.sqrtm( num.linalg.inv(cov.covariance_matrix)) logger.info('Inverting scene covariance matrix done.') return self._noise_weight_matrix @property def scene(self): return self._ds.get_kite_scene(self.scene_id) def post_process(self, engine, source, statics): """Applies the objective function. As a result the weighted misfits are given and the observed and synthetic data. For the satellite target the orbital ramp is calculated and applied here.""" scene = self.scene quadtree = scene.quadtree obs = quadtree.leaf_medians if self.misfit_config.optimise_orbital_ramp: stat_level = num.full_like(obs, self.parameter_values['offset']) stat_level += (quadtree.leaf_center_distance[:, 0] * self.parameter_values['ramp_east']) stat_level += (quadtree.leaf_center_distance[:, 1] * self.parameter_values['ramp_north']) statics['displacement.los'] += stat_level stat_syn = statics['displacement.los'] res = obs - stat_syn misfit_value = res misfit_norm = obs mf = num.vstack([misfit_value, misfit_norm]).T result = SatelliteMisfitResult( misfits=mf) if self._result_mode == 'full': result.statics_syn = statics result.statics_obs = quadtree.leaf_medians return result def get_combined_weight(self): if self._combined_weight is None: # invcov = self.scene.covariance.weight_matrix # self._combined_weight = invcov * self.manual_weight self._combined_weight = num.full(self.nmisfits, self.manual_weight) return self._combined_weight def prepare_modelling(self, engine, source, targets): return [self] def finalize_modelling( self, engine, source, modelling_targets, modelling_results): return modelling_results[0] def init_bootstrap_residuals(self, nbootstraps, rstate=None, nthreads=0): logger.info( 'Scene "%s", initializing bootstrapping residuals from noise ' 'pertubations...' % self.scene_id) if rstate is None: rstate = num.random.RandomState() scene = self.scene qt = scene.quadtree cov = scene.covariance bootstraps = num.zeros((nbootstraps, qt.nleaves)) try: # TODO:mi Signal handler is not given back to the main task! # This is a python3.7 bug warnings.warn('Using multi-threading for SatelliteTargets. ' 'Python 3.7 needs to be killed hard:' ' `killall grond`', UserWarning) from concurrent.futures import ThreadPoolExecutor nthreads = os.cpu_count() if not nthreads else nthreads with ThreadPoolExecutor(max_workers=nthreads) as executor: res = executor.map( cov.getQuadtreeNoise, [rstate for _ in range(nbootstraps)]) for ibs, bs in enumerate(res): bootstraps[ibs, :] = bs except ImportError: for ibs in range(nbootstraps): if not (ibs+1) % 5: logger.info('Calculating noise realisation %d/%d.' % (ibs+1, nbootstraps)) bootstraps[ibs, :] = cov.getQuadtreeNoise(rstate=rstate) self.set_bootstrap_residuals(bootstraps) @classmethod def get_plot_classes(cls): from . import plot plots = super(SatelliteMisfitTarget, cls).get_plot_classes() plots.extend(plot.get_plot_classes()) return plots
class DoubleDCProblem(Problem): problem_parameters = [ Parameter('time', 's', label='Time'), Parameter('north_shift', 'm', label='Northing', **as_km), Parameter('east_shift', 'm', label='Easting', **as_km), Parameter('depth', 'm', label='Depth', **as_km), Parameter('magnitude', label='Magnitude'), Parameter('strike1', 'deg', label='Strike 1'), Parameter('dip1', 'deg', label='Dip 1'), Parameter('rake1', 'deg', label='Rake 1'), Parameter('strike2', 'deg', label='Strike 2'), Parameter('dip2', 'deg', label='Dip 2'), Parameter('rake2', 'deg', label='Rake 2'), Parameter('delta_time', 's', label='$\\Delta$ Time'), Parameter('delta_depth', 'm', label='$\\Delta$ Depth'), Parameter('azimuth', 'deg', label='Azimuth'), Parameter('distance', 'm', label='Distance'), Parameter('mix', label='Mix'), Parameter('duration1', 's', label='Duration 1'), Parameter('duration2', 's', label='Duration 2')] dependants = [] distance_min = Float.T(default=0.0) def get_source(self, x): d = self.get_parameter_dict(x) p = {} for k in self.base_source.keys(): if k in d: p[k] = float( self.ranges[k].make_relative(self.base_source[k], d[k])) stf1 = gf.HalfSinusoidSTF(duration=float(d.duration1)) stf2 = gf.HalfSinusoidSTF(duration=float(d.duration2)) source = self.base_source.clone(stf1=stf1, stf2=stf2, **p) return source def make_dependant(self, xs, pname): if xs.ndim == 1: return self.make_dependant(xs[num.newaxis, :], pname)[0] raise KeyError(pname) def pack(self, source): arr = self.get_parameter_array(source) for ip, p in enumerate(self.parameters): if p.name == 'time': arr[ip] -= self.base_source.time if p.name == 'duration1': arr[ip] = source.stf1.duration if source.stf1 else 0.0 if p.name == 'duration2': arr[ip] = source.stf2.duration if source.stf2 else 0.0 return arr def random_uniform(self, xbounds): x = num.zeros(self.nparameters) for i in range(self.nparameters): x[i] = num.random.uniform(xbounds[i, 0], xbounds[i, 1]) return x.tolist() def preconstrain(self, x): source = self.get_source(x) if any(self.distance_min > source.distance_to(t) for t in self.targets): raise Forbidden() return num.array(x, dtype=num.float) @classmethod def get_plot_classes(cls): from .. import plot plots = super(DoubleDCProblem, cls).get_plot_classes() plots.extend([plot.HudsonPlot, plot.MTDecompositionPlot, plot.MTLocationPlot]) return plots
class CMTProblem(Problem): problem_parameters = [ Parameter('time', 's', label='Time'), Parameter('north_shift', 'm', label='Northing', **as_km), Parameter('east_shift', 'm', label='Easting', **as_km), Parameter('depth', 'm', label='Depth', **as_km), Parameter('magnitude', label='Magnitude'), Parameter('rmnn', label='$m_{nn} / M_0$'), Parameter('rmee', label='$m_{ee} / M_0$'), Parameter('rmdd', label='$m_{dd} / M_0$'), Parameter('rmne', label='$m_{ne} / M_0$'), Parameter('rmnd', label='$m_{nd} / M_0$'), Parameter('rmed', label='$m_{ed} / M_0$'), Parameter('duration', 's', label='Duration') ] dependants = [ Parameter('strike1', u'\u00b0', label='Strike 1'), Parameter('dip1', u'\u00b0', label='Dip 1'), Parameter('rake1', u'\u00b0', label='Rake 1'), Parameter('strike2', u'\u00b0', label='Strike 2'), Parameter('dip2', u'\u00b0', label='Dip 2'), Parameter('rake2', u'\u00b0', label='Rake 2'), Parameter('rel_moment_iso', label='$M_{0}^{ISO}/M_{0}$'), Parameter('rel_moment_clvd', label='$M_{0}^{CLVD}/M_{0}$') ] distance_min = Float.T(default=0.0) mt_type = StringChoice.T(default='full', choices=['full', 'deviatoric', 'dc']) def __init__(self, **kwargs): Problem.__init__(self, **kwargs) self.deps_cache = {} def get_source(self, x): d = self.get_parameter_dict(x) rm6 = num.array([d.rmnn, d.rmee, d.rmdd, d.rmne, d.rmnd, d.rmed], dtype=num.float) m0 = mtm.magnitude_to_moment(d.magnitude) m6 = rm6 * m0 p = {} for k in self.base_source.keys(): if k in d: p[k] = float(self.ranges[k].make_relative( self.base_source[k], d[k])) stf = gf.HalfSinusoidSTF(duration=float(d.duration)) source = self.base_source.clone(m6=m6, stf=stf, **p) return source def make_dependant(self, xs, pname): cache = self.deps_cache if xs.ndim == 1: return self.make_dependant(xs[num.newaxis, :], pname)[0] if pname not in self.dependant_names: raise KeyError(pname) mt = self.base_source.pyrocko_moment_tensor() sdrs_ref = mt.both_strike_dip_rake() y = num.zeros(xs.shape[0]) for i, x in enumerate(xs): k = tuple(x.tolist()) if k not in cache: source = self.get_source(x) mt = source.pyrocko_moment_tensor() res = mt.standard_decomposition() sdrs = mt.both_strike_dip_rake() if sdrs_ref: sdrs = mtm.order_like(sdrs, sdrs_ref) cache[k] = mt, res, sdrs mt, res, sdrs = cache[k] if pname == 'rel_moment_iso': ratio_iso, m_iso = res[0][1:3] y[i] = ratio_iso * num.sign(m_iso[0, 0]) elif pname == 'rel_moment_clvd': ratio_clvd, m_clvd = res[2][1:3] evals, evecs = mtm.eigh_check(m_clvd) ii = num.argmax(num.abs(evals)) y[i] = ratio_clvd * num.sign(evals[ii]) else: isdr = {'strike': 0, 'dip': 1, 'rake': 2}[pname[:-1]] y[i] = sdrs[int(pname[-1]) - 1][isdr] return y def pack(self, source): m6 = source.m6 mt = source.pyrocko_moment_tensor() rm6 = m6 / mt.scalar_moment() x = num.array([ source.time - self.base_source.time, source.north_shift, source.east_shift, source.depth, mt.moment_magnitude(), ] + rm6.tolist() + [source.stf.duration], dtype=num.float) return x def random_uniform(self, xbounds): x = num.zeros(self.nparameters) for i in range(self.nparameters): x[i] = num.random.uniform(xbounds[i, 0], xbounds[i, 1]) x[5:11] = mtm.random_m6() return x.tolist() def preconstrain(self, x): d = self.get_parameter_dict(x) m6 = num.array([d.rmnn, d.rmee, d.rmdd, d.rmne, d.rmnd, d.rmed], dtype=num.float) m9 = mtm.symmat6(*m6) if self.mt_type == 'deviatoric': trace_m = num.trace(m9) m_iso = num.diag([trace_m / 3., trace_m / 3., trace_m / 3.]) m9 -= m_iso elif self.mt_type == 'dc': mt = mtm.MomentTensor(m=m9) m9 = mt.standard_decomposition()[1][2] m0_unscaled = math.sqrt(num.sum(m9.A**2)) / math.sqrt(2.) m9 /= m0_unscaled m6 = mtm.to6(m9) d.rmnn, d.rmee, d.rmdd, d.rmne, d.rmnd, d.rmed = m6 x = self.get_parameter_array(d) source = self.get_source(x) for t in self.targets: if (self.distance_min > num.asarray(t.distance_to(source))).any(): raise Forbidden() return x def get_dependant_bounds(self): out = [(0., 360.), (0., 90.), (-180., 180.), (0., 360.), (0., 90.), (-180., 180.), (-1., 1.), (-1., 1.)] return out @classmethod def get_plot_classes(cls): from .. import plot plots = super(CMTProblem, cls).get_plot_classes() plots.extend([ plot.HudsonPlot, plot.MTDecompositionPlot, plot.MTLocationPlot, plot.MTFuzzyPlot ]) return plots