diff --git a/src/main/java/gov/usgs/earthquake/nshmp/model/ClusterRuptureSet.java b/src/main/java/gov/usgs/earthquake/nshmp/model/ClusterRuptureSet.java index b17de10cc833448ad910515919f457ebdb07be02..85c9e9ece965efe154344d0309ec936f65fe802c 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/model/ClusterRuptureSet.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/model/ClusterRuptureSet.java @@ -8,12 +8,16 @@ import static java.util.stream.Collectors.toList; import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.TreeMap; import java.util.function.Predicate; +import gov.usgs.earthquake.nshmp.data.XySequence; import gov.usgs.earthquake.nshmp.geo.Location; import gov.usgs.earthquake.nshmp.geo.json.Feature; import gov.usgs.earthquake.nshmp.mfd.Mfd; -import gov.usgs.earthquake.nshmp.tree.LogicGroup; +import gov.usgs.earthquake.nshmp.mfd.Mfd.Type; +import gov.usgs.earthquake.nshmp.tree.Branch; import gov.usgs.earthquake.nshmp.tree.LogicTree; /** @@ -36,13 +40,36 @@ import gov.usgs.earthquake.nshmp.tree.LogicTree; */ public class ClusterRuptureSet extends AbstractRuptureSet<ClusterSource> { + /* + * Developer notes: + * + * A ClusterRuptureSet is composed of two or more FaultRuptureSets (fault + * sections) that rupture together according to some rate or logic tree of + * rates. Each nested FaultRuptureSet itself contains a logic tree of SINGLE + * MFDs, usually labelled 'M1', 'M2', etc. The MFDs for each FaultRuptureSet + * are the same size and have identical weights. Technically, the M1 fault + * branches across each cluster should be considered together, the M2 fault + * branches together, etc. in the cluster hazard calculation. However, the + * magnitude branch exceedance curves are currently collapsed prior to doing + * the cluster calculation. + * + * For the purposes of reporting an MFD tree for a ClusterRuptureSet, we + * create MFDs for each magnitude branch spanning the individual sections in + * the cluster. In some cases there may be identical magnitudes in a cluster + * such that the cluster rate reflected in the MFD may be doubled or more. + * + */ + + @Deprecated final ModelData data; // holds cluster rate tree private final List<ClusterSource> source; + private final LogicTree<Mfd> mfdTree; private ClusterRuptureSet(Builder builder) { super(builder); this.data = builder.data; this.source = List.of(builder.source); + this.mfdTree = builder.mfdTree; } @Override @@ -65,23 +92,7 @@ public class ClusterRuptureSet extends AbstractRuptureSet<ClusterSource> { @Override public LogicTree<Mfd> mfdTree() { - /* - * A ClusterSource is composed of multiple FaultRuptureSets that rupture - * together at some rate. For the purposes of reporting an MFD tree for the - * rupture set, we combine the SINGLE MFDs associated with each - * FaultRuptureSet and set the rate of each magnitude to its weight. We then - * build a logic group where the weight (scale) of each branch is set to the - * cluster rate. - */ - ClusterSource clusterSource = source.get(0); - double rate = clusterSource.rate(); - - LogicGroup.Builder<Mfd> builder = LogicGroup.builder(); - for (RuptureSet<? extends Source> ruptureSet : clusterSource.ruptureSets()) { - Mfd mfd = ModelTrees.reduceMfdTree(ruptureSet.mfdTree()); - builder.addBranch(ruptureSet.name(), mfd, rate); - } - return builder.build(); + return mfdTree; } /* @@ -168,6 +179,7 @@ public class ClusterRuptureSet extends AbstractRuptureSet<ClusterSource> { private ModelData data; // holds cluster rate-tree private ClusterSource source; + private LogicTree<Mfd> mfdTree; private Builder() { super(SourceType.FAULT_CLUSTER); @@ -205,8 +217,37 @@ public class ClusterRuptureSet extends AbstractRuptureSet<ClusterSource> { checkNotNull(source, "%s cluster source", label); } + LogicTree<Mfd> createMfdTree() { + LogicTree.Builder<Mfd> builder = LogicTree.builder("cluster-mfd"); + LogicTree<Mfd> model = source.ruptureSets().get(0).mfdTree(); + /* loop magnitude branches */ + for (int i = 0; i < model.size(); i++) { + /* collect cluster magnitudes and rate; all M have cluster rate */ + Map<Double, Double> magMap = new TreeMap<>(); + String magId = model.get(i).id(); + double magWeight = model.get(i).weight(); + for (RuptureSet<? extends Source> ruptureSet : source.ruptureSets()) { + Branch<Mfd> mfd = ruptureSet.mfdTree().get(i); + /* check magnitude branch IDs and weights match */ + checkState(mfd.id().equals(magId)); + checkState(mfd.value().properties().type() == Type.SINGLE); + magMap.merge( + mfd.value().data().x(0), + source.rate(), + (v1, v2) -> v1 + v2); + } + XySequence mfdXy = XySequence.create( + magMap.keySet(), + magMap.values()); + Mfd clusterMfd = Mfd.create(mfdXy); + builder.addBranch(magId, clusterMfd, magWeight); + } + return builder.build(); + } + ClusterRuptureSet build() { validateAndInit("ClusterRuptureSet.Builder"); + mfdTree = createMfdTree(); return new ClusterRuptureSet(this); } }