package org.opensha2.calc;
import static com.google.common.base.CaseFormat.LOWER_CAMEL;
import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.padEnd;
import static com.google.common.base.Strings.repeat;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.opensha2.data.XySequence.create;
import static org.opensha2.data.XySequence.immutableCopyOf;
import static org.opensha2.internal.Parsing.enumsToString;
import static org.opensha2.internal.TextUtils.LOG_INDENT;
import static org.opensha2.internal.TextUtils.LOG_VALUE_COLUMN;
import static org.opensha2.internal.TextUtils.NEWLINE;
import org.opensha2.data.Data;
import org.opensha2.data.XySequence;
import org.opensha2.eq.model.SourceType;
import org.opensha2.gmm.Gmm;
import org.opensha2.gmm.GroundMotionModel;
import org.opensha2.gmm.Imt;
import com.google.common.base.Optional;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* Calculation configuration.
*
* @author Peter Powers
*/
public final class CalcConfig {
static final String FILE_NAME = "config.json";
private static final String ID = CalcConfig.class.getSimpleName();
private static final String STATE_ERROR = "%s %s not set";
static final String DEFAULT_OUT = "curves";
/**
* The resource from which {@code this} was derived. This field may be empty.
*/
public final transient Optional<Path> resource;
/** Hazard calculation configuration. */
public final Hazard hazard;
/** Deaggregation configuration. */
public final Deagg deagg;
/** Earthquake rate configuration. */
public final Rate rate;
/** Default site settings. */
public final SiteDefaults site;
/** Output configuration. */
public final Output output;
/** Performance and optimization configuration. */
public final Performance performance;
private CalcConfig(
Optional<Path> resource,
Hazard hazard,
SiteDefaults site,
Performance performance,
Output output,
Deagg deagg,
Rate rate) {
this.resource = resource;
this.hazard = hazard;
this.site = site;
this.performance = performance;
this.output = output;
this.deagg = deagg;
this.rate = rate;
}
/**
* Hazard calculation configuration.
*/
public final static class Hazard {
static final String ID = CalcConfig.ID + "." + Hazard.class.getSimpleName();
/**
* The probability distribution model to use when computing hazard curves.
*
* <p><b>Default:</b> {@link ExceedanceModel#TRUNCATION_UPPER_ONLY}
*/
public final ExceedanceModel exceedanceModel;
/**
* The number of standard deviations (σ) at which to truncate a
* distribution. This field is ignored if an {@link ExceedanceModel} does
* not implement truncation.
*
* <p><b>Default:</b> {@code 3.0}
*/
public final double truncationLevel;
/**
* The {@code Set} of IMTs for which calculations should be performed.
*
* <p><b>Default:</b> [{@link Imt#PGA}, {@link Imt#SA0P2}, {@link Imt#SA1P0}
* ]
*/
public final Set<Imt> imts;
/**
* Whether to consider additional ground motion model uncertainty, or not.
* Currently this is only applicable when using the PEER NGA-West or
* NGA-West2 {@link Gmm}s with USGS hazard models.
*
* <p><b>Default:</b> {@code false}
*/
public final boolean gmmUncertainty;
/**
* The value format for hazard curves.
*
* <p><b>Default:</b> {@link ValueFormat#ANNUAL_RATE}
*/
public final ValueFormat valueFormat;
private final double[] defaultImls;
private final Map<Imt, double[]> customImls;
/* Do not serialize to JSON */
private final transient Map<Imt, XySequence> modelCurves;
private final transient Map<Imt, XySequence> logModelCurves;
private Hazard(
ExceedanceModel exceedanceModel,
double truncationLevel,
Set<Imt> imts,
boolean gmmUncertainty,
ValueFormat valueFormat,
double[] defaultImls,
Map<Imt, double[]> customImls,
Map<Imt, XySequence> modelCurves,
Map<Imt, XySequence> logModelCurves) {
this.exceedanceModel = exceedanceModel;
this.truncationLevel = truncationLevel;
this.imts = imts;
this.gmmUncertainty = gmmUncertainty;
this.valueFormat = valueFormat;
this.defaultImls = defaultImls;
this.customImls = customImls;
this.modelCurves = modelCurves;
this.logModelCurves = logModelCurves;
}
/**
* An empty linear curve for the requested {@code Imt}.
* @param imt to get curve for
* @see #modelCurves() defaults
*/
public XySequence modelCurve(Imt imt) {
return modelCurves.get(imt);
}
/**
* An immutable map of model curves where x-values are in linear space. In
* the absence of specific values being provided in a config file, the
* following defaults are used:
*
* <p><b>{@code PGA}</b> and <b>{@code SA}</b>: {@code [0.0025,
* 0.0045, 0.0075, 0.0113, 0.0169, 0.0253, 0.0380, 0.0570, 0.0854, 0.128,
* 0.192, 0.288, 0.432, 0.649, 0.973, 1.46, 2.19, 3.28, 4.92, 7.38]}
*
* <p><b>{@code PGV}</b>: {@code [0.0100, 0.0177, 0.0312, 0.0552,
* 0.0976, 0.173, 0.305, 0.539, 0.953, 1.68, 2.98, 5.26, 9.30, 16.4,
* 29.1, 51.3, 90.8, 160, 284, 501]}
*/
public Map<Imt, XySequence> modelCurves() {
return modelCurves;
}
/**
* An immutable map of model curves where x-values are in natural-log space.
* @see #modelCurves() linear space defaults
*/
public Map<Imt, XySequence> logModelCurves() {
return logModelCurves;
}
private StringBuilder asString() {
StringBuilder imlSb = new StringBuilder();
if (!customImls.isEmpty()) {
for (Entry<Imt, double[]> entry : customImls.entrySet()) {
String imtStr = "imls (" + entry.getKey().name() + ")";
imlSb.append(formatEntry(imtStr))
.append(wrap(Arrays.toString(entry.getValue()), false));
}
}
return new StringBuilder()
.append(LOG_INDENT).append("Hazard")
.append(formatEntry(Key.EXCEEDANCE_MODEL, exceedanceModel.name()))
.append(formatEntry(Key.TRUNCATION_LEVEL, truncationLevel))
.append(formatEntry(Key.IMTS, enumsToString(imts, Imt.class)))
.append(formatEntry(Key.GMM_UNCERTAINTY, gmmUncertainty))
.append(formatEntry(Key.VALUE_FORMAT, valueFormat.name()))
.append(formatEntry(Key.DEFAULT_IMLS, wrap(Arrays.toString(defaultImls), false)))
.append(imlSb);
}
private static final class Builder {
ExceedanceModel exceedanceModel;
Double truncationLevel;
Set<Imt> imts;
Boolean gmmUncertainty;
ValueFormat valueFormat;
double[] defaultImls;
Map<Imt, double[]> customImls;
Hazard build() {
return new Hazard(
exceedanceModel,
truncationLevel,
Sets.immutableEnumSet(imts),
gmmUncertainty,
valueFormat,
defaultImls,
customImls,
createCurveMap(),
createLogCurveMap());
}
void copy(Hazard that) {
this.exceedanceModel = that.exceedanceModel;
this.truncationLevel = that.truncationLevel;
this.imts = that.imts;
this.gmmUncertainty = that.gmmUncertainty;
this.valueFormat = that.valueFormat;
this.defaultImls = that.defaultImls;
this.customImls = that.customImls;
}
void extend(Builder that) {
if (that.exceedanceModel != null) {
this.exceedanceModel = that.exceedanceModel;
}
if (that.truncationLevel != null) {
this.truncationLevel = that.truncationLevel;
}
if (that.imts != null) {
this.imts = that.imts;
}
if (that.gmmUncertainty != null) {
this.gmmUncertainty = that.gmmUncertainty;
}
if (that.valueFormat != null) {
this.valueFormat = that.valueFormat;
}
if (that.defaultImls != null) {
this.defaultImls = that.defaultImls;
}
if (that.customImls != null) {
this.customImls.putAll(that.customImls);
}
}
/* Slightly modified version of NSHM 5Hz curve, size = 20 */
private static final double[] IMLS_PGA_SA = new double[] { 0.0025, 0.0045, 0.0075, 0.0113,
0.0169, 0.0253, 0.0380, 0.0570, 0.0854, 0.128, 0.192, 0.288, 0.432, 0.649, 0.973, 1.46,
2.19, 3.28, 4.92, 7.38 };
private static final double[] IMLS_PGV = new double[] { 0.0100, 0.0177, 0.0312, 0.0552,
0.0976, 0.173, 0.305, 0.539, 0.953, 1.68, 2.98, 5.26, 9.30, 16.4, 29.1, 51.3, 90.8,
160.0, 284.0, 501.0 };
static Builder defaults() {
Builder b = new Builder();
b.exceedanceModel = ExceedanceModel.TRUNCATION_UPPER_ONLY;
b.truncationLevel = 3.0;
b.imts = EnumSet.of(Imt.PGA, Imt.SA0P2, Imt.SA1P0);
b.gmmUncertainty = false;
b.valueFormat = ValueFormat.ANNUAL_RATE;
b.defaultImls = IMLS_PGA_SA;
b.customImls = Maps.newHashMap();
b.customImls.put(Imt.PGV, IMLS_PGV);
return b;
}
void validate() {
checkNotNull(exceedanceModel, STATE_ERROR, Hazard.ID, Key.EXCEEDANCE_MODEL);
checkNotNull(truncationLevel, STATE_ERROR, Hazard.ID, Key.TRUNCATION_LEVEL);
checkNotNull(imts, STATE_ERROR, Hazard.ID, Key.IMTS);
checkNotNull(defaultImls, STATE_ERROR, Hazard.ID, Key.DEFAULT_IMLS);
checkNotNull(customImls, STATE_ERROR, Hazard.ID, Key.CUSTOM_IMLS);
}
Map<Imt, XySequence> createLogCurveMap() {
Map<Imt, XySequence> curveMap = Maps.newEnumMap(Imt.class);
for (Imt imt : imts) {
double[] imls = imlsForImt(imt);
imls = Arrays.copyOf(imls, imls.length);
Data.ln(imls);
curveMap.put(imt, immutableCopyOf(create(imls, null)));
}
return Maps.immutableEnumMap(curveMap);
}
Map<Imt, XySequence> createCurveMap() {
Map<Imt, XySequence> curveMap = Maps.newEnumMap(Imt.class);
for (Imt imt : imts) {
double[] imls = imlsForImt(imt);
imls = Arrays.copyOf(imls, imls.length);
curveMap.put(imt, immutableCopyOf(create(imls, null)));
}
return Maps.immutableEnumMap(curveMap);
}
double[] imlsForImt(Imt imt) {
return customImls.containsKey(imt) ? customImls.get(imt) : defaultImls;
}
}
}
/**
* Deaggregation configuration.
*/
public static final class Deagg {
static final String ID = CalcConfig.ID + "." + Deagg.class.getSimpleName();
/**
* The distance, magnitude, and epsilon bins into which contributing sources
* to hazard are sorted.
*/
public final Bins bins;
/**
* The minimum contribution (in %) that a source must make to hazard to be
* included on the contributor source list in deaggregation result.
*/
public final Double contributorLimit;
private Deagg(
Bins bins,
double contributorLimit) {
this.bins = bins;
this.contributorLimit = contributorLimit;
}
private StringBuilder asString() {
return new StringBuilder()
.append(LOG_INDENT).append("Deaggregation")
.append(formatEntry("rBins"))
.append("min=").append(bins.rMin).append(", ")
.append("max=").append(bins.rMax).append(", ")
.append("Δ=").append(bins.Δr)
.append(formatEntry("mBins"))
.append("min=").append(bins.mMin).append(", ")
.append("max=").append(bins.mMax).append(", ")
.append("Δ=").append(bins.Δm)
.append(formatEntry("εBins"))
.append("min=").append(bins.εMin).append(", ")
.append("max=").append(bins.εMax).append(", ")
.append("Δ=").append(bins.Δε)
.append(formatEntry(Key.CONTRIBUTOR_LIMIT, contributorLimit));
}
/**
* The distance, magnitude, and epsilon bins into which contributing sources
* to hazard will be sorted.
*/
public static final class Bins {
static final String ID = CalcConfig.ID + "." + Deagg.ID + "." + Bins.class.getSimpleName();
/** Minimum distance. Lower edge of smallest distance bin. */
public final Double rMin;
/** Maximum distance. Upper edge of largest distance bin. */
public final Double rMax;
/** Distance bin width. */
public final Double Δr;
/** Minimum magnitude. Lower edge of smallest magnitude bin. */
public final Double mMin;
/** Maximum magnitude. Upper edge of largest magnitude bin. */
public final Double mMax;
/** Magnitude bin width. */
public final Double Δm;
/** Minimum epsilon. Lower edge of smallest epsilon bin. */
public final Double εMin;
/** Maximum epsilon. Upper edge of largest epsilon bin. */
public final Double εMax;
/** Epsilon bin width. */
public final Double Δε;
Bins(
double rMin, double rMax, double Δr,
double mMin, double mMax, double Δm,
double εMin, double εMax, double Δε) {
this.rMin = rMin;
this.rMax = rMax;
this.Δr = Δr;
this.mMin = mMin;
this.mMax = mMax;
this.Δm = Δm;
this.εMin = εMin;
this.εMax = εMax;
this.Δε = Δε;
}
static Bins defaults() {
return new Bins(
0.0, 1000.0, 20.0,
4.4, 9.4, 0.2,
-3.0, 3.0, 0.5);
}
}
private static final class Builder {
Bins bins;
Double contributorLimit;
Deagg build() {
return new Deagg(
bins,
contributorLimit);
}
void copy(Deagg that) {
this.bins = that.bins;
this.contributorLimit = that.contributorLimit;
}
void extend(Builder that) {
if (that.bins != null) {
this.bins = that.bins;
}
if (that.contributorLimit != null) {
this.contributorLimit = that.contributorLimit;
}
}
static Builder defaults() {
Builder b = new Builder();
b.bins = Bins.defaults();
b.contributorLimit = 1.0;
return b;
}
void validate() {
checkNotNull(bins, STATE_ERROR, Deagg.ID, Key.BINS);
checkNotNull(bins.rMin, STATE_ERROR, Deagg.ID, Key.BINS + ".rMin");
checkNotNull(bins.rMax, STATE_ERROR, Deagg.ID, Key.BINS + ".rMax");
checkNotNull(bins.Δr, STATE_ERROR, Deagg.ID, Key.BINS + ".Δr");
checkNotNull(bins.mMin, STATE_ERROR, Deagg.ID, Key.BINS + ".mMin");
checkNotNull(bins.mMax, STATE_ERROR, Deagg.ID, Key.BINS + ".mMax");
checkNotNull(bins.Δm, STATE_ERROR, Deagg.ID, Key.BINS + ".Δm");
checkNotNull(bins.εMin, STATE_ERROR, Deagg.ID, Key.BINS + ".εMin");
checkNotNull(bins.εMax, STATE_ERROR, Deagg.ID, Key.BINS + ".εMax");
checkNotNull(bins.Δε, STATE_ERROR, Deagg.ID, Key.BINS + ".Δε");
checkNotNull(contributorLimit, STATE_ERROR, Deagg.ID, Key.CONTRIBUTOR_LIMIT);
}
}
}
/**
* Magnitude-frequency distribution configuration.
*/
public static final class Rate {
static final String ID = CalcConfig.ID + "." + Rate.class.getSimpleName();
/**
* The magnitude discretization.
*/
public final Bins bins;
/**
* The distance from a site within which all sources should be included.
*
* <p><b>Default:</b> {@code 20 km}
*/
public final Double distance;
/**
* The rate data distribution type.
*
* <p><b>Default:</b> {@link DistributionFormat#INCREMENTAL}
*/
public final DistributionFormat distributionFormat;
/**
* The value format for rate data.
*
* <p><b>Default:</b> {@link ValueFormat#ANNUAL_RATE}
*/
public final ValueFormat valueFormat;
/**
* The timespan of interest when computing Poisson probabilities.
*
* <p><b>Default:</b> {@code 30 years}
*/
public final Double timespan;
private Rate(
Bins bins,
double distance,
DistributionFormat distributionFormat,
ValueFormat valueFormat,
double timespan) {
this.bins = bins;
this.distance = distance;
this.distributionFormat = distributionFormat;
this.valueFormat = valueFormat;
this.timespan = timespan;
}
private StringBuilder asString() {
return new StringBuilder()
.append(LOG_INDENT).append("Rate")
.append(formatEntry("mBins"))
.append("min=").append(bins.mMin).append(", ")
.append("max=").append(bins.mMax).append(", ")
.append("Δ=").append(bins.Δm)
.append(formatEntry(Key.DISTANCE, distance))
.append(formatEntry(Key.DISTRIBUTION_FORMAT, distributionFormat.name()))
.append(formatEntry(Key.VALUE_FORMAT, valueFormat.name()))
.append(formatEntry(Key.TIMESPAN, timespan));
}
private static final class Builder {
Bins bins;
Double distance;
DistributionFormat distributionFormat;
ValueFormat valueFormat;
Double timespan;
Rate build() {
return new Rate(
bins,
distance,
distributionFormat,
valueFormat,
timespan);
}
void copy(Rate that) {
this.bins = that.bins;
this.distance = that.distance;
this.distributionFormat = that.distributionFormat;
this.valueFormat = that.valueFormat;
this.timespan = that.timespan;
}
void extend(Builder that) {
if (that.bins != null) {
this.bins = that.bins;
}
if (that.distance != null) {
this.distance = that.distance;
}
if (that.distributionFormat != null) {
this.distributionFormat = that.distributionFormat;
}
if (that.valueFormat != null) {
this.valueFormat = that.valueFormat;
}
if (that.timespan != null) {
this.timespan = that.timespan;
}
}
static Builder defaults() {
Builder b = new Builder();
b.bins = Bins.defaults();
b.distance = 20.0;
b.distributionFormat = DistributionFormat.INCREMENTAL;
b.valueFormat = ValueFormat.ANNUAL_RATE;
b.timespan = 30.0;
return b;
}
void validate() {
checkNotNull(bins, STATE_ERROR, Rate.ID, Key.BINS);
checkNotNull(bins.mMin, STATE_ERROR, Rate.ID, Key.BINS + ".mMin");
checkNotNull(bins.mMax, STATE_ERROR, Rate.ID, Key.BINS + ".mMax");
checkNotNull(bins.Δm, STATE_ERROR, Rate.ID, Key.BINS + ".Δm");
checkNotNull(distance, STATE_ERROR, Rate.ID, Key.DISTANCE);
checkNotNull(distributionFormat, STATE_ERROR, Rate.ID, Key.DISTRIBUTION_FORMAT);
checkNotNull(valueFormat, STATE_ERROR, Rate.ID, Key.VALUE_FORMAT);
checkNotNull(timespan, STATE_ERROR, Rate.ID, Key.TIMESPAN);
}
}
/**
* The mfd magnitude discretization.
*/
public static final class Bins {
static final String ID = CalcConfig.ID + "." + Rate.ID + "." + Bins.class.getSimpleName();
/** Minimum magnitude. Lower edge of smallest magnitude bin. */
public final Double mMin;
/** Maximum magnitude. Upper edge of largest magnitude bin. */
public final Double mMax;
/** Magnitude bin width. */
public final Double Δm;
Bins(double mMin, double mMax, double Δm) {
this.mMin = mMin;
this.mMax = mMax;
this.Δm = Δm;
}
static Bins defaults() {
return new Bins(4.2, 9.4, 0.1);
}
}
}
/**
* Default site settings.
*/
public static final class SiteDefaults {
static final String ID = CalcConfig.ID + "." + SiteDefaults.class.getSimpleName();
/**
* The default average shear-wave velocity down to 30 meters depth.
*
* <p><b>Default:</b> {@code 760.0} m/sec
*/
public final double vs30;
/**
* Whether Vs30 was inferred, {@code true}, or measured, {@code false}.
*
* <p><b>Default:</b> {@code true} (inferred)
*/
public final boolean vsInferred;
/**
* Depth to the shear-wave velocity horizon of 1.0 km/sec, in km.
*
* <p><b>Default:</b> {@code NaN} ({@link GroundMotionModel}s will use a
* default value or model)
*/
public final double z1p0;
/**
* Depth to the shear-wave velocity horizon of 2.5 km/sec, in km;
*
* <p><b>Default:</b> {@code NaN} ({@link GroundMotionModel}s will use a
* default value or model)
*/
public final double z2p5;
private SiteDefaults(
double vs30,
boolean vsInferred,
double z1p0,
double z2p5) {
this.vs30 = vs30;
this.vsInferred = vsInferred;
this.z1p0 = z1p0;
this.z2p5 = z2p5;
}
private StringBuilder asString() {
return new StringBuilder()
.append(LOG_INDENT).append("Site")
.append(formatEntry(Key.VS30, vs30))
.append(formatEntry(Key.VS_INF, vsInferred))
.append(formatEntry(Key.Z1P0, z1p0))
.append(formatEntry(Key.Z2P5, z2p5));
}
private static final class Builder {
Double vs30;
Boolean vsInferred;
Double z1p0;
Double z2p5;
SiteDefaults build() {
return new SiteDefaults(
vs30,
vsInferred,
z1p0,
z2p5);
}
void copy(SiteDefaults that) {
this.vs30 = that.vs30;
this.vsInferred = that.vsInferred;
this.z1p0 = that.z1p0;
this.z2p5 = that.z2p5;
}
void extend(Builder that) {
if (that.vs30 != null) {
this.vs30 = that.vs30;
}
if (that.vsInferred != null) {
this.vsInferred = that.vsInferred;
}
if (that.z1p0 != null) {
this.z1p0 = that.z1p0;
}
if (that.z2p5 != null) {
this.z2p5 = that.z2p5;
}
}
static Builder defaults() {
Builder b = new Builder();
b.vs30 = Site.VS_30_DEFAULT;
b.vsInferred = Site.VS_INF_DEFAULT;
b.z1p0 = Site.Z1P0_DEFAULT;
b.z2p5 = Site.Z2P5_DEFAULT;
return b;
}
void validate() {
checkNotNull(vs30, STATE_ERROR, SiteDefaults.ID, Key.VS30);
checkNotNull(vsInferred, STATE_ERROR, SiteDefaults.ID, Key.VS_INF);
checkNotNull(z1p0, STATE_ERROR, SiteDefaults.ID, Key.Z1P0);
checkNotNull(z2p5, STATE_ERROR, SiteDefaults.ID, Key.Z2P5);
}
}
}
/**
* Data and file output settings.
*/
public static final class Output {
static final String ID = CalcConfig.ID + "." + Output.class.getSimpleName();
/**
* The directory to write any results to.
*
* <p><b>Default:</b> {@code "curves"} for hazard and deaggregation
* calculations; {@code "eq-rate"} or {@code "eq-prob"} for rate
* calculations.
*/
public final Path directory;
/**
* The different {@linkplain DataType types} of data to save. Note that
* {@link DataType#TOTAL} will <i>always</i> be included in this set,
* regardless of any user settings.
*
* <p><b>Default:</b> [{@link DataType#TOTAL}]
*/
public final Set<DataType> dataTypes;
/**
* The number of results (one per {@code Site}) to store before writing to
* file(s). A larger number requires more memory.
*
* <p><b>Default:</b> {@code 20}
*/
public final int flushLimit;
private Output(
Path directory,
Set<DataType> dataTypes,
int flushLimit) {
this.directory = directory;
this.dataTypes = Sets.immutableEnumSet(
DataType.TOTAL,
dataTypes.toArray(new DataType[dataTypes.size()]));
this.flushLimit = flushLimit;
}
private StringBuilder asString() {
return new StringBuilder()
.append(LOG_INDENT).append("Output")
.append(formatEntry(Key.DIRECTORY, directory.toAbsolutePath().normalize()))
.append(formatEntry(Key.DATA_TYPES, enumsToString(dataTypes, DataType.class)))
.append(formatEntry(Key.FLUSH_LIMIT, flushLimit));
}
private static final class Builder {
Path directory;
Set<DataType> dataTypes;
Integer flushLimit;
Output build() {
return new Output(
directory,
dataTypes,
flushLimit);
}
void copy(Output that) {
this.directory = that.directory;
this.dataTypes = that.dataTypes;
this.flushLimit = that.flushLimit;
}
void extend(Builder that) {
if (that.directory != null) {
this.directory = that.directory;
}
if (that.dataTypes != null) {
this.dataTypes = that.dataTypes;
}
if (that.flushLimit != null) {
this.flushLimit = that.flushLimit;
}
}
static Builder defaults() {
Builder b = new Builder();
b.directory = Paths.get(DEFAULT_OUT);
b.dataTypes = EnumSet.of(DataType.TOTAL);
b.flushLimit = 5;
return b;
}
void validate() {
checkNotNull(directory, STATE_ERROR, Output.ID, Key.DIRECTORY);
checkNotNull(dataTypes, STATE_ERROR, Output.ID, Key.DATA_TYPES);
checkNotNull(flushLimit, STATE_ERROR, Output.ID, Key.FLUSH_LIMIT);
}
}
}
/**
* Performance and optimization settings.
*/
public static final class Performance {
static final String ID = CalcConfig.ID + "." + Performance.class.getSimpleName();
/**
* Whether to optimize grid source sets, or not.
*
* <p><b>Default:</b> {@code true}
*/
public final boolean optimizeGrids;
/**
* Whether to collapse/combine magnitude-frequency distributions, or not.
* Doing so prevents uncertainty analysis as logic-tree branches are
* obscured.
*
* <p><b>Default:</b> {@code true}
*/
public final boolean collapseMfds;
/**
* The partition or batch size to use when distributing
* {@link SourceType#SYSTEM} calculations.
*
* <p><b>Default:</b> {@code 1000}
*/
public final int systemPartition;
/**
* The number of threads to use when distributing calculations.
*
* <p><b>Default:</b> {@link ThreadCount#ALL}
*/
public final ThreadCount threadCount;
private Performance(
boolean optimizeGrids,
boolean collapseMfds,
int systemPartition,
ThreadCount threadCount) {
this.optimizeGrids = optimizeGrids;
this.collapseMfds = collapseMfds;
this.systemPartition = systemPartition;
this.threadCount = threadCount;
}
private StringBuilder asString() {
return new StringBuilder()
.append(LOG_INDENT).append("Performance")
.append(formatEntry(Key.OPTIMIZE_GRIDS, optimizeGrids))
.append(formatEntry(Key.COLLAPSE_MFDS, collapseMfds))
.append(formatEntry(Key.SYSTEM_PARTITION, systemPartition))
.append(formatEntry(Key.THREAD_COUNT, threadCount.name()));
}
private static final class Builder {
Boolean optimizeGrids;
Boolean collapseMfds;
Integer systemPartition;
ThreadCount threadCount;
Performance build() {
return new Performance(
optimizeGrids,
collapseMfds,
systemPartition,
threadCount);
}
void copy(Performance that) {
this.optimizeGrids = that.optimizeGrids;
this.collapseMfds = that.collapseMfds;
this.systemPartition = that.systemPartition;
this.threadCount = that.threadCount;
}
void extend(Builder that) {
if (that.optimizeGrids != null) {
this.optimizeGrids = that.optimizeGrids;
}
if (that.collapseMfds != null) {
this.collapseMfds = that.collapseMfds;
}
if (that.systemPartition != null) {
this.systemPartition = that.systemPartition;
}
if (that.threadCount != null) {
this.threadCount = that.threadCount;
}
}
static Builder defaults() {
Builder b = new Builder();
b.optimizeGrids = true;
b.collapseMfds = true;
b.systemPartition = 1000;
b.threadCount = ThreadCount.ALL;
return b;
}
void validate() {
checkNotNull(optimizeGrids, STATE_ERROR, Performance.ID, Key.OPTIMIZE_GRIDS);
checkNotNull(collapseMfds, STATE_ERROR, Performance.ID, Key.COLLAPSE_MFDS);
checkNotNull(systemPartition, STATE_ERROR, Performance.ID, Key.SYSTEM_PARTITION);
checkNotNull(threadCount, STATE_ERROR, Performance.ID, Key.THREAD_COUNT);
}
}
}
private enum Key {
RESOURCE,
/* hazard */
EXCEEDANCE_MODEL,
TRUNCATION_LEVEL,
IMTS,
GMM_UNCERTAINTY,
VALUE_FORMAT,
DEFAULT_IMLS,
CUSTOM_IMLS,
/* site defaults */
VS30,
VS_INF,
Z1P0,
Z2P5,
/* performance */
OPTIMIZE_GRIDS,
COLLAPSE_MFDS,
SYSTEM_PARTITION,
THREAD_COUNT,
/* output */
DIRECTORY,
DATA_TYPES,
FLUSH_LIMIT,
/* deagg */
BINS,
CONTRIBUTOR_LIMIT,
/* rate */
DISTANCE,
DISTRIBUTION_FORMAT,
TIMESPAN;
private String label;
private Key() {
this.label = UPPER_UNDERSCORE.to(LOWER_CAMEL, name());
}
@Override
public String toString() {
return label;
}
}
@Override
public String toString() {
return new StringBuilder("Calc Config: ")
.append(resource.isPresent()
? resource.get().toAbsolutePath().normalize()
: "(from defaults)")
.append(hazard.asString())
.append(deagg.asString())
.append(rate.asString())
.append(site.asString())
.append(output.asString())
.append(performance.asString())
.toString();
}
private static final int MAX_COL = 100;
private static final int VALUE_WIDTH = MAX_COL - LOG_VALUE_COLUMN;
private static final String KEY_INDENT = LOG_INDENT + " ";
private static final String VALUE_INDENT = NEWLINE + repeat(" ", LOG_VALUE_COLUMN);
private static String formatEntry(String key) {
return padEnd(KEY_INDENT + '.' + key + ':', LOG_VALUE_COLUMN, ' ');
}
private static <E extends Enum<E>> String formatEntry(E key, Object value) {
return padEnd(KEY_INDENT + '.' + key + ':', LOG_VALUE_COLUMN, ' ') + value;
}
/* wrap a commma-delimited string */
private static String wrap(String s, boolean pad) {
if (s.length() <= VALUE_WIDTH) {
return pad ? VALUE_INDENT + s : s;
}
StringBuilder sb = new StringBuilder();
int lastCommaIndex = s.substring(0, VALUE_WIDTH).lastIndexOf(',') + 1;
if (pad) {
sb.append(VALUE_INDENT);
}
sb.append(s.substring(0, lastCommaIndex));
sb.append(wrap(s.substring(lastCommaIndex).trim(), true));
return sb.toString();
}
private static final Gson GSON = new GsonBuilder()
.setPrettyPrinting()
.enableComplexMapKeySerialization()
.serializeNulls()
.registerTypeAdapter(Double.class, new DoubleSerializer())
.registerTypeHierarchyAdapter(Path.class, new PathConverter())
.create();
private static class DoubleSerializer implements JsonSerializer<Double> {
@Override
public JsonElement serialize(
Double value,
Type type,
JsonSerializationContext context) {
return Double.isNaN(value) ? null : new JsonPrimitive(value);
}
}
private static class PathConverter implements JsonSerializer<Path>, JsonDeserializer<Path> {
@Override
public Path deserialize(
JsonElement json,
Type type,
JsonDeserializationContext context) throws JsonParseException {
return Paths.get(json.getAsString());
}
@Override
public JsonElement serialize(
Path path,
Type type,
JsonSerializationContext context) {
return new JsonPrimitive(path.toAbsolutePath().normalize().toString());
}
}
/**
* Save this config in JSON format to the speciifed directory.
*
* @param dir the directory to write to
* @throws IOException if there is a problem writing the file
*/
public void write(Path dir) throws IOException {
Path file = dir.resolve(FILE_NAME);
Writer writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8);
GSON.toJson(this, writer);
writer.close();
}
/**
* A builder of configuration instances.
*/
public static final class Builder {
private boolean built = false;
private Path resource;
private Hazard.Builder hazard;
private SiteDefaults.Builder site;
private Performance.Builder performance;
private Output.Builder output;
private Deagg.Builder deagg;
private Rate.Builder rate;
private Builder() {
hazard = new Hazard.Builder();
site = new SiteDefaults.Builder();
performance = new Performance.Builder();
output = new Output.Builder();
deagg = new Deagg.Builder();
rate = new Rate.Builder();
}
/**
* Initialize a new builder with values copied from the supplied config.
*/
public static Builder copyOf(CalcConfig config) {
Builder b = new Builder();
if (config.resource.isPresent()) {
b.resource = config.resource.get();
}
b.hazard.copy(config.hazard);
b.site.copy(config.site);
b.performance.copy(config.performance);
b.output.copy(config.output);
b.deagg.copy(config.deagg);
b.rate.copy(config.rate);
return b;
}
/**
* Create a new builder from the resource at the specified path. This will
* only set those fields that are explicitely defined.
*
* @param path to configuration file or resource
* @throws IOException
*/
public static Builder fromFile(Path path) throws IOException {
checkNotNull(path);
// TODO test with zip files
Path configPath = Files.isDirectory(path) ? path.resolve(FILE_NAME) : path;
Reader reader = Files.newBufferedReader(configPath, UTF_8);
Builder b = GSON.fromJson(reader, Builder.class);
reader.close();
b.resource = configPath;
return b;
}
/**
* Initialize a new builder with all fields initialized to default values.
*/
public static Builder withDefaults() {
Builder b = new Builder();
b.hazard = Hazard.Builder.defaults();
b.site = SiteDefaults.Builder.defaults();
b.performance = Performance.Builder.defaults();
b.output = Output.Builder.defaults();
b.deagg = Deagg.Builder.defaults();
b.rate = Rate.Builder.defaults();
return b;
}
/**
* Extend {@code this} builder to match {@code that} builder. Fields in that
* builder take precedence unless they are not set.
*/
public Builder extend(final Builder that) {
checkNotNull(that);
this.resource = that.resource;
this.hazard.extend(that.hazard);
this.site.extend(that.site);
this.performance.extend(that.performance);
this.output.extend(that.output);
this.deagg.extend(that.deagg);
this.rate.extend(that.rate);
return this;
}
/*
* Those values for which web services require custom configurations
* (overrides) are exposed below.
*/
/**
* Set the IMTs for which results should be calculated.
*
* @see org.opensha2.calc.CalcConfig.Hazard#imts
*/
public Builder imts(Set<Imt> imts) {
this.hazard.imts = checkNotNull(imts);
return this;
}
/**
* Set the timespan for earthquake probabilities. Calling this method also
* sets {@link Rate#valueFormat} to {@link ValueFormat#POISSON_PROBABILITY}
* to ensure consistency.
*
* @see Rate#timespan
*/
public Builder timespan(double timespan) {
this.rate.timespan = timespan;
this.rate.valueFormat = ValueFormat.POISSON_PROBABILITY;
return this;
}
/**
* Set the cutoff distance within which to include all sources when
* computing earthquake rates or probabilities.
*
* @see Rate#distance
*/
public Builder distance(double distance) {
this.rate.distance = distance;
return this;
}
private void validateState() {
checkState(!built, "This %s instance as already been used", ID + ".Builder");
hazard.validate();
site.validate();
performance.validate();
output.validate();
deagg.validate();
rate.validate();
built = true;
}
/**
* Build a new calculation configuration.
*/
public CalcConfig build() {
validateState();
return new CalcConfig(
Optional.fromNullable(resource),
hazard.build(),
site.build(),
performance.build(),
output.build(),
deagg.build(),
rate.build());
}
}
}