package org.opensha2.eq.model;
import static com.google.common.base.Preconditions.checkState;
import static org.opensha2.internal.Parsing.readBoolean;
import static org.opensha2.internal.Parsing.readDouble;
import static org.opensha2.internal.Parsing.readString;
import static org.opensha2.internal.Parsing.toDoubleArray;
import static org.opensha2.internal.SourceAttribute.A;
import static org.opensha2.internal.SourceAttribute.B;
import static org.opensha2.internal.SourceAttribute.C_MAG;
import static org.opensha2.internal.SourceAttribute.D_MAG;
import static org.opensha2.internal.SourceAttribute.FLOATS;
import static org.opensha2.internal.SourceAttribute.M;
import static org.opensha2.internal.SourceAttribute.MAGS;
import static org.opensha2.internal.SourceAttribute.M_MAX;
import static org.opensha2.internal.SourceAttribute.M_MIN;
import static org.opensha2.internal.SourceAttribute.RATE;
import static org.opensha2.internal.SourceAttribute.RATES;
import static org.opensha2.internal.SourceAttribute.WEIGHT;
import org.opensha2.internal.SourceAttribute;
import org.opensha2.mfd.MfdType;
import org.xml.sax.Attributes;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Doubles;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/*
* MFD data handler class. Stores default data and creates copies with
* overridden (non-default) fields. This class ensures that all required
* attributes for default Mfds are present.
*/
class MfdHelper {
/*
* For documentation:
*
* MFD default recombination rules
*
* Grid, Slab sources (with spatially varying rates)
*
* - a <Node> can only be of one type
*
* - will map to the default of that type (e.g. CA in 2008 had downweighted
* M>6.5 in places that led to a node being either pure GR or INCR, but never
* both)
*
* - only support multiple defaults of the same type
*
* Fault, Interface sources
*
* System
*
* - only supports a single SINGLE default MFD at this time
*
* How to manage MFD Type and Id mixing and matching?
*
* Other notes:
*
* Most source files will have a <settings> block; those that don't will not
* trigger build() on a MfdHelper.Builder so most parsers preempt this
* possibility be creating and empty Helper that is then ocverridden in most
* cases.
*/
// mfd data instances
private final List<SingleData> singleDefaults;
private final List<GR_Data> grDefaults;
private final List<IncrData> incrDefaults;
private final List<TaperData> taperDefaults;
private MfdHelper(
List<SingleData> singleDefaults,
List<GR_Data> grDefaults,
List<IncrData> incrDefaults,
List<TaperData> taperDefaults) {
this.singleDefaults = singleDefaults;
this.grDefaults = grDefaults;
this.incrDefaults = incrDefaults;
this.taperDefaults = taperDefaults;
}
static Builder builder() {
return new Builder();
}
List<SingleData> singleData(Attributes atts) {
if (singleDefaults.isEmpty()) {
return ImmutableList.of(new SingleData(atts));
}
List<SingleData> dataList = new ArrayList<>();
for (SingleData singleDefault : singleDefaults) {
dataList.add(new SingleData(atts, singleDefault));
}
Collections.sort(dataList);
return ImmutableList.copyOf(dataList);
}
List<GR_Data> grData(Attributes atts) {
if (grDefaults.isEmpty()) {
return ImmutableList.of(new GR_Data(atts));
}
ImmutableList.Builder<GR_Data> builder = ImmutableList.builder();
for (GR_Data grDefault : grDefaults) {
builder.add(new GR_Data(atts, grDefault));
}
return builder.build();
}
List<IncrData> incrementalData(Attributes atts) {
if (incrDefaults.isEmpty()) {
return ImmutableList.of(new IncrData(atts));
}
ImmutableList.Builder<IncrData> builder = ImmutableList.builder();
for (IncrData incrDefault : incrDefaults) {
builder.add(new IncrData(atts, incrDefault));
}
return builder.build();
}
List<TaperData> taperData(Attributes atts) {
if (taperDefaults.isEmpty()) {
return ImmutableList.of(new TaperData(atts));
}
ImmutableList.Builder<TaperData> builder = ImmutableList.builder();
for (TaperData taperDefault : taperDefaults) {
builder.add(new TaperData(atts, taperDefault));
}
return builder.build();
}
/*
* Used to by parsers to impose restrictions on the types of default MFDs
* allowed.
*/
int typeCount(MfdType type) {
switch (type) {
case GR:
return grDefaults.size();
case INCR:
return incrDefaults.size();
case SINGLE:
return singleDefaults.size();
case GR_TAPER:
return taperDefaults.size();
default:
throw new IllegalArgumentException("Unknown MFD type: " + type);
}
}
int size() {
int size = 0;
for (MfdType type : MfdType.values()) {
size += typeCount(type);
}
return size;
}
/* Re-usable */
static final class Builder {
private ImmutableList.Builder<SingleData> singleBuilder = ImmutableList.builder();
private ImmutableList.Builder<GR_Data> grBuilder = ImmutableList.builder();
private ImmutableList.Builder<IncrData> incrBuilder = ImmutableList.builder();
private ImmutableList.Builder<TaperData> taperBuilder = ImmutableList.builder();
/* Add a default MFD. */
Builder addDefault(Attributes atts) {
MfdType type = MfdType.valueOf(atts.getValue("type"));
switch (type) {
case GR:
grBuilder.add(new GR_Data(atts));
break;
case INCR:
incrBuilder.add(new IncrData(atts));
break;
case SINGLE:
singleBuilder.add(new SingleData(atts));
break;
case GR_TAPER:
taperBuilder.add(new TaperData(atts));
break;
default:
throw new IllegalArgumentException("Unknown MFD type: " + type);
}
return this;
}
MfdHelper build() {
// MfdHelpers can be empty if no defaults
// defined for a SourceSet
return new MfdHelper(
singleBuilder.build(),
grBuilder.build(),
incrBuilder.build(),
taperBuilder.build());
}
}
static final class SingleData implements Comparable<SingleData> {
final double rate;
final double m;
final boolean floats;
final double weight;
/*
* Requires all attributes be present, but does not throw an exception for
* extra and unknown attributes.
*/
private SingleData(Attributes atts) {
rate = readDouble(RATE, atts);
m = readDouble(M, atts);
floats = readBoolean(FLOATS, atts);
weight = readDouble(WEIGHT, atts);
}
/*
* Iterates supplied attributes; any unkown or extra attributes will result
* in an exception being thrown.
*/
private SingleData(Attributes atts, SingleData ref) {
// set defaults locally
double rate = ref.rate;
double m = ref.m;
boolean floats = ref.floats;
double weight = ref.weight;
for (int i = 0; i < atts.getLength(); i++) {
SourceAttribute att = SourceAttribute.fromString(atts.getQName(i));
switch (att) {
case RATE:
rate = readDouble(RATE, atts);
break;
case M:
m = readDouble(M, atts);
break;
case FLOATS:
floats = readBoolean(FLOATS, atts);
break;
case WEIGHT:
weight = readDouble(WEIGHT, atts);
break;
case TYPE:
break; // ignore
default:
throw new IllegalStateException("Invalid attribute for SINGLE MFD: " + att);
}
}
// export final fields
this.rate = rate;
this.m = m;
this.floats = floats;
this.weight = weight;
}
@Override
public int compareTo(SingleData other) {
return Doubles.compare(m, other.m);
}
}
// TODO rename to GrData & TaperedGrData
static final class GR_Data {
final double a;
final double b;
final double dMag;
final double mMin;
final double mMax;
final double weight;
private GR_Data(Attributes atts) {
a = readDouble(A, atts);
b = readDouble(B, atts);
dMag = readDouble(D_MAG, atts);
mMax = readDouble(M_MAX, atts);
mMin = readDouble(M_MIN, atts);
weight = readDouble(WEIGHT, atts);
}
private GR_Data(Attributes atts, GR_Data ref) {
// set defaults locally
double a = ref.a;
double b = ref.b;
double dMag = ref.dMag;
double mMax = ref.mMax;
double mMin = ref.mMin;
double weight = ref.weight;
for (int i = 0; i < atts.getLength(); i++) {
SourceAttribute att = SourceAttribute.fromString(atts.getQName(i));
switch (att) {
case A:
a = readDouble(A, atts);
break;
case B:
b = readDouble(B, atts);
break;
case D_MAG:
dMag = readDouble(D_MAG, atts);
break;
case M_MIN:
mMin = readDouble(M_MIN, atts);
break;
case M_MAX:
mMax = readDouble(M_MAX, atts);
break;
case WEIGHT:
weight = readDouble(WEIGHT, atts);
break;
case TYPE:
break; // ignore
default:
throw new IllegalStateException("Invalid attribute for GR MFD: " + att);
}
}
// export final fields
this.a = a;
this.b = b;
this.dMag = dMag;
this.mMax = mMax;
this.mMin = mMin;
this.weight = weight;
}
}
static final class TaperData {
final double a;
final double b;
final double cMag;
final double dMag;
final double mMin;
final double mMax;
final double weight;
private TaperData(Attributes atts) {
a = readDouble(A, atts);
b = readDouble(B, atts);
cMag = readDouble(C_MAG, atts);
dMag = readDouble(D_MAG, atts);
mMax = readDouble(M_MAX, atts);
mMin = readDouble(M_MIN, atts);
weight = readDouble(WEIGHT, atts);
}
private TaperData(Attributes atts, TaperData ref) {
// set defaults locally
double a = ref.a;
double b = ref.b;
double cMag = ref.cMag;
double dMag = ref.dMag;
double mMax = ref.mMax;
double mMin = ref.mMin;
double weight = ref.weight;
for (int i = 0; i < atts.getLength(); i++) {
SourceAttribute att = SourceAttribute.fromString(atts.getQName(i));
switch (att) {
case A:
a = readDouble(A, atts);
break;
case B:
b = readDouble(B, atts);
break;
case C_MAG:
dMag = readDouble(D_MAG, atts);
break;
case D_MAG:
dMag = readDouble(D_MAG, atts);
break;
case M_MIN:
mMin = readDouble(M_MIN, atts);
break;
case M_MAX:
mMax = readDouble(M_MAX, atts);
break;
case WEIGHT:
weight = readDouble(WEIGHT, atts);
break;
case TYPE:
break; // ignore
default:
throw new IllegalStateException("Invalid attribute for TAPER MFD: " + att);
}
}
// export final fields
this.a = a;
this.b = b;
this.cMag = cMag;
this.dMag = dMag;
this.mMax = mMax;
this.mMin = mMin;
this.weight = weight;
}
}
static final class IncrData {
final double[] mags;
final double[] rates;
final double weight;
private IncrData(Attributes atts) {
mags = toDoubleArray(readString(MAGS, atts));
rates = toDoubleArray(readString(RATES, atts));
checkState(mags.length == rates.length,
"Inconsistent INCR MFD mag[%s] and rate[%s] arrays",
mags.length, rates.length);
weight = readDouble(WEIGHT, atts);
}
private IncrData(Attributes atts, IncrData ref) {
// NOTE values in ref arrays are mutable
// set defaults locally
double[] mags = ref.mags;
double[] rates = ref.rates;
double weight = ref.weight;
for (int i = 0; i < atts.getLength(); i++) {
SourceAttribute att = SourceAttribute.fromString(atts.getQName(i));
switch (att) {
case MAGS:
mags = toDoubleArray(readString(MAGS, atts));
break;
case RATES:
rates = toDoubleArray(readString(RATES, atts));
break;
case WEIGHT:
weight = readDouble(WEIGHT, atts);
break;
case TYPE:
break; // ignore
case FOCAL_MECH_MAP:
break; // SYSTEM (UCERF3) grid sources; ignore
default:
throw new IllegalStateException("Invalid attribute for INCR MFD: " + att);
}
}
checkState(mags.length == rates.length,
"Inconsistent INCR MFD mag[%s] and rate[%s] arrays",
mags.length, rates.length);
// export final fields
this.mags = mags;
this.rates = rates;
this.weight = weight;
}
}
}