From 84561f73cfb69d17aca2a06306128033e6b77861 Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Mon, 4 Nov 2024 11:11:05 -0700
Subject: [PATCH 1/9] field edits wus --> nga

---
 .../nshmp/gmm/UsgsPrviBackbone2025.java          | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/main/java/gov/usgs/earthquake/nshmp/gmm/UsgsPrviBackbone2025.java b/src/main/java/gov/usgs/earthquake/nshmp/gmm/UsgsPrviBackbone2025.java
index 8cb0141e..5adb0489 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/gmm/UsgsPrviBackbone2025.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/gmm/UsgsPrviBackbone2025.java
@@ -82,7 +82,7 @@ public abstract class UsgsPrviBackbone2025 implements GroundMotionModel {
 
     final double τ1, τ2, τ3, τ4;
     final double a, b;
-    final double φs2sPrvi, φs2sWus;
+    final double φs2sPrvi, φs2sNga;
     final double τSub, φSub, φSubSS, φSubS2S;
 
     Coefficients(
@@ -104,7 +104,7 @@ public abstract class UsgsPrviBackbone2025 implements GroundMotionModel {
 
       Map<String, Double> coeffsPhiS2S = ccPhiS2S.get(imt);
       φs2sPrvi = coeffsPhiS2S.get("phiS2S_PRVI");
-      φs2sWus = coeffsPhiS2S.get("phiS2S_WUS");
+      φs2sNga = coeffsPhiS2S.get("phiS2S_WUS");
 
       Map<String, Double> coeffsSubSigma = ccSubSigma.get(imt);
       τSub = coeffsSubSigma.get("tau_mean");
@@ -244,9 +244,9 @@ public abstract class UsgsPrviBackbone2025 implements GroundMotionModel {
       Coefficients c = super.coeffs;
       double Ï„ = calcTau(in.Mw, c);
       double φSS = calcPhiSS(in.Mw, c);
-      double σWus = sqrt(τ * τ + c.φs2sWus * c.φs2sWus + φSS * φSS);
+      double σNga = sqrt(τ * τ + c.φs2sNga * c.φs2sNga + φSS * φSS);
       double σPrvi = sqrt(τ * τ + c.φs2sPrvi * c.φs2sPrvi + φSS * φSS);
-      return new double[] { σWus, σPrvi };
+      return new double[] { σNga, σPrvi };
     }
 
     // active crust tau
@@ -400,9 +400,9 @@ public abstract class UsgsPrviBackbone2025 implements GroundMotionModel {
     @Override
     double[] calcSigmas(GmmInput in) {
       Coefficients c = super.coeffs;
-      double σ = sqrt(c.τSub * c.τSub + c.φSub * c.φSub);
+      double σNga = sqrt(c.τSub * c.τSub + c.φSub * c.φSub);
       double σPrvi = sqrt(c.τSub * c.τSub + c.φSubS2S * c.φSubS2S + c.φSubSS * c.φSubSS);
-      return new double[] { σ, σPrvi };
+      return new double[] { σNga, σPrvi };
     }
   }
 
@@ -535,9 +535,9 @@ public abstract class UsgsPrviBackbone2025 implements GroundMotionModel {
     @Override
     double[] calcSigmas(GmmInput in) {
       Coefficients c = super.coeffs;
-      double σ = sqrt(c.τSub * c.τSub + c.φSub * c.φSub);
+      double σNga = sqrt(c.τSub * c.τSub + c.φSub * c.φSub);
       double σPrvi = sqrt(c.τSub * c.τSub + c.φSubS2S * c.φSubS2S + c.φSubSS * c.φSubSS);
-      return new double[] { σ, σPrvi };
+      return new double[] { σNga, σPrvi };
     }
   }
 
-- 
GitLab


From 10d097372fb80b41f73c2317c57b510e4134afd3 Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Mon, 4 Nov 2024 11:13:46 -0700
Subject: [PATCH 2/9] removed unused sysRupSet builder fields and methods

---
 .../nshmp/model/SystemRuptureSet.java         | 23 -------------------
 1 file changed, 23 deletions(-)

diff --git a/src/main/java/gov/usgs/earthquake/nshmp/model/SystemRuptureSet.java b/src/main/java/gov/usgs/earthquake/nshmp/model/SystemRuptureSet.java
index a9943527..329ccea2 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/model/SystemRuptureSet.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/model/SystemRuptureSet.java
@@ -473,17 +473,8 @@ public class SystemRuptureSet extends AbstractRuptureSet<SystemRuptureSet.System
     private Map<Integer, SystemSection> sectionMap;
     private Path ruptures;
 
-    /* Unfiltered UCERF3: FM31 = 253,706 FM32 = 305,709 */
-    static final int RUP_SET_SIZE = 306000;
-
     static final String ID = "SystemRuptureSet.Builder";
 
-    private List<GriddedSurface> sections;
-    private List<String> sectionNames;
-
-    private double mMin = Double.POSITIVE_INFINITY;
-    private double mMax = Double.NEGATIVE_INFINITY;
-
     private Builder(SourceType type) {
       super(type);
     }
@@ -520,20 +511,6 @@ public class SystemRuptureSet extends AbstractRuptureSet<SystemRuptureSet.System
       return this;
     }
 
-    Builder sections(List<GriddedSurface> sections) {
-      checkNotNull(sections, "Section surface list is null");
-      checkArgument(sections.size() > 0, "Section surface list is empty");
-      this.sections = sections;
-      return this;
-    }
-
-    Builder sectionNames(List<String> names) {
-      checkNotNull(names, "Section name list is null");
-      checkArgument(names.size() > 0, "Section name list is empty");
-      this.sectionNames = names;
-      return this;
-    }
-
     private void validateAndInit(String label) {
       validateState(label);
       checkNotNull(data, "%s model data", label);
-- 
GitLab


From f4359e9f490bf9038f2837b574c19fb9070f1341 Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Mon, 4 Nov 2024 12:36:31 -0700
Subject: [PATCH 3/9] system sections init with spacing from config

---
 .../nshmp/model/SystemRuptureSet.java         | 25 ++++++++++++-------
 1 file changed, 16 insertions(+), 9 deletions(-)

diff --git a/src/main/java/gov/usgs/earthquake/nshmp/model/SystemRuptureSet.java b/src/main/java/gov/usgs/earthquake/nshmp/model/SystemRuptureSet.java
index 329ccea2..43591f08 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/model/SystemRuptureSet.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/model/SystemRuptureSet.java
@@ -84,7 +84,6 @@ public class SystemRuptureSet extends AbstractRuptureSet<SystemRuptureSet.System
   final Map<Integer, SystemSection> sectionMap;
   private final Path ruptures;
 
-  // from SSSb
   final GriddedSurface[] sections;
   final String[] sectionNames;
 
@@ -115,14 +114,16 @@ public class SystemRuptureSet extends AbstractRuptureSet<SystemRuptureSet.System
     this.sectionMap = builder.sectionMap;
     this.ruptures = builder.ruptures;
 
-    Function<SystemSection, GriddedSurface> surfaceFunction = (type() == SourceType.FAULT_SYSTEM)
-        ? SystemRuptureSet::featureToCrustalSurface
-        : SystemRuptureSet::featureToInterfaceSurface;
+    SourceConfig config = builder.data.sourceConfig();
 
     this.sections = sectionMap.keySet().stream()
         .sorted()
         .map(sectionMap::get)
-        .map(surfaceFunction)
+        .map(f -> {
+          return (type() == SourceType.FAULT_SYSTEM)
+              ? featureToCrustalSurface(f, config.asFault().surfaceSpacing)
+              : featureToInterfaceSurface(f, config.asInterface().surfaceSpacing);
+        })
         .toArray(GriddedSurface[]::new);
 
     this.sectionNames = sectionMap.keySet().stream()
@@ -219,7 +220,10 @@ public class SystemRuptureSet extends AbstractRuptureSet<SystemRuptureSet.System
     return IntStream.rangeClosed(min, max);
   }
 
-  private static GriddedSurface featureToCrustalSurface(SourceFeature.SystemSection section) {
+  private static GriddedSurface featureToCrustalSurface(
+      SourceFeature.SystemSection section,
+      double spacing) {
+
     return DefaultGriddedSurface.builder()
         .depth(section.upperDepth)
         .lowerDepth(section.lowerDepth)
@@ -227,15 +231,18 @@ public class SystemRuptureSet extends AbstractRuptureSet<SystemRuptureSet.System
         .dip(section.dip)
         .dipDir(section.dipDirection)
         .trace(section.traces.get(0))
-        .spacing(1.0)
+        .spacing(spacing)
         .build();
   }
 
-  private static GriddedSurface featureToInterfaceSurface(SourceFeature.SystemSection section) {
+  private static GriddedSurface featureToInterfaceSurface(
+      SourceFeature.SystemSection section,
+      double spacing) {
+
     return new ApproxGriddedSurface(
         section.traces.get(0),
         section.traces.get(1),
-        5.0);
+        spacing);
   }
 
   @Override
-- 
GitLab


From f1903a50aa793b32697c7e29e94d968389efd032 Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Mon, 4 Nov 2024 12:40:52 -0700
Subject: [PATCH 4/9] removed unused duplicate surface instance

---
 .../earthquake/nshmp/model/InterfaceRuptureSet.java | 13 -------------
 1 file changed, 13 deletions(-)

diff --git a/src/main/java/gov/usgs/earthquake/nshmp/model/InterfaceRuptureSet.java b/src/main/java/gov/usgs/earthquake/nshmp/model/InterfaceRuptureSet.java
index 8b965edb..1351e523 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/model/InterfaceRuptureSet.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/model/InterfaceRuptureSet.java
@@ -14,8 +14,6 @@ import java.util.Map;
 import java.util.function.Predicate;
 
 import gov.usgs.earthquake.nshmp.Earthquakes;
-import gov.usgs.earthquake.nshmp.fault.surface.ApproxGriddedSurface;
-import gov.usgs.earthquake.nshmp.fault.surface.GriddedSurface;
 import gov.usgs.earthquake.nshmp.geo.Location;
 import gov.usgs.earthquake.nshmp.geo.LocationList;
 import gov.usgs.earthquake.nshmp.geo.Locations;
@@ -40,8 +38,6 @@ public class InterfaceRuptureSet extends AbstractRuptureSet<InterfaceSource> {
   final LogicTree<Mfd> mfdTree;
   final Mfd mfdTotal;
 
-  final List<Integer> sectionIds;// reference: actually needed?
-  final GriddedSurface surface;
   private final List<InterfaceSource> source;
 
   InterfaceRuptureSet(Builder builder) {
@@ -52,8 +48,6 @@ public class InterfaceRuptureSet extends AbstractRuptureSet<InterfaceSource> {
     this.mfdTree = builder.mfdTree;
     this.mfdTotal = builder.mfdTotal;
 
-    this.sectionIds = builder.sectionIds;
-    this.surface = builder.surface;
     this.source = List.of(createSource());
   }
 
@@ -193,7 +187,6 @@ public class InterfaceRuptureSet extends AbstractRuptureSet<InterfaceSource> {
     /* created on build */
     private LogicTree<Mfd> mfdTree;
     private Mfd mfdTotal;
-    private GriddedSurface surface;
 
     private Builder() {
       super(SourceType.INTERFACE);
@@ -254,12 +247,6 @@ public class InterfaceRuptureSet extends AbstractRuptureSet<InterfaceSource> {
       mfdTotal = ModelTrees.reduceMfdTree(mfdTree);
 
       feature = createFeature();
-
-      SourceConfig.Interface config = data.sourceConfig().asInterface();
-      surface = new ApproxGriddedSurface(
-          feature.traces.get(0),
-          feature.traces.get(1),
-          config.surfaceSpacing);
     }
 
     private SourceFeature.Interface createFeature() {
-- 
GitLab


From 500bbb111d722562a4da94a5a515e74e460c0ef8 Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Mon, 4 Nov 2024 13:32:48 -0700
Subject: [PATCH 5/9] changed return period fields from int to double

---
 .../java/gov/usgs/earthquake/nshmp/calc/CalcConfig.java   | 8 ++++----
 .../gov/usgs/earthquake/nshmp/calc/CalcConfigTests.java   | 4 ++--
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/main/java/gov/usgs/earthquake/nshmp/calc/CalcConfig.java b/src/main/java/gov/usgs/earthquake/nshmp/calc/CalcConfig.java
index 37fb6e6d..126015be 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/calc/CalcConfig.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/calc/CalcConfig.java
@@ -542,7 +542,7 @@ public final class CalcConfig {
      * The return period (in years) at which disaggregations will be performed
      * unless period-specific ground motion levels are included in a sites file.
      */
-    public final Integer returnPeriod;
+    public final Double returnPeriod;
 
     /**
      * The distance, magnitude, and epsilon bins into which contributing sources
@@ -611,7 +611,7 @@ public final class CalcConfig {
 
     private static final class Builder {
 
-      Integer returnPeriod;
+      Double returnPeriod;
       Bins bins;
       Double contributorLimit;
 
@@ -793,7 +793,7 @@ public final class CalcConfig {
      *
      * <p><b>Default:</b> [475, 975, 2475]
      */
-    public final List<Integer> returnPeriods;
+    public final List<Double> returnPeriods;
 
     private Output(Builder b) {
       this.directory = b.directory;
@@ -805,7 +805,7 @@ public final class CalcConfig {
 
       Path directory;
       Set<DataType> dataTypes;
-      List<Integer> returnPeriods;
+      List<Double> returnPeriods;
 
       Output build() {
         checkNotNull(directory);
diff --git a/src/test/java/gov/usgs/earthquake/nshmp/calc/CalcConfigTests.java b/src/test/java/gov/usgs/earthquake/nshmp/calc/CalcConfigTests.java
index 53246033..53f2b189 100644
--- a/src/test/java/gov/usgs/earthquake/nshmp/calc/CalcConfigTests.java
+++ b/src/test/java/gov/usgs/earthquake/nshmp/calc/CalcConfigTests.java
@@ -370,7 +370,7 @@ class CalcConfigTests {
 
   @Test
   void testOutputMember() {
-    List<Integer> defaultReturnPeroiods = List.of(475, 975, 2475, 10000);
+    List<Double> defaultReturnPeroiods = List.of(475.0, 975.0, 2475.0, 10000.0);
 
     CalcConfig.Output def = DEFAULTS.output;
     assertEquals(Path.of("hazout"), def.directory);
@@ -381,7 +381,7 @@ class CalcConfigTests {
     assertEquals(Path.of("custom"), def.directory);
     // also tests automatic addition of TOTAL and MAP
     assertEquals(Set.of(DataType.TOTAL, DataType.MAP, DataType.GMM), def.dataTypes);
-    assertEquals(List.of(100, 200), def.returnPeriods);
+    assertEquals(List.of(100.0, 200.0), def.returnPeriods);
 
     def = EXTENDS_EMPTY.output;
     assertEquals(Path.of("hazout"), def.directory);
-- 
GitLab


From 630fa407e4e242d754cd5b27d5d5bd855c9dd665 Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Mon, 4 Nov 2024 13:39:03 -0700
Subject: [PATCH 6/9] fixed ba08 site amp in MA05

---
 .../nshmp/gmm/MotazedianAtkinson_2005.java           | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/main/java/gov/usgs/earthquake/nshmp/gmm/MotazedianAtkinson_2005.java b/src/main/java/gov/usgs/earthquake/nshmp/gmm/MotazedianAtkinson_2005.java
index 1024432d..6cb0affe 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/gmm/MotazedianAtkinson_2005.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/gmm/MotazedianAtkinson_2005.java
@@ -242,14 +242,14 @@ public class MotazedianAtkinson_2005 implements GroundMotionModel {
       // Get μ from MA_05_BASE for current IMT (interpolate if necessary)
       double μ = GroundMotions.combine(super.calc(in)).mean();
 
-      double μPga = calcMean(coeffsPGA, in.rRup, in.Mw);
+      double pgaRef = Math.exp(calcMean(coeffsPGA, in.rRup, in.Mw));
       double site = siteAmp.isPresent()
-          ? siteAmp.get().siteAmp(μPga, in.vs30)
-          : calcInterpolatedSite(super.coeffs.imt, μPga, in.vs30);
+          ? siteAmp.get().siteAmp(pgaRef, in.vs30)
+          : calcInterpolatedSite(super.coeffs.imt, pgaRef, in.vs30);
       return GroundMotions.createTree(μ + site, SIGMA);
     }
 
-    private static double calcInterpolatedSite(Imt imt, double μPga, double vs30) {
+    private static double calcInterpolatedSite(Imt imt, double pgaRef, double vs30) {
       Range<Imt> imtRange = INTERPOLATED_SITE_IMTS.get(imt);
       Imt imtLo = imtRange.lowerEndpoint();
       Imt imtHi = imtRange.upperEndpoint();
@@ -263,8 +263,8 @@ public class MotazedianAtkinson_2005 implements GroundMotionModel {
       double tTarget = imt.period();
       BooreAtkinson_2008 ba08lo = (BooreAtkinson_2008) BA_08_BASE.instance(imtLo);
       BooreAtkinson_2008 ba08hi = (BooreAtkinson_2008) BA_08_BASE.instance(imtHi);
-      double siteLo = ba08lo.siteAmp(μPga, vs30);
-      double siteHi = ba08hi.siteAmp(μPga, vs30);
+      double siteLo = ba08lo.siteAmp(pgaRef, vs30);
+      double siteHi = ba08hi.siteAmp(pgaRef, vs30);
       return Interpolator.findY(tLo, siteLo, tHi, siteHi, tTarget);
     }
   }
-- 
GitLab


From 55a324ad858285304cd89ca6e512d58294d5cb65 Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Tue, 12 Nov 2024 10:16:50 -0700
Subject: [PATCH 7/9] javadoc edit

---
 src/main/java/gov/usgs/earthquake/nshmp/calc/Transforms.java | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/main/java/gov/usgs/earthquake/nshmp/calc/Transforms.java b/src/main/java/gov/usgs/earthquake/nshmp/calc/Transforms.java
index b8c59a40..07a8fa38 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/calc/Transforms.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/calc/Transforms.java
@@ -509,6 +509,10 @@ final class Transforms {
    * for sources in a cluster. Note that this is only to be used with cluster
    * sources as the weight of each magnitude variant is stored in the
    * HazardInput.rate field, which is kinda KLUDGY, but works.
+   *
+   * TODO: Investigate sensitivity to collapsing magnitude variants on each
+   * fault in a cluster [F1(M1,M2,M3), F2(M1 M2,M3), ...] versus doing each
+   * magnitude branch separately [(F1M1,F2M1,...), (F1M2,F2M2,...)]
    */
   private static final class ClusterGroundMotionsToCurves implements
       Function<ClusterGroundMotions, ClusterCurves> {
-- 
GitLab


From 1fc75e963affa8d55d96c046c477d61ff9a82f5f Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Tue, 12 Nov 2024 10:18:39 -0700
Subject: [PATCH 8/9] cleanup

---
 .../java/gov/usgs/earthquake/nshmp/model/Models.java     | 9 +--------
 1 file changed, 1 insertion(+), 8 deletions(-)

diff --git a/src/main/java/gov/usgs/earthquake/nshmp/model/Models.java b/src/main/java/gov/usgs/earthquake/nshmp/model/Models.java
index aa70ac99..b823cdb5 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/model/Models.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/model/Models.java
@@ -17,9 +17,6 @@ import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-
 import gov.usgs.earthquake.nshmp.data.XySequence;
 import gov.usgs.earthquake.nshmp.geo.json.Feature;
 import gov.usgs.earthquake.nshmp.geo.json.FeatureCollection;
@@ -37,10 +34,6 @@ import gov.usgs.earthquake.nshmp.tree.LogicTree;
  */
 public class Models {
 
-  static final Gson GSON = new GsonBuilder()
-      .setPrettyPrinting()
-      .create();
-
   /**
    * Returns an object for JSON serialization with the name and ID of all source
    * logic tree groups in the supplied model organized by tectonic setting and
@@ -226,7 +219,7 @@ public class Models {
 
       /*
        * Map source tree into a logic tree of MFDs. Note that the mfdTree
-       * created below is actually a LogicGroup becuase a SourceTree may contain
+       * created below is actually a LogicGroup because a SourceTree may contain
        * null (do-nothing) branches such that weights do not sum to one.
        */
       LogicTree<Mfd> mfdTree = ModelTrees.toMfdTree(tree);
-- 
GitLab


From 3b1b573f1ccc30771059019008c3254048e6f772 Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Tue, 12 Nov 2024 10:23:34 -0700
Subject: [PATCH 9/9] refactored cluster mfdTree reporting

---
 .../nshmp/model/ClusterRuptureSet.java        | 77 ++++++++++++++-----
 1 file changed, 59 insertions(+), 18 deletions(-)

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 b17de10c..85c9e9ec 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);
     }
   }
-- 
GitLab