From 79ef4a9cd6e875b88cfc027226d13c3f12ea0c6b Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Mon, 3 Apr 2017 10:50:55 -0600
Subject: [PATCH] added EqRate calculator

---
 src/org/opensha2/calc/EqRate.java             | 213 +++++++++++++++++-
 .../opensha2/eq/model/SystemSourceSet.java    |  61 ++++-
 2 files changed, 263 insertions(+), 11 deletions(-)

diff --git a/src/org/opensha2/calc/EqRate.java b/src/org/opensha2/calc/EqRate.java
index 1a38e1444..c6be87234 100644
--- a/src/org/opensha2/calc/EqRate.java
+++ b/src/org/opensha2/calc/EqRate.java
@@ -1,24 +1,225 @@
 package org.opensha2.calc;
 
+import org.opensha2.data.IntervalArray;
 import org.opensha2.data.XySequence;
+import org.opensha2.eq.model.ClusterSource;
+import org.opensha2.eq.model.ClusterSourceSet;
+import org.opensha2.eq.model.Distance;
+import org.opensha2.eq.model.HazardModel;
+import org.opensha2.eq.model.Rupture;
+import org.opensha2.eq.model.Source;
+import org.opensha2.eq.model.SourceSet;
 import org.opensha2.eq.model.SourceType;
+import org.opensha2.eq.model.SystemSourceSet;
+import org.opensha2.geo.Location;
+import org.opensha2.mfd.Mfds;
 
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+
+import java.util.EnumMap;
 import java.util.Map;
