diff --git a/src/org/opensha2/calc/CalcConfig.java b/src/org/opensha2/calc/CalcConfig.java
index c141fe59f94057581da26e7c2163378557905aff..f686df1225d7a48002c7f1522111fc57d6683d2a 100644
--- a/src/org/opensha2/calc/CalcConfig.java
+++ b/src/org/opensha2/calc/CalcConfig.java
@@ -32,48 +32,28 @@ import com.google.gson.GsonBuilder;
 
 /**
  * Calculation configuration.
- * 
- * All config fields are immutable and all methods return immutable objects.
- * 
  * @author Peter Powers
  */
 public final class CalcConfig {
 
-	/**
-	 * Returns models of the intensity measure levels for each {@code Imt}
-	 * adressed by this calculation. The {@code Map} returned by this method is
-	 * an immutable {@code EnumMap}.
-	 * 
-	 * @see Maps#immutableEnumMap(Map)
-	 */
-
-	/**
-	 * Returns models of the intensity measure levels for each {@code Imt}
-	 * adressed by this calculation. The x-values in each sequence are in
-	 * natural log space. The {@code Map} returned by this method is an
-	 * immutable {@code EnumMap}.
-	 * 
-	 * @see Maps#immutableEnumMap(Map)
-	 */
-
-	// TODO revisit privatization, comments, and immutability
-
 	static final String FILE_NAME = "config.json";
 
-	final Path resource;
+	private final Path resource;
 
-	final ExceedanceModel exceedanceModel;
-	final double truncationLevel;
-	final Set<Imt> imts;
+	private final ExceedanceModel exceedanceModel;
+	private final double truncationLevel;
+	private final Set<Imt> imts;
 	private final double[] defaultImls;
 	private final Map<Imt, double[]> customImls;
-	final DeaggData deagg;
+	private final boolean optimizeGrids;
+	private final boolean gmmUncertainty;
+
+	private final DeaggData deagg;
+
 	private final SiteSet sites;
-	
-	final boolean optimizeGrids;
 
-	final Map<Imt, XySequence> modelCurves;
-	final Map<Imt, XySequence> logModelCurves;
+	private final Map<Imt, XySequence> modelCurves;
+	private final Map<Imt, XySequence> logModelCurves;
 
 	private static final Gson GSON = new GsonBuilder()
 		.registerTypeAdapter(Site.class, new Site.Deserializer())
@@ -87,9 +67,10 @@ public final class CalcConfig {
 			Set<Imt> imts,
 			double[] defaultImls,
 			Map<Imt, double[]> customImls,
+			boolean optimizeGrids,
+			boolean gmmUncertainty,
 			DeaggData deagg,
 			SiteSet sites,
-			boolean optimizeGrids,
 			Map<Imt, XySequence> modelCurves,
 			Map<Imt, XySequence> logModelCurves) {
 
@@ -99,9 +80,10 @@ public final class CalcConfig {
 		this.imts = imts;
 		this.defaultImls = defaultImls;
 		this.customImls = customImls;
+		this.optimizeGrids = optimizeGrids;
+		this.gmmUncertainty = gmmUncertainty;
 		this.deagg = deagg;
 		this.sites = sites;
-		this.optimizeGrids = optimizeGrids;
 		this.modelCurves = modelCurves;
 		this.logModelCurves = logModelCurves;
 	}
@@ -113,6 +95,8 @@ public final class CalcConfig {
 		IMTS,
 		DEFAULT_IMLS,
 		CUSTOM_IMLS,
+		GMM_UNCERTAINTY,
+		OPTIMIZE_GRIDS,
 		DEAGG,
 		SITES;
 
@@ -145,6 +129,8 @@ public final class CalcConfig {
 			.append(format(Key.IMTS)).append(Parsing.enumsToString(imts, Imt.class))
 			.append(format(Key.DEFAULT_IMLS)).append(Arrays.toString(defaultImls))
 			.append(customImlStr)
+			.append(format(Key.OPTIMIZE_GRIDS)).append(optimizeGrids)
+			.append(format(Key.GMM_UNCERTAINTY)).append(gmmUncertainty)
 			.append(format("Deaggregation R"))
 			.append("min=").append(deagg.rMin).append(", ")
 			.append("max=").append(deagg.rMax).append(", ")
@@ -162,30 +148,83 @@ public final class CalcConfig {
 	}
 
 	/**
-	 * Return an unmodifiable iterator over the {@code Site}s specified by this
-	 * configuration.
+	 * The probability distribution model to use when computing hazard curves.
 	 */
-	public Iterable<Site> sites() {
-		return sites;
+	public ExceedanceModel exceedanceModel() {
+		return exceedanceModel; // TODO probabilitModel
 	}
 
 	/**
-	 * Return the unmodifiable {@code Set} of IMTs for which calculations should
-	 * be performed.
+	 * The number of standard deviations at which to truncate a distribution.
+	 * This field is ignored if a model does not implement truncation.
+	 */
+	public double truncationLevel() {
+		return truncationLevel;
+	}
+
+	/**
+	 * The unmodifiable {@code Set} of IMTs for which calculations should be
+	 * performed.
 	 */
 	public Set<Imt> imts() {
 		return imts;
 	}
 
 	/**
-	 * Return an empty linear (i.e. not log) curve for the requested {@code Imt}
-	 * .
+	 * Whether to optimize grid source sets, or not.
+	 */
+	public boolean optimizeGrids() {
+		return optimizeGrids;
+	}
+
+	/**
+	 * Whether to consider additional ground motion model uncertainty, or not.
+	 */
+	public boolean gmmUncertainty() {
+		return gmmUncertainty;
+	}
+
+	/**
+	 * Deaggregation configuration data.
+	 */
+	public DeaggData deagg() {
+		return deagg;
+	}
+
+	/**
+	 * An unmodifiable iterator over the {@code Site}s at which hazard should be
+	 * calculated.
+	 */
+	public Iterable<Site> sites() {
+		return sites;
+	}
+
+	/**
+	 * An empty linear curve for the requested {@code Imt}.
 	 * @param imt to get curve for
 	 */
 	public XySequence modelCurve(Imt imt) {
 		return modelCurves.get(imt);
 	}
 
+	/**
+	 * An immutable map of model curves where x-values are in linear space.
+	 */
+	public Map<Imt, XySequence> modelCurves() {
+		return modelCurves;
+	}
+
+	/**
+	 * An immutable map of model curves where x-values are in natural-log space.
+	 */
+	public Map<Imt, XySequence> logModelCurves() {
+		return logModelCurves;
+	}
+
+	/**
+	 * Deaggregation configuration data container.
+	 */
+	@SuppressWarnings("javadoc")
 	public static final class DeaggData {
 
 		public final double rMin;
@@ -213,7 +252,6 @@ public final class CalcConfig {
 			εMax = 3.0;
 			Δε = 0.5;
 		}
-
 	}
 
 	/**
@@ -246,16 +284,22 @@ public final class CalcConfig {
 		private static final String ID = "CalcConfig.Builder";
 		private boolean built = false;
 
+		// TODO should resource be Optional; if created with defaults
+		// there will be no path
 		private Path resource;
 		private ExceedanceModel exceedanceModel;
 		private Double truncationLevel;
 		private Set<Imt> imts;
 		private double[] defaultImls;
 		private Map<Imt, double[]> customImls;
+		private Boolean optimizeGrids;
+		private Boolean gmmUncertainty;
 		private DeaggData deagg;
 		private SiteSet sites;
-		private Boolean optimizeGrids;
 
+		/**
+		 * Initialize a new builder with a copy of that supplied.
+		 */
 		public Builder copy(CalcConfig config) {
 			checkNotNull(config);
 			this.resource = config.resource;
@@ -264,12 +308,16 @@ public final class CalcConfig {
 			this.imts = config.imts;
 			this.defaultImls = config.defaultImls;
 			this.customImls = config.customImls;
+			this.optimizeGrids = config.optimizeGrids;
+			this.gmmUncertainty = config.gmmUncertainty;
 			this.deagg = config.deagg;
 			this.sites = config.sites;
-			this.optimizeGrids = config.optimizeGrids;
 			return this;
 		}
 
+		/**
+		 * Initialize a new builder with defaults.
+		 */
 		public Builder withDefaults() {
 			this.exceedanceModel = ExceedanceModel.TRUNCATION_UPPER_ONLY;
 			this.truncationLevel = 3.0;
@@ -279,12 +327,17 @@ public final class CalcConfig {
 				0.0380, 0.0570, 0.0854, 0.128, 0.192, 0.288, 0.432, 0.649, 0.973, 1.46,
 				2.19, 3.28, 4.92, 7.38 };
 			this.customImls = Maps.newHashMap();
+			this.optimizeGrids = true;
+			this.gmmUncertainty = false;
 			this.deagg = new DeaggData();
 			this.sites = new SiteSet(Lists.newArrayList(Site.builder().build()));
-			this.optimizeGrids = true;
 			return this;
 		}
 
+		/**
+		 * Extend {@code this} builder to match {@code that} builder. Fields in
+		 * that builder take precedence unless they are not set.
+		 */
 		public Builder extend(final Builder that) {
 			checkNotNull(that);
 			if (that.resource != null) this.resource = that.resource;
@@ -293,12 +346,16 @@ public final class CalcConfig {
 			if (that.imts != null) this.imts = that.imts;
 			if (that.defaultImls != null) this.defaultImls = that.defaultImls;
 			if (that.customImls != null) this.customImls = that.customImls;
+			if (that.optimizeGrids != null) this.optimizeGrids = that.optimizeGrids;
+			if (that.gmmUncertainty != null) this.gmmUncertainty = that.gmmUncertainty;
 			if (that.deagg != null) this.deagg = that.deagg;
 			if (that.sites != null) this.sites = that.sites;
-			if (that.optimizeGrids != null) this.optimizeGrids = that.optimizeGrids;
 			return this;
 		}
 
+		/**
+		 * Set the IMTs for which results should be calculated.
+		 */
 		public Builder imts(Set<Imt> imts) {
 			this.imts = checkNotNull(imts);
 			return this;
@@ -338,19 +395,32 @@ public final class CalcConfig {
 			checkNotNull(imts, MSSG, buildId, Key.IMTS);
 			checkNotNull(defaultImls, MSSG, buildId, Key.DEFAULT_IMLS);
 			checkNotNull(customImls, MSSG, buildId, Key.CUSTOM_IMLS);
+			checkNotNull(optimizeGrids, MSSG, buildId, Key.OPTIMIZE_GRIDS);
+			checkNotNull(gmmUncertainty, MSSG, buildId, Key.GMM_UNCERTAINTY);
 			checkNotNull(deagg, MSSG, buildId, Key.DEAGG);
 			checkNotNull(sites, MSSG, buildId, Key.SITES);
 			built = true;
 		}
 
+		/**
+		 * Build a new calculation configuration.
+		 */
 		public CalcConfig build() {
 			validateState(ID);
 			Set<Imt> finalImts = Sets.immutableEnumSet(imts);
 			Map<Imt, XySequence> curves = createCurveMap();
 			Map<Imt, XySequence> logCurves = createLogCurveMap();
 			return new CalcConfig(
-				resource, exceedanceModel, truncationLevel, finalImts,
-				defaultImls, customImls, deagg, sites, optimizeGrids,
+				resource,
+				exceedanceModel,
+				truncationLevel,
+				finalImts,
+				defaultImls,
+				customImls,
+				optimizeGrids,
+				gmmUncertainty,
+				deagg,
+				sites,
 				curves, logCurves);
 		}
 
diff --git a/src/org/opensha2/calc/Calcs.java b/src/org/opensha2/calc/Calcs.java
index 06f804d27dfdc9074074b81fdd632a511557647e..0c923317c8d77b9855d1a0313f68e475397e80ce 100644
--- a/src/org/opensha2/calc/Calcs.java
+++ b/src/org/opensha2/calc/Calcs.java
@@ -156,7 +156,7 @@ public class Calcs {
 
 				case GRID:
 					GridSourceSet gss = (GridSourceSet) sourceSet;
-					if (config.optimizeGrids && gss.sourceType() != FIXED_STRIKE) {
+					if (config.optimizeGrids() && gss.sourceType() != FIXED_STRIKE) {
 						gridTables.add(transform(immediateFuture(gss),
 							GridSourceSet.toTableFunction(site.location), ex));
 						break;
@@ -210,7 +210,7 @@ public class Calcs {
 			switch (sourceSet.type()) {
 				case GRID:
 					GridSourceSet gss = (GridSourceSet) sourceSet;
-					if (config.optimizeGrids && gss.sourceType() != FIXED_STRIKE) {
+					if (config.optimizeGrids() && gss.sourceType() != FIXED_STRIKE) {
 						sourceSet = GridSourceSet.toTableFunction(site.location).apply(gss);
 						log(log, MSSG_GRID_INIT, sourceSet.name(), duration(swSource));
 					}
diff --git a/src/org/opensha2/calc/Deaggregation.java b/src/org/opensha2/calc/Deaggregation.java
index 45f748cf8ba7d016218bf9b3484c6a0a54c483e6..f938110fd35efb00a2308ecd44aa6f745c6fc89b 100644
--- a/src/org/opensha2/calc/Deaggregation.java
+++ b/src/org/opensha2/calc/Deaggregation.java
@@ -221,8 +221,8 @@ public final class Deaggregation {
 				.dataModel(
 					Dataset.builder(hazard.config).build())
 				.probabilityModel(
-					hazard.config.exceedanceModel,
-					hazard.config.truncationLevel);
+					hazard.config.exceedanceModel(),
+					hazard.config.truncationLevel());
 		}
 
 		/* Reusable builder */
@@ -908,7 +908,7 @@ public final class Deaggregation {
 		 * @see CalcConfig
 		 */
 		static Builder builder(CalcConfig config) {
-			DeaggData d = config.deagg;
+			DeaggData d = config.deagg();
 			return builder(
 				d.rMin, d.rMax, d.Δr,
 				d.mMin, d.mMax, d.Δm,
diff --git a/src/org/opensha2/calc/Hazard.java b/src/org/opensha2/calc/Hazard.java
index 8a71da65f2e364fe955ab2106d625b4124c3cceb..0c15e841d03c815b962e1660dd6fedb3a43452ac 100644
--- a/src/org/opensha2/calc/Hazard.java
+++ b/src/org/opensha2/calc/Hazard.java
@@ -72,7 +72,7 @@ public final class Hazard {
 						sb.append(curveSet.hazardGroundMotionsList.get(0).inputs.size());
 						break;
 					case GRID:
-						if (ss instanceof GridSourceSet.Table && config.optimizeGrids) {
+						if (ss instanceof GridSourceSet.Table && config.optimizeGrids()) {
 							GridSourceSet.Table gsst = (GridSourceSet.Table) curveSet.sourceSet;
 							sb.append(gsst.parentCount());
 							sb.append(" (").append(curveSet.hazardGroundMotionsList.size());
@@ -143,7 +143,7 @@ public final class Hazard {
 		private Builder(CalcConfig config) {
 			this.config = checkNotNull(config);
 			totalCurves = new EnumMap<>(Imt.class);
-			for (Entry<Imt, XySequence> entry : config.logModelCurves.entrySet()) {
+			for (Entry<Imt, XySequence> entry : config.logModelCurves().entrySet()) {
 				totalCurves.put(entry.getKey(), emptyCopyOf(entry.getValue()));
 			}
 			curveMapBuilder = ImmutableSetMultimap.builder();
diff --git a/src/org/opensha2/calc/Results.java b/src/org/opensha2/calc/Results.java
index ccac36d068e52999a72e6161947631b662919c8e..cb40d4e4f0384cb67085139bf69a36c1da29832f 100644
--- a/src/org/opensha2/calc/Results.java
+++ b/src/org/opensha2/calc/Results.java
@@ -78,7 +78,7 @@ public class Results {
 				headings.add("lat");
 				Iterable<? extends Object> header = Iterables.concat(
 					headings,
-					demo.config.modelCurves.get(imt).xValues());
+					demo.config.modelCurves().get(imt).xValues());
 				lineList.add(Parsing.join(header, Delimiter.COMMA));
 			}
 			lineMap.put(imt, lineList);
diff --git a/src/org/opensha2/calc/Transforms.java b/src/org/opensha2/calc/Transforms.java
index 0066557ed25afbc4b72d3602235f70b89026a325..7fb4ac0682b1392c114a8c3e7055eb829d7180ec 100644
--- a/src/org/opensha2/calc/Transforms.java
+++ b/src/org/opensha2/calc/Transforms.java
@@ -15,6 +15,7 @@ import org.opensha2.eq.model.ClusterSource;
 import org.opensha2.eq.model.ClusterSourceSet;
 import org.opensha2.eq.model.Distance;
 import org.opensha2.eq.model.FaultSource;
+import org.opensha2.eq.model.GmmSet;
 import org.opensha2.eq.model.HazardModel;
 import org.opensha2.eq.model.Rupture;
 import org.opensha2.eq.model.Source;
@@ -155,9 +156,9 @@ final class Transforms {
 		private final double truncationLevel;
 
 		GroundMotionsToCurves(CalcConfig config) {
-			this.modelCurves = config.logModelCurves;
-			this.exceedanceModel = config.exceedanceModel;
-			this.truncationLevel = config.truncationLevel;
+			this.modelCurves = config.logModelCurves();
+			this.exceedanceModel = config.exceedanceModel();
+			this.truncationLevel = config.truncationLevel();
 		}
 
 		@Override public HazardCurves apply(GroundMotions groundMotions) {
@@ -172,15 +173,17 @@ final class Transforms {
 				XySequence utilCurve = XySequence.copyOf(modelCurve);
 				XySequence gmmCurve = XySequence.copyOf(modelCurve);
 
-				Map<Gmm, List<Double>> gmmMeans = groundMotions.means.get(imt);
-				Map<Gmm, List<Double>> gmmSigmas = groundMotions.sigmas.get(imt);
+				// Map<Gmm, List<Double>> gmmMeans =
+				// groundMotions.means.get(imt);
+				// Map<Gmm, List<Double>> gmmSigmas =
+				// groundMotions.sigmas.get(imt);
 
-				for (Gmm gmm : gmmMeans.keySet()) {
+				for (Gmm gmm : groundMotions.means.get(imt).keySet()) {
 
 					gmmCurve.clear();
 
-					List<Double> means = gmmMeans.get(gmm);
-					List<Double> sigmas = gmmSigmas.get(gmm);
+					List<Double> means = groundMotions.means.get(imt).get(gmm);
+					List<Double> sigmas = groundMotions.sigmas.get(imt).get(gmm);
 
 					for (int i = 0; i < means.size(); i++) {
 						exceedanceModel.exceedance(
@@ -199,6 +202,91 @@ final class Transforms {
 		}
 	}
 
+	static final class GroundMotionsToCurvesWithUncertainty implements
+			Function<GroundMotions, HazardCurves> {
+
+		private final GmmSet gmmSet;
+		private final Map<Imt, XySequence> modelCurves;
+		private final ExceedanceModel exceedanceModel;
+		private final double truncationLevel;
+
+		GroundMotionsToCurvesWithUncertainty(GmmSet gmmSet, CalcConfig config) {
+			this.gmmSet = gmmSet;
+			this.modelCurves = config.logModelCurves();
+			this.exceedanceModel = config.exceedanceModel();
+			this.truncationLevel = config.truncationLevel();
+		}
+
+		@Override public HazardCurves apply(GroundMotions groundMotions) {
+
+			HazardCurves.Builder curveBuilder = HazardCurves.builder(groundMotions);
+
+			// initialize uncertainty for each input
+			InputList inputs = groundMotions.inputs;
+			double[] uncertainties = new double[inputs.size()];
+			double[] rates = new double[inputs.size()];
+			for (int i = 0; i < inputs.size(); i++) {
+				HazardInput input = inputs.get(i);
+				rates[i] = input.rate;
+				uncertainties[i] = gmmSet.epiValue(input.Mw, input.rJB);
+			}
+
+			for (Entry<Imt, XySequence> entry : modelCurves.entrySet()) {
+
+				XySequence modelCurve = entry.getValue();
+				Imt imt = entry.getKey();
+
+				XySequence utilCurve = XySequence.copyOf(modelCurve);
+				XySequence gmmCurve = XySequence.copyOf(modelCurve);
+
+				for (Gmm gmm : groundMotions.means.get(imt).keySet()) {
+
+					gmmCurve.clear();
+
+					List<Double> means = groundMotions.means.get(imt).get(gmm);
+					List<Double> sigmas = groundMotions.sigmas.get(imt).get(gmm);
+
+					for (int i = 0; i < means.size(); i++) {
+						double mean = means.get(i);
+						double epi = uncertainties[i];
+						double[] epiMeans = new double[] { mean - epi, mean, mean + epi };
+						exceedanceCurve(
+							epiMeans,
+							sigmas.get(i),
+							imt,
+							utilCurve);
+						utilCurve.multiply(groundMotions.inputs.get(i).rate);
+						gmmCurve.add(utilCurve);
+					}
+					curveBuilder.addCurve(imt, gmm, gmmCurve);
+				}
+			}
+			return curveBuilder.build();
+		}
+
+		/* Construct an exceedance curve considering uncertain ground motions. */
+		private XySequence exceedanceCurve(
+				final double[] means,
+				final double sigma,
+				final Imt imt,
+				final XySequence curve) {
+
+			XySequence utilCurve = XySequence.emptyCopyOf(curve);
+			double[] weights = gmmSet.epiWeights();
+			for (int i = 0; i < means.length; i++) {
+				exceedanceModel.exceedance(
+					means[i],
+					sigma,
+					truncationLevel,
+					imt,
+					utilCurve);
+				utilCurve.multiply(weights[i]);
+				curve.add(utilCurve);
+			}
+			return curve;
+		}
+	}
+
 	/*
 	 * Source --> HazardCurves
 	 * 
@@ -216,12 +304,16 @@ final class Transforms {
 				CalcConfig config,
 				Site site) {
 
-			Set<Gmm> gmms = sources.groundMotionModels().gmms();
-			Map<Imt, Map<Gmm, GroundMotionModel>> gmmTable = instances(config.imts, gmms);
+			GmmSet gmmSet = sources.groundMotionModels();
+			Map<Imt, Map<Gmm, GroundMotionModel>> gmmTable = instances(
+				config.imts(),
+				gmmSet.gmms());
 
 			this.sourceToInputs = new SourceToInputs(site);
 			this.inputsToGroundMotions = new InputsToGroundMotions(gmmTable);
-			this.groundMotionsToCurves = new GroundMotionsToCurves(config);
+			this.groundMotionsToCurves = config.gmmUncertainty() && gmmSet.epiUncertainty() ?
+				new GroundMotionsToCurvesWithUncertainty(gmmSet, config) :
+				new GroundMotionsToCurves(config);
 		}
 
 		@Override public HazardCurves apply(Source source) {
@@ -246,7 +338,7 @@ final class Transforms {
 				CalcConfig config) {
 
 			this.sources = sources;
-			this.modelCurves = config.logModelCurves;
+			this.modelCurves = config.logModelCurves();
 		}
 
 		@Override public HazardCurveSet apply(List<HazardCurves> curvesList) {
@@ -283,12 +375,16 @@ final class Transforms {
 				CalcConfig config,
 				Site site) {
 
-			Set<Gmm> gmms = sources.groundMotionModels().gmms();
-			Map<Imt, Map<Gmm, GroundMotionModel>> gmmTable = instances(config.imts, gmms);
+			GmmSet gmmSet = sources.groundMotionModels();
+			Map<Imt, Map<Gmm, GroundMotionModel>> gmmTable = instances(
+				config.imts(),
+				gmmSet.gmms());
 
 			this.sourcesToInputs = SystemSourceSet.toInputsFunction(site);
 			this.inputsToGroundMotions = new InputsToGroundMotions(gmmTable);
-			this.groundMotionsToCurves = new GroundMotionsToCurves(config);
+			this.groundMotionsToCurves = config.gmmUncertainty() && gmmSet.epiUncertainty() ?
+				new GroundMotionsToCurvesWithUncertainty(gmmSet, config) :
+				new GroundMotionsToCurves(config);
 			this.curveConsolidator = new CurveConsolidator(sources, config);
 		}
 
@@ -373,9 +469,9 @@ final class Transforms {
 		private final double truncationLevel;
 
 		ClusterGroundMotionsToCurves(CalcConfig config) {
-			this.logModelCurves = config.logModelCurves;
-			this.exceedanceModel = config.exceedanceModel;
-			this.truncationLevel = config.truncationLevel;
+			this.logModelCurves = config.logModelCurves();
+			this.exceedanceModel = config.exceedanceModel();
+			this.truncationLevel = config.truncationLevel();
 		}
 
 		@Override public ClusterCurves apply(ClusterGroundMotions clusterGroundMotions) {
@@ -447,7 +543,7 @@ final class Transforms {
 				Site site) {
 
 			Set<Gmm> gmms = sources.groundMotionModels().gmms();
-			Map<Imt, Map<Gmm, GroundMotionModel>> gmmTable = instances(config.imts, gmms);
+			Map<Imt, Map<Gmm, GroundMotionModel>> gmmTable = instances(config.imts(), gmms);
 
 			this.sourceToInputs = new ClusterSourceToInputs(site);
 			this.inputsToGroundMotions = new ClusterInputsToGroundMotions(gmmTable);
@@ -477,7 +573,7 @@ final class Transforms {
 				CalcConfig config) {
 
 			this.sources = sources;
-			this.modelCurves = config.logModelCurves;
+			this.modelCurves = config.logModelCurves();
 		}
 
 		@Override public HazardCurveSet apply(List<ClusterCurves> curvesList) {
diff --git a/src/org/opensha2/eq/model/GmmSet.java b/src/org/opensha2/eq/model/GmmSet.java
index fe68049525152c981800e8b52ffea697414fe760..37204d56dc29a08c408d3a932ad53521c059bd57 100644
--- a/src/org/opensha2/eq/model/GmmSet.java
+++ b/src/org/opensha2/eq/model/GmmSet.java
@@ -6,10 +6,12 @@ import static com.google.common.base.Preconditions.checkState;
 import static org.opensha2.data.Data.checkInRange;
 import static org.opensha2.data.Data.checkWeightSum;
 
+import java.util.Arrays;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
+import org.opensha2.data.Data;
 import org.opensha2.gmm.Gmm;
 import org.opensha2.gmm.GroundMotionModel;
 
@@ -47,7 +49,7 @@ public final class GmmSet {
 	private final UncertType uncertainty;
 	private final double epiValue;
 	private final double[][] epiValues;
-	private final double[] epiWeights;
+	private final double[] epiWeights; // TODO DataArray (vs. Table, Volume)
 
 	GmmSet(Map<Gmm, Double> weightMapLo, double maxDistLo, Map<Gmm, Double> weightMapHi,
 			double maxDistHi, double[] epiValues, double[] epiWeights) {
@@ -128,16 +130,20 @@ public final class GmmSet {
 		};
 	}
 
+	public boolean epiUncertainty() {
+		return uncertainty != UncertType.NONE;
+	}
+	
 	/*
-	 * Returns the epistemic uncertainty for the supplied magnitude (M) and
-	 * distance (D) that
+	 * Returns the epistemic uncertainty for the supplied magnitude and distance
+	 * in natural log units of ground motion.
 	 */
-	private double getUncertainty(double M, double D) {
+	public double epiValue(double m, double r) {
 		switch (uncertainty) {
 			case MULTI:
-				int mi = (M < 6) ? 0 : (M < 7) ? 1 : 2;
-				int di = (D < 10) ? 0 : (D < 30) ? 1 : 2;
-				return epiValues[di][mi];
+				int mi = (m < 6) ? 0 : (m < 7) ? 1 : 2;
+				int ri = (r < 10) ? 0 : (r < 30) ? 1 : 2;
+				return epiValues[ri][mi];
 			case SINGLE:
 				return epiValue;
 			default:
@@ -145,7 +151,16 @@ public final class GmmSet {
 		}
 	}
 
-	static enum UncertType {
+	/*
+	 * Returns the 3 weights used for low, median, and high branches of the epistemic
+	 * uncertainty distribution.
+	 * TODO needs to be immutable, This is a good case for DataArray
+	 */
+	public double[] epiWeights() {
+		return epiWeights;
+	}
+
+	private static enum UncertType {
 		NONE,
 		SINGLE,
 		MULTI;
@@ -201,6 +216,7 @@ public final class GmmSet {
 				"Values must contain 1 or 9 values");
 			checkArgument(checkNotNull(weights, "Weights are null").length == 3,
 				"Weights must contain 3 values");
+			checkArgument(Data.sum(weights) == 1.0, "%s uncertainty weights must sum to 1");
 			uncValues = values;
 			uncWeights = weights;
 			return this;
@@ -227,6 +243,7 @@ public final class GmmSet {
 
 			if (uncValues != null) checkNotNull(uncWeights, "%s uncertainty weights not set", id);
 			if (uncWeights != null) checkNotNull(uncValues, "%s uncertainty values not set", id);
+	
 
 			built = true;
 		}