package org.opensha2.eq.model;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static org.opensha2.data.Data.checkInRange;
import static org.opensha2.data.Data.checkWeights;
import org.opensha2.data.Data;
import org.opensha2.gmm.Gmm;
import org.opensha2.gmm.GroundMotionModel;
import com.google.common.collect.Maps;
import com.google.common.collect.Range;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Wrapper class for {@link GroundMotionModel}s associated with a
* {@link SourceSet}. In addition to carrying {@code Map}s of
* {@code GroundMotionModel}s and their associated weights, this class
* encapsulates any additional epistemic uncertainty that should be applied to
* the ground motions computed from these models, as well as model weight
* variants associated with different distance scales.
*
* <p>A {@code GmmSet} can not be created directly; it may only be created by a
* private parser. Currently, although {@code hashCode} and {@code equals} are
* consistent, they do not consider any supplied uncertainty model.
*
* @author Peter Powers
*/
public final class GmmSet {
/*
* TODO There is a lot of null checking and handling in this class that would
* be better served by Optional
*/
private final Map<Gmm, Double> weightMapLo;
private final double maxDistLo;
private final Map<Gmm, Double> weightMapHi; // may be null
private final double maxDistHi; // used by distance filters
private final boolean singular;
private final int hashCode;
private final UncertType uncertainty;
private final double epiValue;
private final double[][] epiValues;
private final double[] epiWeights; // TODO DataArray (vs. Table, Volume)
GmmSet(
Map<Gmm, Double> weightMapLo,
double maxDistLo,
Map<Gmm, Double> weightMapHi,
double maxDistHi,
double[] epiValues,
double[] epiWeights) {
this.weightMapLo = weightMapLo;
this.maxDistLo = maxDistLo;
this.weightMapHi = weightMapHi;
this.maxDistHi = (weightMapHi != null) ? maxDistHi : maxDistLo;
this.singular = weightMapHi == null;
this.hashCode = Objects.hash(
this.weightMapLo, this.maxDistLo,
this.weightMapHi, this.maxDistHi);
uncertainty = (epiValues == null) ? UncertType.NONE : (epiValues.length == 1)
? UncertType.SINGLE : UncertType.MULTI;
this.epiWeights = epiWeights;
if (uncertainty == UncertType.NONE) {
this.epiValue = Double.NaN;
this.epiValues = null;
} else if (uncertainty == UncertType.SINGLE) {
this.epiValue = epiValues[0];
this.epiValues = null;
} else {
this.epiValue = Double.NaN;
this.epiValues = initEpiValues(epiValues);
}
}
/**
* The {@code Set} of {@link GroundMotionModel} identifiers.
*/
public Set<Gmm> gmms() {
return weightMapLo.keySet();
}
/**
* The {@code Map} of {@link GroundMotionModel} identifiers and associated
* weights to use at a given {@code distance} from a {@code Site}.
* @param distance
*/
public Map<Gmm, Double> gmmWeightMap(double distance) {
/*
* Note that below (maxDistance) is used by all distance filters, so here,
* as long as distance isn't < maxDistLo, weightMapHi will be used.
*/
return singular ? weightMapLo : distance <= maxDistLo ? weightMapLo : weightMapHi;
}
/**
* The maximum distance for which the contained {@link GroundMotionModel}s are
* applicable.
*/
public double maxDistance() {
return maxDistHi;
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof GmmSet)) {
return false;
}
GmmSet that = (GmmSet) obj;
return Objects.equals(this.weightMapLo, that.weightMapLo) &&
this.maxDistLo == that.maxDistLo &&
Objects.equals(this.weightMapHi, that.weightMapHi) &&
this.maxDistHi == that.maxDistHi;
}
private static double[][] initEpiValues(double[] v) {
return new double[][] {
{ v[0], v[1], v[2] },
{ v[3], v[4], v[5] },
{ v[6], v[7], v[8] }
};
}
public boolean epiUncertainty() {
return uncertainty != UncertType.NONE;
}
/*
* Returns the epistemic uncertainty for the supplied magnitude and distance
* in natural log units of ground motion.
*/
public double epiValue(double m, double r) {
switch (uncertainty) {
case MULTI:
int mi = (m < 6) ? 0 : (m < 7) ? 1 : 2;
int ri = (r < 10) ? 0 : (r < 30) ? 1 : 2;
return epiValues[ri][mi];
case SINGLE:
return epiValue;
default:
return 0.0;
}
}
/*
* Returns the 3 weights used for low, median, and high branches of the
* epistemic uncertainty distribution. TODO needs to be immutable, This is a
* good case for DataArray
*/
public double[] epiWeights() {
return epiWeights;
}
private static enum UncertType {
NONE,
SINGLE,
MULTI;
}
/* Single use builder */
static class Builder {
static final String ID = "GmmSet.Builder";
boolean built = false;
private static final Range<Double> MAX_DIST_RANGE = Range.closed(50.0, 1000.0);
private Map<Gmm, Double> gmmWtMapLo;
private Map<Gmm, Double> gmmWtMapHi;
private Double maxDistanceLo;
private double maxDistanceHi;
// optional
private double[] uncValues;
private double[] uncWeights;
// leave maxDistanceHi as primitive unless validation required
// at some later date; GmmSet throws NPE if Double used
Builder primaryModelMap(Map<Gmm, Double> gmmWtMap) {
checkArgument(checkNotNull(gmmWtMap, "Map is null").size() > 0, "Map is empty");
checkWeights(gmmWtMap.values());
gmmWtMapLo = Maps.immutableEnumMap(gmmWtMap);
return this;
}
Builder primaryMaxDistance(double maxDistance) {
maxDistanceLo = checkInRange(MAX_DIST_RANGE, "Max distance", maxDistance);
return this;
}
Builder secondaryModelMap(Map<Gmm, Double> gmmWtMap) {
checkArgument(checkNotNull(gmmWtMap, "Map is null").size() > 0, "Map is empty");
checkWeights(gmmWtMap.values());
gmmWtMapHi = Maps.immutableEnumMap(gmmWtMap);
return this;
}
Builder secondaryMaxDistance(double maxDistance) {
maxDistanceHi = checkInRange(MAX_DIST_RANGE, "Max distance", maxDistance);
return this;
}
Builder uncertainty(double[] values, double[] weights) {
checkNotNull(values, "Values is null");
checkArgument(values.length == 9 || values.length == 1,
"Values must contain 1 or 9 values");
checkArgument(checkNotNull(weights, "Weights are null").length == 3,
"Weights must contain 3 values");
checkArgument(Data.sum(weights) == 1.0, "%s uncertainty weights must sum to 1");
uncValues = values;
uncWeights = weights;
return this;
}
void validateState(String id) {
// at a minimum the '*Lo' fields must be set
checkState(!built, "This %s instance as already been used", id);
checkState(gmmWtMapLo != null, "%s primary weight map not set", id);
checkState(maxDistanceLo != null, "%s primary max distance not set", id);
if (gmmWtMapHi != null) {
// hi gmms must be same as or subset of lo gmms
checkState(gmmWtMapLo.keySet().containsAll(gmmWtMapHi.keySet()),
"%s secondary models must be a subset of primary models", id);
// maxDistanceHi must also be set and greater than maxDistanceLo
checkNotNull(maxDistanceHi,
"%s secondary distance must be set for secondary models", id);
checkState(maxDistanceHi > maxDistanceLo,
"%s secondary distance [%s] ≤ primary distance [%s]", id, maxDistanceHi,
maxDistanceLo);
}
if (uncValues != null) {
checkNotNull(uncWeights, "%s uncertainty weights not set", id);
}
if (uncWeights != null) {
checkNotNull(uncValues, "%s uncertainty values not set", id);
}
built = true;
}
GmmSet build() {
validateState(ID);
try {
GmmSet gmmSet = new GmmSet(
gmmWtMapLo, maxDistanceLo,
gmmWtMapHi, maxDistanceHi,
uncValues, uncWeights);
return gmmSet;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
}