+import java.util.Map.Entry;
 
 /**
- * Magnitude-frequency distribution data container. The class may be used for a
- * single source or a region.
+ * General purpose magnitude-frequency distribution data container. CUrrent;y
+ * implemented for annual rate only.
  *
  * @author Peter Powers
  */
 public class EqRate {
 
+  final XySequence totalMfd;
+  final Map<SourceType, XySequence> typeMfds;
+
+  private EqRate(XySequence totalMfd, Map<SourceType, XySequence> typeMfds) {
+    this.totalMfd = totalMfd;
+    this.typeMfds = typeMfds;
+  }
+  
+  /*
+   * Developer notes:
+   * 
+   * TODO the receiver MFD needs to be built by querying hazard model for min-max
+   * magnitudes; currently it is built using fixed, known values from the 2014
+   * COUS model.
+   */
+
+  public static EqRate createIncremental(
+      HazardModel model,
+      Location location,
+      double distance) {
+
+    IntervalArray modelMfd = IntervalArray.Builder
+        .withRows(4.7, 9.4, 0.1)
+        .build();
+
+    /* Initialize SourceType mfd builders. */
+    Map<SourceType, IntervalArray.Builder> typeMfdBuilders = new EnumMap<>(SourceType.class);
+    for (SourceType type : model.types()) {
+      typeMfdBuilders.put(type, IntervalArray.Builder.fromModel(modelMfd));
+    }
+
+    /* Populate builders. */
+    for (SourceSet<? extends Source> sourceSet : model) {
+      IntervalArray sourceSetMfd = mfd(sourceSet, location, distance, modelMfd);
+      typeMfdBuilders.get(sourceSet.type()).add(sourceSetMfd);
+    }
+
+    /* Compute total and convert to sequences. */
+    IntervalArray.Builder totalMfd = IntervalArray.Builder.fromModel(modelMfd);
+    ImmutableMap.Builder<SourceType, XySequence> typeMfds = ImmutableMap.builder();
+    for (Entry<SourceType, IntervalArray.Builder> entry : typeMfdBuilders.entrySet()) {
+      IntervalArray typeMfd = entry.getValue().build();
+      totalMfd.add(typeMfd);
+    }
+
+    return new EqRate(
+        totalMfd.build().values(),
+        typeMfds.build());
+  }
+
+  /**
+   * Get the total 
+   * @param model
+   * @param location
+   * @param distance
+   * @return
+   */
+  public static EqRate createCumulative(
+      HazardModel model,
+      Location location,
+      double distance) {
+
+    EqRate incremental = createIncremental(model, location, distance);
+    XySequence cumulativeTotal = Mfds.toCumulative(incremental.totalMfd);
+    ImmutableMap.Builder<SourceType, XySequence> cumulativeTypes = ImmutableMap.builder();
+    for (Entry<SourceType, XySequence> entry : incremental.typeMfds.entrySet()) {
+      cumulativeTypes.put(
+          entry.getKey(),
+          Mfds.toCumulative(entry.getValue()));
+    }
+    return new EqRate(
+        cumulativeTotal,
+        cumulativeTypes.build());
+  }
+
+  static EqRate combine(EqRate... eqRates) {
+    // validate xs; or not if only for internal use where we know the receiver
+    // mfds are all sources from the same model
+
+    XySequence totalMfd = XySequence.emptyCopyOf(eqRates[0].totalMfd);
+    EnumMap<SourceType, XySequence> typeMfds = new EnumMap<>(SourceType.class);
+    for (EqRate eqRate : eqRates) {
+      totalMfd.add(eqRate.totalMfd);
+      for (Entry<SourceType, XySequence> entry : eqRate.typeMfds.entrySet()) {
+        SourceType type = entry.getKey();
+        if (!typeMfds.containsKey(type)) {
+          XySequence typeMfd = XySequence.emptyCopyOf(totalMfd);
+          typeMfds.put(type, typeMfd);
+        }
+        typeMfds.get(type).add(entry.getValue());
+      }
+    }
+    return new EqRate(
+        XySequence.immutableCopyOf(totalMfd),
+        Maps.immutableEnumMap(typeMfds));
+  }
+
+  private static IntervalArray mfd(
+      SourceSet<? extends Source> sourceSet,
+      Location location,
+      double distance,
+      IntervalArray modelMfd) {
+
+    switch (sourceSet.type()) {
+      case GRID:
+        return gridMfd(sourceSet, location, distance, modelMfd);
+      case SLAB:
+        return gridMfd(sourceSet, location, distance, modelMfd);
+      case CLUSTER:
+        return clusterMfd((ClusterSourceSet) sourceSet, location, distance, modelMfd);
+      case SYSTEM:
+        return systemMfd((SystemSourceSet) sourceSet, location, distance, modelMfd);
+      default:
+        return faultMfd(sourceSet, location, distance, modelMfd);
+      // TODO AREA?
+    }
+  }
+
+  /*
+   * Short-circuit GridSourceSet by summing the relevant node mfds. Handles both
+   * GRID and SLAB types.
+   */
+  private static IntervalArray gridMfd(
+      SourceSet<? extends Source> sourceSet,
+      Location location,
+      double distance,
+      IntervalArray modelMfd) {
+
+    IntervalArray.Builder sourceSetMfd = IntervalArray.Builder.fromModel(modelMfd);
+    for (Source source : sourceSet.iterableForLocation(location, distance)) {
+      for (XySequence mfd : source.mfds()) {
+        sourceSetMfd.addEach(mfd);
+      }
+    }
+    return sourceSetMfd.build();
+  }
+
+  /*
+   * Special case ClusterSourceSet.
+   */
+  private static IntervalArray clusterMfd(
+      ClusterSourceSet sourceSet,
+      Location location,
+      double distance,
+      IntervalArray modelMfd) {
+
+    IntervalArray.Builder sourceSetMfd = IntervalArray.Builder.fromModel(modelMfd);
+    for (Source source : sourceSet.iterableForLocation(location, distance)) {
+      ClusterSource clusterSource = (ClusterSource) source;
+      sourceSetMfd.add(faultMfd(
+          clusterSource.faults(),
+          location,
+          distance,
+          modelMfd));
+    }
+    return sourceSetMfd.build();
+  }
+
+  /*
+   * Special case and delegate to SystemSourceSet.
+   */
+  static IntervalArray systemMfd(
+      SystemSourceSet sourceSet,
+      Location location,
+      double distance,
+      IntervalArray modelMfd) {
+
+    return SystemSourceSet
+        .toRatesFunction(location, distance, modelMfd)
+        .apply(sourceSet);
+  }
+
   /*
-   * Depending on operation, this could be the raw combination of multiple MFDs,
-   * or resampled to some uniform spacing.
+   * Default approach: distance filter on ruptures.
    */
-  XySequence totalMfd;
+  private static IntervalArray faultMfd(
+      SourceSet<? extends Source> sourceSet,
+      Location location,
+      double distance,
+      IntervalArray modelMfd) {
 
-  Map<SourceType, XySequence> sourceMfds;
+    IntervalArray.Builder sourceSetMfd = IntervalArray.Builder.fromModel(modelMfd);
+    for (Source source : sourceSet.iterableForLocation(location, distance)) {
+      for (Rupture rupture : source) {
+        Distance d = rupture.surface().distanceTo(location);
+        if (d.rJB <= distance) {
+          sourceSetMfd.add(rupture.mag(), rupture.rate());
+        }
+      }
+    }
+    return sourceSetMfd.build();
+  }
 
 }
