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 java.util.logging.Level.FINEST; 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.toMap; import static org.opensha2.internal.SourceAttribute.DEPTH; import static org.opensha2.internal.SourceAttribute.DIP; import static org.opensha2.internal.SourceAttribute.ID; import static org.opensha2.internal.SourceAttribute.NAME; import static org.opensha2.internal.SourceAttribute.RAKE; import static org.opensha2.internal.SourceAttribute.RUPTURE_SCALING; import static org.opensha2.internal.SourceAttribute.TYPE; import static org.opensha2.internal.SourceAttribute.WEIGHT; import static org.opensha2.internal.SourceAttribute.WIDTH; import org.opensha2.data.Data; import org.opensha2.eq.Earthquakes; 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.geo.LocationList; 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.Lists; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.logging.Logger; import javax.xml.parsers.SAXParser; /* * Non-validating fault source parser. SAX parser 'Attributes' are stateful and * cannot be stored. This class is not thread safe. * * @author Peter Powers */ @SuppressWarnings("incomplete-switch") class FaultParser extends DefaultHandler { private final Logger log = Logger.getLogger(FaultParser.class.getName()); private final SAXParser sax; private boolean used = false; private Locator locator; private GmmSet gmmSet; private ModelConfig config; private FaultSourceSet sourceSet; private FaultSourceSet.Builder sourceSetBuilder; private FaultSource.Builder sourceBuilder; private RuptureScaling rupScaling; // Data applying to all sourceSet private MagUncertainty unc = null; private Map<String, String> epiAtts = null; private Map<String, String> aleaAtts = null; // Default MFD data private boolean parsingDefaultMFDs = false; private MfdHelper.Builder mfdHelperBuilder; private MfdHelper mfdHelper; // Traces are the only text content in source files private boolean readingTrace = false; private StringBuilder traceBuilder = null; private FaultParser(SAXParser sax) { this.sax = sax; } static FaultParser create(SAXParser sax) { return new FaultParser(checkNotNull(sax)); } FaultSourceSet 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); checkState(sourceSet.size() > 0, "FaultSourceSet is empty"); 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); } try { switch (e) { case FAULT_SOURCE_SET: String name = readString(NAME, atts); double weight = readDouble(WEIGHT, atts); int id = readInt(ID, atts); sourceSetBuilder = new FaultSourceSet.Builder(); sourceSetBuilder .name(name) .id(id) .weight(weight) .gmms(gmmSet); if (log.isLoggable(FINE)) { log.fine(""); log.fine(" Name: " + name); log.fine(" Weight: " + weight); } mfdHelperBuilder = MfdHelper.builder(); // dummy; usually overwritten mfdHelper = mfdHelperBuilder.build(); break; case DEFAULT_MFDS: parsingDefaultMFDs = true; break; case EPISTEMIC: epiAtts = toMap(atts); break; case ALEATORY: aleaAtts = toMap(atts); break; case SOURCE_PROPERTIES: rupScaling = readEnum(RUPTURE_SCALING, atts, RuptureScaling.class); log.fine("Rup scaling: " + rupScaling); break; case SOURCE: String srcName = readString(NAME, atts); int srcId = readInt(ID, atts); sourceBuilder = new FaultSource.Builder() .name(srcName) .id(srcId) .ruptureScaling(rupScaling) .ruptureFloating(config.ruptureFloating) .ruptureVariability(config.ruptureVariability) .surfaceSpacing(config.surfaceSpacing); log.fine(" Source: " + srcName + " [" + srcId + "]"); if (srcId < 0) { log.warning(" Invalid Id [" + srcId + ", " + srcName + "]"); } break; case INCREMENTAL_MFD: if (parsingDefaultMFDs) { mfdHelperBuilder.addDefault(atts); break; } sourceBuilder.mfds(buildMfds(atts)); break; case GEOMETRY: sourceBuilder.depth(readDouble(DEPTH, atts)) .dip(readDouble(DIP, atts)) .rake(readDouble(RAKE, atts)) .width(readDouble(WIDTH, atts)); break; case TRACE: readingTrace = true; traceBuilder = new StringBuilder(); break; } // @formatter:on } catch (Exception ex) { throw new SAXParseException("Error parsing <" + qName + ">", locator, ex); } } @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); } try { switch (e) { case DEFAULT_MFDS: parsingDefaultMFDs = false; mfdHelper = mfdHelperBuilder.build(); break; case SETTINGS: // may not have mag uncertainty element so create the // uncertainty container upon leaving 'Settings' unc = MagUncertainty.create(epiAtts, aleaAtts); if (log.isLoggable(FINE)) { log.fine(unc.toString()); } break; case TRACE: readingTrace = false; sourceBuilder.trace(LocationList.fromString(traceBuilder.toString())); break; case SOURCE: sourceSetBuilder.source(sourceBuilder.buildFaultSource()); log.finer(""); // insert blank line for detailed output break; case FAULT_SOURCE_SET: sourceSet = sourceSetBuilder.buildFaultSet(); break; } } catch (Exception ex) { throw new SAXParseException("Error parsing <" + qName + ">", locator, ex); } } @Override public void characters(char ch[], int start, int length) throws SAXException { if (readingTrace) { traceBuilder.append(ch, start, length); } } @Override public void setDocumentLocator(Locator locator) { this.locator = locator; } private List<IncrementalMfd> buildMfds(Attributes atts) { MfdType type = readEnum(TYPE, atts, MfdType.class); switch (type) { case GR: return buildGR(atts); case SINGLE: return buildSingle(atts); case INCR: return buildIncremental(atts); default: throw new IllegalStateException(type + " not supported"); } } /* * FaultSource.Builder creates an ImmutableList; so no need for one in methods * below. */ /* * Build INCR MFDs. NOTE Incremental ignores uncertainty models. */ private List<IncrementalMfd> buildIncremental(Attributes atts) { List<IncrementalMfd> mfdList = new ArrayList<>(); for (IncrData incrData : mfdHelper.incrementalData(atts)) { mfdList.addAll(buildIncremental(incrData)); } return mfdList; } private List<IncrementalMfd> buildIncremental(IncrData data) { List<IncrementalMfd> mfds = Lists.newArrayList(); IncrementalMfd mfd = Mfds.newIncrementalMFD(data.mags, Data.multiply(data.weight, data.rates)); mfds.add(mfd); return mfds; } /* * Build GR MFDs. Method throws IllegalStateException if attribute values * yield an MFD with no magnitude bins. */ private List<IncrementalMfd> buildGR(Attributes atts) { List<IncrementalMfd> mfdList = new ArrayList<>(); for (GR_Data grData : mfdHelper.grData(atts)) { mfdList.addAll(buildGR(grData)); } return mfdList; } private List<IncrementalMfd> buildGR(GR_Data data) { int nMag = Mfds.magCount(data.mMin, data.mMax, data.dMag); checkState(nMag > 0, "GR MFD with no mags [%s]", sourceBuilder.name); double tmr = Mfds.totalMoRate(data.mMin, nMag, data.dMag, data.a, data.b); List<IncrementalMfd> mfds = Lists.newArrayList(); // this was handled previously by GR_Data.hasMagExceptions() // TODO edge cases (Rush Peak in brange.3dip.gr.in) suggest // data.mMin should be used instead of unc.epiCutoff boolean uncertAllowed = unc.hasEpistemic && (data.mMax + unc.epiDeltas[0]) >= unc.epiCutoff; if (unc.hasEpistemic && uncertAllowed) { for (int i = 0; i < unc.epiCount; i++) { // update mMax and nMag double mMaxEpi = data.mMax + unc.epiDeltas[i]; int nMagEpi = Mfds.magCount(data.mMin, mMaxEpi, data.dMag); if (nMagEpi > 0) { double weightEpi = data.weight * unc.epiWeights[i]; // epi branches preserve Mo between mMin and dMag(nMag-1), // not mMax to ensure that Mo is 'spent' on earthquakes // represented by the epi GR distribution with adj. mMax. IncrementalMfd mfd = Mfds.newGutenbergRichterMoBalancedMFD(data.mMin, data.dMag, nMagEpi, data.b, tmr * weightEpi); mfds.add(mfd); log.finer(" MFD type: GR [+epi -alea] " + epiBranch(i)); if (log.isLoggable(FINEST)) { log.finest(mfd.getMetadataString()); } } else { log.warning(" GR MFD epi branch with no mags [" + sourceBuilder.name + "]"); } } } else { IncrementalMfd mfd = Mfds.newGutenbergRichterMoBalancedMFD(data.mMin, data.dMag, nMag, data.b, tmr * data.weight); mfds.add(mfd); log.finer(" MFD type: GR [-epi -alea]"); if (log.isLoggable(FINEST)) { log.finest(mfd.getMetadataString()); } } return mfds; } /* Build SINGLE MFDs. */ private List<IncrementalMfd> buildSingle(Attributes atts) { List<IncrementalMfd> mfdList = new ArrayList<>(); for (SingleData singleData : mfdHelper.singleData(atts)) { mfdList.addAll(buildSingle(singleData)); } return mfdList; } private List<IncrementalMfd> buildSingle(SingleData data) { List<IncrementalMfd> mfds = Lists.newArrayList(); // total moment rate double tmr = data.rate * Earthquakes.magToMoment(data.m); // total event rate double tcr = data.rate; // this was handled previously by GR_Data.hasMagExceptions() // need to catch the single floaters that are less than 6.5 // note: unc.epiDeltas[0] may be null double minUncertMag = data.m + (unc.hasEpistemic ? unc.epiDeltas[0] : 0.0); boolean uncertAllowed = !((minUncertMag < unc.epiCutoff) && data.floats); // loop over epistemic uncertainties if (unc.hasEpistemic && uncertAllowed) { for (int i = 0; i < unc.epiCount; i++) { double epiMag = data.m + unc.epiDeltas[i]; double mfdWeight = data.weight * unc.epiWeights[i]; if (unc.hasAleatory) { IncrementalMfd mfd = (unc.moBalance) ? Mfds.newGaussianMoBalancedMFD( epiMag, unc.aleaSigma, unc.aleaCount, mfdWeight * tmr, data.floats) : Mfds.newGaussianMFD( epiMag, unc.aleaSigma, unc.aleaCount, mfdWeight * tcr, data.floats); mfds.add(mfd); log.finer(" MFD type: SINGLE [+epi +alea] " + epiBranch(i)); if (log.isLoggable(FINEST)) { log.finest(mfd.getMetadataString()); } } else { // single Mfds with epi uncertainty are moment balanced at // the // central/single magnitude of the distribution double moRate = tmr * mfdWeight; IncrementalMfd mfd = Mfds.newSingleMoBalancedMFD(epiMag, moRate, data.floats); mfds.add(mfd); log.finer(" MFD type: SINGLE [+epi -alea] " + epiBranch(i)); if (log.isLoggable(FINEST)) { log.finest(mfd.getMetadataString()); } } } } else { if (unc.hasAleatory && uncertAllowed) { IncrementalMfd mfd = (unc.moBalance) ? Mfds.newGaussianMoBalancedMFD( data.m, unc.aleaSigma, unc.aleaCount, data.weight * tmr, data.floats) : Mfds.newGaussianMFD( data.m, unc.aleaSigma, unc.aleaCount, data.weight * tcr, data.floats); mfds.add(mfd); log.finer(" MFD type: SINGLE [-epi +alea]"); if (log.isLoggable(FINEST)) { log.finest(mfd.getMetadataString()); } } else { IncrementalMfd mfd = Mfds.newSingleMFD(data.m, data.weight * data.rate, data.floats); mfds.add(mfd); log.finer(" MFD type: SINGLE [-epi -alea]"); if (log.isLoggable(FINEST)) { log.finest(mfd.getMetadataString()); } } } return mfds; } private static String epiBranch(int i) { return (i == 0) ? "(M-epi)" : (i == 2) ? "(M+epi)" : "(M)"; } }