diff --git a/src/main/java/gov/usgs/earthquake/nshmp/calc/EqRate.java b/src/main/java/gov/usgs/earthquake/nshmp/calc/EqRate.java
index e451de5f73ef81f7a9b95a97c694c06fa0c26615..7a7071391118f125d948e11aba876bf488218bf0 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/calc/EqRate.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/calc/EqRate.java
@@ -1,15 +1,14 @@
 package gov.usgs.earthquake.nshmp.calc;
 
-import static com.google.common.base.Preconditions.checkArgument;
-
+import java.util.ArrayList;
 import java.util.EnumMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.concurrent.Callable;
 import java.util.function.Consumer;
 
 import com.google.common.base.Converter;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 
 import gov.usgs.earthquake.nshmp.Maths;
@@ -32,6 +31,8 @@ import gov.usgs.earthquake.nshmp.model.Source;
 import gov.usgs.earthquake.nshmp.model.SourceTree;
 import gov.usgs.earthquake.nshmp.model.SourceType;
 import gov.usgs.earthquake.nshmp.model.SystemRuptureSet;
+import gov.usgs.earthquake.nshmp.model.TectonicSetting;
+import gov.usgs.earthquake.nshmp.tree.Branch;
 
 /**
  * General purpose earthquake rate and probability data container. This class
@@ -51,14 +52,19 @@ public class EqRate {
   /** The MFDs for each contributing source type. */
   public final Map<SourceType, XySequence> typeMfds;
 