diff --git a/src/org/opensha2/eq/model/SystemSourceSet.java b/src/org/opensha2/eq/model/SystemSourceSet.java
index 0431df6b1..7545f89dc 100644
--- a/src/org/opensha2/eq/model/SystemSourceSet.java
+++ b/src/org/opensha2/eq/model/SystemSourceSet.java
@@ -18,6 +18,7 @@ import org.opensha2.calc.InputList;
 import org.opensha2.calc.Site;
 import org.opensha2.calc.SystemInputList;
 import org.opensha2.data.Indexing;
+import org.opensha2.data.IntervalArray;
 import org.opensha2.data.XySequence;
 import org.opensha2.eq.fault.Faults;
 import org.opensha2.eq.fault.surface.GriddedSurface;
@@ -125,8 +126,7 @@ public final class SystemSourceSet extends AbstractSourceSet<SystemSourceSet.Sys
   }
 
   @Override
-  public Predicate<SystemSource> distanceFilter(Location loc,
-      double distance) {
+  public Predicate<SystemSource> distanceFilter(Location loc, double distance) {
     BitSet siteBitset = bitsetForLocation(loc, distance);
     return new BitsetFilter(siteBitset);
   }
@@ -395,6 +395,59 @@ public final class SystemSourceSet extends AbstractSourceSet<SystemSourceSet.Sys
           stats);
     }
   }
+  
+  /*
+   * Handle rate calculations internally as SystemSource is not fully implemented.
+   * If/when it is, this should be removed in favor using iterableForLocation
+   * and getRupture(0).
+   */
+  
+  /**
+   * Return an instance of a {@code Function} that converts a
+   * {@code SystemSourceSet} to a ground motion model {@code InputList}.
+   *
+   * @param location with which to initialize instance.
+   * @param distance if interest (relevant source radius)
+   * @param modelMfd MFD to populate
+   */
+  public static Function<SystemSourceSet, IntervalArray> toRatesFunction(
+      Location location,
+      double distance,
+      IntervalArray modelMfd) {
+    return new ToRates(location, distance, modelMfd);
+  }
+
+  private static final class ToRates implements Function<SystemSourceSet, IntervalArray> {
+
+    private final Location location;
+    private final double distance;
+    private final IntervalArray modelMfd;
+
+    ToRates(
+        final Location location,
+        final double distance,
+        final IntervalArray modelMfd) {
+      
+      this.location = location;
+      this.distance = distance;
+      this.modelMfd = modelMfd;
+    }
+
+    @Override
+    public IntervalArray apply(final SystemSourceSet sourceSet) {
+      IntervalArray.Builder mfdForLocation = IntervalArray.Builder.fromModel(modelMfd);
+      BitSet bitsetForLocation = sourceSet.bitsetForLocation(location, distance);
+      if (bitsetForLocation.isEmpty()) {
+        return modelMfd;
+      }
+      int[] sourceIndices = Indexing.bitsToIndices(bitsetForLocation);
+      for (int i : sourceIndices) {
+        mfdForLocation.add(sourceSet.mags[i], sourceSet.rates[i]);
+      }
+      return mfdForLocation.build();
+    }
+  }
+
 
   /*
    * System source calculation pipeline.
@@ -506,9 +559,7 @@ public final class SystemSourceSet extends AbstractSourceSet<SystemSourceSet.Sys
 
         /* Create inputs. */
         Map<Integer, double[]> rMap = rMapBuilder.build();
-        Function<SystemSource, HazardInput> inputGenerator = new InputGenerator(
-            rMap,
-            site);
+        Function<SystemSource, HazardInput> inputGenerator = new InputGenerator(rMap, site);
         Predicate<SystemSource> rFilter = new BitsetFilter(siteBitset);
         Iterable<SystemSource> sources = Iterables.filter(sourceSet, rFilter);
 
-- 
GitLab