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.SourceAttribute.DEPTH; import static org.opensha2.internal.SourceAttribute.DIP; import static org.opensha2.internal.SourceAttribute.ID; import static org.opensha2.internal.SourceAttribute.M; import static org.opensha2.internal.SourceAttribute.NAME; import static org.opensha2.internal.SourceAttribute.RAKE; import static org.opensha2.internal.SourceAttribute.RATE; 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.eq.fault.surface.RuptureScaling; 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.math.DoubleMath; import java.io.IOException; import java.io.InputStream; import java.util.logging.Logger; import javax.xml.parsers.SAXParser; /* * Non-validating cluster source parser. SAX parser 'Attributes' are stateful * and cannot be stored. This class is not thread safe. * * NOTE: Cluster sources only support SINGLE magnitude frequency distributions * and do not support epistemic or aleatory uncertainty on magnitude. These * restrictions could be lifted in the future. * * NOTE: A ClusterSource wraps a FaultSourceSet that it delegates to for various * methods. * * @author Peter Powers */ @SuppressWarnings("incomplete-switch") class ClusterParser extends DefaultHandler { private final Logger log = Logger.getLogger(ClusterParser.class.getName()); private final SAXParser sax; private boolean used = false; private Locator locator; private GmmSet gmmSet; private ModelConfig config; private ClusterSourceSet sourceSet; private ClusterSourceSet.Builder clusterSetBuilder; private ClusterSource.Builder clusterBuilder; private FaultSourceSet.Builder faultSetBuilder; private FaultSource.Builder faultBuilder; private double clusterRate; // required, but not used, by FaultSources private RuptureScaling rupScaling; // Default MFD data private boolean parsingDefaultMFDs = false; // Traces are the only text content in source files private boolean readingTrace = false; private StringBuilder traceBuilder = null; private ClusterParser(SAXParser sax) { this.sax = sax; } static ClusterParser create(SAXParser sax) { return new ClusterParser(checkNotNull(sax)); } ClusterSourceSet 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, "ClusterSourceSet 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 CLUSTER_SOURCE_SET: String name = readString(NAME, atts); int id = readInt(ID, atts); double weight = readDouble(WEIGHT, atts); clusterSetBuilder = new ClusterSourceSet.Builder(); clusterSetBuilder .name(name) .id(id) .weight(weight) .gmms(gmmSet); if (log.isLoggable(FINE)) { log.fine(""); log.fine(" Name: " + name); log.fine(" Weight: " + weight); } break; case DEFAULT_MFDS: parsingDefaultMFDs = true; break; case MAG_UNCERTAINTY: // we could just ignore <MagUncertainty> bu favor // explicitely // NOT supporting it for now. throw new IllegalStateException( "Cluster sources do no support magnitude uncertainty"); case SOURCE_PROPERTIES: // this isn't really needed for cluster sources, // but nested faults can't be built without it rupScaling = readEnum(RUPTURE_SCALING, atts, RuptureScaling.class); log.fine("Rup scaling: " + rupScaling + " (not used)"); break; case CLUSTER: String clustName = readString(NAME, atts); double clustWeight = readDouble(WEIGHT, atts); int clustId = readInt(ID, atts); clusterBuilder = new ClusterSource.Builder(); clusterBuilder.rate(clusterRate); faultSetBuilder = new FaultSourceSet.Builder(); faultSetBuilder .name(clustName) .id(clustId) .weight(clustWeight) .gmms(gmmSet); if (log.isLoggable(FINE)) { log.fine(""); log.fine(" Cluster: " + clustName); log.fine(" Weight: " + clustWeight); log.fine(" Ret. per: " + Math.rint(1.0 / clusterRate)); } break; case SOURCE: String srcName = readString(NAME, atts); int srcId = readInt(ID, atts); faultBuilder = new FaultSource.Builder() .name(srcName) .id(srcId) .ruptureScaling(rupScaling) .ruptureFloating(config.ruptureFloating) .ruptureVariability(config.ruptureVariability) .surfaceSpacing(config.surfaceSpacing); log.finer(" Fault: " + srcName); break; case INCREMENTAL_MFD: if (parsingDefaultMFDs) { checkState(readEnum(TYPE, atts, MfdType.class) == MfdType.SINGLE, "Only SINGLE MFDs are supported by cluster sources"); clusterRate = readDouble(RATE, atts); break; } faultBuilder.mfd(buildMFD(atts)); break; case GEOMETRY: faultBuilder.depth(readDouble(DEPTH, atts)) .dip(readDouble(DIP, atts)) .width(readDouble(WIDTH, atts)) .rake(readDouble(RAKE, atts)); break; case TRACE: readingTrace = true; traceBuilder = new StringBuilder(); break; } } 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; break; case TRACE: readingTrace = false; faultBuilder.trace(LocationList.fromString(traceBuilder.toString())); break; case SOURCE: FaultSource faultSource = faultBuilder.buildFaultSource(); checkMagVariantWeights(faultSource); faultSetBuilder.source(faultSource); break; case CLUSTER: clusterBuilder.faults(faultSetBuilder.buildFaultSet()); clusterSetBuilder.source(clusterBuilder.buildClusterSource()); break; case CLUSTER_SOURCE_SET: log.fine(""); sourceSet = clusterSetBuilder.buildClusterSet(); 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 IncrementalMfd buildMFD(Attributes atts) { MfdType type = readEnum(TYPE, atts, MfdType.class); switch (type) { case SINGLE: IncrementalMfd mfd = Mfds.newSingleMFD(readDouble(M, atts), readDouble(WEIGHT, atts), false); log.finer(" MFD type: SINGLE"); if (log.isLoggable(FINEST)) { log.finest(mfd.getMetadataString()); } return mfd; default: throw new IllegalStateException(type + " not yet implemented"); } } /* * This method ensures that the weights of the mag variants of a source in a * cluster (wherein a source weight is stored in the rate field) sum to 1. */ private static void checkMagVariantWeights(FaultSource source) { double totalWeight = 0.0; for (IncrementalMfd mfd : source.mfds) { totalWeight += mfd.getMinY(); } checkState( DoubleMath.fuzzyEquals(totalWeight, 1.0, 0.00001), "Magnitude variant weights (%s) in a cluster source must sum to 1.0", totalWeight); } }