diff --git a/src/main/java/gov/usgs/earthquake/nshmp/calc/ExceedanceModel.java b/src/main/java/gov/usgs/earthquake/nshmp/calc/ExceedanceModel.java
index 30d385a93195ab9c443f89577260cbeb0c645e45..7fadca4c5f8da488c93431cb3e82b0f4767aa6d4 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/calc/ExceedanceModel.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/calc/ExceedanceModel.java
@@ -11,6 +11,7 @@ import java.util.List;
 
 import gov.usgs.earthquake.nshmp.Maths;
 import gov.usgs.earthquake.nshmp.data.MutableXySequence;
+import gov.usgs.earthquake.nshmp.data.XyPoint;
 import gov.usgs.earthquake.nshmp.data.XySequence;
 import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.gmm.MultiScalarGroundMotion;
@@ -37,6 +38,10 @@ public enum ExceedanceModel {
    * TODO We probably want to refactor this to probability model and provide
    * 'occurrence' in addition to exceedence. See commented distribution function
    * at eof.
+   *
+   * Developer note: Exceedance methods can be called millions of times in a
+   * hazard calculation; do not be tempted to use streams to update XySequence
+   * values.
    */
 
   /**
@@ -52,8 +57,11 @@ public enum ExceedanceModel {
     }
 
     @Override
-    XySequence exceedance(double μ, double σ, double n, Imt imt, XySequence sequence) {
-      sequence.stream().forEach(p -> p.set(Maths.stepFunction(μ, p.x())));
+    MutableXySequence exceedance(double μ, double σ, double n, Imt imt,
+        MutableXySequence sequence) {
+      for (XyPoint p : sequence) {
+        p.set(Maths.stepFunction(μ, p.x()));
+      }
       return sequence;
     }
   },
@@ -70,7 +78,8 @@ public enum ExceedanceModel {
     }
 
     @Override
-    XySequence exceedance(double μ, double σ, double n, Imt imt, XySequence sequence) {
+    MutableXySequence exceedance(double μ, double σ, double n, Imt imt,
+        MutableXySequence sequence) {
       return boundedCcdFn(μ, σ, sequence, 0.0, 1.0);
     }
   },
@@ -87,7 +96,8 @@ public enum ExceedanceModel {
     }
 
     @Override
-    XySequence exceedance(double μ, double σ, double n, Imt imt, XySequence sequence) {
+    MutableXySequence exceedance(double μ, double σ, double n, Imt imt,
+        MutableXySequence sequence) {
       return boundedCcdFn(μ, σ, sequence, prob(μ, σ, n), 1.0);
     }
   },
@@ -105,7 +115,8 @@ public enum ExceedanceModel {
     }
 
     @Override
-    XySequence exceedance(double μ, double σ, double n, Imt imt, XySequence sequence) {
+    MutableXySequence exceedance(double μ, double σ, double n, Imt imt,
+        MutableXySequence sequence) {
       double pHi = prob(μ, σ, n);
       return boundedCcdFn(μ, σ, sequence, pHi, 1.0 - pHi);
     }
@@ -123,7 +134,8 @@ public enum ExceedanceModel {
     }
 
     @Override
-    XySequence exceedance(double μ, double σ, double n, Imt imt, XySequence sequence) {
+    MutableXySequence exceedance(double μ, double σ, double n, Imt imt,
+        MutableXySequence sequence) {
       return Ccdfs.UPPER_3SIGMA.get(μ, σ, sequence);
     }
   },
@@ -141,7 +153,8 @@ public enum ExceedanceModel {
     }
 
     @Override
-    XySequence exceedance(double μ, double σ, double n, Imt imt, XySequence sequence) {
+    MutableXySequence exceedance(double μ, double σ, double n, Imt imt,
+        MutableXySequence sequence) {
       return boundedCcdFn(μ, 0.65, sequence, 0.0, 1.0);
     }
   },
@@ -164,8 +177,11 @@ public enum ExceedanceModel {
     }
 
     @Override
-    XySequence exceedance(double μ, double σ, double n, Imt imt, XySequence sequence) {
-      sequence.stream().forEach(p -> p.set(exceedance(μ, σ, n, imt, p.x())));
+    MutableXySequence exceedance(double μ, double σ, double n, Imt imt,
+        MutableXySequence sequence) {
+      for (XyPoint p : sequence) {
+        p.set(exceedance(μ, σ, n, imt, p.x()));
+      }
       return sequence;
     }
   },
@@ -185,7 +201,8 @@ public enum ExceedanceModel {
     }
 
     @Override
-    XySequence exceedance(double μ, double σ, double n, Imt imt, XySequence sequence) {
+    MutableXySequence exceedance(double μ, double σ, double n, Imt imt,
+        MutableXySequence sequence) {
       double pHi = prob(μ, σ, n, Math.log(ceusMaxValue(imt)));
       return boundedCcdFn(μ, σ, sequence, pHi, 1.0);
     }
@@ -209,7 +226,8 @@ public enum ExceedanceModel {
     }
 
     @Override
-    XySequence exceedance(double μ, double σ, double n, Imt imt, XySequence sequence) {
+    MutableXySequence exceedance(double μ, double σ, double n, Imt imt,
+        MutableXySequence sequence) {
       double lnMaxGm = Math.log(ceusMaxValue(imt));
       double ln3σGm = μ + 3.0 * σ;
       if (ln3σGm < lnMaxGm) {
@@ -249,7 +267,12 @@ public enum ExceedanceModel {
    *        {@link #NSHM_CEUS_MAX_INTENSITY}
    * @param value for which to compute the exceedance probability
    */
-  abstract double exceedance(double μ, double σ, double n, Imt imt, double value);
+  abstract double exceedance(
+      double μ,
+      double σ,
+      double n,
+      Imt imt,
+      double value);
 
   /**
    * Compute the probability of exceeding a sequence of x-values.
@@ -262,7 +285,12 @@ public enum ExceedanceModel {
    * @param sequence the x-values for which to compute exceedance probabilities
    * @return the supplied {@code sequence}
    */
-  abstract XySequence exceedance(double μ, double σ, double n, Imt imt, XySequence sequence);
+  abstract MutableXySequence exceedance(
+      double μ,
+      double σ,
+      double n,
+      Imt imt,
+      MutableXySequence sequence);
 
   /*
    * Return a list of exceedance curves, one for each tree branch in the
@@ -285,8 +313,7 @@ public enum ExceedanceModel {
     for (int i = 0; i < means.length; i++) {
       double μ = means[i];
       for (int j = 0; j < sigmas.length; j++) {
-        curves.add(MutableXySequence.copyOf(
-            exceedance(μ, sigmas[j], n, imt, MutableXySequence.emptyCopyOf(sequence))));
+        curves.add(exceedance(μ, sigmas[j], n, imt, MutableXySequence.emptyCopyOf(sequence)));
       }
     }
     return curves;
@@ -343,14 +370,16 @@ public enum ExceedanceModel {
    * lower probability limits. Return the supplied {@code XySequence} populated
    * with probabilities.
    */
-  private static XySequence boundedCcdFn(
+  private static MutableXySequence boundedCcdFn(
       double μ,
       double σ,
-      XySequence sequence,
+      MutableXySequence sequence,
       double pHi,
       double pLo) {
 
-    sequence.stream().forEach(p -> p.set(boundedCcdFn(μ, σ, p.x(), pHi, pLo)));
+    for (XyPoint p : sequence) {
+      p.set(boundedCcdFn(μ, σ, p.x(), pHi, pLo));
+    }
     return sequence;
   }
 
@@ -462,8 +491,10 @@ public enum ExceedanceModel {
       return 0.0;
     }
 
-    XySequence get(double μ, double σ, XySequence sequence) {
-      sequence.stream().forEach(p -> p.set(get(μ, σ, p.x())));
+    MutableXySequence get(double μ, double σ, MutableXySequence sequence) {
+      for (XyPoint p : sequence) {
+        p.set(get(μ, σ, p.x()));
+      }
       return sequence;
     }
   }
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/calc/GmmProcessor.java b/src/main/java/gov/usgs/earthquake/nshmp/calc/GmmProcessor.java
index 92d16b6db9719428f1a41d9ac7465d3d41826deb..ffb0d7b3a39f3f1cdf14b072a2274c1b24eeb306 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/calc/GmmProcessor.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/calc/GmmProcessor.java
@@ -54,9 +54,6 @@ abstract class GmmProcessor {
   private static final class DefaultInstance extends GmmProcessor {
     @Override
     public ScalarGroundMotion apply(GroundMotionModel model, GmmInput in, Imt imt, Gmm gmm) {
-      ScalarGroundMotion sgm = model.calc(in);
-      // System.out.println(in);
-      // System.out.println(sgm);
       return model.calc(in);
     }
   }
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/data/XySequence.java b/src/main/java/gov/usgs/earthquake/nshmp/data/XySequence.java
index 9677a0a58c17a5d660c5929357ad9caae31386fc..bf08baafdd352e12ff72691f30d857f3b784976d 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/data/XySequence.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/data/XySequence.java
@@ -105,6 +105,7 @@ public interface XySequence extends Iterable<XyPoint> {
       Map<E, MutableXySequence> map,
       XySequence sequence) {
 
+    // TODO replace or deprecate/remove in favor of Map.merge
     if (map.containsKey(key)) {
       map.get(key).add(sequence);
     } else {
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/internal/www/meta/ParamType.java b/src/main/java/gov/usgs/earthquake/nshmp/internal/www/meta/ParamType.java
index 6671a1894ad84227787c4d8a75f20f72d0e7df20..73718df1e321dc87868f33f20a469633a3f20472 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/internal/www/meta/ParamType.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/internal/www/meta/ParamType.java
@@ -1,5 +1,6 @@
 package gov.usgs.earthquake.nshmp.internal.www.meta;
 
+@Deprecated
 public enum ParamType {
   INTEGER,
   NUMBER,
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/model/FaultRuptureSet.java b/src/main/java/gov/usgs/earthquake/nshmp/model/FaultRuptureSet.java
index 09b4fed78f6647ebceed2e739392b0ebf647c9ef..5cfd7fa79ebd34a6674c30b2a051def12186a6f3 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/model/FaultRuptureSet.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/model/FaultRuptureSet.java
@@ -379,6 +379,8 @@ public class FaultRuptureSet implements RuptureSet {
             : (rBranch.value() <= 1.0)
                 ? rBranch.value() // rate in 1/yr
                 : Maths.roundToDigits(1.0 / rBranch.value(), 4); // rate in yr
+        // TODO above line and rounding should go away, we'll create labels
+        // from 1/rate; all rate-trees will have annual rate, not years
         for (Branch<Mfd.Properties> mBranch : mfdPropsTree) {
           Single props = mBranch.value().getAsSingle();
           String id = String.join(":", props.type().name(), mBranch.id(), rBranch.id());
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/model/GridLoader.java b/src/main/java/gov/usgs/earthquake/nshmp/model/GridLoader.java
index c4738f2bc7ffc2d5ed397fd71dda920af210975e..863b0c38ace8003e79f515b678215bb108d90b09 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/model/GridLoader.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/model/GridLoader.java
@@ -306,7 +306,11 @@ class GridLoader {
           ? record.getOptionalDouble(Key.MMAX)
           : OptionalDouble.empty();
 
-      String gridId = zoneForLocation(loc);
+      Optional<String> gridIdOpt = zoneForLocation(loc);
+      // if (gridIdOpt.isEmpty()) {
+      // return;
+      // }
+      String gridId = gridIdOpt.orElseThrow();
 
       FeatureData featureData = dataMap.get(gridId);
       featureData.locations.add(loc);
@@ -366,12 +370,15 @@ class GridLoader {
       return Map.copyOf(modelMfds);
     }
 
-    private String zoneForLocation(Location location) {
+    // TODO there are probably going to be curcumstances under which
+    // it is unreasonable to expect that every single point in a smoothed
+    // seismicity model is contained in a polygon
+    private Optional<String> zoneForLocation(Location location) {
       return featureMap.values().stream()
           .filter(grid -> grid.contains(location))
           .map(grid -> grid.name)
-          .findFirst()
-          .orElseThrow();
+          .findFirst();
+      // .orElseThrow();
     }
   }
 
@@ -416,6 +423,7 @@ class GridLoader {
   }
 
   static GridSourceSet createGrid(
+      SourceType type,
       SourceConfig.Grid config,
       Feature feature,
       List<Location> locations,
@@ -433,6 +441,7 @@ class GridLoader {
         .weight(weight)
         .gmms(gmms);
     builder.gridConfig(config);
+    builder.type(type);
     props.getDouble(Key.STRIKE).ifPresent(builder::strike);
 
     // System.out.println(mfds.get(0).properties().type());
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/model/GridRuptureSet.java b/src/main/java/gov/usgs/earthquake/nshmp/model/GridRuptureSet.java
index 1a263b5b046ce60b1d8160b794ca04df4f7155e9..d8ad89188b2d330800c6bd4668a627228e4e60a7 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/model/GridRuptureSet.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/model/GridRuptureSet.java
@@ -65,6 +65,7 @@ class GridRuptureSet implements RuptureSet {
   private GridSourceSet createSourceSet(double weight) {
     SourceConfig.Grid config = data.sourceConfig().asGrid();
     GridSourceSet grid = GridLoader.createGrid(
+        SourceType.GRID,
         config,
         feature.source,
         locations,
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/model/GridSourceSet.java b/src/main/java/gov/usgs/earthquake/nshmp/model/GridSourceSet.java
index fbd1c592c267b2b8341beb43958f1b421f41fe9e..1e980795066d76e7516669358eb5387874e2765b 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/model/GridSourceSet.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/model/GridSourceSet.java
@@ -54,6 +54,7 @@ import gov.usgs.earthquake.nshmp.tree.LogicTree;
  */
 public class GridSourceSet extends AbstractSourceSet<PointSource> {
 
+  final SourceType type;
   final List<Location> locations;
   final List<Mfd> mfds;
   final LogicTree<List<Mfd>> mfdsTree;
@@ -86,6 +87,7 @@ public class GridSourceSet extends AbstractSourceSet<PointSource> {
 
   private GridSourceSet(Builder builder) {
     super(builder);
+    this.type = builder.type;
     this.locations = builder.locations;
     this.mfds = builder.mfds;
     this.mfdsTree = builder.mfdsTree;
@@ -105,8 +107,11 @@ public class GridSourceSet extends AbstractSourceSet<PointSource> {
      */
     // System.out.println(Δm);
     // this.optimizable = (sourceType() != FIXED_STRIKE) && !Double.isNaN(Δm);
-    this.optimizable = (sourceType() != FIXED_STRIKE) && this.magMaster.isPresent();
-
+    // TODO magMaster is always present, no?
+    this.optimizable =
+        (type != SourceType.ZONE) &&
+            (sourceType() != FIXED_STRIKE) &&
+            this.magMaster.isPresent();
     // System.out.println(Arrays.toString(magMaster.orElseThrow()));
 
     double[] depthMags = this.mfds.get(0).data().xValues().toArray();
@@ -136,7 +141,7 @@ public class GridSourceSet extends AbstractSourceSet<PointSource> {
 
   @Override
   public SourceType type() {
-    return GRID;
+    return type;
   }
 
   /**
@@ -252,6 +257,7 @@ public class GridSourceSet extends AbstractSourceSet<PointSource> {
 
     private static final String ID = "GridSourceSet.Builder";
 
+    private SourceType type;
     private List<Location> locations = Lists.newArrayList();
     private List<Mfd> mfds = Lists.newArrayList();
     private LogicTree<List<Mfd>> mfdsTree;
@@ -269,6 +275,11 @@ public class GridSourceSet extends AbstractSourceSet<PointSource> {
     private Map<FocalMech, Double> mechMap;
     private boolean singularMechs = true;
 
+    Builder type(SourceType type) {
+      this.type = type;
+      return this;
+    }
+
     Builder strike(double strike) {
       this.strike = OptionalDouble.of(checkStrike(strike));
       this.sourceType = FIXED_STRIKE;
@@ -411,8 +422,9 @@ public class GridSourceSet extends AbstractSourceSet<PointSource> {
     @Override
     void validateState(String buildId) {
       super.validateState(buildId);
+      checkState(type != null, "%s source type not set", buildId);
       checkState(strike != null, "%s strike not set", buildId);
-      checkState(sourceType != null, "%s source type not set", buildId);
+      checkState(sourceType != null, "%s point source type not set", buildId);
       checkState(!locations.isEmpty(), "%s has no locations", buildId);
       checkState(!mfds.isEmpty(), "%s has no Mfds", buildId);
       checkState(rupScaling != null, "%s has no rupture-scaling relation set", buildId);
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/model/HazardModel.java b/src/main/java/gov/usgs/earthquake/nshmp/model/HazardModel.java
index ba1757cb61fcb3441e925cdbdce9a7de38c78ff9..0b7801ae2a988dab432aba07912009b638f79e72 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/model/HazardModel.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/model/HazardModel.java
@@ -20,7 +20,6 @@ import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
 import com.google.gson.annotations.SerializedName;
 
-import gov.usgs.earthquake.nshmp.Maths;
 import gov.usgs.earthquake.nshmp.calc.CalcConfig;
 import gov.usgs.earthquake.nshmp.gmm.Gmm;
 import gov.usgs.earthquake.nshmp.gmm.GroundMotionModel;
@@ -268,19 +267,42 @@ public final class HazardModel implements Iterable<SourceSet<? extends Source>>
         return;
       }
 
-      /* FAULT: each leaf is a FaultSource in a FaultSourceSet */
-      // System.out.println(tree.singleType());
-      if (tree.singleType()) {
+      /* Check if mixed fault/cluster types */
+
+      // boolean mixed = tree.branchMap().keySet().stream()
+      // .map(Leaf::ruptureSet)
+      // .map(RuptureSet::type)
+      // .forEach(System.out::println);
+      // TODO want to get rid of this but currently needed
+      // to distinguish trees made up of CLUSTER and FAULT
+
+      // TODO what should this return for mixed fault/cluster ??
+      // @Deprecated
+      // public boolean singleType() {
+      // // leafBranches.keySet().forEach(System.out::println);
+      // return leafBranches.keySet().stream()
+      // .map(Leaf::ruptureSet)
+      // .map(RuptureSet::type)
+      // .distinct()
+      // .count() == 1;
+      // }
+
+      /*
+       * FAULT/CLUSTER: each FAULT leaf is a FaultSource in a FaultSourceSet,
+       * each CLUSTER leaf is an individual clusterSourceSet
+       */
+
+      FaultSourceSet.Builder fss = new FaultSourceSet.Builder();
+      fss.name(tree.name());
+      fss.weight(1.0);
+      fss.gmms(tree.gmms());
 
-        FaultSourceSet.Builder fss = new FaultSourceSet.Builder();
-        fss.name(tree.name());
-        fss.weight(1.0);
-        fss.gmms(tree.gmms());
+      for (Leaf leaf : tree.branchMap().keySet()) {
 
-        for (Leaf leaf : tree.branchMap().keySet()) {
-          RuptureSet rs = leaf.ruptureSet;
-          // System.out.println("Flt to FaultSrcSet: " + rs.type() + " " +
-          // rs.name());
+        RuptureSet rs = leaf.ruptureSet;
+        double branchWeight = tree.branchWeights().get(leaf);
+
+        if (leaf.ruptureSet.type() == SourceType.FAULT) {
 
           double leafWeight = tree.branchWeights().get(leaf);
           FaultRuptureSet frs = (FaultRuptureSet) rs;
@@ -302,29 +324,12 @@ public final class HazardModel implements Iterable<SourceSet<? extends Source>>
           } else {
             System.out.println("      No source ruptures: " + faultSource.name());
           }
-        }
-        if (fss.sources.size() > 0) {
-          addSourceSet(fss.buildFaultSet());
         } else {
-          System.out.println("      Empty Source Set: " + fss.name);
-        }
-        return;
-      }
-
-      /* Otherwise types are mixed, e.g. New Madrid, Wasatch */
-      for (Leaf leaf : tree.branchMap().keySet()) {
-
-        RuptureSet rs = leaf.ruptureSet;
-
-        double branchWeight = tree.branchWeights().get(leaf);
-
-        if (leaf.ruptureSet.type() == SourceType.FAULT_CLUSTER) {
 
           // System.out.println("Flt to ClusterSrcSet: " + rs.type() + " " +
           // rs.name());
 
           ClusterRuptureSet crs = (ClusterRuptureSet) rs;
-
           ClusterSourceSet.Builder css = new ClusterSourceSet.Builder();
           css.name(crs.name);
           css.id(crs.id);
@@ -336,38 +341,39 @@ public final class HazardModel implements Iterable<SourceSet<? extends Source>>
 
           for (Branch<Double> rateBranch : rateTree) {
 
-            double returnPeriod = rateBranch.value();
-            double rate = (returnPeriod <= 0.0)
-                ? 0.0
-                : Maths.roundToDigits(1.0 / returnPeriod, 4);
-
-            String clusterLabel = crs.name + " : " + ((int) returnPeriod) + "-yr";
+            double rate = (rateBranch.value() <= 0.0) ? 0.0 : rateBranch.value();
+            String rateLabel = (rate == 0.0) ? "0" : String.valueOf((int) (1.0 / rate));
+            String clusterLabel = crs.name + " : " + rateLabel + "-yr";
             ClusterSource.Builder csBuilder = new ClusterSource.Builder()
                 .name(clusterLabel)
                 .id(crs.id)
                 .rate(rate); // cluster rate
 
-            FaultSourceSet.Builder fss = new FaultSourceSet.Builder();
-            fss.name(clusterLabel);
-            fss.id(crs.id);
-            fss.gmms(tree.gmms());
+            FaultSourceSet.Builder cfss = new FaultSourceSet.Builder();
+            cfss.name(clusterLabel);
+            cfss.id(crs.id);
+            cfss.gmms(tree.gmms());
 
-            // The weight of the fss gets called by ClusterSource.weight()
+            // The weight of the cfss gets called by ClusterSource.weight()
             // and is used in HazardCurveSet.Builder.addCurves()
-            fss.weight(rateBranch.weight());
+            cfss.weight(rateBranch.weight());
 
             for (FaultRuptureSet frs : crs.faultRuptureSets) {
-              fss.source(faultRuptureSetToSource(frs, 1.0), 1.0);
+              cfss.source(faultRuptureSetToSource(frs, 1.0), 1.0);
             }
-            csBuilder.faults(fss.buildFaultSet());
+            csBuilder.faults(cfss.buildFaultSet());
             css.source(csBuilder.buildClusterSource());
 
           }
 
           addSourceSet(css.buildClusterSet());
-
         }
       }
+      if (fss.sources.size() > 0) {
+        addSourceSet(fss.buildFaultSet());
+      } else {
+        System.out.println("      Empty Source Set: " + fss.name);
+      }
     }
 
     private FaultSource faultRuptureSetToSource(
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/model/MfdTrees.java b/src/main/java/gov/usgs/earthquake/nshmp/model/MfdTrees.java
index c67635db24cda4d3c276460ce785498c05f38ca5..a27d8583a9f62dbdca187842d73bb90f037a1561 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/model/MfdTrees.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/model/MfdTrees.java
@@ -248,6 +248,16 @@ class MfdTrees {
     return pathTree.build();
   }
 
+  /* Create an exact copy of the supplied tree. */
+  static <T> LogicTree<T> copy(LogicTree<T> tree) {
+    LogicTree.Builder<T> copy = LogicTree.builder(tree.name());
+    tree.forEach(branch -> copy.addBranch(
+        branch.id(),
+        branch.value(),
+        branch.weight()));
+    return copy.build();
+  }
+
   /*
    * Transpose a list of logic trees to a logic tree of immutable lists.
    * Supplied logic trees are assumed to have same branch names and IDs.
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/model/ModelLoader.java b/src/main/java/gov/usgs/earthquake/nshmp/model/ModelLoader.java
index f8c6756a807f3a1526dea3320fe4914e1155097f..8a93b27a8a259d4cfa8b3818c4d7aa116deb57cb 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/model/ModelLoader.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/model/ModelLoader.java
@@ -868,7 +868,12 @@ abstract class ModelLoader {
 
         /* Expect one or more geojson polygon files. */
         data.gridFeatureMap(ModelFiles.readGridFeatures(dir).orElseThrow());
-        LogicTree<Path> rateTree = data.gridRateTree().orElseThrow();
+
+        /*
+         * Path based gridRateTree may be used on multiple branches; we
+         * therefore need to use a copy because graph won't support edge reuse.
+         */
+        LogicTree<Path> rateTree = MfdTrees.copy(data.gridRateTree().orElseThrow());
         treeBuilder.addBranches(branch, rateTree);
 
         for (Branch<Path> rateBranch : rateTree) {
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/model/SlabRuptureSet.java b/src/main/java/gov/usgs/earthquake/nshmp/model/SlabRuptureSet.java
index b3ca51efe8862fa05ccd41dff796451bc0dc2c70..182e8be265099061ab1985cc2fb1a372461aa1f5 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/model/SlabRuptureSet.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/model/SlabRuptureSet.java
@@ -46,6 +46,7 @@ class SlabRuptureSet implements RuptureSet {
   private SlabSourceSet createSourceSet(double weight) {
     SourceConfig.Grid config = data.sourceConfig().asGrid();
     GridSourceSet delegate = GridLoader.createGrid(
+        SourceType.INTRASLAB,
         config,
         feature.source,
         locations,
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/model/SourceTree.java b/src/main/java/gov/usgs/earthquake/nshmp/model/SourceTree.java
index 1c3efd010f997920f590fc72e254cff9ba8383ba..835e39a0718e10aa4435600fa867917512f219ac 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/model/SourceTree.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/model/SourceTree.java
@@ -84,21 +84,10 @@ public class SourceTree {
 
   /** The type of sources in this tree. */
   public SourceType type() {
+    // TODO what should this return for mixed fault/cluster ??
     return type;
   }
 
-  // TODO want to get rid of this but currently needed
-  // to distinguish trees made up of CLUSTER and FAULT
-  @Deprecated
-  public boolean singleType() {
-    // leafBranches.keySet().forEach(System.out::println);
-    return leafBranches.keySet().stream()
-        .map(Leaf::ruptureSet)
-        .map(RuptureSet::type)
-        .distinct()
-        .count() == 1;
-  }
-
   /** The ground motion models to use with this tree. */
   public GmmSet gmms() {
     return gmms;
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/model/ZoneRuptureSet.java b/src/main/java/gov/usgs/earthquake/nshmp/model/ZoneRuptureSet.java
index 97e7298ff6428dd73a146761d6ed2b76a083676c..70c039ce56c5ae09571dfcb8b3b300529b3131c6 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/model/ZoneRuptureSet.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/model/ZoneRuptureSet.java
@@ -63,6 +63,7 @@ class ZoneRuptureSet implements RuptureSet {
     SourceConfig.Grid config = data.sourceConfig().asGrid();
 
     GridSourceSet grid = GridLoader.createGrid(
+        SourceType.ZONE,
         config,
         feature.source,
         locations,