+  /** MFD contributions to total organized by source tree. */
+  public final List<Contributor> mfds;
+
   private EqRate(
       Site site,
       XySequence totalMfd,
-      Map<SourceType, XySequence> typeMfds) {
+      Map<SourceType, XySequence> typeMfds,
+      List<Contributor> mfds) {
 
     this.site = site;
     this.totalMfd = totalMfd;
     this.typeMfds = typeMfds;
+    this.mfds = mfds;
   }
 
   /**
@@ -126,18 +132,32 @@ public class EqRate {
       typeMfdBuilders.put(type, IntervalArray.Builder.fromModel(modelMfd));
     }
 
+    List<Contributor> mfds = new ArrayList<>();
+
     /* Populate builders. */
     for (SourceTree tree : model) {
-      for (var branch : tree) {
+
+      IntervalArray.Builder treeMfd = IntervalArray.Builder.fromModel(modelMfd);
+
+      for (Branch<RuptureSet<? extends Source>> branch : tree) {
         RuptureSet<? extends Source> ruptures = branch.value();
         IntervalArray rupturesMfd = mfd(ruptures, site.location(), distance, modelMfd);
         typeMfdBuilders.get(ruptures.type()).add(rupturesMfd);
+        treeMfd.add(rupturesMfd);
       }
+
+      Contributor contrib = new Contributor(
+          tree.name(),
+          tree.id(),
+          tree.setting(),
+          tree.type(),
+          treeMfd.build().values());
+      mfds.add(contrib);
     }
 
     /* Compute total and convert to sequences. */
     IntervalArray.Builder totalMfd = IntervalArray.Builder.fromModel(modelMfd);
-    ImmutableMap.Builder<SourceType, XySequence> typeMfds = ImmutableMap.builder();
+    Map<SourceType, XySequence> typeMfds = new EnumMap<>(SourceType.class);
     for (Entry<SourceType, IntervalArray.Builder> entry : typeMfdBuilders.entrySet()) {
       IntervalArray typeMfd = entry.getValue().build();
       typeMfds.put(entry.getKey(), typeMfd.values());
@@ -147,7 +167,8 @@ public class EqRate {
     return new EqRate(
         site,
         totalMfd.build().values(),
-        typeMfds.build());
+        typeMfds,
+        mfds);
   }
 
   /**
@@ -158,16 +179,28 @@ public class EqRate {
   public static EqRate toCumulative(EqRate incremental) {
 
     XySequence cumulativeTotal = Sequences.toCumulative(incremental.totalMfd);
-    ImmutableMap.Builder<SourceType, XySequence> cumulativeTypes = ImmutableMap.builder();
+    Map<SourceType, XySequence> cumulativeTypes = new EnumMap<>(SourceType.class);
     for (Entry<SourceType, XySequence> entry : incremental.typeMfds.entrySet()) {
       cumulativeTypes.put(
           entry.getKey(),
           Sequences.toCumulative(entry.getValue()));
     }
+    List<Contributor> cumulativeMfds = new ArrayList<>();
+    for (Contributor incrContrib : incremental.mfds) {
+      Contributor cumContrib = new Contributor(
+          incrContrib.component,
+          incrContrib.id,
+          incrContrib.setting,
+          incrContrib.type,
+          Sequences.toCumulative(incrContrib.mfd));
+      cumulativeMfds.add(cumContrib);
+    }
+
     return new EqRate(
         incremental.site,
         cumulativeTotal,
-        cumulativeTypes.build());
+        cumulativeTypes,
+        cumulativeMfds);
   }
 
   /**
@@ -195,45 +228,24 @@ public class EqRate {
               .copyOf(entry.getValue())
               .transform(pointConvert));
     }
-    return new EqRate(
-        annualRates.site,
-        XySequence.copyOf(totalMfd),
-        Maps.immutableEnumMap(typeMfds));
-  }
 
-  /**
-   * Create a new earthquake rate container with the sum of the supplied
-   * {@code rates}.
-   *
-   * <p><b>NOTE:</b> This operation is additive and will produce meaningless
-   * results if {@code rates} have already been converted to
-   * {@link #toPoissonProbability(EqRate, double) probabilities}, or are not all
-   * of {@link DistributionFormat#INCREMENTAL} or
-   * {@link DistributionFormat#CUMULATIVE} distribution format.
-   *
-   * <p>Buyer beware.
-   *
-   * @param rates to combine
-   */
-  @Deprecated
-  public static EqRate combine(EqRate... rates) {
-    Site referenceSite = rates[0].site;
-    MutableXySequence totalMfd = MutableXySequence.emptyCopyOf(rates[0].totalMfd);
-    EnumMap<SourceType, MutableXySequence> typeMfds = new EnumMap<>(SourceType.class);
-    for (EqRate rate : rates) {
-      checkArgument(
-          rate.site.location().equals(referenceSite.location()),
-          "Site locations are not the same:\n\ts1: %s\n\ts2: %s",
-          referenceSite, rate.site);
-      totalMfd.add(rate.totalMfd);
-      for (Entry<SourceType, XySequence> entry : rate.typeMfds.entrySet()) {
-        XySequence.addToMap(entry.getKey(), typeMfds, entry.getValue());
-      }
+    List<Contributor> cumulativeMfds = new ArrayList<>();
+    for (Contributor incrContrib : annualRates.mfds) {
+      Contributor cumContrib = new Contributor(
+          incrContrib.component,
+          incrContrib.id,
+          incrContrib.setting,
+          incrContrib.type,
+          MutableXySequence.copyOf(incrContrib.mfd)
+              .transform(pointConvert));
+      cumulativeMfds.add(cumContrib);
     }
+
     return new EqRate(
-        referenceSite,
+        annualRates.site,
         XySequence.copyOf(totalMfd),
-        Maps.immutableEnumMap(typeMfds));
+        Maps.immutableEnumMap(typeMfds),
+        cumulativeMfds);
   }
 
   private static IntervalArray mfd(
@@ -368,4 +380,40 @@ public class EqRate {
     }
   }
 
+  /**
+   * A contributor to the total earthquake rate. Each contributor wraps the
+   * earthquake rate from an individual source tree in a model.
+   */
+  public static final class Contributor {
+
+    /** The contribution source tree name. */
+    public final String component;
+
+    /** The contribution source tree id. */
+    public final Integer id;
+
+    /** The contribution tectonic setting. */
+    public final TectonicSetting setting;
+
+    /** The contribution source type. */
+    public final SourceType type;
+
+    /** The contribution MFD. */
+    public final XySequence mfd;
+
+    Contributor(
+        String component,
+        Integer id,
+        TectonicSetting setting,
+        SourceType type,
+        XySequence mfd) {
+
+      this.component = component;
+      this.id = id;
+      this.setting = setting;
+      this.type = type;
+      this.mfd = mfd;
+    }
+  }
+
 }