package org.opensha2.calc;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static org.opensha2.data.XySequence.copyOf;
import static org.opensha2.data.XySequence.emptyCopyOf;
import static org.opensha2.data.XySequence.immutableCopyOf;
import org.opensha2.data.XySequence;
import org.opensha2.eq.model.Source;
import org.opensha2.eq.model.SourceSet;
import org.opensha2.eq.model.SourceType;
import org.opensha2.gmm.Gmm;
import org.opensha2.gmm.Imt;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* Container class for hazard curves derived from a {@code SourceSet}. Class
* stores the {@code GroundMotions}s associated with each {@code Source} used in
* a hazard calculation and the individual curves for each
* {@code GroundMotionModel} used.
*
* <p>The {@code Builder} for this class is used to aggregate the HazardCurves
* associated with each {@code Source} in a {@code SourceSet}, scaled by the
* {@code SourceSet} weight. It also scales curves by their associated
* {@code GroundMotionModel} weight, using distance-dependent weights when
* appropriate. Note that this may lead to the dropping of some curves that are
* not appropriate at large distances.
*
* <p>A HazardCurveSet is compatible with all types of {@code SourceSet}s,
* including {@code ClusterSourceSet}s, which are handled differently in hazard
* calculations. This container marks a point in the calculation pipeline where
* results from cluster and other sources may be recombined into a single
* {@code Hazard} result, regardless of {@code SourceSet.type()} for all
* relevant {@code SourceSet}s.
*
* <p>For reasons related to deaggregation, this class also retains the curves
* for each {@code ClusterSource} in a {@code ClusterSourceSet}. Specifically,
* deaggregation of cluster sources is not a straightforward decomposition of
* contributions from individual sources and its helpful to have the total curve
* for each {@code ClusterSource} available. Given the relatively specializaed
* and infrequent use of {@code ClusterSource}s, this incurs little additional
* overhead.
*
* @author Peter Powers
*/
final class HazardCurveSet {
final SourceSet<? extends Source> sourceSet;
final List<GroundMotions> hazardGroundMotionsList;
final List<ClusterGroundMotions> clusterGroundMotionsList;
final Map<Imt, List<Map<Gmm, XySequence>>> clusterCurveLists;
final Map<Imt, Map<Gmm, XySequence>> curveMap;
final Map<Imt, XySequence> totalCurves;
// TODO separate references by what is needed for hazard vs deagg
// deagg of cluster and system types requires us to hold onto some
// objects created when generating hazard curves
private HazardCurveSet(
SourceSet<? extends Source> sourceSet,
List<GroundMotions> hazardGroundMotionsList,
List<ClusterGroundMotions> clusterGroundMotionsList,
Map<Imt, List<Map<Gmm, XySequence>>> clusterCurveLists,
Map<Imt, Map<Gmm, XySequence>> curveMap,
Map<Imt, XySequence> totalCurves) {
this.sourceSet = sourceSet;
this.hazardGroundMotionsList = hazardGroundMotionsList;
this.clusterGroundMotionsList = clusterGroundMotionsList;
this.clusterCurveLists = clusterCurveLists;
this.curveMap = curveMap;
this.totalCurves = totalCurves;
}
static Builder builder(SourceSet<? extends Source> sourceSet,
Map<Imt, XySequence> modelCurves) {
return new Builder(sourceSet, modelCurves);
}
/*
* A HazardCurveSet is relatively heavyweight wrt initializing its builder.
* This provides a placeholder for functions where a HazardCurveSet is
* required but for which there may be no HazardCurves because all sources
* were out of range of a site. We only retain a reference to the original
* source set.
*/
static HazardCurveSet empty(SourceSet<? extends Source> sourceSet) {
return new HazardCurveSet(sourceSet, null, null, null, null, null);
}
boolean isEmpty() {
return totalCurves == null;
}
static class Builder {
private static final String ID = "HazardCurveSet.Builder";
private boolean built = false;
private final Map<Imt, XySequence> modelCurves;
private final SourceSet<? extends Source> sourceSet;
private final List<GroundMotions> hazardGroundMotionsList;
private final List<ClusterGroundMotions> clusterGroundMotionsList;
private final Map<Imt, List<Map<Gmm, XySequence>>> clusterCurveLists;
private final Map<Imt, Map<Gmm, XySequence>> curveMap;
private final Map<Imt, XySequence> totalCurves;
private Builder(
SourceSet<? extends Source> sourceSet,
Map<Imt, XySequence> modelCurves) {
this.sourceSet = sourceSet;
this.modelCurves = modelCurves;
boolean cluster = sourceSet.type() == SourceType.CLUSTER;
if (cluster) {
hazardGroundMotionsList = null;
clusterGroundMotionsList = new ArrayList<>();
clusterCurveLists = new EnumMap<>(Imt.class);
curveMap = new EnumMap<>(Imt.class);
} else {
hazardGroundMotionsList = new ArrayList<>();
clusterGroundMotionsList = null;
clusterCurveLists = null;
curveMap = new EnumMap<>(Imt.class);
}
Set<Gmm> gmms = sourceSet.groundMotionModels().gmms();
Set<Imt> imts = modelCurves.keySet();
for (Imt imt : imts) {
Map<Gmm, XySequence> gmmMap = new EnumMap<>(Gmm.class);
for (Gmm gmm : gmms) {
XySequence emptyCurve = emptyCopyOf(modelCurves.get(imt));
gmmMap.put(gmm, emptyCurve);
}
curveMap.put(imt, gmmMap);
if (cluster) {
List<Map<Gmm, XySequence>> clusterCurveList = new ArrayList<>();
clusterCurveLists.put(imt, clusterCurveList);
}
}
totalCurves = new EnumMap<>(Imt.class);
}
Builder addCurves(HazardCurves curvesIn) {
checkNotNull(hazardGroundMotionsList, "%s only supports ClusterCurves", ID);
hazardGroundMotionsList.add(curvesIn.groundMotions);
double distance = curvesIn.groundMotions.inputs.minDistance;
Map<Gmm, Double> gmmWeightMap = sourceSet.groundMotionModels().gmmWeightMap(distance);
// loop Imts based on what's been calculated
for (Imt imt : curvesIn.curveMap.keySet()) {
Map<Gmm, XySequence> curveMapIn = curvesIn.curveMap.get(imt);
Map<Gmm, XySequence> curveMapBuild = curveMap.get(imt);
// loop Gmms based on what's supported at this distance
for (Gmm gmm : gmmWeightMap.keySet()) {
double weight = gmmWeightMap.get(gmm) * sourceSet.weight();
curveMapBuild.get(gmm).add(copyOf(curveMapIn.get(gmm)).multiply(weight));
}
}
return this;
}
Builder addCurves(ClusterCurves curvesIn) {
checkNotNull(clusterGroundMotionsList, "%s only supports HazardCurves", ID);
clusterGroundMotionsList.add(curvesIn.clusterGroundMotions);
double clusterWeight = curvesIn.clusterGroundMotions.parent.weight();
double distance = curvesIn.clusterGroundMotions.minDistance;
Map<Gmm, Double> gmmWeightMap = sourceSet.groundMotionModels().gmmWeightMap(distance);
// loop Imts based on what's been calculated
for (Imt imt : curvesIn.curveMap.keySet()) {
Map<Gmm, XySequence> curveMapIn = curvesIn.curveMap.get(imt);
Map<Gmm, XySequence> curveMapBuild = curveMap.get(imt);
/*
* Retain references to the total curve for each cluster source
* for deaggregation in clusterCurveLists. These lists of maps by
* GMM will contain only those curves for the GMMs approprate for
* the source-site distance. When deaggreagting, the same distance
* cutoff will be considered so retrieval of curves from the maps
* should never thrwo an NPE.
*/
Map<Gmm, XySequence> clusterCurves = new EnumMap<>(Gmm.class);
// loop Gmms based on what's supported at this distance
for (Gmm gmm : gmmWeightMap.keySet()) {
double weight = gmmWeightMap.get(gmm) * sourceSet.weight() * clusterWeight;
XySequence clusterCurve = copyOf(curveMapIn.get(gmm)).multiply(weight);
curveMapBuild.get(gmm).add(clusterCurve);
clusterCurves.put(gmm, clusterCurve);
}
clusterCurveLists.get(imt).add(clusterCurves);
}
return this;
}
HazardCurveSet build() {
checkState(!built, "This %s instance has already been used", ID);
built = true;
computeFinal();
return new HazardCurveSet(
sourceSet,
hazardGroundMotionsList,
clusterGroundMotionsList,
clusterCurveLists,
curveMap,
totalCurves);
}
/*
* Create the final wieghted (Gmm) combined curve. The Gmm curves were
* scaled by their weights while building (above).
*/
private void computeFinal() {
for (Entry<Imt, Map<Gmm, XySequence>> entry : curveMap.entrySet()) {
Imt imt = entry.getKey();
XySequence totalCurve = emptyCopyOf(modelCurves.get(imt));
for (XySequence curve : entry.getValue().values()) {
totalCurve.add(curve);
}
totalCurves.put(imt, immutableCopyOf(totalCurve));
}
}
}
}