package org.opensha2.calc; import static com.google.common.util.concurrent.Futures.allAsList; import static com.google.common.util.concurrent.Futures.getUnchecked; import static com.google.common.util.concurrent.Futures.immediateFuture; import static com.google.common.util.concurrent.Futures.transform; import static org.opensha2.gmm.Gmm.instances; import org.opensha2.calc.ClusterCurves.Builder; import org.opensha2.data.XySequence; import org.opensha2.eq.fault.Faults; import org.opensha2.eq.fault.surface.RuptureSurface; import org.opensha2.eq.model.ClusterSource; import org.opensha2.eq.model.ClusterSourceSet; import org.opensha2.eq.model.Distance; import org.opensha2.eq.model.FaultSource; import org.opensha2.eq.model.GmmSet; import org.opensha2.eq.model.HazardModel; import org.opensha2.eq.model.Rupture; import org.opensha2.eq.model.Source; import org.opensha2.eq.model.SourceSet; import org.opensha2.eq.model.SystemSourceSet; import org.opensha2.gmm.Gmm; import org.opensha2.gmm.GmmInput; import org.opensha2.gmm.GroundMotionModel; import org.opensha2.gmm.Imt; import org.opensha2.gmm.ScalarGroundMotion; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.ListMultimap; import com.google.common.collect.MultimapBuilder; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.Executor; /** * Data transform {@link Function}s. These are called exclusively from * {@link CalcFactory}. * * <p>These transforms perform all the work of computing probabilistic seismic * hazard and facilitate the reduction of calculations into component tasks that * may be executed concurrently. * * <p>For convenience, some functions coalesce multiple tasks. For example, * {@link SourceToCurves} merges the work of {@link SourceToInputs}, * {@link InputsToGroundMotions}, and {@link GroundMotionsToCurves}. There are * also custom functions to handle special case cluster and system * {@code Source}s. * * @author Peter Powers * @see CalcFactory */ final class Transforms { /* * Source --> InputList * * Create a list of ground motion inputs from a source. */ static final class SourceToInputs implements Function<Source, InputList> { private final Site site; SourceToInputs(Site site) { this.site = site; } @Override public SourceInputList apply(Source source) { SourceInputList hazardInputs = new SourceInputList(source); for (Rupture rup : source) { RuptureSurface surface = rup.surface(); Distance distances = surface.distanceTo(site.location); double dip = surface.dip(); double width = surface.width(); double zTop = surface.depth(); double zHyp = Faults.hypocentralDepth(dip, width, zTop); HazardInput input = new HazardInput( rup.rate(), rup.mag(), distances.rJB, distances.rRup, distances.rX, dip, width, zTop, zHyp, rup.rake(), site.vs30, site.vsInferred, site.z1p0, site.z2p5); hazardInputs.add(input); } return hazardInputs; } } /* * InputList --> GroundMotions * * Calculate ground motions for a list of ground motion inputs. */ static final class InputsToGroundMotions implements Function<InputList, GroundMotions> { private final Map<Imt, Map<Gmm, GroundMotionModel>> gmmTable; InputsToGroundMotions(Map<Imt, Map<Gmm, GroundMotionModel>> gmmTable) { this.gmmTable = gmmTable; } @Override public GroundMotions apply(InputList inputs) { Set<Imt> imtKeys = gmmTable.keySet(); Set<Gmm> gmmKeys = gmmTable.get(imtKeys.iterator().next()).keySet(); GroundMotions.Builder builder = GroundMotions.builder( inputs, imtKeys, gmmKeys); for (Imt imt : imtKeys) { for (Gmm gmm : gmmKeys) { GroundMotionModel model = gmmTable.get(imt).get(gmm); for (GmmInput gmmInput : inputs) { builder.add(imt, gmm, model.calc(gmmInput)); } } } return builder.build(); } } /* * GroundMotions --> HazardCurves * * Derive hazard curves for a set of ground motions. */ static final class GroundMotionsToCurves implements Function<GroundMotions, HazardCurves> { private final Map<Imt, XySequence> modelCurves; private final ExceedanceModel exceedanceModel; private final double truncationLevel; GroundMotionsToCurves(CalcConfig config) { this.modelCurves = config.hazard.logModelCurves(); this.exceedanceModel = config.hazard.exceedanceModel; this.truncationLevel = config.hazard.truncationLevel; } @Override public HazardCurves apply(GroundMotions gms) { HazardCurves.Builder curveBuilder = HazardCurves.builder(gms); for (Entry<Imt, Map<Gmm, List<ScalarGroundMotion>>> imtEntry : gms.gmMap.entrySet()) { Imt imt = imtEntry.getKey(); XySequence modelCurve = modelCurves.get(imt); XySequence utilCurve = XySequence.copyOf(modelCurve); XySequence gmmCurve = XySequence.copyOf(modelCurve); for (Entry<Gmm, List<ScalarGroundMotion>> gmmEntry : imtEntry.getValue().entrySet()) { gmmCurve.clear(); int i = 0; for (ScalarGroundMotion sgm : gmmEntry.getValue()) { exceedanceModel.exceedance( sgm, truncationLevel, imt, utilCurve); utilCurve.multiply(gms.inputs.get(i++).rate); gmmCurve.add(utilCurve); } curveBuilder.addCurve(imt, gmmEntry.getKey(), gmmCurve); } } return curveBuilder.build(); } } /* * GroundMotions --> HazardCurves * * Derive hazard curves for a set of ground motions considering an additional * epistemic uncertinaty model. */ static final class GroundMotionsToCurvesWithUncertainty implements Function<GroundMotions, HazardCurves> { private final GmmSet gmmSet; private final Map<Imt, XySequence> modelCurves; private final ExceedanceModel exceedanceModel; private final double truncationLevel; GroundMotionsToCurvesWithUncertainty(GmmSet gmmSet, CalcConfig config) { this.gmmSet = gmmSet; this.modelCurves = config.hazard.logModelCurves(); this.exceedanceModel = config.hazard.exceedanceModel; this.truncationLevel = config.hazard.truncationLevel; } @Override public HazardCurves apply(GroundMotions gms) { HazardCurves.Builder curveBuilder = HazardCurves.builder(gms); // initialize uncertainty for each input InputList inputs = gms.inputs; double[] uncertainties = new double[inputs.size()]; double[] rates = new double[inputs.size()]; for (int i = 0; i < inputs.size(); i++) { HazardInput input = inputs.get(i); rates[i] = input.rate; uncertainties[i] = gmmSet.epiValue(input.Mw, input.rJB); } for (Entry<Imt, Map<Gmm, List<ScalarGroundMotion>>> imtEntry : gms.gmMap.entrySet()) { Imt imt = imtEntry.getKey(); XySequence modelCurve = modelCurves.get(imt); XySequence utilCurve = XySequence.copyOf(modelCurve); XySequence gmmCurve = XySequence.copyOf(modelCurve); for (Entry<Gmm, List<ScalarGroundMotion>> gmmEntry : imtEntry.getValue().entrySet()) { gmmCurve.clear(); int i = 0; for (ScalarGroundMotion gm : gmmEntry.getValue()) { double mean = gm.mean(); double epi = uncertainties[i]; double[] epiMeans = new double[] { mean - epi, mean, mean + epi }; exceedanceCurve( epiMeans, gm.sigma(), imt, utilCurve.clear()); utilCurve.multiply(inputs.get(i++).rate); gmmCurve.add(utilCurve); } curveBuilder.addCurve(imt, gmmEntry.getKey(), gmmCurve); } } return curveBuilder.build(); } /* * Construct an exceedance curve considering uncertain ground motions. * * TODO this has not yet been refactored to accomodate a ScalarGroundMotion * object as we know this is only used in WUS whereas sgm refactoring was * done (experimentally) to handle NGA-East in the CEUS. */ private XySequence exceedanceCurve( final double[] means, final double sigma, final Imt imt, final XySequence curve) { XySequence utilCurve = XySequence.emptyCopyOf(curve); double[] weights = gmmSet.epiWeights(); for (int i = 0; i < means.length; i++) { exceedanceModel.exceedance( means[i], sigma, truncationLevel, imt, utilCurve); utilCurve.multiply(weights[i]); curve.add(utilCurve); } return curve; } } /* * Source --> HazardCurves * * Compute hazard curves for a source. This function coalesces the four * preceeding functions into one. */ static final class SourceToCurves implements Function<Source, HazardCurves> { private final Function<Source, InputList> sourceToInputs; private final Function<InputList, GroundMotions> inputsToGroundMotions; private final Function<GroundMotions, HazardCurves> groundMotionsToCurves; SourceToCurves( SourceSet<? extends Source> sources, CalcConfig config, Site site) { GmmSet gmmSet = sources.groundMotionModels(); Map<Imt, Map<Gmm, GroundMotionModel>> gmmTable = instances( config.hazard.imts, gmmSet.gmms()); this.sourceToInputs = new SourceToInputs(site); this.inputsToGroundMotions = new InputsToGroundMotions(gmmTable); this.groundMotionsToCurves = config.hazard.gmmUncertainty && gmmSet.epiUncertainty() ? new GroundMotionsToCurvesWithUncertainty(gmmSet, config) : new GroundMotionsToCurves(config); } @Override public HazardCurves apply(Source source) { return groundMotionsToCurves.apply( inputsToGroundMotions.apply( sourceToInputs.apply(source))); } } /* * List<HazardCurves> --> HazardCurveSet * * Reduce multiple source curves. */ static final class CurveConsolidator implements Function<List<HazardCurves>, HazardCurveSet> { private final SourceSet<? extends Source> sources; private final Map<Imt, XySequence> modelCurves; CurveConsolidator( SourceSet<? extends Source> sources, CalcConfig config) { this.sources = sources; this.modelCurves = config.hazard.logModelCurves(); } @Override public HazardCurveSet apply(List<HazardCurves> curvesList) { if (curvesList.isEmpty()) { return HazardCurveSet.empty(sources); } HazardCurveSet.Builder curveSetBuilder = HazardCurveSet.builder( sources, modelCurves); for (HazardCurves curves : curvesList) { curveSetBuilder.addCurves(curves); } return curveSetBuilder.build(); } } /* * A note on system sources: * * SystemSourceSets contain many single sources and the functions here handle * them collectively. Rather than creating lists of input lists for each * source, one large input list is created. This may change if it is decided * to apply a magnitude distribution on each system source. This motivated * reordering of the SourceType enum such that SystemSourceSets are processed * first and granted a thread early in the calculation process. */ /* * SYSTEM: SystemSourceSet --> HazardCurveSet * * Compute hazard curves for system sources. This function derives all inputs * for an entire SystemSourceSet before being composing them with standard * ground motion and hazard curve functions. */ static final class SystemToCurves implements Function<SystemSourceSet, HazardCurveSet> { private final Site site; private final CalcConfig config; SystemToCurves(CalcConfig config, Site site) { this.site = site; this.config = config; } @Override public HazardCurveSet apply(SystemSourceSet sources) { InputList inputs = SystemSourceSet.toInputsFunction(site).apply(sources); if (inputs.isEmpty()) { return HazardCurveSet.empty(sources); } GmmSet gmmSet = sources.groundMotionModels(); Map<Imt, Map<Gmm, GroundMotionModel>> gmmTable = instances( config.hazard.imts, gmmSet.gmms()); InputsToGroundMotions inputsToGm = new InputsToGroundMotions(gmmTable); GroundMotions gms = inputsToGm.apply(inputs); Function<GroundMotions, HazardCurves> gmToCurves = config.hazard.gmmUncertainty && gmmSet.epiUncertainty() ? new GroundMotionsToCurvesWithUncertainty(gmmSet, config) : new GroundMotionsToCurves(config); HazardCurves curves = gmToCurves.apply(gms); CurveConsolidator consolidator = new CurveConsolidator(sources, config); return consolidator.apply(ImmutableList.of(curves)); } } /* * SYSTEM: SystemSourceSet --> HazardCurveSet * * Compute hazard curves for system sources concurrently. This function * derives all inputs for an entire SystemSourceSet and partitions them before * composing them with standard ground motion and hazard curve functions. */ static final class ParallelSystemToCurves implements Function<SystemSourceSet, HazardCurveSet> { private final Site site; private final Executor ex; private final CalcConfig config; ParallelSystemToCurves( Site site, CalcConfig config, Executor ex) { this.site = site; this.ex = ex; this.config = config; } @Override public HazardCurveSet apply(SystemSourceSet sources) { // create input list InputList master = SystemSourceSet.toInputsFunction(site).apply(sources); if (master.isEmpty()) { return HazardCurveSet.empty(sources); } // calculate curves from list in parallel InputsToCurves inputsToCurves = new InputsToCurves(sources, config); AsyncList<HazardCurves> asyncCurvesList = AsyncList.create(); int size = config.performance.systemPartition; for (InputList partition : master.partition(size)) { asyncCurvesList.add(transform( immediateFuture(partition), inputsToCurves, ex)); } List<HazardCurves> curvesList = getUnchecked(allAsList(asyncCurvesList)); // combine and consolidate HazardCurves hazardCurves = HazardCurves.combine(master, curvesList); CurveConsolidator consolidator = new CurveConsolidator(sources, config); return consolidator.apply(ImmutableList.of(hazardCurves)); } } /* * SYSTEM: InputList --> HazardCurves * * Compute hazard curves from an input list. Although this function is * generally applicable to all source types, it is presently only used to * process partitioned input lists derived from a system source. */ static final class InputsToCurves implements Function<InputList, HazardCurves> { private final Function<InputList, GroundMotions> inputsToGroundMotions; private final Function<GroundMotions, HazardCurves> groundMotionsToCurves; InputsToCurves( SourceSet<? extends Source> sources, CalcConfig config) { GmmSet gmmSet = sources.groundMotionModels(); Map<Imt, Map<Gmm, GroundMotionModel>> gmmTable = instances( config.hazard.imts, gmmSet.gmms()); this.inputsToGroundMotions = new InputsToGroundMotions(gmmTable); this.groundMotionsToCurves = config.hazard.gmmUncertainty && gmmSet.epiUncertainty() ? new GroundMotionsToCurvesWithUncertainty(gmmSet, config) : new GroundMotionsToCurves(config); } @Override public HazardCurves apply(InputList inputs) { return groundMotionsToCurves.apply(inputsToGroundMotions.apply(inputs)); } } /* * CLUSTER: ClusterSource --> ClusterInputs * * Create a list of ground motion inputs from a cluster source. */ static final class ClusterSourceToInputs implements Function<ClusterSource, ClusterInputs> { private final SourceToInputs transform; ClusterSourceToInputs(Site site) { transform = new SourceToInputs(site); } @Override public ClusterInputs apply(ClusterSource clusterSource) { ClusterInputs clusterInputs = new ClusterInputs(clusterSource); for (FaultSource faultSource : clusterSource.faults()) { clusterInputs.add(transform.apply(faultSource)); } return clusterInputs; } } /* * CLUSTER: ClusterInputs --> ClusterGroundMotions * * Calculate ground motions for a list of cluster ground motion inputs. */ static final class ClusterInputsToGroundMotions implements Function<ClusterInputs, ClusterGroundMotions> { private final InputsToGroundMotions transform; ClusterInputsToGroundMotions(Map<Imt, Map<Gmm, GroundMotionModel>> gmmTable) { transform = new InputsToGroundMotions(gmmTable); } @Override public ClusterGroundMotions apply(ClusterInputs clusterInputs) { ClusterGroundMotions clusterGroundMotions = new ClusterGroundMotions(clusterInputs.parent); for (SourceInputList hazardInputs : clusterInputs) { clusterGroundMotions.add(transform.apply(hazardInputs)); } return clusterGroundMotions; } } /* * CLUSTER: ClusterGroundMotions --> ClusterCurves * * Collapse magnitude variants and compute the joint probability of exceedence * for sources in a cluster. Note that this is only to be used with cluster * sources as the weight of each magnitude variant is stored in the * HazardInput.rate field, which is kinda KLUDGY, but works. */ static final class ClusterGroundMotionsToCurves implements Function<ClusterGroundMotions, ClusterCurves> { private final Map<Imt, XySequence> logModelCurves; private final ExceedanceModel exceedanceModel; private final double truncationLevel; ClusterGroundMotionsToCurves(CalcConfig config) { this.logModelCurves = config.hazard.logModelCurves(); this.exceedanceModel = config.hazard.exceedanceModel; this.truncationLevel = config.hazard.truncationLevel; } @Override public ClusterCurves apply(ClusterGroundMotions clusterGroundMotions) { Builder builder = ClusterCurves.builder(clusterGroundMotions); for (Entry<Imt, XySequence> entry : logModelCurves.entrySet()) { XySequence modelCurve = entry.getValue(); Imt imt = entry.getKey(); // aggregator of curves for each fault in a cluster ListMultimap<Gmm, XySequence> faultCurves = MultimapBuilder .enumKeys(Gmm.class) .arrayListValues(clusterGroundMotions.size()) .build(); XySequence utilCurve = XySequence.copyOf(modelCurve); for (GroundMotions groundMotions : clusterGroundMotions) { Map<Gmm, List<ScalarGroundMotion>> gmmGmMap = groundMotions.gmMap.get(imt); for (Gmm gmm : gmmGmMap.keySet()) { XySequence magVarCurve = XySequence.copyOf(modelCurve); List<ScalarGroundMotion> sgms = gmmGmMap.get(gmm); int i = 0; for (ScalarGroundMotion sgm : sgms) { exceedanceModel.exceedance( sgm, truncationLevel, imt, utilCurve); utilCurve.multiply(groundMotions.inputs.get(i++).rate); magVarCurve.add(utilCurve); } faultCurves.put(gmm, magVarCurve); } } double rate = clusterGroundMotions.parent.rate(); for (Gmm gmm : faultCurves.keySet()) { XySequence clusterCurve = ExceedanceModel.clusterExceedance(faultCurves.get(gmm)); builder.addCurve(imt, gmm, clusterCurve.multiply(rate)); } } return builder.build(); } } /* * CLUSTER: ClusterSource --> ClusterCurves * * Compute hazard curves for a cluster source. This function coalesces the * three preceeding functions into one. */ static final class ClusterToCurves implements Function<ClusterSource, ClusterCurves> { private final Function<ClusterSource, ClusterInputs> sourceToInputs; private final Function<ClusterInputs, ClusterGroundMotions> inputsToGroundMotions; private final Function<ClusterGroundMotions, ClusterCurves> groundMotionsToCurves; ClusterToCurves( ClusterSourceSet sources, CalcConfig config, Site site) { Set<Gmm> gmms = sources.groundMotionModels().gmms(); Map<Imt, Map<Gmm, GroundMotionModel>> gmmTable = instances(config.hazard.imts, gmms); this.sourceToInputs = new ClusterSourceToInputs(site); this.inputsToGroundMotions = new ClusterInputsToGroundMotions(gmmTable); this.groundMotionsToCurves = new ClusterGroundMotionsToCurves(config); } @Override public ClusterCurves apply(ClusterSource source) { return groundMotionsToCurves.apply( inputsToGroundMotions.apply( sourceToInputs.apply(source))); } } /* * CLUSTER: List<ClusterCurves> --> HazardCurveSet * * Reduce multiple cluster source curves. */ static final class ClusterCurveConsolidator implements Function<List<ClusterCurves>, HazardCurveSet> { private final ClusterSourceSet sources; private final Map<Imt, XySequence> modelCurves; ClusterCurveConsolidator( ClusterSourceSet sources, CalcConfig config) { this.sources = sources; this.modelCurves = config.hazard.logModelCurves(); } @Override public HazardCurveSet apply(List<ClusterCurves> curvesList) { if (curvesList.isEmpty()) { return HazardCurveSet.empty(sources); } HazardCurveSet.Builder curveSetBuilder = HazardCurveSet.builder( sources, modelCurves); for (ClusterCurves curves : curvesList) { curveSetBuilder.addCurves(curves); } return curveSetBuilder.build(); } } /* * ALL: List<HazardCurveSet> --> HazardResult * * Final 'fan-in' consolidator function used for all source types. */ static final class CurveSetConsolidator implements Function<List<HazardCurveSet>, Hazard> { private final HazardModel model; private final CalcConfig config; private final Site site; CurveSetConsolidator( HazardModel model, CalcConfig config, Site site) { this.model = model; this.config = config; this.site = site; } @Override public Hazard apply(List<HazardCurveSet> curveSetList) { Hazard.Builder resultBuilder = Hazard.builder(config) .model(model) .site(site); for (HazardCurveSet curveSet : curveSetList) { if (curveSet.isEmpty()) { continue; } resultBuilder.addCurveSet(curveSet); } return resultBuilder.build(); } } }