package org.opensha2.eq.model;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static java.util.logging.Level.FINE;
import static org.opensha2.eq.model.SourceType.GRID;
import static org.opensha2.internal.Parsing.readDouble;
import static org.opensha2.internal.Parsing.readEnum;
import static org.opensha2.internal.Parsing.readInt;
import static org.opensha2.internal.Parsing.readString;
import static org.opensha2.internal.Parsing.stringToEnumWeightMap;
import static org.opensha2.internal.Parsing.stringToValueValueWeightMap;
import static org.opensha2.internal.SourceAttribute.FOCAL_MECH_MAP;
import static org.opensha2.internal.SourceAttribute.ID;
import static org.opensha2.internal.SourceAttribute.MAG_DEPTH_MAP;
import static org.opensha2.internal.SourceAttribute.MAX_DEPTH;
import static org.opensha2.internal.SourceAttribute.NAME;
import static org.opensha2.internal.SourceAttribute.RUPTURE_SCALING;
import static org.opensha2.internal.SourceAttribute.STRIKE;
import static org.opensha2.internal.SourceAttribute.TYPE;
import static org.opensha2.internal.SourceAttribute.WEIGHT;
import org.opensha2.data.Data;
import org.opensha2.data.XySequence;
import org.opensha2.eq.Earthquakes;
import org.opensha2.eq.fault.FocalMech;
import org.opensha2.eq.fault.surface.RuptureScaling;
import org.opensha2.eq.model.MfdHelper.GR_Data;
import org.opensha2.eq.model.MfdHelper.IncrData;
import org.opensha2.eq.model.MfdHelper.SingleData;
import org.opensha2.eq.model.MfdHelper.TaperData;
import org.opensha2.geo.Location;
import org.opensha2.internal.SourceElement;
import org.opensha2.mfd.IncrementalMfd;
import org.opensha2.mfd.MfdType;
import org.opensha2.mfd.Mfds;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
import com.google.common.collect.Iterables;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.logging.Logger;
import javax.xml.parsers.SAXParser;
/*
* Non-validating grid source parser. SAX parser 'Attributes' are stateful and
* cannot be stored. This class is not thread safe.
*
* @author Peter Powers
*/
@SuppressWarnings("incomplete-switch")
class GridParser extends DefaultHandler {
private final Logger log = Logger.getLogger(GridParser.class.getName());
private final SAXParser sax;
private boolean used = false;
private Locator locator;
// Default MFD data
private boolean parsingDefaultMFDs = false;
private MfdHelper.Builder mfdHelperBuilder;
private MfdHelper mfdHelper;
private GmmSet gmmSet;
private ModelConfig config;
private GridSourceSet sourceSet;
private GridSourceSet.Builder sourceSetBuilder;
// master magnitude list data
private double minMag = Earthquakes.MAG_RANGE.upperEndpoint();
private double maxMag = Earthquakes.MAG_RANGE.lowerEndpoint();
private double deltaMag;
// Node locations are the only text content in source files
private boolean readingLoc = false;
private StringBuilder locBuilder = null;
// TODO why are these fields being initialized to null; necessary?
// Per-node MFD and mechMap
private XySequence nodeMFD = null;
private Map<FocalMech, Double> nodeMechMap = null;
// Exposed for use when validating depths in subclasses
SourceType type = GRID;
private GridParser(SAXParser sax) {
this.sax = checkNotNull(sax);
}
static GridParser create(SAXParser sax) {
return new GridParser(sax);
}
GridSourceSet parse(InputStream in, GmmSet gmmSet, ModelConfig config) throws SAXException,
IOException {
checkState(!used, "This parser has expired");
this.gmmSet = gmmSet;
this.config = config;
sax.parse(in, this);
used = true;
return sourceSet;
}
@Override
public void startElement(String uri, String localName, String qName, Attributes atts)
throws SAXException {
SourceElement e = null;
try {
e = SourceElement.fromString(qName);
} catch (IllegalArgumentException iae) {
throw new SAXParseException("Invalid element <" + qName + ">", locator, iae);
}
switch (e) {
case GRID_SOURCE_SET:
String name = readString(NAME, atts);
int id = readInt(ID, atts);
double weight = readDouble(WEIGHT, atts);
sourceSetBuilder = new GridSourceSet.Builder();
sourceSetBuilder
.name(name)
.id(id)
.weight(weight);
sourceSetBuilder.gmms(gmmSet);
if (log.isLoggable(FINE)) {
log.fine("");
log.fine(" Name: " + name);
log.fine(" Weight: " + weight);
}
mfdHelperBuilder = MfdHelper.builder();
mfdHelper = mfdHelperBuilder.build(); // dummy; usually
// overwritten
break;
case DEFAULT_MFDS:
parsingDefaultMFDs = true;
break;
case INCREMENTAL_MFD:
if (parsingDefaultMFDs) {
mfdHelperBuilder.addDefault(atts);
}
break;
case SOURCE_PROPERTIES:
String depthMapStr = readString(MAG_DEPTH_MAP, atts);
NavigableMap<Double, Map<Double, Double>> depthMap =
stringToValueValueWeightMap(depthMapStr);
double maxDepth = readDouble(MAX_DEPTH, atts);
String mechMapStr = readString(FOCAL_MECH_MAP, atts);
Map<FocalMech, Double> mechMap = stringToEnumWeightMap(mechMapStr, FocalMech.class);
RuptureScaling rupScaling = readEnum(RUPTURE_SCALING, atts, RuptureScaling.class);
sourceSetBuilder
.depthMap(depthMap, type)
.maxDepth(maxDepth, type)
.mechs(mechMap)
.ruptureScaling(rupScaling);
double strike = readDouble(STRIKE, atts);
// first validate strike by setting it in builder
sourceSetBuilder.strike(strike);
// then possibly override type if strike is set
PointSourceType type = config.pointSourceType;
if (!Double.isNaN(strike)) {
type = PointSourceType.FIXED_STRIKE;
}
sourceSetBuilder.sourceType(type);
if (log.isLoggable(FINE)) {
log.fine(" Depths: " + depthMap);
log.fine(" Max depth: " + maxDepth);
log.fine("Focal mechs: " + mechMap);
log.fine("Rup scaling: " + rupScaling);
log.fine(" Strike: " + strike);
String typeOverride = (type != config.pointSourceType) ? " (" +
config.pointSourceType + " overridden)" : "";
log.fine("Source type: " + type + typeOverride);
}
break;
case NODE:
readingLoc = true;
locBuilder = new StringBuilder();
nodeMFD = processNode(atts);
minMag = Math.min(minMag, nodeMFD.min().x());
maxMag = Math.max(maxMag, nodeMFD.max().x());
try {
String nodeMechMapStr = readString(FOCAL_MECH_MAP, atts);
nodeMechMap = stringToEnumWeightMap(nodeMechMapStr, FocalMech.class);
} catch (NullPointerException npe) {
nodeMechMap = null;
}
break;
/*
* TODO we need to check that delta mag, if included in a node, is
* consistent with deltaMag of all default MFDs. Or, we check that all
* defaults are consistent and don't permit inclusion of deltaMag as node
* attribute. The same could be done for mMin. This ensures a basic
* consistency of structure.
*/
}
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
SourceElement e = null;
try {
e = SourceElement.fromString(qName);
} catch (IllegalArgumentException iae) {
throw new SAXParseException("Invalid element <" + qName + ">", locator, iae);
}
switch (e) {
case DEFAULT_MFDS:
parsingDefaultMFDs = false;
mfdHelper = mfdHelperBuilder.build();
break;
case NODE:
readingLoc = false;
Location loc = Location.fromString(locBuilder.toString());
if (nodeMechMap != null) {
sourceSetBuilder.location(loc, nodeMFD, nodeMechMap);
} else {
sourceSetBuilder.location(loc, nodeMFD);
}
nodeMFD = null;
nodeMechMap = null;
break;
case GRID_SOURCE_SET:
sourceSetBuilder.mfdData(minMag, maxMag, deltaMag);
sourceSet = sourceSetBuilder.build();
if (log.isLoggable(FINE)) {
// TODO there must be a better way to organize this so that
// we
// can log the depth model without having to give it package
// vis
log.fine(" Size: " + sourceSet.size());
log.finer(" MFD count: " + mfdHelper.size());
log.finer(" Mag count: " + sourceSet.depthModel.magMaster.size());
log.finer(" Mag master: " + sourceSet.depthModel.magMaster);
log.finer(" MFD index: " + sourceSet.depthModel.magDepthIndices);
log.finer(" Depths: " + sourceSet.depthModel.magDepthDepths);
log.finer(" Weights: " + sourceSet.depthModel.magDepthWeights);
log.fine("");
}
break;
}
}
@Override
public void characters(char ch[], int start, int length) throws SAXException {
if (readingLoc) {
locBuilder.append(ch, start, length);
}
}
@Override
public void setDocumentLocator(Locator locator) {
this.locator = locator;
}
/*
* Currently, grid sources may have multiple defaults of a uniform type. No
* checking is done to see if node types match defaults. Defaults are
* collapsed into a single MFD.
*/
private XySequence processNode(Attributes atts) {
MfdType type = readEnum(TYPE, atts, MfdType.class);
switch (type) {
case GR:
return buildCollapsedGR(atts);
case INCR:
return buildIncr(atts);
case SINGLE:
return buildCollapsedSingle(atts);
case GR_TAPER:
return buildTapered(atts);
default:
throw new IllegalStateException(type + " not yet implemented");
}
}
private XySequence buildGR(Attributes atts) {
List<GR_Data> grDataList = mfdHelper.grData(atts);
GR_Data grData = grDataList.get(0);
deltaMag = grData.dMag;
return Mfds.toSequence(buildGR(grData));
}
private XySequence buildCollapsedGR(Attributes atts) {
List<GR_Data> dataList = mfdHelper.grData(atts);
// validate callapsability
GR_Data grModel = dataList.get(0);
double mMin = grModel.mMin;
double dMag = grModel.dMag;
double mMax = grModel.mMax;
for (GR_Data grData : Iterables.skip(dataList, 1)) {
checkState(grData.mMin == mMin, "All mMin must be equal");
checkState(grData.dMag == dMag, "All dMag must be equal");
mMax = Math.max(grData.mMax, mMax);
}
deltaMag = dMag;
double[] mags = Data.buildCleanSequence(mMin, mMax, dMag, true, 2);
double[] rates = new double[mags.length];
for (GR_Data grData : dataList) {
IncrementalMfd mfd = buildGR(grData);
List<Double> mfdRates = mfd.yValues();
for (int i = 0; i < mfdRates.size(); i++) {
rates[i] += mfdRates.get(i);
}
}
return XySequence.createImmutable(mags, rates);
}
private static IncrementalMfd buildGR(GR_Data grData) {
int nMagGR = Mfds.magCount(grData.mMin, grData.mMax, grData.dMag);
IncrementalMfd mfdGR = Mfds.newGutenbergRichterMFD(grData.mMin, grData.dMag,
nMagGR, grData.b, 1.0);
mfdGR.scaleToIncrRate(grData.mMin, Mfds.incrRate(grData.a, grData.b, grData.mMin) *
grData.weight);
return mfdGR;
}
// TODO are there circumstances under which one would
// combine multiple INCR MFDs??
private XySequence buildIncr(Attributes atts) {
List<IncrData> incrDataList = mfdHelper.incrementalData(atts);
IncrData incrData = incrDataList.get(0);
deltaMag = incrData.mags[1] - incrData.mags[0];
return Mfds.toSequence(buildIncr(incrData));
}
private static IncrementalMfd buildIncr(IncrData incrData) {
IncrementalMfd mfdIncr = Mfds.newIncrementalMFD(incrData.mags,
Data.multiply(incrData.weight, incrData.rates));
return mfdIncr;
}
private XySequence buildSingle(Attributes atts) {
List<SingleData> singleDataList = mfdHelper.singleData(atts);
SingleData singleData = singleDataList.get(0);
deltaMag = Double.NaN;
return Mfds.toSequence(buildSingle(singleData));
}
private XySequence buildCollapsedSingle(Attributes atts) {
List<SingleData> dataList = mfdHelper.singleData(atts);
deltaMag = Double.NaN;
double[] mags = new double[dataList.size()];
double[] rates = new double[mags.length];
for (int i = 0; i < dataList.size(); i++) {
SingleData data = dataList.get(i);
mags[i] = data.m;
rates[i] = data.rate * data.weight;
}
return XySequence.createImmutable(mags, rates);
}
private static IncrementalMfd buildSingle(SingleData singleData) {
return Mfds.newSingleMFD(singleData.m, singleData.rate * singleData.weight,
singleData.floats);
}
private XySequence buildTapered(Attributes atts) {
List<TaperData> taperDataList = mfdHelper.taperData(atts);
TaperData taperData = taperDataList.get(0);
deltaMag = taperData.dMag;
return Mfds.toSequence(buildTapered(taperData));
}
private static IncrementalMfd buildTapered(TaperData taperData) {
int nMagTaper = Mfds.magCount(taperData.mMin, taperData.mMax, taperData.dMag);
IncrementalMfd mfdTaper = Mfds.newTaperedGutenbergRichterMFD(taperData.mMin,
taperData.dMag, nMagTaper, taperData.a, taperData.b, taperData.cMag,
taperData.weight);
return mfdTaper;
}
}