From 9d426582afc850fd9864b5cee18bcfcc4285959b Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Mon, 21 Mar 2016 14:09:50 -0600
Subject: [PATCH 01/17] calcConfig refactoring; new fields

---
 src/org/opensha2/calc/CalcConfig.java         | 178 ++++++++++--------
 src/org/opensha2/calc/Calcs.java              |   4 +-
 src/org/opensha2/calc/Deaggregation.java      |   6 +-
 src/org/opensha2/calc/Hazard.java             |   2 +-
 src/org/opensha2/calc/Results.java            |   2 +-
 src/org/opensha2/calc/Transforms.java         |  26 +--
 src/org/opensha2/eq/model/ModelConfig.java    |  21 +--
 test/org/opensha2/eq/model/peer/PeerTest.java |   4 +-
 8 files changed, 135 insertions(+), 108 deletions(-)

diff --git a/src/org/opensha2/calc/CalcConfig.java b/src/org/opensha2/calc/CalcConfig.java
index 5e5b9ff8b..624540982 100644
--- a/src/org/opensha2/calc/CalcConfig.java
+++ b/src/org/opensha2/calc/CalcConfig.java
@@ -14,6 +14,7 @@ import java.io.IOException;
 import java.io.Reader;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.EnumSet;
 import java.util.Map;
@@ -23,6 +24,8 @@ import java.util.Set;
 import org.opensha2.calc.Results.HazardFormat;
 import org.opensha2.data.Data;
 import org.opensha2.data.XySequence;
+import org.opensha2.eq.model.SourceType;
+import org.opensha2.gmm.Gmm;
 import org.opensha2.gmm.Imt;
 import org.opensha2.util.Parsing;
 
@@ -41,34 +44,81 @@ public final class CalcConfig {
 
 	private final Path resource;
 
-	private final ExceedanceModel exceedanceModel;
-	private final double truncationLevel;
-	private final Set<Imt> imts;
-	private final double[] defaultImls;
-	private final Map<Imt, double[]> customImls;
-	private final boolean optimizeGrids;
-	private final boolean gmmUncertainty;
+	/**
+	 * The probability distribution model to use when computing hazard curves.
+	 */
+	public final ExceedanceModel exceedanceModel;
+	// TODO refactor to probabilitModel
+
+	/**
+	 * The number of standard deviations at which to truncate a distribution.
+	 * This field is ignored if a model does not implement truncation.
+	 */
+	public final double truncationLevel;
+
+	/**
+	 * The {@code Set} of IMTs for which calculations should be performed.
+	 */
+	public final Set<Imt> imts;
 
-	private final HazardFormat hazardFormat;
+	/**
+	 * Whether to optimize grid source sets, or not.
+	 */
+	public final boolean optimizeGrids;
 
-	private final DeaggData deagg;
+	/**
+	 * The partition or batch size to use when distributing
+	 * {@link SourceType#SYSTEM} calculations.
+	 */
+	public final int systemPartition;
+
+	/**
+	 * Whether to consider additional ground motion model uncertainty, or not.
+	 * Currently this is only applicable when using the PEER NGA-West or
+	 * NGA-West2 {@link Gmm}s with USGS hazard models.
+	 */
+	public final boolean gmmUncertainty;
 
+	/** The hazard output format. */
+	public final HazardFormat hazardFormat;
+
+	/** The directory to write any results to. */
+	public final Path outputDir;
+
+	/**
+	 * The number of results to write at a time. A larger number requires more
+	 * memory.
+	 */
+	public final int outputBatchSize;
+
+	/**
+	 * Deaggregation configuration data.
+	 */
+	public final DeaggData deagg;
+
+	/*
+	 * Iml fields preserved for toString() exclusively. Imls should be retrieved
+	 * using modelCurves() or logModelCurves().
+	 */
+	private final double[] defaultImls;
+	private final Map<Imt, double[]> customImls;
 	private final Map<Imt, XySequence> modelCurves;
 	private final Map<Imt, XySequence> logModelCurves;
 
-	private static final Gson GSON = new GsonBuilder().create();
-
 	private CalcConfig(
 			Path resource,
 			ExceedanceModel exceedanceModel,
 			double truncationLevel,
 			Set<Imt> imts,
-			double[] defaultImls,
-			Map<Imt, double[]> customImls,
 			boolean optimizeGrids,
+			int systemPartition,
 			boolean gmmUncertainty,
 			HazardFormat hazardFormat,
+			Path outputDir,
+			int outputBatchSize,
 			DeaggData deagg,
+			double[] defaultImls,
+			Map<Imt, double[]> customImls,
 			Map<Imt, XySequence> modelCurves,
 			Map<Imt, XySequence> logModelCurves) {
 
@@ -76,12 +126,15 @@ public final class CalcConfig {
 		this.exceedanceModel = exceedanceModel;
 		this.truncationLevel = truncationLevel;
 		this.imts = imts;
-		this.defaultImls = defaultImls;
-		this.customImls = customImls;
 		this.optimizeGrids = optimizeGrids;
+		this.systemPartition = systemPartition;
 		this.gmmUncertainty = gmmUncertainty;
 		this.hazardFormat = hazardFormat;
+		this.outputDir = outputDir;
+		this.outputBatchSize = outputBatchSize;
 		this.deagg = deagg;
+		this.defaultImls = defaultImls;
+		this.customImls = customImls;
 		this.modelCurves = modelCurves;
 		this.logModelCurves = logModelCurves;
 	}
@@ -91,12 +144,15 @@ public final class CalcConfig {
 		EXCEEDANCE_MODEL,
 		TRUNCATION_LEVEL,
 		IMTS,
-		DEFAULT_IMLS,
-		CUSTOM_IMLS,
+		OPTIMIZE_GRIDS,
 		GMM_UNCERTAINTY,
+		SYSTEM_PARTITION,
 		HAZARD_FORMAT,
-		OPTIMIZE_GRIDS,
-		DEAGG;
+		OUTPUT_DIR,
+		OUTPUT_BATCH_SIZE,
+		DEAGG,
+		DEFAULT_IMLS,
+		CUSTOM_IMLS;
 
 		private String label;
 
@@ -130,8 +186,11 @@ public final class CalcConfig {
 			.append(format(Key.DEFAULT_IMLS)).append(wrap(Arrays.toString(defaultImls)))
 			.append(customImlStr)
 			.append(format(Key.OPTIMIZE_GRIDS)).append(optimizeGrids)
+			.append(format(Key.SYSTEM_PARTITION)).append(systemPartition)
 			.append(format(Key.GMM_UNCERTAINTY)).append(gmmUncertainty)
 			.append(format(Key.HAZARD_FORMAT)).append(hazardFormat)
+			.append(format(Key.OUTPUT_DIR)).append(outputDir.toAbsolutePath().normalize())
+			.append(format(Key.OUTPUT_BATCH_SIZE)).append(outputBatchSize)
 			.append(format("Deaggregation R"))
 			.append("min=").append(deagg.rMin).append(", ")
 			.append("max=").append(deagg.rMax).append(", ")
@@ -147,54 +206,6 @@ public final class CalcConfig {
 			.toString();
 	}
 
-	/**
-	 * The probability distribution model to use when computing hazard curves.
-	 */
-	public ExceedanceModel exceedanceModel() {
-		return exceedanceModel; // TODO probabilitModel
-	}
-
-	/**
-	 * 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;
-	}
-
-	/**
-	 * 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;
-	}
-
-	public HazardFormat hazardFormat() {
-		return hazardFormat;
-	}
-
-	/**
-	 * Deaggregation configuration data.
-	 */
-	public DeaggData deagg() {
-		return deagg;
-	}
-
 	/**
 	 * An empty linear curve for the requested {@code Imt}.
 	 * @param imt to get curve for
@@ -250,6 +261,8 @@ public final class CalcConfig {
 		}
 	}
 
+	private static final Gson GSON = new GsonBuilder().create();
+
 	/**
 	 * Create a new calculation configuration builder from the resource at the
 	 * specified {@code path}.
@@ -287,8 +300,11 @@ public final class CalcConfig {
 		private double[] defaultImls;
 		private Map<Imt, double[]> customImls;
 		private Boolean optimizeGrids;
+		private Integer systemPartition;
 		private Boolean gmmUncertainty;
 		private HazardFormat hazardFormat;
+		private Path outputDir;
+		private Integer outputBatchSize;
 		private DeaggData deagg;
 
 		private Builder() {}
@@ -305,8 +321,11 @@ public final class CalcConfig {
 			this.defaultImls = config.defaultImls;
 			this.customImls = config.customImls;
 			this.optimizeGrids = config.optimizeGrids;
+			this.systemPartition = config.systemPartition;
 			this.gmmUncertainty = config.gmmUncertainty;
 			this.hazardFormat = config.hazardFormat;
+			this.outputDir = config.outputDir;
+			this.outputBatchSize = config.outputBatchSize;
 			this.deagg = config.deagg;
 			return this;
 		}
@@ -324,8 +343,11 @@ public final class CalcConfig {
 					3.28, 4.92, 7.38 };
 			this.customImls = Maps.newHashMap();
 			this.optimizeGrids = true;
+			this.systemPartition = 1000;
 			this.gmmUncertainty = false;
 			this.hazardFormat = HazardFormat.TOTAL;
+			this.outputDir = Paths.get(".");
+			this.outputBatchSize = 20;
 			this.deagg = new DeaggData();
 			return this;
 		}
@@ -343,8 +365,11 @@ public final class CalcConfig {
 			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.systemPartition != null) this.systemPartition = that.systemPartition;
 			if (that.gmmUncertainty != null) this.gmmUncertainty = that.gmmUncertainty;
 			if (that.hazardFormat != null) this.hazardFormat = that.hazardFormat;
+			if (that.outputDir != null) this.outputDir = that.outputDir;
+			if (that.outputBatchSize != null) this.outputBatchSize = that.outputBatchSize;
 			if (that.deagg != null) this.deagg = that.deagg;
 			return this;
 		}
@@ -392,8 +417,11 @@ public final class CalcConfig {
 			checkNotNull(defaultImls, MSSG, buildId, Key.DEFAULT_IMLS);
 			checkNotNull(customImls, MSSG, buildId, Key.CUSTOM_IMLS);
 			checkNotNull(optimizeGrids, MSSG, buildId, Key.OPTIMIZE_GRIDS);
+			checkNotNull(systemPartition, MSSG, buildId, Key.SYSTEM_PARTITION);
 			checkNotNull(gmmUncertainty, MSSG, buildId, Key.GMM_UNCERTAINTY);
 			checkNotNull(hazardFormat, MSSG, buildId, Key.HAZARD_FORMAT);
+			checkNotNull(outputDir, MSSG, buildId, Key.OUTPUT_DIR);
+			checkNotNull(outputBatchSize, MSSG, buildId, Key.OUTPUT_BATCH_SIZE);
 			checkNotNull(deagg, MSSG, buildId, Key.DEAGG);
 			built = true;
 		}
@@ -403,22 +431,22 @@ public final class CalcConfig {
 		 */
 		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,
+				Sets.immutableEnumSet(imts),
 				optimizeGrids,
+				systemPartition,
 				gmmUncertainty,
 				hazardFormat,
+				outputDir,
+				outputBatchSize,
 				deagg,
-				curves,
-				logCurves);
+				defaultImls,
+				customImls,
+				createCurveMap(),
+				createLogCurveMap());
 		}
 	}
 
diff --git a/src/org/opensha2/calc/Calcs.java b/src/org/opensha2/calc/Calcs.java
index 6f3b4792b..bf441b90b 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 f938110fd..45f748cf8 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 0c15e841d..935fef1c8 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());
diff --git a/src/org/opensha2/calc/Results.java b/src/org/opensha2/calc/Results.java
index e8b8eb2d6..bdcd4e05f 100644
--- a/src/org/opensha2/calc/Results.java
+++ b/src/org/opensha2/calc/Results.java
@@ -171,7 +171,7 @@ public class Results {
 		Hazard demo = batch.get(0);
 		boolean newFile = options.length == 0;
 		boolean namedSites = demo.site.name != Site.NO_NAME;
-		boolean detailed = demo.config.hazardFormat().equals(HazardFormat.DETAILED);
+		boolean detailed = demo.config.hazardFormat.equals(HazardFormat.DETAILED);
 
 		Map<Imt, List<String>> totalLineMap = Maps.newEnumMap(Imt.class);
 		Map<Imt, Map<SourceType, List<String>>> typeLineMap = Maps.newEnumMap(Imt.class);
diff --git a/src/org/opensha2/calc/Transforms.java b/src/org/opensha2/calc/Transforms.java
index 1b9866543..2b4af4e37 100644
--- a/src/org/opensha2/calc/Transforms.java
+++ b/src/org/opensha2/calc/Transforms.java
@@ -151,8 +151,8 @@ final class Transforms {
 
 		GroundMotionsToCurves(CalcConfig config) {
 			this.modelCurves = config.logModelCurves();
-			this.exceedanceModel = config.exceedanceModel();
-			this.truncationLevel = config.truncationLevel();
+			this.exceedanceModel = config.exceedanceModel;
+			this.truncationLevel = config.truncationLevel;
 		}
 
 		@Override public HazardCurves apply(GroundMotions groundMotions) {
@@ -208,8 +208,8 @@ final class Transforms {
 		GroundMotionsToCurvesWithUncertainty(GmmSet gmmSet, CalcConfig config) {
 			this.gmmSet = gmmSet;
 			this.modelCurves = config.logModelCurves();
-			this.exceedanceModel = config.exceedanceModel();
-			this.truncationLevel = config.truncationLevel();
+			this.exceedanceModel = config.exceedanceModel;
+			this.truncationLevel = config.truncationLevel;
 		}
 
 		@Override public HazardCurves apply(GroundMotions groundMotions) {
@@ -301,12 +301,12 @@ final class Transforms {
 
 			GmmSet gmmSet = sources.groundMotionModels();
 			Map<Imt, Map<Gmm, GroundMotionModel>> gmmTable = instances(
-				config.imts(),
+				config.imts,
 				gmmSet.gmms());
 
 			this.sourceToInputs = new SourceToInputs(site);
 			this.inputsToGroundMotions = new InputsToGroundMotions(gmmTable);
-			this.groundMotionsToCurves = config.gmmUncertainty() && gmmSet.epiUncertainty() ?
+			this.groundMotionsToCurves = config.gmmUncertainty && gmmSet.epiUncertainty() ?
 				new GroundMotionsToCurvesWithUncertainty(gmmSet, config) :
 				new GroundMotionsToCurves(config);
 		}
@@ -387,14 +387,14 @@ final class Transforms {
 
 			GmmSet gmmSet = sources.groundMotionModels();
 			Map<Imt, Map<Gmm, GroundMotionModel>> gmmTable = instances(
-				config.imts(),
+				config.imts,
 				gmmSet.gmms());
 
 			InputsToGroundMotions inputsToGm = new InputsToGroundMotions(gmmTable);
 			GroundMotions gms = inputsToGm.apply(inputs);
 
 			Function<GroundMotions, HazardCurves> gmToCurves =
-				config.gmmUncertainty() && gmmSet.epiUncertainty() ?
+				config.gmmUncertainty && gmmSet.epiUncertainty() ?
 					new GroundMotionsToCurvesWithUncertainty(gmmSet, config) :
 					new GroundMotionsToCurves(config);
 			HazardCurves curves = gmToCurves.apply(gms);
@@ -472,11 +472,11 @@ final class Transforms {
 
 			GmmSet gmmSet = sources.groundMotionModels();
 			Map<Imt, Map<Gmm, GroundMotionModel>> gmmTable = instances(
-				config.imts(),
+				config.imts,
 				gmmSet.gmms());
 
 			this.inputsToGroundMotions = new InputsToGroundMotions(gmmTable);
-			this.groundMotionsToCurves = config.gmmUncertainty() && gmmSet.epiUncertainty() ?
+			this.groundMotionsToCurves = config.gmmUncertainty && gmmSet.epiUncertainty() ?
 				new GroundMotionsToCurvesWithUncertainty(gmmSet, config) :
 				new GroundMotionsToCurves(config);
 		}
@@ -549,8 +549,8 @@ final class Transforms {
 
 		ClusterGroundMotionsToCurves(CalcConfig config) {
 			this.logModelCurves = config.logModelCurves();
-			this.exceedanceModel = config.exceedanceModel();
-			this.truncationLevel = config.truncationLevel();
+			this.exceedanceModel = config.exceedanceModel;
+			this.truncationLevel = config.truncationLevel;
 		}
 
 		@Override public ClusterCurves apply(ClusterGroundMotions clusterGroundMotions) {
@@ -622,7 +622,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);
diff --git a/src/org/opensha2/eq/model/ModelConfig.java b/src/org/opensha2/eq/model/ModelConfig.java
index 2d5a13dc8..3cc8ad6c9 100644
--- a/src/org/opensha2/eq/model/ModelConfig.java
+++ b/src/org/opensha2/eq/model/ModelConfig.java
@@ -4,10 +4,7 @@ import static com.google.common.base.CaseFormat.LOWER_CAMEL;
 import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.base.Strings.padStart;
 import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.opensha2.util.TextUtils.ALIGN_COL;
-import static org.opensha2.util.TextUtils.NEWLINE;
 import static org.opensha2.util.TextUtils.format;
 
 import java.io.IOException;
@@ -35,12 +32,12 @@ final class ModelConfig {
 
 	private final Path resource;
 
-	public final String name;
-	public final double surfaceSpacing;
-	public final RuptureFloating ruptureFloating;
-	public final boolean ruptureVariability;
-	public final PointSourceType pointSourceType;
-	public final GridScaling areaGridScaling;
+	final String name;
+	final double surfaceSpacing;
+	final RuptureFloating ruptureFloating;
+	final boolean ruptureVariability;
+	final PointSourceType pointSourceType;
+	final GridScaling areaGridScaling;
 
 	private ModelConfig(
 			String name,
@@ -75,12 +72,14 @@ final class ModelConfig {
 			this.label = UPPER_UNDERSCORE.to(LOWER_CAMEL, name());
 		}
 
-		@Override public String toString() {
+		@Override
+		public String toString() {
 			return label;
 		}
 	}
 
-	@Override public String toString() {
+	@Override
+	public String toString() {
 		return new StringBuilder("Model config:")
 			.append(format(Key.NAME)).append(name)
 			.append(format(Key.RESOURCE)).append(resource.toAbsolutePath().normalize())
diff --git a/test/org/opensha2/eq/model/peer/PeerTest.java b/test/org/opensha2/eq/model/peer/PeerTest.java
index e8f76c147..4926b32db 100644
--- a/test/org/opensha2/eq/model/peer/PeerTest.java
+++ b/test/org/opensha2/eq/model/peer/PeerTest.java
@@ -146,8 +146,8 @@ public class PeerTest {
 		Iterable<Site> sites = Sites.fromCsv(MODEL_DIR.resolve(modelId).resolve("sites.csv"));
 
 		// ensure that only PGA is being used
-		checkState(config.imts().size() == 1);
-		checkState(config.imts().iterator().next() == Imt.PGA);
+		checkState(config.imts.size() == 1);
+		checkState(config.imts.iterator().next() == Imt.PGA);
 
 		List<Object[]> argsList = new ArrayList<>();
 		for (Site site : sites) {
-- 
GitLab


From e81a82d647b8fcfde4ee89b10aed74a4bd9486ac Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Mon, 21 Mar 2016 14:23:43 -0600
Subject: [PATCH 02/17] batch sioze now pulled from config

---
 src/org/opensha2/programs/HazardCalc.java | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/src/org/opensha2/programs/HazardCalc.java b/src/org/opensha2/programs/HazardCalc.java
index 0a88b6092..7fd55c44f 100644
--- a/src/org/opensha2/programs/HazardCalc.java
+++ b/src/org/opensha2/programs/HazardCalc.java
@@ -40,9 +40,6 @@ import com.google.common.base.Throwables;
  */
 public class HazardCalc {
 
-	// TODO move to config
-	private static final int FLUSH_LIMIT = 5;
-
 	/**
 	 * Entry point for a hazard calculation.
 	 * 
@@ -173,7 +170,7 @@ public class HazardCalc {
 		for (Site site : sites) {
 			Hazard result = calc(model, config, site, executor);
 			results.add(result);
-			if (results.size() == FLUSH_LIMIT) {
+			if (results.size() == config.outputBatchSize) {
 				OpenOption[] opts = firstBatch ? WRITE_OPTIONS : APPEND_OPTIONS;
 				firstBatch = false;
 				Results.writeResults(out, results, opts);
-- 
GitLab


From 11128ec5d8f10fb4938f7cb6f990caa4d27c03cb Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Mon, 21 Mar 2016 14:25:51 -0600
Subject: [PATCH 03/17] system partition now pulled from config

---
 src/org/opensha2/calc/Transforms.java | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/org/opensha2/calc/Transforms.java b/src/org/opensha2/calc/Transforms.java
index 2b4af4e37..1c271139f 100644
--- a/src/org/opensha2/calc/Transforms.java
+++ b/src/org/opensha2/calc/Transforms.java
@@ -417,7 +417,6 @@ final class Transforms {
 		private final Site site;
 		private final Executor ex;
 		private final CalcConfig config;
-		private final int size = 20000;
 
 		ParallelSystemToCurves(
 				Site site,
@@ -438,7 +437,7 @@ final class Transforms {
 			// calculate curves from list in parallel
 			InputsToCurves inputsToCurves = new InputsToCurves(sources, config);
 			AsyncList<HazardCurves> asyncCurvesList = AsyncList.create();
-			for (InputList partition : master.partition(size)) {
+			for (InputList partition : master.partition(config.systemPartition)) {
 				asyncCurvesList.add(transform(
 					immediateFuture(partition),
 					inputsToCurves,
-- 
GitLab


From dbe77a4c57bfa050c326916205e57f526a5c9502 Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Mon, 21 Mar 2016 16:45:08 -0600
Subject: [PATCH 04/17] updated examples to be consistent with config changes

---
 .gitignore                                    |  2 +-
 etc/examples/1-hazard-curve/README.md         |  4 +-
 etc/examples/2-custom-config/README.md        | 11 ++----
 etc/examples/3-sites-file/README.md           |  6 +--
 etc/examples/4-hazard-map/README.md           |  6 +--
 etc/examples/5-complex-model/README.md        | 18 +++++----
 etc/examples/5-complex-model/config-map.json  |  4 ++
 .../5-complex-model/config-sites.json         |  4 ++
 etc/examples/5-complex-model/config.json      |  3 --
 etc/examples/6-enhanced-output/README.md      |  4 +-
 src/org/opensha2/calc/CalcConfig.java         | 39 ++++++++++++++++++-
 src/org/opensha2/calc/Results.java            |  3 +-
 src/org/opensha2/programs/HazardCalc.java     |  9 ++---
 13 files changed, 75 insertions(+), 38 deletions(-)
 create mode 100644 etc/examples/5-complex-model/config-map.json
 create mode 100644 etc/examples/5-complex-model/config-sites.json
 delete mode 100644 etc/examples/5-complex-model/config.json

diff --git a/.gitignore b/.gitignore
index d1038274c..d27f7d99d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,6 @@
 /dist
 /docs
 /tmp
-curves/
+curves*/
 Scratch*.java
 /src/META-INF
diff --git a/etc/examples/1-hazard-curve/README.md b/etc/examples/1-hazard-curve/README.md
index daf1961bd..3647eee34 100644
--- a/etc/examples/1-hazard-curve/README.md
+++ b/etc/examples/1-hazard-curve/README.md
@@ -15,6 +15,8 @@ The result of this calculation should be available as a single comma-delimited f
 
 Note that not all [calculation configuration](https://github.com/usgs/nshmp-haz/wiki/Configuration) parameters need be supplied; see the [configuration file](../../peer/models/Set1-Case1/config.json) for this example model.
 
-Also note that all output is written to the current working directory. In the next example, we'll override the model supplied configuration with a custom file.
+Also note that all output is written to the current working directory, but the ouput destination can be specified via the [`outputDir`](https://github.com/usgs/nshmp-haz/wiki/Configuration) parameter.
+
+In the next example, we'll override the model supplied configuration with a custom file.
 
 #### Next: [Example 2 – A custom configuration](../2-custom-config)
diff --git a/etc/examples/2-custom-config/README.md b/etc/examples/2-custom-config/README.md
index 47de1dc34..cff4d28ec 100644
--- a/etc/examples/2-custom-config/README.md
+++ b/etc/examples/2-custom-config/README.md
@@ -1,23 +1,20 @@
 Example 2: A custom configuration
 -------------------------------
 
-__Working directory:__ `/path/to/nshmp-haz/etc/examples`
+__Working directory:__ `/path/to/nshmp-haz/etc/examples/2-custom-config`
 
-Navigate up one level to the `examples/` directory and execute the following:
+Navigate to the directory above and execute the following:
 
 ```Shell
-hazard ../peer/models/Set1-Case1 "San Francisco,-122.40,37.75" 2-custom-config/config.json
+hazard ../../peer/models/Set1-Case1 "San Francisco,-122.40,37.75" config.json
 ```
 
 In this example we've overridden the configuration supplied by the model. Specifically:
 
 * The upper end of each hazard curve has been truncated at 3 standard deviations.
-* Hazard curves have been calculated for 3 `imts` ([intensity measure types](http://usgs.github.io/nshmp-haz/javadoc/index.html?org/opensha2/gmm/Imt.html), or spectral periods) and written to the directory containing the config file.
+* Hazard curves have been calculated for 3 `imts` ([intensity measures](http://usgs.github.io/nshmp-haz/javadoc/index.html?org/opensha2/gmm/Imt.html), or spectral periods).
 * The `imls` (intensity measure levels or x-values) of the resultant curves have been explicitely defined for each `imt`.
-* And two different sites have been specified.
 
 See the [configuration specification](https://github.com/usgs/nshmp-haz/wiki/Configuration) for details on default values and supported options and formats.
 
-**A note on output:** Because we supplied a specific configuration file, all program output is written to the directory where the config resides, thus keeping a record of calculation settings along with any results.
-
 #### Next: [Example 3 – Using a custom sites file](../3-sites-file)
diff --git a/etc/examples/3-sites-file/README.md b/etc/examples/3-sites-file/README.md
index 0b614226c..4e3ee65d1 100644
--- a/etc/examples/3-sites-file/README.md
+++ b/etc/examples/3-sites-file/README.md
@@ -1,18 +1,18 @@
 Example 3: Using a custom sites file
 ------------------------------------
 
-__Working directory:__ `/path/to/nshmp-haz/etc/examples`
+__Working directory:__ `/path/to/nshmp-haz/etc/examples/3-sites-file`
 
 To compute hazard at more than one site, one may supply a comma-delimited (\*.csv) or [GeoJSON](http://geojson.org) (\*.geojson) formatted site data file instead:
 
 ```Shell
-hazard ../peer/models/Set1-Case1  3-sites-file/sites.csv 3-sites-file/config.json
+hazard ../../peer/models/Set1-Case1  sites.csv config.json
 ```
 
 or
 
 ```Shell
-hazard ../peer/models/Set1-Case1 3-sites-file/sites.geojson 3-sites-file/config.json
+hazard ../../peer/models/Set1-Case1 sites.geojson config.json
 ```
 
 The [site specification](https://github.com/usgs/nshmp-haz/wiki/Sites) wiki page provides details on the two file formats. Note that with either format, if the name of a site is supplied, it will be included in the first column of any output curve files.
diff --git a/etc/examples/4-hazard-map/README.md b/etc/examples/4-hazard-map/README.md
index 68c0742ee..4a300ff98 100644
--- a/etc/examples/4-hazard-map/README.md
+++ b/etc/examples/4-hazard-map/README.md
@@ -1,12 +1,12 @@
 Example 4: A simple hazard map
 ------------------------------
 
-__Working directory:__ `/path/to/nshmp-haz/etc/examples`
+__Working directory:__ `/path/to/nshmp-haz/etc/examples/4-hazard-map`
 
-A hazard map is just a lot of hazard curves. To compute curves at reqularly spaced intervals in latitude and longitude over a region, a [GeoJSON site file](https://github.com/usgs/nshmp-haz/wiki/Sites) may instead specify a polygon and a site spacing.
+A hazard map is just a collection of values plucked from a lot of hazard curves. To compute curves at reqularly spaced intervals in latitude and longitude over a region, a [GeoJSON site file](https://github.com/usgs/nshmp-haz/wiki/Sites) may instead specify a polygon and a site spacing.
 
 ```Shell
-hazard ../peer/models/Set1-Case1 4-hazard-map/map.geojson 4-hazard-map/config.json
+hazard ../../peer/models/Set1-Case1 map.geojson config.json
 ```
 
 #### Next: [Example 5 – A more complex model](../5-complex-model)
diff --git a/etc/examples/5-complex-model/README.md b/etc/examples/5-complex-model/README.md
index 4605796fd..3acaf6517 100644
--- a/etc/examples/5-complex-model/README.md
+++ b/etc/examples/5-complex-model/README.md
@@ -1,28 +1,30 @@
 Example 5: A more complex model
 -------------------------------
 
-__Working directory:__ `/path/to/nshmp-haz/etc/examples`
+__Working directory:__ `/path/to/nshmp-haz/etc/examples/5-complex-model`
 
 Most PSHAs involve the use of more complex source models, the components of which might use different ground motion models. For this and ensuing examples, we'll use the 2008 USGS National Seismic Hazard Model (NSHM) for the western U.S.
 
-First, clone the 2008 USGS NSHM. Assuming `examples/` is the current working directory, the following will create a copy of the model adjacent to nshmp-haz:
+First, clone the 2008 USGS NSHM. Assuming you are in the current working directory (above), the following will create a copy of the model adjacent to nshmp-haz:
 
 ```Shell
-git clone https://github.com/usgs/nshmp-model-cous-2008.git ../../../nshmp-model-cous-2008
+git clone https://github.com/usgs/nshmp-model-cous-2008.git ../../../../nshmp-model-cous-2008
 ```
 
-The 2008 NSHM repository contains two source models: one for the western U.S. and a one for the central and eastern U.S. To compute hazard for a few sites in the Western U.S., execute:
+The 2008 NSHM repository contains two source models: one for the western U.S. and a one for the central and eastern U.S. To compute hazard for a few sites in the Western U.S. at 1.0s and 2.0s spectral periods, execute:
 
 ```Shell
-hazard ../../../nshmp-model-cous-2008/Western\ US 5-complex-model/sites.geojson 5-complex-model/config.json
+hazard ../../../../nshmp-model-cous-2008/Western\ US sites.geojson config-sites.json
 ```
 
-Note that more complex models take longer to initialize, although this only occurs once per calculation, and make for longer, per-site calculations. However, `HazardCalc` will automatically use all cores available and therefore performs better on multi-core systems. To compute a small, low-resolution map for the central San Francisco bay area, execute:
+Note that more complex models take longer to initialize, although this only occurs once per calculation, and make for longer, per-site calculations. However, `HazardCalc` will automatically use all cores available by default and therefore performs better on multi-core systems.
+
+To compute a small, low-resolution map for the central San Francisco bay area, execute:
 
 ```Shell
-hazard ../../../nshmp-model-cous-2008/Western\ US 5-complex-model/map.geojson 5-complex-model/config.json
+hazard ../../../../nshmp-model-cous-2008/Western\ US map.geojson config-map.json
 ```
 
-This computes 121 curves over a 2° by 2° area and will give you a sense of how long a larger map might take. These results overwrite those of the prior site-specific calculation. For this example we only compute curves at 1.0 and 2.0 second spectral periods.
+This computes 121 curves over a 2° by 2° area and will give you a sense of how long a larger map might take. Note that in the above two examples we specified different output directories for each calculation.
 
 #### Next: [Example 6 – Enhanced output](../6-enhanced-output)
diff --git a/etc/examples/5-complex-model/config-map.json b/etc/examples/5-complex-model/config-map.json
new file mode 100644
index 000000000..741d91296
--- /dev/null
+++ b/etc/examples/5-complex-model/config-map.json
@@ -0,0 +1,4 @@
+{
+  "imts": ["SA1P0", "SA2P0"],
+  "outputDir": "curves-map"
+}
diff --git a/etc/examples/5-complex-model/config-sites.json b/etc/examples/5-complex-model/config-sites.json
new file mode 100644
index 000000000..6093b2603
--- /dev/null
+++ b/etc/examples/5-complex-model/config-sites.json
@@ -0,0 +1,4 @@
+{
+  "imts": ["SA1P0", "SA2P0"],
+  "outputDir": "curves-sites"
+}
diff --git a/etc/examples/5-complex-model/config.json b/etc/examples/5-complex-model/config.json
deleted file mode 100644
index e972ec090..000000000
--- a/etc/examples/5-complex-model/config.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-  "imts": ["SA1P0", "SA2P0"]
-}
diff --git a/etc/examples/6-enhanced-output/README.md b/etc/examples/6-enhanced-output/README.md
index 84c92898b..778aeec93 100644
--- a/etc/examples/6-enhanced-output/README.md
+++ b/etc/examples/6-enhanced-output/README.md
@@ -1,12 +1,12 @@
 Example 6: Enhanced output
 --------------------------
 
-__Working directory:__ `/path/to/nshmp-haz/etc/examples`
+__Working directory:__ `/path/to/nshmp-haz/etc/examples/6-enhanced-output`
 
 While mean hazard is of broad interest, it can be useful to preserve individual components of a total curve, particularly with more complex models. Execute the following to write curves for each source type and ground motion model (GMM) used in the 2008 NSHM:
 
 ```Shell
-hazard ../../../nshmp-model-cous-2008/Western\ US 6-enhanced-output/sites.geojson 6-enhanced-output/config.json
+hazard ../../../../nshmp-model-cous-2008/Western\ US sites.geojson config.json
 ```
 
 Note that the output curves directory now contains additional directories of curves by source type and GMM.
diff --git a/src/org/opensha2/calc/CalcConfig.java b/src/org/opensha2/calc/CalcConfig.java
index 624540982..a702f4b6a 100644
--- a/src/org/opensha2/calc/CalcConfig.java
+++ b/src/org/opensha2/calc/CalcConfig.java
@@ -12,6 +12,7 @@ import static org.opensha2.util.TextUtils.wrap;
 
 import java.io.IOException;
 import java.io.Reader;
+import java.lang.reflect.Type;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -33,6 +34,10 @@ import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
 
 /**
  * Calculation configuration.
@@ -261,7 +266,17 @@ public final class CalcConfig {
 		}
 	}
 
-	private static final Gson GSON = new GsonBuilder().create();
+	private static final Gson GSON = new GsonBuilder()
+		.registerTypeAdapter(Path.class, new JsonDeserializer<Path>() {
+			@Override
+			public Path deserialize(
+					JsonElement json,
+					Type type,
+					JsonDeserializationContext context) throws JsonParseException {
+				return Paths.get(json.getAsString());
+			}
+		})
+		.create();
 
 	/**
 	 * Create a new calculation configuration builder from the resource at the
@@ -281,6 +296,14 @@ public final class CalcConfig {
 		return configBuilder;
 	}
 
+	public static void main(String[] args) throws IOException {
+		CalcConfig cc = builder()
+			.withDefaults()
+			.extend(builder(Paths.get("etc/examples/5-complex-model/config-sites.json")))
+			.build();
+		System.out.println(cc);
+	}
+
 	/**
 	 * Create a new empty calculation configuration builder.
 	 */
@@ -346,7 +369,7 @@ public final class CalcConfig {
 			this.systemPartition = 1000;
 			this.gmmUncertainty = false;
 			this.hazardFormat = HazardFormat.TOTAL;
-			this.outputDir = Paths.get(".");
+			this.outputDir = Paths.get("curves");
 			this.outputBatchSize = 20;
 			this.deagg = new DeaggData();
 			return this;
@@ -450,4 +473,16 @@ public final class CalcConfig {
 		}
 	}
 
+	// static final class PathDeserializer implements JsonDeserializer<Path> {
+	//
+	// @Override
+	// public Path deserialize(
+	// JsonElement json,
+	// Type type,
+	// JsonDeserializationContext context) {
+	//
+	//
+	// }
+	// }
+
 }
diff --git a/src/org/opensha2/calc/Results.java b/src/org/opensha2/calc/Results.java
index bdcd4e05f..8ab56900f 100644
--- a/src/org/opensha2/calc/Results.java
+++ b/src/org/opensha2/calc/Results.java
@@ -42,7 +42,6 @@ public class Results {
 
 	private static final String CURVE_FILE_SUFFIX = ".csv";
 	private static final String RATE_FMT = "%.8e";
-	private static final String CURVE_DIR = "curves";
 
 	/**
 	 * Hazard output format.
@@ -258,7 +257,7 @@ public class Results {
 		for (Entry<Imt, List<String>> totalEntry : totalLineMap.entrySet()) {
 			Imt imt = totalEntry.getKey();
 
-			Path imtDir = dir.resolve(CURVE_DIR).resolve(imt.name());
+			Path imtDir = dir.resolve(imt.name());
 			Files.createDirectories(imtDir);
 			Path totalFile = imtDir.resolve("total" + CURVE_FILE_SUFFIX);
 			Files.write(totalFile, totalEntry.getValue(), US_ASCII, options);
diff --git a/src/org/opensha2/programs/HazardCalc.java b/src/org/opensha2/programs/HazardCalc.java
index 7fd55c44f..5fff8c191 100644
--- a/src/org/opensha2/programs/HazardCalc.java
+++ b/src/org/opensha2/programs/HazardCalc.java
@@ -94,14 +94,12 @@ public class HazardCalc {
 			HazardModel model = HazardModel.load(modelPath);
 
 			CalcConfig config = model.config();
-			Path out = Paths.get(StandardSystemProperty.USER_DIR.value());
 			if (argCount == 3) {
 				Path userConfigPath = Paths.get(args[2]);
 				config = CalcConfig.builder()
 					.copy(model.config())
 					.extend(CalcConfig.builder(userConfigPath))
 					.build();
-				out = userConfigPath.getParent();
 			}
 			log.info(config.toString());
 
@@ -109,7 +107,7 @@ public class HazardCalc {
 			log.info("");
 			log.info("Sites: " + sites);
 
-			calc(model, config, sites, out, log);
+			calc(model, config, sites, log);
 			log.info(PROGRAM + ": finished");
 			return Optional.absent();
 
@@ -151,7 +149,6 @@ public class HazardCalc {
 			HazardModel model,
 			CalcConfig config,
 			Iterable<Site> sites,
-			Path out,
 			Logger log) throws IOException {
 
 		ExecutorService execSvc = createExecutor();
@@ -173,7 +170,7 @@ public class HazardCalc {
 			if (results.size() == config.outputBatchSize) {
 				OpenOption[] opts = firstBatch ? WRITE_OPTIONS : APPEND_OPTIONS;
 				firstBatch = false;
-				Results.writeResults(out, results, opts);
+				Results.writeResults(config.outputDir, results, opts);
 				log.info("     batch: " + (count + 1) + "  " + batchWatch +
 					"  total: " + totalWatch);
 				results.clear();
@@ -184,7 +181,7 @@ public class HazardCalc {
 		// write final batch
 		if (!results.isEmpty()) {
 			OpenOption[] opts = firstBatch ? WRITE_OPTIONS : APPEND_OPTIONS;
-			Results.writeResults(out, results, opts);
+			Results.writeResults(config.outputDir, results, opts);
 		}
 		log.info(PROGRAM + ": " + count + " complete " + totalWatch);
 
-- 
GitLab


From d0496842bf7efc9eb67068b33b74cc5e42d526be Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Mon, 21 Mar 2016 17:25:11 -0600
Subject: [PATCH 05/17] special cased rectangular map polygons to use
 Regions.createRectangular()

---
 etc/examples/5-complex-model/README.md |  2 +-
 src/org/opensha2/calc/Sites.java       | 23 ++++++---
 src/org/opensha2/calc/Transforms.java  | 67 ++++++++++++++++----------
 3 files changed, 60 insertions(+), 32 deletions(-)

diff --git a/etc/examples/5-complex-model/README.md b/etc/examples/5-complex-model/README.md
index 3acaf6517..72e498dd8 100644
--- a/etc/examples/5-complex-model/README.md
+++ b/etc/examples/5-complex-model/README.md
@@ -25,6 +25,6 @@ To compute a small, low-resolution map for the central San Francisco bay area, e
 hazard ../../../../nshmp-model-cous-2008/Western\ US map.geojson config-map.json
 ```
 
-This computes 121 curves over a 2° by 2° area and will give you a sense of how long a larger map might take. Note that in the above two examples we specified different output directories for each calculation.
+This computes 121 curves over a 2° by 2° area and will give you a sense of how long a larger map might take. Note that in the above two examples we specified different output directories in the config files for each calculation.
 
 #### Next: [Example 6 – Enhanced output](../6-enhanced-output)
diff --git a/src/org/opensha2/calc/Sites.java b/src/org/opensha2/calc/Sites.java
index 479fb283e..f8cb483bf 100644
--- a/src/org/opensha2/calc/Sites.java
+++ b/src/org/opensha2/calc/Sites.java
@@ -7,7 +7,6 @@ import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.opensha2.geo.BorderType.MERCATOR_LINEAR;
 import static org.opensha2.util.GeoJson.validateProperty;
 import static org.opensha2.util.Parsing.splitToList;
-import static org.opensha2.util.Parsing.trimEnds;
 import static org.opensha2.util.TextUtils.ALIGN_COL;
 import static org.opensha2.util.TextUtils.format;
 
@@ -329,7 +328,19 @@ public final class Sites {
 			JsonObject properties = sitesFeature.getAsJsonObject(GeoJson.Key.PROPERTIES);
 			String mapName = readName(properties, "Unnamed Map");
 
-			Region calcRegion = Regions.create(mapName, border, MERCATOR_LINEAR);
+			/*
+			 * We special case a 5-coordinate border that defines a mercator
+			 * recangle so as to create a region that includes sites on the
+			 * north and east borders.
+			 */
+
+			Region calcRegion = null;
+			try {
+				Bounds b = validateExtents(border).bounds();
+				calcRegion = Regions.createRectangular(mapName, b.min(), b.max());
+			} catch (IllegalArgumentException iae) {
+				calcRegion = Regions.create(mapName, border, MERCATOR_LINEAR);
+			}
 			checkState(
 				properties.has(GeoJson.Properties.Key.SPACING),
 				"A \"spacing\" : value (in degrees) must be defined in \"properties\"");
@@ -378,12 +389,12 @@ public final class Sites {
 		JsonArray coords = geometry.getAsJsonArray(GeoJson.Key.COORDINATES);
 		LocationList border = GeoJson.fromCoordinates(coords);
 
-		checkState(
+		checkArgument(
 			border.size() > 2,
 			"A GeoJSON polygon must have at least 3 coordinates:%s",
 			border);
 
-		checkState(
+		checkArgument(
 			border.first().equals(border.last()),
 			"The first and last points in a GeoJSON polygon must be the same:%s",
 			border);
@@ -397,7 +408,7 @@ public final class Sites {
 	}
 
 	private static LocationList validateExtents(LocationList locs) {
-		checkState(locs.size() == 5,
+		checkArgument(locs.size() == 5,
 			"Extents polygon must contain 5 coordinates:%s", locs);
 		Location p1 = locs.get(0);
 		Location p2 = locs.get(1);
@@ -411,7 +422,7 @@ public final class Sites {
 				p2.latRad() == p3.latRad() &&
 				p1.lonRad() == p2.lonRad() &&
 				p3.lonRad() == p4.lonRad());
-		checkState(rectangular,
+		checkArgument(rectangular,
 			"Extents polygon does not define a lat-lon Mercator rectangle:%s", locs);
 		return locs;
 	}
diff --git a/src/org/opensha2/calc/Transforms.java b/src/org/opensha2/calc/Transforms.java
index 1c271139f..6c27d5c22 100644
--- a/src/org/opensha2/calc/Transforms.java
+++ b/src/org/opensha2/calc/Transforms.java
@@ -68,7 +68,8 @@ final class Transforms {
 			this.site = site;
 		}
 
-		@Override public SourceInputList apply(Source source) {
+		@Override
+		public SourceInputList apply(Source source) {
 			SourceInputList hazardInputs = new SourceInputList(source);
 
 			for (Rupture rup : source) {
@@ -116,7 +117,8 @@ final class Transforms {
 			this.gmmTable = gmmTable;
 		}
 
-		@Override public GroundMotions apply(InputList inputs) {
+		@Override
+		public GroundMotions apply(InputList inputs) {
 
 			Set<Imt> imtKeys = gmmTable.keySet();
 			Set<Gmm> gmmKeys = gmmTable.get(imtKeys.iterator().next()).keySet();
@@ -155,7 +157,8 @@ final class Transforms {
 			this.truncationLevel = config.truncationLevel;
 		}
 
-		@Override public HazardCurves apply(GroundMotions groundMotions) {
+		@Override
+		public HazardCurves apply(GroundMotions groundMotions) {
 
 			HazardCurves.Builder curveBuilder = HazardCurves.builder(groundMotions);
 
@@ -212,7 +215,8 @@ final class Transforms {
 			this.truncationLevel = config.truncationLevel;
 		}
 
-		@Override public HazardCurves apply(GroundMotions groundMotions) {
+		@Override
+		public HazardCurves apply(GroundMotions groundMotions) {
 
 			HazardCurves.Builder curveBuilder = HazardCurves.builder(groundMotions);
 
@@ -259,7 +263,9 @@ final class Transforms {
 			return curveBuilder.build();
 		}
 
-		/* Construct an exceedance curve considering uncertain ground motions. */
+		/*
+		 * Construct an exceedance curve considering uncertain ground motions.
+		 */
 		private XySequence exceedanceCurve(
 				final double[] means,
 				final double sigma,
@@ -306,12 +312,13 @@ final class Transforms {
 
 			this.sourceToInputs = new SourceToInputs(site);
 			this.inputsToGroundMotions = new InputsToGroundMotions(gmmTable);
-			this.groundMotionsToCurves = config.gmmUncertainty && gmmSet.epiUncertainty() ?
-				new GroundMotionsToCurvesWithUncertainty(gmmSet, config) :
-				new GroundMotionsToCurves(config);
+			this.groundMotionsToCurves = config.gmmUncertainty && gmmSet.epiUncertainty()
+				? new GroundMotionsToCurvesWithUncertainty(gmmSet, config)
+				: new GroundMotionsToCurves(config);
 		}
 
-		@Override public HazardCurves apply(Source source) {
+		@Override
+		public HazardCurves apply(Source source) {
 			return groundMotionsToCurves.apply(
 				inputsToGroundMotions.apply(
 					sourceToInputs.apply(source)));
@@ -336,7 +343,8 @@ final class Transforms {
 			this.modelCurves = config.logModelCurves();
 		}
 
-		@Override public HazardCurveSet apply(List<HazardCurves> curvesList) {
+		@Override
+		public HazardCurveSet apply(List<HazardCurves> curvesList) {
 
 			if (curvesList.isEmpty()) return HazardCurveSet.empty(sources);
 
@@ -380,7 +388,8 @@ final class Transforms {
 			this.config = config;
 		}
 
-		@Override public HazardCurveSet apply(SystemSourceSet sources) {
+		@Override
+		public HazardCurveSet apply(SystemSourceSet sources) {
 
 			InputList inputs = SystemSourceSet.toInputsFunction(site).apply(sources);
 			if (inputs.isEmpty()) return HazardCurveSet.empty(sources);
@@ -394,9 +403,9 @@ final class Transforms {
 			GroundMotions gms = inputsToGm.apply(inputs);
 
 			Function<GroundMotions, HazardCurves> gmToCurves =
-				config.gmmUncertainty && gmmSet.epiUncertainty() ?
-					new GroundMotionsToCurvesWithUncertainty(gmmSet, config) :
-					new GroundMotionsToCurves(config);
+				config.gmmUncertainty && gmmSet.epiUncertainty()
+					? new GroundMotionsToCurvesWithUncertainty(gmmSet, config)
+					: new GroundMotionsToCurves(config);
 			HazardCurves curves = gmToCurves.apply(gms);
 
 			CurveConsolidator consolidator = new CurveConsolidator(sources, config);
@@ -428,7 +437,8 @@ final class Transforms {
 			this.config = config;
 		}
 
-		@Override public HazardCurveSet apply(SystemSourceSet sources) {
+		@Override
+		public HazardCurveSet apply(SystemSourceSet sources) {
 
 			// create input list
 			InputList master = SystemSourceSet.toInputsFunction(site).apply(sources);
@@ -475,12 +485,13 @@ final class Transforms {
 				gmmSet.gmms());
 
 			this.inputsToGroundMotions = new InputsToGroundMotions(gmmTable);
-			this.groundMotionsToCurves = config.gmmUncertainty && gmmSet.epiUncertainty() ?
-				new GroundMotionsToCurvesWithUncertainty(gmmSet, config) :
-				new GroundMotionsToCurves(config);
+			this.groundMotionsToCurves = config.gmmUncertainty && gmmSet.epiUncertainty()
+				? new GroundMotionsToCurvesWithUncertainty(gmmSet, config)
+				: new GroundMotionsToCurves(config);
 		}
 
-		@Override public HazardCurves apply(InputList inputs) {
+		@Override
+		public HazardCurves apply(InputList inputs) {
 			return groundMotionsToCurves.apply(inputsToGroundMotions.apply(inputs));
 		}
 	}
@@ -498,7 +509,8 @@ final class Transforms {
 			transform = new SourceToInputs(site);
 		}
 
-		@Override public ClusterInputs apply(ClusterSource clusterSource) {
+		@Override
+		public ClusterInputs apply(ClusterSource clusterSource) {
 			ClusterInputs clusterInputs = new ClusterInputs(clusterSource);
 			for (FaultSource faultSource : clusterSource.faults()) {
 				clusterInputs.add(transform.apply(faultSource));
@@ -521,7 +533,8 @@ final class Transforms {
 			transform = new InputsToGroundMotions(gmmTable);
 		}
 
-		@Override public ClusterGroundMotions apply(ClusterInputs clusterInputs) {
+		@Override
+		public ClusterGroundMotions apply(ClusterInputs clusterInputs) {
 			ClusterGroundMotions clusterGroundMotions = new ClusterGroundMotions(
 				clusterInputs.parent);
 			for (SourceInputList hazardInputs : clusterInputs) {
@@ -552,7 +565,8 @@ final class Transforms {
 			this.truncationLevel = config.truncationLevel;
 		}
 
-		@Override public ClusterCurves apply(ClusterGroundMotions clusterGroundMotions) {
+		@Override
+		public ClusterCurves apply(ClusterGroundMotions clusterGroundMotions) {
 
 			Builder builder = ClusterCurves.builder(clusterGroundMotions);
 
@@ -628,7 +642,8 @@ final class Transforms {
 			this.groundMotionsToCurves = new ClusterGroundMotionsToCurves(config);
 		}
 
-		@Override public ClusterCurves apply(ClusterSource source) {
+		@Override
+		public ClusterCurves apply(ClusterSource source) {
 			return groundMotionsToCurves.apply(
 				inputsToGroundMotions.apply(
 					sourceToInputs.apply(source)));
@@ -654,7 +669,8 @@ final class Transforms {
 			this.modelCurves = config.logModelCurves();
 		}
 
-		@Override public HazardCurveSet apply(List<ClusterCurves> curvesList) {
+		@Override
+		public HazardCurveSet apply(List<ClusterCurves> curvesList) {
 
 			if (curvesList.isEmpty()) return HazardCurveSet.empty(sources);
 
@@ -690,7 +706,8 @@ final class Transforms {
 			this.site = site;
 		}
 
-		@Override public Hazard apply(List<HazardCurveSet> curveSetList) {
+		@Override
+		public Hazard apply(List<HazardCurveSet> curveSetList) {
 
 			Hazard.Builder resultBuilder = Hazard.builder(config)
 				.model(model)
-- 
GitLab


From ff86e47ceb47dc0d6bb57a41c40df75d3c878c71 Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Tue, 22 Mar 2016 09:52:36 -0600
Subject: [PATCH 06/17] new config identifiers for concurrency and curve value
 types

---
 src/org/opensha2/calc/CurveValue.java  | 16 +++++++++++
 src/org/opensha2/calc/ThreadCount.java | 38 ++++++++++++++++++++++++++
 2 files changed, 54 insertions(+)
 create mode 100644 src/org/opensha2/calc/CurveValue.java
 create mode 100644 src/org/opensha2/calc/ThreadCount.java

diff --git a/src/org/opensha2/calc/CurveValue.java b/src/org/opensha2/calc/CurveValue.java
new file mode 100644
index 000000000..108874d6c
--- /dev/null
+++ b/src/org/opensha2/calc/CurveValue.java
@@ -0,0 +1,16 @@
+package org.opensha2.calc;
+
+/**
+ * Hazard curve value types. 
+ *
+ * @author Peter Powers
+ */
+public enum CurveValue {
+	
+	/** Annual-rate. */
+	ANNUAL_RATE,
+	
+	/** Poisson probability. */
+	POISSON_PROBABILITY;
+
+}
diff --git a/src/org/opensha2/calc/ThreadCount.java b/src/org/opensha2/calc/ThreadCount.java
new file mode 100644
index 000000000..eaef8470f
--- /dev/null
+++ b/src/org/opensha2/calc/ThreadCount.java
@@ -0,0 +1,38 @@
+package org.opensha2.calc;
+
+import java.util.concurrent.ExecutorService;
+
+/**
+ * The number of threads with which to intialize thread pools. Values reference
+ * the number of non-competing threads that could be supported on a particular
+ * system as determined by calling {@link Runtime#availableProcessors()}.
+ *
+ * @author Peter Powers
+ */
+public enum ThreadCount {
+
+	/**
+	 * A single thread. Use of a single thread will generally prevent an
+	 * {@link ExecutorService} from being used, and all calculations will be run
+	 * on the thread from which a program was called.
+	 */
+	ONE,
+
+	/**
+	 * Half of {@code ALL}.
+	 */
+	HALF,
+
+	/**
+	 * Two less than {@code ALL}. So as to not commandeer all available
+	 * resources.
+	 */
+	N_MINUS_2,
+
+	/**
+	 * All possible non-competing threads. The number of threads will equal the
+	 * number of available processors.
+	 */
+	ALL;
+
+}
-- 
GitLab


From 70ed9e2f6134b7aacf352e4ed4985f93e8026301 Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Wed, 23 Mar 2016 20:55:55 -0600
Subject: [PATCH 07/17] big config refactor; added new fields and structure

---
 etc/examples/2-custom-config/config.json      |  16 +-
 etc/examples/3-sites-file/config.json         |  16 +-
 etc/examples/4-hazard-map/config.json         |   8 +-
 etc/examples/5-complex-model/config-map.json  |   8 +-
 .../5-complex-model/config-sites.json         |   8 +-
 etc/examples/6-enhanced-output/config.json    |   4 +-
 src/org/opensha2/calc/CalcConfig.java         | 932 ++++++++++++------
 src/org/opensha2/calc/Calcs.java              |   4 +-
 src/org/opensha2/calc/CurveType.java          |  31 +
 src/org/opensha2/calc/Deaggregation.java      |   8 +-
 src/org/opensha2/calc/Hazard.java             |   5 +-
 src/org/opensha2/calc/Results.java            |  30 +-
 src/org/opensha2/calc/ThreadCount.java        |   3 +-
 src/org/opensha2/calc/Transforms.java         |  39 +-
 src/org/opensha2/eq/model/Loader.java         |  17 +-
 src/org/opensha2/eq/model/ModelConfig.java    |  96 +-
 src/org/opensha2/mfd/Mfds.java                |   2 +-
 src/org/opensha2/programs/HazardCalc.java     |  11 +-
 src/org/opensha2/util/TextUtils.java          |  20 +-
 test/org/opensha2/eq/model/peer/PeerTest.java |   6 +-
 20 files changed, 788 insertions(+), 476 deletions(-)
 create mode 100644 src/org/opensha2/calc/CurveType.java

diff --git a/etc/examples/2-custom-config/config.json b/etc/examples/2-custom-config/config.json
index 2911536f6..36b538061 100644
--- a/etc/examples/2-custom-config/config.json
+++ b/etc/examples/2-custom-config/config.json
@@ -1,10 +1,12 @@
 {
-  "exceedanceModel": "TRUNCATION_UPPER_ONLY",
-  "truncationLevel": 3.0,
-  "imts": ["PGA", "SA0P2", "SA1P0"],
-  "customImls": {
-    "PGA":   [0.0050, 0.0070, 0.0098, 0.0137, 0.0192, 0.0269, 0.0376, 0.0527, 0.0738, 0.103, 0.145, 0.203, 0.284, 0.397, 0.556, 0.778, 1.09, 1.52, 2.2, 3.3],
-    "SA0P2": [0.0050, 0.0075, 0.0113, 0.0169, 0.0253, 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],
-    "SA1P0": [0.0025, 0.00375, 0.00563, 0.00844, 0.0127, 0.0190, 0.0285, 0.0427, 0.0641, 0.0961, 0.144, 0.216, 0.324, 0.487, 0.730, 1.09, 1.64, 2.46, 3.69, 5.54]
+  "curve": {
+    "exceedanceModel": "TRUNCATION_UPPER_ONLY",
+    "truncationLevel": 3.0,
+    "imts": ["PGA", "SA0P2", "SA1P0"],
+    "customImls": {
+      "PGA":   [0.0050, 0.0070, 0.0098, 0.0137, 0.0192, 0.0269, 0.0376, 0.0527, 0.0738, 0.103, 0.145, 0.203, 0.284, 0.397, 0.556, 0.778, 1.09, 1.52, 2.2, 3.3],
+      "SA0P2": [0.0050, 0.0075, 0.0113, 0.0169, 0.0253, 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],
+      "SA1P0": [0.0025, 0.00375, 0.00563, 0.00844, 0.0127, 0.0190, 0.0285, 0.0427, 0.0641, 0.0961, 0.144, 0.216, 0.324, 0.487, 0.730, 1.09, 1.64, 2.46, 3.69, 5.54]
+    }
   }
 }
diff --git a/etc/examples/3-sites-file/config.json b/etc/examples/3-sites-file/config.json
index 2911536f6..36b538061 100644
--- a/etc/examples/3-sites-file/config.json
+++ b/etc/examples/3-sites-file/config.json
@@ -1,10 +1,12 @@
 {
-  "exceedanceModel": "TRUNCATION_UPPER_ONLY",
-  "truncationLevel": 3.0,
-  "imts": ["PGA", "SA0P2", "SA1P0"],
-  "customImls": {
-    "PGA":   [0.0050, 0.0070, 0.0098, 0.0137, 0.0192, 0.0269, 0.0376, 0.0527, 0.0738, 0.103, 0.145, 0.203, 0.284, 0.397, 0.556, 0.778, 1.09, 1.52, 2.2, 3.3],
-    "SA0P2": [0.0050, 0.0075, 0.0113, 0.0169, 0.0253, 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],
-    "SA1P0": [0.0025, 0.00375, 0.00563, 0.00844, 0.0127, 0.0190, 0.0285, 0.0427, 0.0641, 0.0961, 0.144, 0.216, 0.324, 0.487, 0.730, 1.09, 1.64, 2.46, 3.69, 5.54]
+  "curve": {
+    "exceedanceModel": "TRUNCATION_UPPER_ONLY",
+    "truncationLevel": 3.0,
+    "imts": ["PGA", "SA0P2", "SA1P0"],
+    "customImls": {
+      "PGA":   [0.0050, 0.0070, 0.0098, 0.0137, 0.0192, 0.0269, 0.0376, 0.0527, 0.0738, 0.103, 0.145, 0.203, 0.284, 0.397, 0.556, 0.778, 1.09, 1.52, 2.2, 3.3],
+      "SA0P2": [0.0050, 0.0075, 0.0113, 0.0169, 0.0253, 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],
+      "SA1P0": [0.0025, 0.00375, 0.00563, 0.00844, 0.0127, 0.0190, 0.0285, 0.0427, 0.0641, 0.0961, 0.144, 0.216, 0.324, 0.487, 0.730, 1.09, 1.64, 2.46, 3.69, 5.54]
+    }
   }
 }
diff --git a/etc/examples/4-hazard-map/config.json b/etc/examples/4-hazard-map/config.json
index 5615e912d..3f0c87702 100644
--- a/etc/examples/4-hazard-map/config.json
+++ b/etc/examples/4-hazard-map/config.json
@@ -1,5 +1,7 @@
 {
-  "exceedanceModel": "TRUNCATION_UPPER_ONLY",
-  "truncationLevel": 3.0,
-  "imts": ["PGA", "SA0P2", "SA1P0"]
+  "curve": {
+    "exceedanceModel": "TRUNCATION_UPPER_ONLY",
+    "truncationLevel": 3.0,
+    "imts": ["PGA", "SA0P2", "SA1P0"]
+  }
 }
diff --git a/etc/examples/5-complex-model/config-map.json b/etc/examples/5-complex-model/config-map.json
index 741d91296..9a474d5bc 100644
--- a/etc/examples/5-complex-model/config-map.json
+++ b/etc/examples/5-complex-model/config-map.json
@@ -1,4 +1,8 @@
 {
-  "imts": ["SA1P0", "SA2P0"],
-  "outputDir": "curves-map"
+  "curve": {
+    "imts": ["SA1P0", "SA2P0"]
+  },
+  "output": {
+    "directory": "curves-map"
+  }
 }
diff --git a/etc/examples/5-complex-model/config-sites.json b/etc/examples/5-complex-model/config-sites.json
index 6093b2603..40ac7f7bf 100644
--- a/etc/examples/5-complex-model/config-sites.json
+++ b/etc/examples/5-complex-model/config-sites.json
@@ -1,4 +1,8 @@
 {
-  "imts": ["SA1P0", "SA2P0"],
-  "outputDir": "curves-sites"
+  "curve": {
+    "imts": ["SA1P0", "SA2P0"]
+  },
+  "output": {
+    "directory": "curves-sites"
+  }
 }
diff --git a/etc/examples/6-enhanced-output/config.json b/etc/examples/6-enhanced-output/config.json
index cb4a34a48..b42a3f733 100644
--- a/etc/examples/6-enhanced-output/config.json
+++ b/etc/examples/6-enhanced-output/config.json
@@ -1,3 +1,5 @@
 {
-  "hazardFormat": "DETAILED"
+  "output": {
+    "curveTypes": ["TOTAL", "GMM", "SOURCE"]
+  }
 }
diff --git a/src/org/opensha2/calc/CalcConfig.java b/src/org/opensha2/calc/CalcConfig.java
index a702f4b6a..147166799 100644
--- a/src/org/opensha2/calc/CalcConfig.java
+++ b/src/org/opensha2/calc/CalcConfig.java
@@ -4,11 +4,15 @@ import static com.google.common.base.CaseFormat.LOWER_CAMEL;
 import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.base.Strings.padEnd;
+import static com.google.common.base.Strings.repeat;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.opensha2.data.XySequence.create;
 import static org.opensha2.data.XySequence.immutableCopyOf;
-import static org.opensha2.util.TextUtils.format;
-import static org.opensha2.util.TextUtils.wrap;
+import static org.opensha2.util.Parsing.enumsToString;
+import static org.opensha2.util.TextUtils.NEWLINE;
+
+// import static org.opensha2.util.TextUtils.*;
 
 import java.io.IOException;
 import java.io.Reader;
@@ -22,14 +26,14 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
 
-import org.opensha2.calc.Results.HazardFormat;
 import org.opensha2.data.Data;
 import org.opensha2.data.XySequence;
 import org.opensha2.eq.model.SourceType;
 import org.opensha2.gmm.Gmm;
 import org.opensha2.gmm.Imt;
-import org.opensha2.util.Parsing;
+import org.opensha2.util.TextUtils;
 
+import com.google.common.base.Optional;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import com.google.gson.Gson;
@@ -41,229 +45,607 @@ import com.google.gson.JsonParseException;
 
 /**
  * Calculation configuration.
+ * 
  * @author Peter Powers
  */
 public final class CalcConfig {
 
 	static final String FILE_NAME = "config.json";
-
-	private final Path resource;
+	private static final String ID = CalcConfig.class.getSimpleName();
+	private static final String STATE_ERROR = "%s %s not set";
+	private static final String DEFAULT_OUT = "curves";
 
 	/**
-	 * The probability distribution model to use when computing hazard curves.
+	 * The resource from which {@code this} was derived. If this configuration
+	 * was built using {@link Builder#extend(Builder)}, this field will reflect
+	 * the field of the extending resource. If this configuration was built only
+	 * from {@link Builder#withDefaults()}, this field will be empty.
 	 */
-	public final ExceedanceModel exceedanceModel;
-	// TODO refactor to probabilitModel
+	public final Optional<Path> resource;
 
-	/**
-	 * The number of standard deviations at which to truncate a distribution.
-	 * This field is ignored if a model does not implement truncation.
-	 */
-	public final double truncationLevel;
-
-	/**
-	 * The {@code Set} of IMTs for which calculations should be performed.
-	 */
-	public final Set<Imt> imts;
+	/** Hazard curve calculation settings */
+	public final Curve curve;
 
-	/**
-	 * Whether to optimize grid source sets, or not.
-	 */
-	public final boolean optimizeGrids;
+	/** Performance and optimization settings. */
+	public final Performance performance;
 
-	/**
-	 * The partition or batch size to use when distributing
-	 * {@link SourceType#SYSTEM} calculations.
-	 */
-	public final int systemPartition;
+	/** Output configuration. */
+	public final Output output;
 
-	/**
-	 * Whether to consider additional ground motion model uncertainty, or not.
-	 * Currently this is only applicable when using the PEER NGA-West or
-	 * NGA-West2 {@link Gmm}s with USGS hazard models.
-	 */
-	public final boolean gmmUncertainty;
+	/** Deaggregation configuration. */
+	public final Deagg deagg;
 
-	/** The hazard output format. */
-	public final HazardFormat hazardFormat;
+	private CalcConfig(
+			Optional<Path> resource,
+			Curve curve,
+			Performance performance,
+			Output output,
+			Deagg deagg) {
 
-	/** The directory to write any results to. */
-	public final Path outputDir;
+		this.resource = resource;
+		this.curve = curve;
+		this.performance = performance;
+		this.output = output;
+		this.deagg = deagg;
+	}
 
 	/**
-	 * The number of results to write at a time. A larger number requires more
-	 * memory.
+	 * Hazard curve calculation configuration.
 	 */
-	public final int outputBatchSize;
+	public final static class Curve {
 
-	/**
-	 * Deaggregation configuration data.
-	 */
-	public final DeaggData deagg;
+		static final String ID = CalcConfig.ID + "." + Curve.class.getSimpleName();
 
-	/*
-	 * Iml fields preserved for toString() exclusively. Imls should be retrieved
-	 * using modelCurves() or logModelCurves().
-	 */
-	private final double[] defaultImls;
-	private final Map<Imt, double[]> customImls;
-	private final Map<Imt, XySequence> modelCurves;
-	private final Map<Imt, XySequence> logModelCurves;
+		/**
+		 * The probability distribution model to use when computing hazard
+		 * curves.
+		 * 
+		 * <p><b>Default:</b> {@link ExceedanceModel#TRUNCATION_UPPER_ONLY}
+		 */
+		public final ExceedanceModel exceedanceModel;
+		// TODO refactor to probabilityModel
 
-	private CalcConfig(
-			Path resource,
-			ExceedanceModel exceedanceModel,
-			double truncationLevel,
-			Set<Imt> imts,
-			boolean optimizeGrids,
-			int systemPartition,
-			boolean gmmUncertainty,
-			HazardFormat hazardFormat,
-			Path outputDir,
-			int outputBatchSize,
-			DeaggData deagg,
-			double[] defaultImls,
-			Map<Imt, double[]> customImls,
-			Map<Imt, XySequence> modelCurves,
-			Map<Imt, XySequence> logModelCurves) {
+		/**
+		 * The number of standard deviations (σ) at which to truncate a
+		 * distribution. This field is ignored if an {@link ExceedanceModel}
+		 * does not implement truncation.
+		 * 
+		 * <p><b>Default:</b> {@code 3.0}
+		 */
+		public final double truncationLevel;
 
-		this.resource = resource;
-		this.exceedanceModel = exceedanceModel;
-		this.truncationLevel = truncationLevel;
-		this.imts = imts;
-		this.optimizeGrids = optimizeGrids;
-		this.systemPartition = systemPartition;
-		this.gmmUncertainty = gmmUncertainty;
-		this.hazardFormat = hazardFormat;
-		this.outputDir = outputDir;
-		this.outputBatchSize = outputBatchSize;
-		this.deagg = deagg;
-		this.defaultImls = defaultImls;
-		this.customImls = customImls;
-		this.modelCurves = modelCurves;
-		this.logModelCurves = logModelCurves;
-	}
+		/**
+		 * The {@code Set} of IMTs for which calculations should be performed.
+		 *
+		 * <p><b>Default:</b> [{@link Imt#PGA}, {@link Imt#SA0P2},
+		 * {@link Imt#SA1P0}]
+		 */
+		public final Set<Imt> imts;
 
-	private enum Key {
-		RESOURCE,
-		EXCEEDANCE_MODEL,
-		TRUNCATION_LEVEL,
-		IMTS,
-		OPTIMIZE_GRIDS,
-		GMM_UNCERTAINTY,
-		SYSTEM_PARTITION,
-		HAZARD_FORMAT,
-		OUTPUT_DIR,
-		OUTPUT_BATCH_SIZE,
-		DEAGG,
-		DEFAULT_IMLS,
-		CUSTOM_IMLS;
+		/**
+		 * Whether to consider additional ground motion model uncertainty, or
+		 * not. Currently this is only applicable when using the PEER NGA-West
+		 * or NGA-West2 {@link Gmm}s with USGS hazard models.
+		 * 
+		 * <p><b>Default:</b> {@code false}
+		 */
+		public final boolean gmmUncertainty;
 
-		private String label;
+		/**
+		 * The value format for hazard curves.
+		 * 
+		 * <p><b>Default:</b> {@link CurveValue#ANNUAL_RATE}
+		 */
+		public final CurveValue valueType;
+
+		private final double[] defaultImls;
+		private final Map<Imt, double[]> customImls;
+
+		private final Map<Imt, XySequence> modelCurves;
+		private final Map<Imt, XySequence> logModelCurves;
+
+		private Curve(
+				ExceedanceModel exceedanceModel,
+				double truncationLevel,
+				Set<Imt> imts,
+				boolean gmmUncertainty,
+				CurveValue valueType,
+				double[] defaultImls,
+				Map<Imt, double[]> customImls,
+				Map<Imt, XySequence> modelCurves,
+				Map<Imt, XySequence> logModelCurves) {
+
+			this.exceedanceModel = exceedanceModel;
+			this.truncationLevel = truncationLevel;
+			this.imts = imts;
+			this.gmmUncertainty = gmmUncertainty;
+			this.valueType = valueType;
+
+			this.defaultImls = defaultImls;
+			this.customImls = customImls;
+			this.modelCurves = modelCurves;
+			this.logModelCurves = logModelCurves;
+		}
 
-		private Key() {
-			this.label = UPPER_UNDERSCORE.to(LOWER_CAMEL, name());
+		/**
+		 * An empty linear curve for the requested {@code Imt}.
+		 * @param imt to get curve for
+		 */
+		public XySequence modelCurve(Imt imt) {
+			return modelCurves.get(imt);
 		}
 
-		@Override
-		public String toString() {
-			return label;
+		/**
+		 * An immutable map of model curves where x-values are in linear space.
+		 */
+		public Map<Imt, XySequence> modelCurves() {
+			return modelCurves;
 		}
-	}
 
-	@Override
-	public String toString() {
-		String customImlStr = "";
-		if (!customImls.isEmpty()) {
-			StringBuilder sb = new StringBuilder();
-			for (Entry<Imt, double[]> entry : customImls.entrySet()) {
-				String imtStr = "(override) " + entry.getKey().name();
-				sb.append(format(imtStr)).append(wrap(Arrays.toString(entry.getValue())));
+		/**
+		 * An immutable map of model curves where x-values are in natural-log
+		 * space.
+		 */
+		public Map<Imt, XySequence> logModelCurves() {
+			return logModelCurves;
+		}
+
+		private StringBuilder asString() {
+			StringBuilder imlSb = new StringBuilder();
+			if (!customImls.isEmpty()) {
+				for (Entry<Imt, double[]> entry : customImls.entrySet()) {
+					String imtStr = "imls (" + entry.getKey().name() + ")";
+					imlSb.append(formatEntry(imtStr))
+						.append(wrap(Arrays.toString(entry.getValue()), false));
+				}
 			}
-			customImlStr = sb.toString();
+			return new StringBuilder()
+				.append(formatGroup("Curve"))
+				.append(formatEntry(Key.EXCEEDANCE_MODEL, exceedanceModel))
+				.append(formatEntry(Key.TRUNCATION_LEVEL, truncationLevel))
+				.append(formatEntry(Key.IMTS, enumsToString(imts, Imt.class)))
+				.append(formatEntry(Key.GMM_UNCERTAINTY, gmmUncertainty))
+				.append(formatEntry(Key.VALUE_TYPE, valueType))
+				.append(formatEntry(Key.DEFAULT_IMLS, wrap(Arrays.toString(defaultImls), false)))
+				.append(imlSb);
 		}
 
-		return new StringBuilder("Calc config:")
-			.append(format(Key.RESOURCE)).append(resource.toAbsolutePath().normalize())
-			.append(format(Key.EXCEEDANCE_MODEL)).append(exceedanceModel)
-			.append(format(Key.TRUNCATION_LEVEL)).append(truncationLevel)
-			.append(format(Key.IMTS)).append(Parsing.enumsToString(imts, Imt.class))
-			.append(format(Key.DEFAULT_IMLS)).append(wrap(Arrays.toString(defaultImls)))
-			.append(customImlStr)
-			.append(format(Key.OPTIMIZE_GRIDS)).append(optimizeGrids)
-			.append(format(Key.SYSTEM_PARTITION)).append(systemPartition)
-			.append(format(Key.GMM_UNCERTAINTY)).append(gmmUncertainty)
-			.append(format(Key.HAZARD_FORMAT)).append(hazardFormat)
-			.append(format(Key.OUTPUT_DIR)).append(outputDir.toAbsolutePath().normalize())
-			.append(format(Key.OUTPUT_BATCH_SIZE)).append(outputBatchSize)
-			.append(format("Deaggregation R"))
-			.append("min=").append(deagg.rMin).append(", ")
-			.append("max=").append(deagg.rMax).append(", ")
-			.append("Δ=").append(deagg.Δr)
-			.append(format("Deaggregation M"))
-			.append("min=").append(deagg.mMin).append(", ")
-			.append("max=").append(deagg.mMax).append(", ")
-			.append("Δ=").append(deagg.Δm)
-			.append(format("Deaggregation ε"))
-			.append("min=").append(deagg.εMin).append(", ")
-			.append("max=").append(deagg.εMax).append(", ")
-			.append("Δ=").append(deagg.Δε)
-			.toString();
-	}
+		private static final class Builder {
+
+			ExceedanceModel exceedanceModel;
+			Double truncationLevel;
+			Set<Imt> imts;
+			Boolean gmmUncertainty;
+			CurveValue valueType;
+			double[] defaultImls;
+			Map<Imt, double[]> customImls;
+
+			Curve build() {
+				return new Curve(
+					exceedanceModel,
+					truncationLevel,
+					Sets.immutableEnumSet(imts),
+					gmmUncertainty,
+					valueType,
+					defaultImls,
+					customImls,
+					createCurveMap(),
+					createLogCurveMap());
+			}
 
-	/**
-	 * An empty linear curve for the requested {@code Imt}.
-	 * @param imt to get curve for
-	 */
-	public XySequence modelCurve(Imt imt) {
-		return modelCurves.get(imt);
+			void copy(Curve that) {
+				this.exceedanceModel = that.exceedanceModel;
+				this.truncationLevel = that.truncationLevel;
+				this.imts = that.imts;
+				this.gmmUncertainty = that.gmmUncertainty;
+				this.valueType = that.valueType;
+				this.defaultImls = that.defaultImls;
+				this.customImls = that.customImls;
+			}
+
+			void extend(Builder that) {
+				if (that.exceedanceModel != null) this.exceedanceModel = that.exceedanceModel;
+				if (that.truncationLevel != null) this.truncationLevel = that.truncationLevel;
+				if (that.imts != null) this.imts = that.imts;
+				if (that.gmmUncertainty != null) this.gmmUncertainty = that.gmmUncertainty;
+				if (that.valueType != null) this.valueType = that.valueType;
+				if (that.defaultImls != null) this.defaultImls = that.defaultImls;
+				if (that.customImls != null) this.customImls = that.customImls;
+			}
+
+			static Builder defaults() {
+				Builder b = new Builder();
+				b.exceedanceModel = ExceedanceModel.TRUNCATION_UPPER_ONLY;
+				b.truncationLevel = 3.0;
+				b.imts = EnumSet.of(Imt.PGA, Imt.SA0P2, Imt.SA1P0);
+				b.gmmUncertainty = false;
+				b.valueType = CurveValue.ANNUAL_RATE;
+				// Slightly modified version of NSHM 5Hz curve, size = 20
+				b.defaultImls = new double[] { 0.0025, 0.0045, 0.0075, 0.0113, 0.0169, 0.0253,
+						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 };
+				b.customImls = Maps.newHashMap();
+				return b;
+			}
+
+			void validate() {
+				checkNotNull(exceedanceModel, STATE_ERROR, Curve.ID, Key.EXCEEDANCE_MODEL);
+				checkNotNull(truncationLevel, STATE_ERROR, Curve.ID, Key.TRUNCATION_LEVEL);
+				checkNotNull(imts, STATE_ERROR, Curve.ID, Key.IMTS);
+				checkNotNull(defaultImls, STATE_ERROR, Curve.ID, Key.DEFAULT_IMLS);
+				checkNotNull(customImls, STATE_ERROR, Curve.ID, Key.CUSTOM_IMLS);
+			}
+
+			Map<Imt, XySequence> createLogCurveMap() {
+				Map<Imt, XySequence> curveMap = Maps.newEnumMap(Imt.class);
+				for (Imt imt : imts) {
+					double[] imls = imlsForImt(imt);
+					imls = Arrays.copyOf(imls, imls.length);
+					Data.ln(imls);
+					curveMap.put(imt, immutableCopyOf(create(imls, null)));
+				}
+				return Maps.immutableEnumMap(curveMap);
+			}
+
+			Map<Imt, XySequence> createCurveMap() {
+				Map<Imt, XySequence> curveMap = Maps.newEnumMap(Imt.class);
+				for (Imt imt : imts) {
+					double[] imls = imlsForImt(imt);
+					imls = Arrays.copyOf(imls, imls.length);
+					curveMap.put(imt, immutableCopyOf(create(imls, null)));
+				}
+				return Maps.immutableEnumMap(curveMap);
+			}
+
+			double[] imlsForImt(Imt imt) {
+				return customImls.containsKey(imt) ? customImls.get(imt) : defaultImls;
+			}
+		}
 	}
 
 	/**
-	 * An immutable map of model curves where x-values are in linear space.
+	 * Performance and optimization settings.
 	 */
-	public Map<Imt, XySequence> modelCurves() {
-		return modelCurves;
+	public static final class Performance {
+
+		static final String ID = CalcConfig.ID + "." + Performance.class.getSimpleName();
+
+		/**
+		 * Whether to optimize grid source sets, or not.
+		 * 
+		 * <p><b>Default:</b> {@code true}
+		 */
+		public final boolean optimizeGrids;
+
+		/**
+		 * Whether to collapse/combine magnitude-frequency distributions, or
+		 * not. Doing so prevents uncertainty analysis as logic-tree branches
+		 * are obscured.
+		 * 
+		 * <p><b>Default:</b> {@code true}
+		 */
+		public final boolean collapseMfds;
+
+		/**
+		 * The partition or batch size to use when distributing
+		 * {@link SourceType#SYSTEM} calculations.
+		 * 
+		 * <p><b>Default:</b> {@code 1000}
+		 */
+		public final int systemPartition;
+
+		/**
+		 * The number of threads to use when distributing calculations.
+		 * 
+		 * <p><b>Default:</b> {@link ThreadCount#ALL}
+		 */
+		public final ThreadCount threadCount;
+
+		private Performance(
+				boolean optimizeGrids,
+				boolean collapseMfds,
+				int systemPartition,
+				ThreadCount threadCount) {
+
+			this.optimizeGrids = optimizeGrids;
+			this.collapseMfds = collapseMfds;
+			this.systemPartition = systemPartition;
+			this.threadCount = threadCount;
+		}
+
+		private StringBuilder asString() {
+			return new StringBuilder()
+				.append(formatGroup("Performance"))
+				.append(formatEntry(Key.OPTIMIZE_GRIDS, optimizeGrids))
+				.append(formatEntry(Key.COLLAPSE_MFDS, collapseMfds))
+				.append(formatEntry(Key.SYSTEM_PARTITION, systemPartition))
+				.append(formatEntry(Key.THREAD_COUNT, threadCount));
+		}
+
+		private static final class Builder {
+
+			Boolean optimizeGrids;
+			Boolean collapseMfds;
+			Integer systemPartition;
+			ThreadCount threadCount;
+
+			Performance build() {
+				return new Performance(
+					optimizeGrids,
+					collapseMfds,
+					systemPartition,
+					threadCount);
+			}
+
+			void copy(Performance that) {
+				this.optimizeGrids = that.optimizeGrids;
+				this.collapseMfds = that.collapseMfds;
+				this.systemPartition = that.systemPartition;
+				this.threadCount = that.threadCount;
+			}
+
+			void extend(Builder that) {
+				if (that.optimizeGrids != null) this.optimizeGrids = that.optimizeGrids;
+				if (that.collapseMfds != null) this.collapseMfds = that.collapseMfds;
+				if (that.systemPartition != null) this.systemPartition = that.systemPartition;
+				if (that.threadCount != null) this.threadCount = that.threadCount;
+			}
+
+			static Builder defaults() {
+				Builder b = new Builder();
+				b.optimizeGrids = true;
+				b.collapseMfds = true;
+				b.systemPartition = 1000;
+				b.threadCount = ThreadCount.ALL;
+				return b;
+			}
+
+			void validate() {
+				checkNotNull(optimizeGrids, STATE_ERROR, Performance.ID, Key.OPTIMIZE_GRIDS);
+				checkNotNull(collapseMfds, STATE_ERROR, Performance.ID, Key.COLLAPSE_MFDS);
+				checkNotNull(systemPartition, STATE_ERROR, Performance.ID, Key.SYSTEM_PARTITION);
+				checkNotNull(threadCount, STATE_ERROR, Performance.ID, Key.THREAD_COUNT);
+			}
+		}
 	}
 
 	/**
-	 * An immutable map of model curves where x-values are in natural-log space.
+	 * Hazard curve and file output settings.
 	 */
-	public Map<Imt, XySequence> logModelCurves() {
-		return logModelCurves;
+	public static final class Output {
+
+		static final String ID = CalcConfig.ID + "." + Output.class.getSimpleName();
+
+		/**
+		 * The directory to write any results to.
+		 * 
+		 * <p><b>Default:</b> {@code "curves"}
+		 */
+		public final Path directory;
+
+		/**
+		 * The different {@linkplain CurveType types} of curves to save. Note
+		 * that {@link CurveType#TOTAL} will <i>always</i> be included in this
+		 * set, regardless of any user settings.
+		 * 
+		 * <p><b>Default:</b> [{@link CurveType#TOTAL}]
+		 */
+		public final Set<CurveType> curveTypes;
+
+		/**
+		 * The number of results (one per {@code Site}) to store before writing
+		 * to file(s). A larger number requires more memory.
+		 * 
+		 * <p><b>Default:</b> {@code 20}
+		 */
+		public final int flushLimit;
+
+		private Output(
+				Path directory,
+				Set<CurveType> curveTypes,
+				int flushLimit) {
+
+			this.directory = directory;
+			curveTypes.add(CurveType.TOTAL);
+			this.curveTypes = Sets.immutableEnumSet(curveTypes);
+			this.flushLimit = flushLimit;
+		}
+
+		private StringBuilder asString() {
+			return new StringBuilder()
+				.append(formatGroup("Output"))
+				.append(formatEntry(Key.DIRECTORY, directory.toAbsolutePath().normalize()))
+				.append(formatEntry(Key.CURVE_TYPES, enumsToString(curveTypes, CurveType.class)))
+				.append(formatEntry(Key.FLUSH_LIMIT, flushLimit));
+		}
+
+		private static final class Builder {
+
+			Path directory;
+			Set<CurveType> curveTypes;
+			Integer flushLimit;
+
+			Output build() {
+				return new Output(
+					directory,
+					curveTypes,
+					flushLimit);
+			}
+
+			void copy(Output that) {
+				this.directory = that.directory;
+				this.curveTypes = that.curveTypes;
+				this.flushLimit = that.flushLimit;
+			}
+
+			void extend(Builder that) {
+				if (that.directory != null) this.directory = that.directory;
+				if (that.curveTypes != null) this.curveTypes = that.curveTypes;
+				if (that.flushLimit != null) this.flushLimit = that.flushLimit;
+			}
+
+			static Builder defaults() {
+				Builder b = new Builder();
+				b.directory = Paths.get(DEFAULT_OUT);
+				b.curveTypes = EnumSet.of(CurveType.TOTAL);
+				b.flushLimit = 20;
+				return b;
+			}
+
+			void validate() {
+				checkNotNull(directory, STATE_ERROR, Performance.ID, Key.DIRECTORY);
+				checkNotNull(curveTypes, STATE_ERROR, Performance.ID, Key.CURVE_TYPES);
+				checkNotNull(flushLimit, STATE_ERROR, Performance.ID, Key.FLUSH_LIMIT);
+			}
+		}
 	}
 
 	/**
 	 * Deaggregation configuration data container.
 	 */
-	@SuppressWarnings("javadoc")
-	public static final class DeaggData {
+	public static final class Deagg {
 
+		static final String ID = CalcConfig.ID + "." + Deagg.class.getSimpleName();
+
+		/** Minimum distance. Lower edge of smallest distance bin. */
 		public final double rMin;
+
+		/** Maximum distance. Upper edge of largest distance bin. */
 		public final double rMax;
+
+		/** Distance bin width. */
 		public final double Δr;
 
+		/** Minimum magnitude. Lower edge of smallest magnitude bin. */
 		public final double mMin;
+
+		/** Maximum magnitude. Upper edge of largest magnitude bin. */
 		public final double mMax;
+
+		/** Magnitude bin width. */
 		public final double Δm;
 
+		/** Minimum epsilon. Lower edge of smallest epsilon bin. */
 		public final double εMin;
+
+		/** Maximum epsilon. Upper edge of largest epsilon bin. */
 		public final double εMax;
+
+		/** Epsilon bin width. */
 		public final double Δε;
 
-		DeaggData() {
+		Deagg() {
 			rMin = 0.0;
 			rMax = 100.0;
 			Δr = 10.0;
-
 			mMin = 5.0;
 			mMax = 7.0;
 			Δm = 0.1;
-
 			εMin = -3;
 			εMax = 3.0;
 			Δε = 0.5;
 		}
+
+		private StringBuilder asString() {
+			return new StringBuilder()
+				.append(formatGroup("Deaggregation"))
+				.append(formatEntry("R"))
+				.append("min=").append(rMin).append(", ")
+				.append("max=").append(rMax).append(", ")
+				.append("Δ=").append(Δr)
+				.append(formatEntry("M"))
+				.append("min=").append(mMin).append(", ")
+				.append("max=").append(mMax).append(", ")
+				.append("Δ=").append(Δm)
+				.append(formatEntry("ε"))
+				.append("min=").append(εMin).append(", ")
+				.append("max=").append(εMax).append(", ")
+				.append("Δ=").append(Δε);
+		}
+	}
+
+	private enum Key {
+		RESOURCE,
+		/* curve */
+		EXCEEDANCE_MODEL,
+		TRUNCATION_LEVEL,
+		IMTS,
+		GMM_UNCERTAINTY,
+		VALUE_TYPE,
+		DEFAULT_IMLS,
+		CUSTOM_IMLS,
+		/* performance */
+		OPTIMIZE_GRIDS,
+		COLLAPSE_MFDS,
+		SYSTEM_PARTITION,
+		THREAD_COUNT,
+		/* output */
+		DIRECTORY,
+		CURVE_TYPES,
+		FLUSH_LIMIT,
+		/* deagg */
+		DEAGG;
+
+		private String label;
+
+		private Key() {
+			this.label = UPPER_UNDERSCORE.to(LOWER_CAMEL, name());
+		}
+
+		@Override
+		public String toString() {
+			return label;
+		}
+	}
+
+	@Override
+	public String toString() {
+		return new StringBuilder(padEnd("Calc Configuration:", VALUE_INDENT_SIZE, ' '))
+			.append(resource.isPresent()
+				? resource.get().toAbsolutePath().normalize()
+				: "(from defaults)")
+			.append(curve.asString())
+			.append(performance.asString())
+			.append(output.asString())
+			.append(deagg.asString())
+			.toString();
+	}
+
+	// public static <E extends Enum<E>> String format(E id) {
+	// return format(id.toString());
+	// }
+
+	private static final int GROUP_INDENT_SIZE = 8;
+	private static final int KEY_INDENT_SIZE = 10;
+	private static final int VALUE_INDENT_SIZE = 28;
+	private static final int MAX_COL = 100;
+	private static final int VALUE_WIDTH = MAX_COL - VALUE_INDENT_SIZE;
+	private static final String S = " ";
+	private static final String GROUP_INDENT = repeat(S, GROUP_INDENT_SIZE);
+	private static final String KEY_INDENT = repeat(S, KEY_INDENT_SIZE);
+	private static final String VALUE_INDENT = repeat(S, VALUE_INDENT_SIZE);
+
+	private static String formatGroup(String group) {
+		return TextUtils.NEWLINE + GROUP_INDENT + group;
+	}
+
+	private static String formatEntry(String key) {
+		return NEWLINE + padEnd(KEY_INDENT + '.' + key + ':', VALUE_INDENT_SIZE, ' ');
+	}
+
+	private static <E extends Enum<E>> String formatEntry(E key, Object value) {
+		return NEWLINE + padEnd(KEY_INDENT + '.' + key + ':', VALUE_INDENT_SIZE, ' ') + value;
+	}
+
+	/* wrap a commma-delimited string */
+	private static String wrap(String s, boolean pad) {
+		if (s.length() <= VALUE_WIDTH) return pad ? NEWLINE + VALUE_INDENT + s : s;
+		StringBuilder sb = new StringBuilder();
+		int lastCommaIndex = s.substring(0, VALUE_WIDTH).lastIndexOf(',') + 1;
+		if (pad) sb.append(NEWLINE).append(VALUE_INDENT);
+		sb.append(s.substring(0, lastCommaIndex));
+		sb.append(wrap(s.substring(lastCommaIndex).trim(), true));
+		return sb.toString();
 	}
 
 	private static final Gson GSON = new GsonBuilder()
@@ -278,101 +660,84 @@ public final class CalcConfig {
 		})
 		.create();
 
-	/**
-	 * Create a new calculation configuration builder from the resource at the
-	 * specified {@code path}.
-	 * 
-	 * @param path to configuration file or resource
-	 * @throws IOException
-	 */
-	public static Builder builder(Path path) throws IOException {
-		checkNotNull(path);
-		// TODO test with zip files
-		Path configPath = Files.isDirectory(path) ? path.resolve(FILE_NAME) : path;
-		Reader reader = Files.newBufferedReader(configPath, UTF_8);
-		Builder configBuilder = GSON.fromJson(reader, Builder.class);
-		configBuilder.resource = configPath;
-		reader.close();
-		return configBuilder;
-	}
-
+	// TODO clean
 	public static void main(String[] args) throws IOException {
-		CalcConfig cc = builder()
+
+		// CalcConfig cc =
+		// Builder.fromFile(Paths.get("etc/examples/5-complex-model/config-sites.json")).build();
+		// System.out.println(cc);
+
+		CalcConfig cc = Builder
 			.withDefaults()
-			.extend(builder(Paths.get("etc/examples/5-complex-model/config-sites.json")))
+			// .extend(Builder.fromFile(Paths.get("etc/examples/6-enhanced-output/config.json")))
+			.extend(Builder.fromFile(Paths.get("etc/examples/2-custom-config/config.json")))
 			.build();
 		System.out.println(cc);
 	}
 
 	/**
-	 * Create a new empty calculation configuration builder.
+	 * A builder of configuration instances.
 	 */
-	public static Builder builder() {
-		return new Builder();
-	}
-
 	public static final class Builder {
 
-		private static final String ID = "CalcConfig.Builder";
 		private boolean built = false;
 
 		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 Integer systemPartition;
-		private Boolean gmmUncertainty;
-		private HazardFormat hazardFormat;
-		private Path outputDir;
-		private Integer outputBatchSize;
-		private DeaggData deagg;
-
-		private Builder() {}
+		private Curve.Builder curve;
+		private Performance.Builder performance;
+		private Output.Builder output;
+		private Deagg deagg;
+
+		private Builder() {
+			curve = new Curve.Builder();
+			performance = new Performance.Builder();
+			output = new Output.Builder();
+		}
 
 		/**
-		 * Initialize a new builder with a copy of that supplied.
+		 * Initialize a new builder with values copied from the supplied config.
 		 */
-		public Builder copy(CalcConfig config) {
-			checkNotNull(config);
-			this.resource = config.resource;
-			this.exceedanceModel = config.exceedanceModel;
-			this.truncationLevel = config.truncationLevel;
-			this.imts = config.imts;
-			this.defaultImls = config.defaultImls;
-			this.customImls = config.customImls;
-			this.optimizeGrids = config.optimizeGrids;
-			this.systemPartition = config.systemPartition;
-			this.gmmUncertainty = config.gmmUncertainty;
-			this.hazardFormat = config.hazardFormat;
-			this.outputDir = config.outputDir;
-			this.outputBatchSize = config.outputBatchSize;
-			this.deagg = config.deagg;
-			return this;
+		public static Builder copyOf(CalcConfig config) {
+			Builder b = new Builder();
+			if (config.resource.isPresent()) {
+				b.resource = config.resource.get();
+			}
+			b.curve.copy(config.curve);
+			b.performance.copy(config.performance);
+			b.output.copy(config.output);
+			b.deagg = config.deagg;
+			return b;
 		}
 
 		/**
-		 * Initialize a new builder with defaults.
+		 * Create a new builder from the resource at the specified path. This
+		 * will only set those fields that are explicitely defined.
+		 * 
+		 * @param path to configuration file or resource
+		 * @throws IOException
 		 */
-		public Builder withDefaults() {
-			this.exceedanceModel = ExceedanceModel.TRUNCATION_UPPER_ONLY;
-			this.truncationLevel = 3.0;
-			this.imts = EnumSet.of(Imt.PGA, Imt.SA0P2, Imt.SA1P0);
-			// Slightly modified version of NSHM 5Hz curve, size = 20
-			this.defaultImls = new double[] { 0.0025, 0.0045, 0.0075, 0.0113, 0.0169, 0.0253,
-					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.systemPartition = 1000;
-			this.gmmUncertainty = false;
-			this.hazardFormat = HazardFormat.TOTAL;
-			this.outputDir = Paths.get("curves");
-			this.outputBatchSize = 20;
-			this.deagg = new DeaggData();
-			return this;
+		public static Builder fromFile(Path path) throws IOException {
+			checkNotNull(path);
+			// TODO test with zip files
+			Path configPath = Files.isDirectory(path) ? path.resolve(FILE_NAME) : path;
+			Reader reader = Files.newBufferedReader(configPath, UTF_8);
+			Builder b = GSON.fromJson(reader, Builder.class);
+			reader.close();
+			b.resource = configPath;
+			return b;
+		}
+
+		/**
+		 * Initialize a new builder with all fields initialized to default
+		 * values.
+		 */
+		public static Builder withDefaults() {
+			Builder b = new Builder();
+			b.curve = Curve.Builder.defaults();
+			b.performance = Performance.Builder.defaults();
+			b.output = Output.Builder.defaults();
+			b.deagg = new Deagg();
+			return b;
 		}
 
 		/**
@@ -381,18 +746,10 @@ public final class CalcConfig {
 		 */
 		public Builder extend(final Builder that) {
 			checkNotNull(that);
-			if (that.resource != null) this.resource = that.resource;
-			if (that.exceedanceModel != null) this.exceedanceModel = that.exceedanceModel;
-			if (that.truncationLevel != null) this.truncationLevel = that.truncationLevel;
-			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.systemPartition != null) this.systemPartition = that.systemPartition;
-			if (that.gmmUncertainty != null) this.gmmUncertainty = that.gmmUncertainty;
-			if (that.hazardFormat != null) this.hazardFormat = that.hazardFormat;
-			if (that.outputDir != null) this.outputDir = that.outputDir;
-			if (that.outputBatchSize != null) this.outputBatchSize = that.outputBatchSize;
+			this.resource = that.resource;
+			this.curve.extend(that.curve);
+			this.performance.extend(that.performance);
+			this.output.extend(that.output);
 			if (that.deagg != null) this.deagg = that.deagg;
 			return this;
 		}
@@ -401,51 +758,16 @@ public final class CalcConfig {
 		 * Set the IMTs for which results should be calculated.
 		 */
 		public Builder imts(Set<Imt> imts) {
-			this.imts = checkNotNull(imts);
+			this.curve.imts = checkNotNull(imts);
 			return this;
 		}
 
-		private Map<Imt, XySequence> createLogCurveMap() {
-			Map<Imt, XySequence> curveMap = Maps.newEnumMap(Imt.class);
-			for (Imt imt : imts) {
-				double[] imls = imlsForImt(imt);
-				imls = Arrays.copyOf(imls, imls.length);
-				Data.ln(imls);
-				curveMap.put(imt, immutableCopyOf(create(imls, null)));
-			}
-			return Maps.immutableEnumMap(curveMap);
-		}
-
-		private Map<Imt, XySequence> createCurveMap() {
-			Map<Imt, XySequence> curveMap = Maps.newEnumMap(Imt.class);
-			for (Imt imt : imts) {
-				double[] imls = imlsForImt(imt);
-				imls = Arrays.copyOf(imls, imls.length);
-				curveMap.put(imt, immutableCopyOf(create(imls, null)));
-			}
-			return Maps.immutableEnumMap(curveMap);
-		}
-
-		private double[] imlsForImt(Imt imt) {
-			return customImls.containsKey(imt) ? customImls.get(imt) : defaultImls;
-		}
-
-		private static final String MSSG = "%s %s not set";
-
-		private void validateState(String buildId) {
-			checkState(!built, "This %s instance as already been used", buildId);
-			checkNotNull(exceedanceModel, MSSG, buildId, Key.EXCEEDANCE_MODEL);
-			checkNotNull(truncationLevel, MSSG, buildId, Key.TRUNCATION_LEVEL);
-			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(systemPartition, MSSG, buildId, Key.SYSTEM_PARTITION);
-			checkNotNull(gmmUncertainty, MSSG, buildId, Key.GMM_UNCERTAINTY);
-			checkNotNull(hazardFormat, MSSG, buildId, Key.HAZARD_FORMAT);
-			checkNotNull(outputDir, MSSG, buildId, Key.OUTPUT_DIR);
-			checkNotNull(outputBatchSize, MSSG, buildId, Key.OUTPUT_BATCH_SIZE);
-			checkNotNull(deagg, MSSG, buildId, Key.DEAGG);
+		private void validateState() {
+			checkState(!built, "This %s instance as already been used", ID + ".Builder");
+			curve.validate();
+			performance.validate();
+			output.validate();
+			checkNotNull(deagg, STATE_ERROR, Deagg.ID, "deagg");
 			built = true;
 		}
 
@@ -453,36 +775,14 @@ public final class CalcConfig {
 		 * Build a new calculation configuration.
 		 */
 		public CalcConfig build() {
-			validateState(ID);
+			validateState();
 			return new CalcConfig(
-				resource,
-				exceedanceModel,
-				truncationLevel,
-				Sets.immutableEnumSet(imts),
-				optimizeGrids,
-				systemPartition,
-				gmmUncertainty,
-				hazardFormat,
-				outputDir,
-				outputBatchSize,
-				deagg,
-				defaultImls,
-				customImls,
-				createCurveMap(),
-				createLogCurveMap());
+				Optional.fromNullable(resource),
+				curve.build(),
+				performance.build(),
+				output.build(),
+				deagg);
 		}
 	}
 
-	// static final class PathDeserializer implements JsonDeserializer<Path> {
-	//
-	// @Override
-	// public Path deserialize(
-	// JsonElement json,
-	// Type type,
-	// JsonDeserializationContext context) {
-	//
-	//
-	// }
-	// }
-
 }
diff --git a/src/org/opensha2/calc/Calcs.java b/src/org/opensha2/calc/Calcs.java
index bf441b90b..df2a65388 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.performance.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.performance.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/CurveType.java b/src/org/opensha2/calc/CurveType.java
new file mode 100644
index 000000000..a7172ba86
--- /dev/null
+++ b/src/org/opensha2/calc/CurveType.java
@@ -0,0 +1,31 @@
+package org.opensha2.calc;
+
+import org.opensha2.eq.model.SourceType;
+import org.opensha2.gmm.Gmm;
+
+/**
+ * Curve type identifiers. These are used to specify the different types of
+ * hazard curves that should be saved after a calculation is complete.
+ *
+ * @author Peter Powers
+ */
+public enum CurveType {
+
+	/** Total mean hazard curves. */
+	TOTAL,
+
+	/** {@linkplain Gmm Ground motion model} curves. */
+	GMM,
+
+	/** Hazard curves by {@link SourceType} */
+	SOURCE,
+
+	/**
+	 * Binary hazard curves. Binary curves may only be saved for map
+	 * calculations for which a map 'extents' region has been defined. See the
+	 * <a href=
+	 * "https://github.com/usgs/nshmp-haz/wiki/Sites#geojson-format-geojson"
+	 * target="_top"> site specification</a> page for more details.
+	 */
+	BINARY;
+}
diff --git a/src/org/opensha2/calc/Deaggregation.java b/src/org/opensha2/calc/Deaggregation.java
index 45f748cf8..0a34f6591 100644
--- a/src/org/opensha2/calc/Deaggregation.java
+++ b/src/org/opensha2/calc/Deaggregation.java
@@ -16,7 +16,7 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
 
-import org.opensha2.calc.CalcConfig.DeaggData;
+import org.opensha2.calc.CalcConfig;
 import org.opensha2.data.Data;
 import org.opensha2.data.DataTable;
 import org.opensha2.data.DataTables;
@@ -221,8 +221,8 @@ public final class Deaggregation {
 				.dataModel(
 					Dataset.builder(hazard.config).build())
 				.probabilityModel(
-					hazard.config.exceedanceModel,
-					hazard.config.truncationLevel);
+					hazard.config.curve.exceedanceModel,
+					hazard.config.curve.truncationLevel);
 		}
 
 		/* Reusable builder */
@@ -908,7 +908,7 @@ public final class Deaggregation {
 		 * @see CalcConfig
 		 */
 		static Builder builder(CalcConfig config) {
-			DeaggData d = config.deagg;
+			CalcConfig.Deagg 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 935fef1c8..d2ed5a780 100644
--- a/src/org/opensha2/calc/Hazard.java
+++ b/src/org/opensha2/calc/Hazard.java
@@ -72,7 +72,8 @@ public final class Hazard {
 						sb.append(curveSet.hazardGroundMotionsList.get(0).inputs.size());
 						break;
 					case GRID:
-						if (ss instanceof GridSourceSet.Table && config.optimizeGrids) {
+						boolean optimized = config.performance.optimizeGrids;
+						if (ss instanceof GridSourceSet.Table && optimized) {
 							GridSourceSet.Table gsst = (GridSourceSet.Table) curveSet.sourceSet;
 							sb.append(gsst.parentCount());
 							sb.append(" (").append(curveSet.hazardGroundMotionsList.size());
@@ -143,7 +144,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.curve.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 8ab56900f..1b94ad464 100644
--- a/src/org/opensha2/calc/Results.java
+++ b/src/org/opensha2/calc/Results.java
@@ -43,30 +43,6 @@ public class Results {
 	private static final String CURVE_FILE_SUFFIX = ".csv";
 	private static final String RATE_FMT = "%.8e";
 
-	/**
-	 * Hazard output format.
-	 */
-	public enum HazardFormat {
-
-		/** Total mean hazard only. */
-		TOTAL,
-
-		/** Additional curves by {@link Gmm} and {@link SourceType}. */
-		DETAILED;
-	}
-	
-	/**
-	 * Curve format.
-	 */
-	public enum CurveFormat {
-		
-		/** Write curves as annual-rate. */
-		ANNUAL_RATE,
-		
-		/** Write curves as Poisson probabilities. */
-		POISSON;
-	}
-
 	/**
 	 * Write a {@code batch} of {@code HazardResult}s to files in the specified
 	 * directory, one for each {@link Imt} in the {@code batch}. See
@@ -105,7 +81,7 @@ public class Results {
 				headings.add("lat");
 				Iterable<?> header = Iterables.concat(
 					headings,
-					demo.config.modelCurves().get(imt).xValues());
+					demo.config.curve.modelCurves().get(imt).xValues());
 				lineList.add(Parsing.join(header, Delimiter.COMMA));
 			}
 			lineMap.put(imt, lineList);
@@ -170,7 +146,7 @@ public class Results {
 		Hazard demo = batch.get(0);
 		boolean newFile = options.length == 0;
 		boolean namedSites = demo.site.name != Site.NO_NAME;
-		boolean detailed = demo.config.hazardFormat.equals(HazardFormat.DETAILED);
+		boolean detailed = false; //demo.config.hazardFormat.equals(HazardFormat.DETAILED);
 
 		Map<Imt, List<String>> totalLineMap = Maps.newEnumMap(Imt.class);
 		Map<Imt, Map<SourceType, List<String>>> typeLineMap = Maps.newEnumMap(Imt.class);
@@ -182,7 +158,7 @@ public class Results {
 			if (newFile) {
 				Iterable<?> header = Iterables.concat(
 					Lists.newArrayList(namedSites ? "name" : null, "lon", "lat"),
-					demo.config.modelCurves().get(imt).xValues());
+					demo.config.curve.modelCurves().get(imt).xValues());
 				lines.add(Parsing.join(header, Delimiter.COMMA));
 			}
 			totalLineMap.put(imt, lines);
diff --git a/src/org/opensha2/calc/ThreadCount.java b/src/org/opensha2/calc/ThreadCount.java
index eaef8470f..cbae63135 100644
--- a/src/org/opensha2/calc/ThreadCount.java
+++ b/src/org/opensha2/calc/ThreadCount.java
@@ -14,7 +14,8 @@ public enum ThreadCount {
 	/**
 	 * A single thread. Use of a single thread will generally prevent an
 	 * {@link ExecutorService} from being used, and all calculations will be run
-	 * on the thread from which a program was called.
+	 * on the thread from which a program was called. This is useful for
+	 * debugging.
 	 */
 	ONE,
 
diff --git a/src/org/opensha2/calc/Transforms.java b/src/org/opensha2/calc/Transforms.java
index 6c27d5c22..3f44327a2 100644
--- a/src/org/opensha2/calc/Transforms.java
+++ b/src/org/opensha2/calc/Transforms.java
@@ -152,9 +152,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.curve.logModelCurves();
+			this.exceedanceModel = config.curve.exceedanceModel;
+			this.truncationLevel = config.curve.truncationLevel;
 		}
 
 		@Override
@@ -210,9 +210,9 @@ final class Transforms {
 
 		GroundMotionsToCurvesWithUncertainty(GmmSet gmmSet, CalcConfig config) {
 			this.gmmSet = gmmSet;
-			this.modelCurves = config.logModelCurves();
-			this.exceedanceModel = config.exceedanceModel;
-			this.truncationLevel = config.truncationLevel;
+			this.modelCurves = config.curve.logModelCurves();
+			this.exceedanceModel = config.curve.exceedanceModel;
+			this.truncationLevel = config.curve.truncationLevel;
 		}
 
 		@Override
@@ -307,12 +307,12 @@ final class Transforms {
 
 			GmmSet gmmSet = sources.groundMotionModels();
 			Map<Imt, Map<Gmm, GroundMotionModel>> gmmTable = instances(
-				config.imts,
+				config.curve.imts,
 				gmmSet.gmms());
 
 			this.sourceToInputs = new SourceToInputs(site);
 			this.inputsToGroundMotions = new InputsToGroundMotions(gmmTable);
-			this.groundMotionsToCurves = config.gmmUncertainty && gmmSet.epiUncertainty()
+			this.groundMotionsToCurves = config.curve.gmmUncertainty && gmmSet.epiUncertainty()
 				? new GroundMotionsToCurvesWithUncertainty(gmmSet, config)
 				: new GroundMotionsToCurves(config);
 		}
@@ -340,7 +340,7 @@ final class Transforms {
 				CalcConfig config) {
 
 			this.sources = sources;
-			this.modelCurves = config.logModelCurves();
+			this.modelCurves = config.curve.logModelCurves();
 		}
 
 		@Override
@@ -396,14 +396,14 @@ final class Transforms {
 
 			GmmSet gmmSet = sources.groundMotionModels();
 			Map<Imt, Map<Gmm, GroundMotionModel>> gmmTable = instances(
-				config.imts,
+				config.curve.imts,
 				gmmSet.gmms());
 
 			InputsToGroundMotions inputsToGm = new InputsToGroundMotions(gmmTable);
 			GroundMotions gms = inputsToGm.apply(inputs);
 
 			Function<GroundMotions, HazardCurves> gmToCurves =
-				config.gmmUncertainty && gmmSet.epiUncertainty()
+				config.curve.gmmUncertainty && gmmSet.epiUncertainty()
 					? new GroundMotionsToCurvesWithUncertainty(gmmSet, config)
 					: new GroundMotionsToCurves(config);
 			HazardCurves curves = gmToCurves.apply(gms);
@@ -447,7 +447,8 @@ final class Transforms {
 			// calculate curves from list in parallel
 			InputsToCurves inputsToCurves = new InputsToCurves(sources, config);
 			AsyncList<HazardCurves> asyncCurvesList = AsyncList.create();
-			for (InputList partition : master.partition(config.systemPartition)) {
+			int size = config.performance.systemPartition;
+			for (InputList partition : master.partition(size)) {
 				asyncCurvesList.add(transform(
 					immediateFuture(partition),
 					inputsToCurves,
@@ -481,11 +482,11 @@ final class Transforms {
 
 			GmmSet gmmSet = sources.groundMotionModels();
 			Map<Imt, Map<Gmm, GroundMotionModel>> gmmTable = instances(
-				config.imts,
+				config.curve.imts,
 				gmmSet.gmms());
 
 			this.inputsToGroundMotions = new InputsToGroundMotions(gmmTable);
-			this.groundMotionsToCurves = config.gmmUncertainty && gmmSet.epiUncertainty()
+			this.groundMotionsToCurves = config.curve.gmmUncertainty && gmmSet.epiUncertainty()
 				? new GroundMotionsToCurvesWithUncertainty(gmmSet, config)
 				: new GroundMotionsToCurves(config);
 		}
@@ -560,9 +561,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.curve.logModelCurves();
+			this.exceedanceModel = config.curve.exceedanceModel;
+			this.truncationLevel = config.curve.truncationLevel;
 		}
 
 		@Override
@@ -635,7 +636,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.curve.imts, gmms);
 
 			this.sourceToInputs = new ClusterSourceToInputs(site);
 			this.inputsToGroundMotions = new ClusterInputsToGroundMotions(gmmTable);
@@ -666,7 +667,7 @@ final class Transforms {
 				CalcConfig config) {
 
 			this.sources = sources;
-			this.modelCurves = config.logModelCurves();
+			this.modelCurves = config.curve.logModelCurves();
 		}
 
 		@Override
diff --git a/src/org/opensha2/eq/model/Loader.java b/src/org/opensha2/eq/model/Loader.java
index 5c4e141f4..6b950ccee 100644
--- a/src/org/opensha2/eq/model/Loader.java
+++ b/src/org/opensha2/eq/model/Loader.java
@@ -87,12 +87,11 @@ class Loader {
 			checkArgument(Files.exists(path), "Specified model does not exist: %s", path);
 			Path typeDirPath = typeDirectory(path);
 
-			ModelConfig modelConfig = ModelConfig.builder(typeDirPath).build();
+			ModelConfig modelConfig = ModelConfig.Builder.fromFile(typeDirPath).build();
 			log.info(modelConfig.toString());
 
-			CalcConfig calcConfig = CalcConfig.builder()
-					.withDefaults()
-					.extend(CalcConfig.builder(typeDirPath))
+			CalcConfig calcConfig = CalcConfig.Builder.withDefaults()
+					.extend(CalcConfig.Builder.fromFile(typeDirPath))
 					.build();
 			builder.config(calcConfig);
 
@@ -190,9 +189,8 @@ class Loader {
 		ModelConfig config = modelConfig;
 		Path configPath = typeDir.resolve(ModelConfig.FILE_NAME);
 		if (Files.exists(configPath)) {
-			config = ModelConfig.builder()
-				.copy(modelConfig)
-				.extend(ModelConfig.builder(configPath))
+			config = ModelConfig.Builder.copyOf(modelConfig)
+				.extend(ModelConfig.Builder.fromFile(configPath))
 				.build();
 			log.info("(override) " + config.toString());
 		}
@@ -262,9 +260,8 @@ class Loader {
 			// config
 			Path nestedConfigPath = sourceDir.resolve(ModelConfig.FILE_NAME);
 			if (Files.exists(nestedConfigPath)) {
-				nestedConfig = ModelConfig.builder()
-					.copy(parentConfig)
-					.extend(ModelConfig.builder(nestedConfigPath))
+				nestedConfig = ModelConfig.Builder.copyOf(parentConfig)
+					.extend(ModelConfig.Builder.fromFile(nestedConfigPath))
 					.build();
 				log.info("(override) " + nestedConfig.toString());
 			}
diff --git a/src/org/opensha2/eq/model/ModelConfig.java b/src/org/opensha2/eq/model/ModelConfig.java
index 3cc8ad6c9..edd4f00af 100644
--- a/src/org/opensha2/eq/model/ModelConfig.java
+++ b/src/org/opensha2/eq/model/ModelConfig.java
@@ -12,11 +12,15 @@ import java.io.Reader;
 import java.nio.file.Files;
 import java.nio.file.Path;
 
+import org.opensha2.calc.CalcConfig;
 import org.opensha2.eq.fault.surface.RuptureFloating;
 import org.opensha2.eq.model.AreaSource.GridScaling;
 
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonStreamParser;
 
 /**
  * Model and calculation configuration class. No defaults; 'config.json' must be
@@ -27,6 +31,9 @@ import com.google.gson.GsonBuilder;
 final class ModelConfig {
 
 	static final String FILE_NAME = "config.json";
+	private static final String ID = ModelConfig.class.getSimpleName();
+	private static final String STATE_ERROR = "%s %s not set";
+	private static final String ELEMENT_NAME = "model";
 
 	private static final Gson GSON = new GsonBuilder().create();
 
@@ -80,7 +87,7 @@ final class ModelConfig {
 
 	@Override
 	public String toString() {
-		return new StringBuilder("Model config:")
+		return new StringBuilder("Model Configuration:")
 			.append(format(Key.NAME)).append(name)
 			.append(format(Key.RESOURCE)).append(resource.toAbsolutePath().normalize())
 			.append(format(Key.SURFACE_SPACING)).append(surfaceSpacing)
@@ -91,31 +98,6 @@ final class ModelConfig {
 			.toString();
 	}
 
-	/**
-	 * Create a new model configuration builder from the resource at the
-	 * specified {@code path}.
-	 * 
-	 * @param path to configuration file or resource
-	 * @throws IOException
-	 */
-	static Builder builder(Path path) throws IOException {
-		// TODO test with zip files
-		checkNotNull(path);
-		Path configPath = Files.isDirectory(path) ? path.resolve(FILE_NAME) : path;
-		Reader reader = Files.newBufferedReader(configPath, UTF_8);
-		Builder configBuilder = GSON.fromJson(reader, Builder.class);
-		configBuilder.resource = configPath;
-		reader.close();
-		return configBuilder;
-	}
-
-	/**
-	 * Create a new empty model configuration builder.
-	 */
-	static Builder builder() {
-		return new Builder();
-	}
-
 	final static class Builder {
 
 		private static final String ID = "ModelConfig.Builder";
@@ -129,16 +111,38 @@ final class ModelConfig {
 		private PointSourceType pointSourceType;
 		private GridScaling areaGridScaling;
 
-		Builder copy(ModelConfig config) {
-			checkNotNull(config);
-			this.name = config.name;
-			this.resource = config.resource;
-			this.surfaceSpacing = config.surfaceSpacing;
-			this.ruptureFloating = config.ruptureFloating;
-			this.ruptureVariability = config.ruptureVariability;
-			this.pointSourceType = config.pointSourceType;
-			this.areaGridScaling = config.areaGridScaling;
-			return this;
+		static Builder copyOf(ModelConfig that) {
+			checkNotNull(that);
+			Builder b = new Builder();
+			b.name = that.name;
+			b.resource = that.resource;
+			b.surfaceSpacing = that.surfaceSpacing;
+			b.ruptureFloating = that.ruptureFloating;
+			b.ruptureVariability = that.ruptureVariability;
+			b.pointSourceType = that.pointSourceType;
+			b.areaGridScaling = that.areaGridScaling;
+			return b;
+		}
+
+		static Builder fromFile(Path path) throws IOException {
+			// TODO test with zip files
+			checkNotNull(path);
+			Path configPath = Files.isDirectory(path) ? path.resolve(FILE_NAME) : path;
+			Reader reader = Files.newBufferedReader(configPath, UTF_8);
+			JsonElement modelRoot = new JsonParser()
+				.parse(reader)
+				.getAsJsonObject()
+				.get(ELEMENT_NAME);
+			Builder configBuilder = GSON.fromJson(
+				checkNotNull(
+					modelRoot,
+					"'%s' element is missing from root of config: %s",
+					ELEMENT_NAME,
+					configPath),
+				Builder.class);
+			configBuilder.resource = configPath;
+			reader.close();
+			return configBuilder;
 		}
 
 		Builder extend(Builder that) {
@@ -153,20 +157,20 @@ final class ModelConfig {
 			return this;
 		}
 
-		private void validateState(String buildId) {
-			checkState(!built, "This %s instance has already been used", buildId);
-			checkNotNull(name, "%s %s not set", buildId, Key.NAME);
-			checkNotNull(resource, "%s %s not set", buildId, Key.RESOURCE);
-			checkNotNull(surfaceSpacing, "%s %s not set", buildId, Key.SURFACE_SPACING);
-			checkNotNull(ruptureFloating, "%s %s not set", buildId, Key.RUPTURE_FLOATING);
-			checkNotNull(ruptureVariability, "%s %s not set", buildId, Key.RUPTURE_VARIABILITY);
-			checkNotNull(pointSourceType, "%s %s not set", buildId, Key.POINT_SOURCE_TYPE);
-			checkNotNull(areaGridScaling, "%s %s not set", buildId, Key.AREA_GRID_SCALING);
+		private void validateState() {
+			checkState(!built, "This %s instance has already been used", ID + ".Builder");
+			checkNotNull(name, STATE_ERROR, ID, Key.NAME);
+			checkNotNull(resource, STATE_ERROR, ID, Key.RESOURCE);
+			checkNotNull(surfaceSpacing, STATE_ERROR, ID, Key.SURFACE_SPACING);
+			checkNotNull(ruptureFloating, STATE_ERROR, ID, Key.RUPTURE_FLOATING);
+			checkNotNull(ruptureVariability, STATE_ERROR, ID, Key.RUPTURE_VARIABILITY);
+			checkNotNull(pointSourceType, STATE_ERROR, ID, Key.POINT_SOURCE_TYPE);
+			checkNotNull(areaGridScaling, STATE_ERROR, ID, Key.AREA_GRID_SCALING);
 			built = true;
 		}
 
 		ModelConfig build() {
-			validateState(ID);
+			validateState();
 			return new ModelConfig(
 				name, resource, surfaceSpacing, ruptureFloating,
 				ruptureVariability, pointSourceType, areaGridScaling);
diff --git a/src/org/opensha2/mfd/Mfds.java b/src/org/opensha2/mfd/Mfds.java
index 347243127..6cfc442ed 100644
--- a/src/org/opensha2/mfd/Mfds.java
+++ b/src/org/opensha2/mfd/Mfds.java
@@ -336,7 +336,7 @@ public final class Mfds {
 	/**
 	 * Return a converter between annual rate and Poisson probability.
 	 */
-	public static Converter<Double, Double> rateToProbConverter() {
+	public static Converter<Double, Double> annualRateToProbabilityConverter() {
 		return AnnRateToPoissProbConverter.INSTANCE;
 	}
 
diff --git a/src/org/opensha2/programs/HazardCalc.java b/src/org/opensha2/programs/HazardCalc.java
index 5fff8c191..eb05b0344 100644
--- a/src/org/opensha2/programs/HazardCalc.java
+++ b/src/org/opensha2/programs/HazardCalc.java
@@ -96,9 +96,8 @@ public class HazardCalc {
 			CalcConfig config = model.config();
 			if (argCount == 3) {
 				Path userConfigPath = Paths.get(args[2]);
-				config = CalcConfig.builder()
-					.copy(model.config())
-					.extend(CalcConfig.builder(userConfigPath))
+				config = CalcConfig.Builder.copyOf(model.config())
+					.extend(CalcConfig.Builder.fromFile(userConfigPath))
 					.build();
 			}
 			log.info(config.toString());
@@ -167,10 +166,10 @@ public class HazardCalc {
 		for (Site site : sites) {
 			Hazard result = calc(model, config, site, executor);
 			results.add(result);
-			if (results.size() == config.outputBatchSize) {
+			if (results.size() == config.output.flushLimit) {
 				OpenOption[] opts = firstBatch ? WRITE_OPTIONS : APPEND_OPTIONS;
 				firstBatch = false;
-				Results.writeResults(config.outputDir, results, opts);
+				Results.writeResults(config.output.directory, results, opts);
 				log.info("     batch: " + (count + 1) + "  " + batchWatch +
 					"  total: " + totalWatch);
 				results.clear();
@@ -181,7 +180,7 @@ public class HazardCalc {
 		// write final batch
 		if (!results.isEmpty()) {
 			OpenOption[] opts = firstBatch ? WRITE_OPTIONS : APPEND_OPTIONS;
-			Results.writeResults(config.outputDir, results, opts);
+			Results.writeResults(config.output.directory, results, opts);
 		}
 		log.info(PROGRAM + ": " + count + " complete " + totalWatch);
 
diff --git a/src/org/opensha2/util/TextUtils.java b/src/org/opensha2/util/TextUtils.java
index a7cc5f146..28e243ef0 100644
--- a/src/org/opensha2/util/TextUtils.java
+++ b/src/org/opensha2/util/TextUtils.java
@@ -17,7 +17,9 @@ import com.google.common.base.Strings;
  */
 public class TextUtils {
 
+	/** System specific newline string. */
 	public static final String NEWLINE = StandardSystemProperty.LINE_SEPARATOR.value();
+	
 	public static final int ALIGN_COL = 24;
 	private static final int MAX_COL = 100;
 	private static final int DELTA_COL = MAX_COL - ALIGN_COL - 2;
@@ -38,26 +40,10 @@ public class TextUtils {
 		return NEWLINE + padStart(s, ALIGN_COL, ' ') + ": ";
 	}
 	
-	public static String wrap(String s) {
-		return wrap(s, false);
-	}
-	
-	/*
-	 * Used internally; pad flag indents lines consistent with format(s)
-	 */
-	private static String wrap(String s, boolean pad) {
-		if (s.length() <= DELTA_COL) return pad ? INDENT + s : s;
-		StringBuilder sb = new StringBuilder();
-		int lastCommaIndex = s.substring(0, DELTA_COL).lastIndexOf(',') + 1;
-		if (pad) sb.append(INDENT);
-		sb.append(s.substring(0, lastCommaIndex));
-		sb.append(wrap(s.substring(lastCommaIndex).trim(), true));
-		return sb.toString();
-	}
-
 	/**
 	 * Verifies that the supplied {@code String} is neither {@code null} or
 	 * empty. Method returns the supplied value and can be used inline.
+	 * 
 	 * @param name to verify
 	 * @throws IllegalArgumentException if name is {@code null} or empty
 	 */
diff --git a/test/org/opensha2/eq/model/peer/PeerTest.java b/test/org/opensha2/eq/model/peer/PeerTest.java
index 4926b32db..edd200fcf 100644
--- a/test/org/opensha2/eq/model/peer/PeerTest.java
+++ b/test/org/opensha2/eq/model/peer/PeerTest.java
@@ -122,7 +122,7 @@ public class PeerTest {
 		// compute y-values converting to Poiss prob
 		double[] actual = Doubles.toArray(
 			FluentIterable.from(result.curves().get(Imt.PGA).yValues())
-				.transform(Mfds.rateToProbConverter())
+				.transform(Mfds.annualRateToProbabilityConverter())
 				.toList());
 		checkArgument(actual.length == expected.length);
 
@@ -146,8 +146,8 @@ public class PeerTest {
 		Iterable<Site> sites = Sites.fromCsv(MODEL_DIR.resolve(modelId).resolve("sites.csv"));
 
 		// ensure that only PGA is being used
-		checkState(config.imts.size() == 1);
-		checkState(config.imts.iterator().next() == Imt.PGA);
+		checkState(config.curve.imts.size() == 1);
+		checkState(config.curve.imts.iterator().next() == Imt.PGA);
 
 		List<Object[]> argsList = new ArrayList<>();
 		for (Site site : sites) {
-- 
GitLab


From 33c27fef2e6441f5637ebecc33d68d96084005ae Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Wed, 23 Mar 2016 21:53:20 -0600
Subject: [PATCH 08/17] updated peer tests to new config format

---
 etc/peer/models/Set1-Case1/config.json       | 23 ++++++++++--------
 etc/peer/models/Set1-Case10-fast/config.json | 23 ++++++++++--------
 etc/peer/models/Set1-Case10/config.json      | 23 ++++++++++--------
 etc/peer/models/Set1-Case11-fast/config.json | 23 ++++++++++--------
 etc/peer/models/Set1-Case11/config.json      | 23 ++++++++++--------
 etc/peer/models/Set1-Case2-fast/config.json  | 23 ++++++++++--------
 etc/peer/models/Set1-Case2/config.json       | 23 ++++++++++--------
 etc/peer/models/Set1-Case3-fast/config.json  | 23 ++++++++++--------
 etc/peer/models/Set1-Case3/config.json       | 23 ++++++++++--------
 etc/peer/models/Set1-Case4-fast/config.json  | 23 ++++++++++--------
 etc/peer/models/Set1-Case4/config.json       | 23 ++++++++++--------
 etc/peer/models/Set1-Case5-fast/config.json  | 23 ++++++++++--------
 etc/peer/models/Set1-Case5/config.json       | 23 ++++++++++--------
 etc/peer/models/Set1-Case6-fast/config.json  | 23 ++++++++++--------
 etc/peer/models/Set1-Case6/config.json       | 23 ++++++++++--------
 etc/peer/models/Set1-Case7-fast/config.json  | 23 ++++++++++--------
 etc/peer/models/Set1-Case7/config.json       | 23 ++++++++++--------
 etc/peer/models/Set1-Case8a/config.json      | 23 ++++++++++--------
 etc/peer/models/Set1-Case8b/config.json      | 25 +++++++++++---------
 etc/peer/models/Set1-Case8c/config.json      | 25 +++++++++++---------
 etc/peer/models/Set2-Case1/config.json       | 16 ++++++++-----
 etc/peer/models/Set2-Case2a-fast/config.json | 23 ++++++++++--------
 etc/peer/models/Set2-Case2a/config.json      | 23 ++++++++++--------
 etc/peer/models/Set2-Case2b-fast/config.json | 23 ++++++++++--------
 etc/peer/models/Set2-Case2b/config.json      | 23 ++++++++++--------
 etc/peer/models/Set2-Case2c-fast/config.json | 23 ++++++++++--------
 etc/peer/models/Set2-Case2c/config.json      | 23 ++++++++++--------
 etc/peer/models/Set2-Case2d-fast/config.json | 23 ++++++++++--------
 etc/peer/models/Set2-Case2d/config.json      | 23 ++++++++++--------
 etc/peer/models/Set2-Case3a-fast/config.json | 23 ++++++++++--------
 etc/peer/models/Set2-Case3a/config.json      | 23 ++++++++++--------
 etc/peer/models/Set2-Case3b-fast/config.json | 23 ++++++++++--------
 etc/peer/models/Set2-Case3b/config.json      | 23 ++++++++++--------
 etc/peer/models/Set2-Case3c-fast/config.json | 23 ++++++++++--------
 etc/peer/models/Set2-Case3c/config.json      | 23 ++++++++++--------
 etc/peer/models/Set2-Case3d-fast/config.json | 23 ++++++++++--------
 etc/peer/models/Set2-Case3d/config.json      | 23 ++++++++++--------
 etc/peer/models/Set2-Case4a-fast/config.json | 23 ++++++++++--------
 etc/peer/models/Set2-Case4a/config.json      | 23 ++++++++++--------
 etc/peer/models/Set2-Case4b-fast/config.json | 23 ++++++++++--------
 etc/peer/models/Set2-Case4b/config.json      | 23 ++++++++++--------
 etc/peer/models/Set2-Case5a/config.json      | 23 ++++++++++--------
 etc/peer/models/Set2-Case5b/config.json      | 23 ++++++++++--------
 43 files changed, 558 insertions(+), 428 deletions(-)

diff --git a/etc/peer/models/Set1-Case1/config.json b/etc/peer/models/Set1-Case1/config.json
index 60ed12198..68b62f868 100644
--- a/etc/peer/models/Set1-Case1/config.json
+++ b/etc/peer/models/Set1-Case1/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set1-Case1",
-  "surfaceSpacing": 1.0,
-  "ruptureFloating": "OFF",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "NONE",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set1-Case1",
+    "surfaceSpacing": 1.0,
+    "ruptureFloating": "OFF",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "NONE",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set1-Case10-fast/config.json b/etc/peer/models/Set1-Case10-fast/config.json
index c5c17620e..f0afc0175 100644
--- a/etc/peer/models/Set1-Case10-fast/config.json
+++ b/etc/peer/models/Set1-Case10-fast/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set1-Case10-fast",
-  "surfaceSpacing": 1.0,
-  "ruptureFloating": "OFF",
-  "ruptureVariability": false,
-  "pointSourceType": "POINT",
-  "areaGridScaling": "UNIFORM_0P05",
-
-  "exceedanceModel": "TRUNCATION_OFF",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set1-Case10-fast",
+    "surfaceSpacing": 1.0,
+    "ruptureFloating": "OFF",
+    "ruptureVariability": false,
+    "pointSourceType": "POINT",
+    "areaGridScaling": "UNIFORM_0P05"
+  },
+  "curve": {
+    "exceedanceModel": "TRUNCATION_OFF",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set1-Case10/config.json b/etc/peer/models/Set1-Case10/config.json
index 10400a378..1adf7e22d 100644
--- a/etc/peer/models/Set1-Case10/config.json
+++ b/etc/peer/models/Set1-Case10/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set1-Case10",
-  "surfaceSpacing": 1.0,
-  "ruptureFloating": "OFF",
-  "ruptureVariability": false,
-  "pointSourceType": "POINT",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "TRUNCATION_OFF",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set1-Case10",
+    "surfaceSpacing": 1.0,
+    "ruptureFloating": "OFF",
+    "ruptureVariability": false,
+    "pointSourceType": "POINT",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "TRUNCATION_OFF",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set1-Case11-fast/config.json b/etc/peer/models/Set1-Case11-fast/config.json
index d755dad61..a5b7cbf32 100644
--- a/etc/peer/models/Set1-Case11-fast/config.json
+++ b/etc/peer/models/Set1-Case11-fast/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set1-Case11-fast",
-  "surfaceSpacing": 1.0,
-  "ruptureFloating": "OFF",
-  "ruptureVariability": false,
-  "pointSourceType": "POINT",
-  "areaGridScaling": "UNIFORM_0P1",
-
-  "exceedanceModel": "TRUNCATION_OFF",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set1-Case11-fast",
+    "surfaceSpacing": 1.0,
+    "ruptureFloating": "OFF",
+    "ruptureVariability": false,
+    "pointSourceType": "POINT",
+    "areaGridScaling": "UNIFORM_0P1"
+  },
+  "curve": {
+    "exceedanceModel": "TRUNCATION_OFF",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set1-Case11/config.json b/etc/peer/models/Set1-Case11/config.json
index 6a887d73d..4da1fc3fc 100644
--- a/etc/peer/models/Set1-Case11/config.json
+++ b/etc/peer/models/Set1-Case11/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set1-Case11",
-  "surfaceSpacing": 1.0,
-  "ruptureFloating": "OFF",
-  "ruptureVariability": false,
-  "pointSourceType": "POINT",
-  "areaGridScaling": "UNIFORM_0P02",
-
-  "exceedanceModel": "TRUNCATION_OFF",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set1-Case11",
+    "surfaceSpacing": 1.0,
+    "ruptureFloating": "OFF",
+    "ruptureVariability": false,
+    "pointSourceType": "POINT",
+    "areaGridScaling": "UNIFORM_0P02"
+  },
+  "curve": {
+    "exceedanceModel": "TRUNCATION_OFF",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set1-Case2-fast/config.json b/etc/peer/models/Set1-Case2-fast/config.json
index 80dde6e19..766fe1685 100644
--- a/etc/peer/models/Set1-Case2-fast/config.json
+++ b/etc/peer/models/Set1-Case2-fast/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set1-Case2-fast",
-  "surfaceSpacing": 0.1,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "NONE",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set1-Case2-fast",
+    "surfaceSpacing": 0.1,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "NONE",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set1-Case2/config.json b/etc/peer/models/Set1-Case2/config.json
index 48fd45036..242d3b806 100644
--- a/etc/peer/models/Set1-Case2/config.json
+++ b/etc/peer/models/Set1-Case2/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set1-Case2",
-  "surfaceSpacing": 0.02,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "NONE",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set1-Case2",
+    "surfaceSpacing": 0.02,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "NONE",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set1-Case3-fast/config.json b/etc/peer/models/Set1-Case3-fast/config.json
index e6462d9e4..b5aeb1fab 100644
--- a/etc/peer/models/Set1-Case3-fast/config.json
+++ b/etc/peer/models/Set1-Case3-fast/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set1-Case3",
-  "surfaceSpacing": 0.2,
-  "ruptureFloating": "ON",
-  "ruptureVariability": true,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "NONE",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set1-Case3",
+    "surfaceSpacing": 0.2,
+    "ruptureFloating": "ON",
+    "ruptureVariability": true,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "NONE",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set1-Case3/config.json b/etc/peer/models/Set1-Case3/config.json
index c20a71883..4eb9be251 100644
--- a/etc/peer/models/Set1-Case3/config.json
+++ b/etc/peer/models/Set1-Case3/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set1-Case3",
-  "surfaceSpacing": 0.05,
-  "ruptureFloating": "ON",
-  "ruptureVariability": true,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "NONE",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set1-Case3",
+    "surfaceSpacing": 0.05,
+    "ruptureFloating": "ON",
+    "ruptureVariability": true,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "NONE",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set1-Case4-fast/config.json b/etc/peer/models/Set1-Case4-fast/config.json
index e7fe89b44..581d6dc0b 100644
--- a/etc/peer/models/Set1-Case4-fast/config.json
+++ b/etc/peer/models/Set1-Case4-fast/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set1-Case4-fast",
-  "surfaceSpacing": 0.2,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "NONE",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set1-Case4-fast",
+    "surfaceSpacing": 0.2,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "NONE",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set1-Case4/config.json b/etc/peer/models/Set1-Case4/config.json
index 8d858cbd5..f0a124c2a 100644
--- a/etc/peer/models/Set1-Case4/config.json
+++ b/etc/peer/models/Set1-Case4/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set1-Case4",
-  "surfaceSpacing": 0.05,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "NONE",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set1-Case4",
+    "surfaceSpacing": 0.05,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "NONE",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set1-Case5-fast/config.json b/etc/peer/models/Set1-Case5-fast/config.json
index f959c64aa..1ff616fd3 100644
--- a/etc/peer/models/Set1-Case5-fast/config.json
+++ b/etc/peer/models/Set1-Case5-fast/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set1-Case5-fast",
-  "surfaceSpacing": 0.5,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "NONE",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set1-Case5-fast",
+    "surfaceSpacing": 0.5,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "NONE",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set1-Case5/config.json b/etc/peer/models/Set1-Case5/config.json
index 12fbfe079..f1149b5d5 100644
--- a/etc/peer/models/Set1-Case5/config.json
+++ b/etc/peer/models/Set1-Case5/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set1-Case5",
-  "surfaceSpacing": 0.1,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "NONE",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set1-Case5",
+    "surfaceSpacing": 0.1,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "NONE",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set1-Case6-fast/config.json b/etc/peer/models/Set1-Case6-fast/config.json
index 379d2f2de..4244e432b 100644
--- a/etc/peer/models/Set1-Case6-fast/config.json
+++ b/etc/peer/models/Set1-Case6-fast/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set1-Case6-fast",
-  "surfaceSpacing": 0.5,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "NONE",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set1-Case6-fast",
+    "surfaceSpacing": 0.5,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "NONE",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set1-Case6/config.json b/etc/peer/models/Set1-Case6/config.json
index 29dfac384..249bb6849 100644
--- a/etc/peer/models/Set1-Case6/config.json
+++ b/etc/peer/models/Set1-Case6/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set1-Case6",
-  "surfaceSpacing": 0.1,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "NONE",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set1-Case6",
+    "surfaceSpacing": 0.1,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "NONE",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set1-Case7-fast/config.json b/etc/peer/models/Set1-Case7-fast/config.json
index 7024b9a28..ca4dd4605 100644
--- a/etc/peer/models/Set1-Case7-fast/config.json
+++ b/etc/peer/models/Set1-Case7-fast/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set1-Case7-fast",
-  "surfaceSpacing": 0.5,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "NONE",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set1-Case7-fast",
+    "surfaceSpacing": 0.5,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "NONE",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set1-Case7/config.json b/etc/peer/models/Set1-Case7/config.json
index bf0cfb6d7..1a15e14ea 100644
--- a/etc/peer/models/Set1-Case7/config.json
+++ b/etc/peer/models/Set1-Case7/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set1-Case7",
-  "surfaceSpacing": 0.1,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "NONE",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set1-Case7",
+    "surfaceSpacing": 0.1,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "NONE",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set1-Case8a/config.json b/etc/peer/models/Set1-Case8a/config.json
index da5f9b233..7485f5683 100644
--- a/etc/peer/models/Set1-Case8a/config.json
+++ b/etc/peer/models/Set1-Case8a/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set1-Case8a",
-  "surfaceSpacing": 0.1,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "TRUNCATION_OFF",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set1-Case8a",
+    "surfaceSpacing": 0.1,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "TRUNCATION_OFF",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set1-Case8b/config.json b/etc/peer/models/Set1-Case8b/config.json
index 0206975f3..565287259 100644
--- a/etc/peer/models/Set1-Case8b/config.json
+++ b/etc/peer/models/Set1-Case8b/config.json
@@ -1,13 +1,16 @@
 {
-  "name": "PEER Test: Set1-Case8b",
-  "surfaceSpacing": 0.05,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "TRUNCATION_UPPER_ONLY",
-  "truncationLevel": 2.0,
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set1-Case8b",
+    "surfaceSpacing": 0.05,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "TRUNCATION_UPPER_ONLY",
+    "truncationLevel": 2.0,
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set1-Case8c/config.json b/etc/peer/models/Set1-Case8c/config.json
index 736456809..bf84bde5b 100644
--- a/etc/peer/models/Set1-Case8c/config.json
+++ b/etc/peer/models/Set1-Case8c/config.json
@@ -1,13 +1,16 @@
 {
-  "name": "PEER Test: Set1-Case8c",
-  "surfaceSpacing": 0.05,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "TRUNCATION_UPPER_ONLY",
-  "truncationLevel": 3.0,
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set1-Case8c",
+    "surfaceSpacing": 0.05,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "TRUNCATION_UPPER_ONLY",
+    "truncationLevel": 3.0,
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set2-Case1/config.json b/etc/peer/models/Set2-Case1/config.json
index 38e948272..709fc41aa 100644
--- a/etc/peer/models/Set2-Case1/config.json
+++ b/etc/peer/models/Set2-Case1/config.json
@@ -1,8 +1,12 @@
 {
-  "name": "PEER Test: Set2-Case1",
-  "ruptureFloating": "ON",
-  "pointSourceType": "POINT",
-  "exceedanceModel": "TRUNCATION_OFF",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set2-Case1",
+    "ruptureFloating": "ON",
+    "pointSourceType": "POINT"
+  },
+  "curve": {
+    "exceedanceModel": "TRUNCATION_OFF",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set2-Case2a-fast/config.json b/etc/peer/models/Set2-Case2a-fast/config.json
index b9e839678..0287a5f59 100644
--- a/etc/peer/models/Set2-Case2a-fast/config.json
+++ b/etc/peer/models/Set2-Case2a-fast/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set2-Case2a-fast",
-  "surfaceSpacing": 1.0,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "TRUNCATION_OFF",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set2-Case2a-fast",
+    "surfaceSpacing": 1.0,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "TRUNCATION_OFF",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set2-Case2a/config.json b/etc/peer/models/Set2-Case2a/config.json
index 03ebdb068..4816a1f59 100644
--- a/etc/peer/models/Set2-Case2a/config.json
+++ b/etc/peer/models/Set2-Case2a/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set2-Case2a",
-  "surfaceSpacing": 0.25,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "TRUNCATION_OFF",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set2-Case2a",
+    "surfaceSpacing": 0.25,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "TRUNCATION_OFF",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set2-Case2b-fast/config.json b/etc/peer/models/Set2-Case2b-fast/config.json
index 099c80c24..bcf2272e5 100644
--- a/etc/peer/models/Set2-Case2b-fast/config.json
+++ b/etc/peer/models/Set2-Case2b-fast/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set2-Case2b-fast",
-  "surfaceSpacing": 1.0,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "TRUNCATION_OFF",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set2-Case2b-fast",
+    "surfaceSpacing": 1.0,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "TRUNCATION_OFF",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set2-Case2b/config.json b/etc/peer/models/Set2-Case2b/config.json
index 69d0c63a9..bd33dd3d5 100644
--- a/etc/peer/models/Set2-Case2b/config.json
+++ b/etc/peer/models/Set2-Case2b/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set2-Case2b",
-  "surfaceSpacing": 0.25,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "TRUNCATION_OFF",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set2-Case2b",
+    "surfaceSpacing": 0.25,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "TRUNCATION_OFF",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set2-Case2c-fast/config.json b/etc/peer/models/Set2-Case2c-fast/config.json
index 7ec62ea1e..3b857e62e 100644
--- a/etc/peer/models/Set2-Case2c-fast/config.json
+++ b/etc/peer/models/Set2-Case2c-fast/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set2-Case2c-fast",
-  "surfaceSpacing": 1.0,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "TRUNCATION_OFF",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set2-Case2c-fast",
+    "surfaceSpacing": 1.0,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "TRUNCATION_OFF",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set2-Case2c/config.json b/etc/peer/models/Set2-Case2c/config.json
index 82e9ba49f..75ffbbb63 100644
--- a/etc/peer/models/Set2-Case2c/config.json
+++ b/etc/peer/models/Set2-Case2c/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set2-Case2c",
-  "surfaceSpacing": 0.25,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "TRUNCATION_OFF",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set2-Case2c",
+    "surfaceSpacing": 0.25,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "TRUNCATION_OFF",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set2-Case2d-fast/config.json b/etc/peer/models/Set2-Case2d-fast/config.json
index 5d8a4ca3a..ae00b4c6b 100644
--- a/etc/peer/models/Set2-Case2d-fast/config.json
+++ b/etc/peer/models/Set2-Case2d-fast/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set2-Case2d-fast",
-  "surfaceSpacing": 1.0,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "TRUNCATION_OFF",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set2-Case2d-fast",
+    "surfaceSpacing": 1.0,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "TRUNCATION_OFF",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set2-Case2d/config.json b/etc/peer/models/Set2-Case2d/config.json
index 42619602b..beb6a9057 100644
--- a/etc/peer/models/Set2-Case2d/config.json
+++ b/etc/peer/models/Set2-Case2d/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set2-Case2d",
-  "surfaceSpacing": 0.25,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "TRUNCATION_OFF",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set2-Case2d",
+    "surfaceSpacing": 0.25,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "TRUNCATION_OFF",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set2-Case3a-fast/config.json b/etc/peer/models/Set2-Case3a-fast/config.json
index 28bdecc09..b062f3c98 100644
--- a/etc/peer/models/Set2-Case3a-fast/config.json
+++ b/etc/peer/models/Set2-Case3a-fast/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set2-Case3a-fast",
-  "surfaceSpacing": 0.1,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "NONE",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set2-Case3a-fast",
+    "surfaceSpacing": 0.1,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "NONE",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set2-Case3a/config.json b/etc/peer/models/Set2-Case3a/config.json
index 84ca1e380..2366c6995 100644
--- a/etc/peer/models/Set2-Case3a/config.json
+++ b/etc/peer/models/Set2-Case3a/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set2-Case3a",
-  "surfaceSpacing": 0.05,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "NONE",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set2-Case3a",
+    "surfaceSpacing": 0.05,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "NONE",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set2-Case3b-fast/config.json b/etc/peer/models/Set2-Case3b-fast/config.json
index a48032860..6cbb46325 100644
--- a/etc/peer/models/Set2-Case3b-fast/config.json
+++ b/etc/peer/models/Set2-Case3b-fast/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set2-Case3b-fast",
-  "surfaceSpacing": 0.1,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "NONE",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set2-Case3b-fast",
+    "surfaceSpacing": 0.1,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "NONE",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set2-Case3b/config.json b/etc/peer/models/Set2-Case3b/config.json
index 94e41f8d6..d72c52a1b 100644
--- a/etc/peer/models/Set2-Case3b/config.json
+++ b/etc/peer/models/Set2-Case3b/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set2-Case3b",
-  "surfaceSpacing": 0.05,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "NONE",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set2-Case3b",
+    "surfaceSpacing": 0.05,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "NONE",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set2-Case3c-fast/config.json b/etc/peer/models/Set2-Case3c-fast/config.json
index e88a38409..d53b45bae 100644
--- a/etc/peer/models/Set2-Case3c-fast/config.json
+++ b/etc/peer/models/Set2-Case3c-fast/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set2-Case3c-fast",
-  "surfaceSpacing": 0.1,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "NONE",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set2-Case3c-fast",
+    "surfaceSpacing": 0.1,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "NONE",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set2-Case3c/config.json b/etc/peer/models/Set2-Case3c/config.json
index d1d1798af..3be21129c 100644
--- a/etc/peer/models/Set2-Case3c/config.json
+++ b/etc/peer/models/Set2-Case3c/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set2-Case3c",
-  "surfaceSpacing": 0.05,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "NONE",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set2-Case3c",
+    "surfaceSpacing": 0.05,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "NONE",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set2-Case3d-fast/config.json b/etc/peer/models/Set2-Case3d-fast/config.json
index 2e25c5b7d..c4709d266 100644
--- a/etc/peer/models/Set2-Case3d-fast/config.json
+++ b/etc/peer/models/Set2-Case3d-fast/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set2-Case3d-fast",
-  "surfaceSpacing": 0.1,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "NONE",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set2-Case3d-fast",
+    "surfaceSpacing": 0.1,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "NONE",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set2-Case3d/config.json b/etc/peer/models/Set2-Case3d/config.json
index 7ad1a9faa..b97e56287 100644
--- a/etc/peer/models/Set2-Case3d/config.json
+++ b/etc/peer/models/Set2-Case3d/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set2-Case3d",
-  "surfaceSpacing": 0.05,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "NONE",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set2-Case3d",
+    "surfaceSpacing": 0.05,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "NONE",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set2-Case4a-fast/config.json b/etc/peer/models/Set2-Case4a-fast/config.json
index ddfa199af..4e90f82ea 100644
--- a/etc/peer/models/Set2-Case4a-fast/config.json
+++ b/etc/peer/models/Set2-Case4a-fast/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set2-Case4a-fast",
-  "surfaceSpacing": 0.1,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "NONE",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set2-Case4a-fast",
+    "surfaceSpacing": 0.1,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "NONE",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set2-Case4a/config.json b/etc/peer/models/Set2-Case4a/config.json
index 71f29d70c..be72d15ae 100644
--- a/etc/peer/models/Set2-Case4a/config.json
+++ b/etc/peer/models/Set2-Case4a/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set2-Case4a",
-  "surfaceSpacing": 0.05,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "NONE",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set2-Case4a",
+    "surfaceSpacing": 0.05,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "NONE",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set2-Case4b-fast/config.json b/etc/peer/models/Set2-Case4b-fast/config.json
index fa6f983ca..c26bd4311 100644
--- a/etc/peer/models/Set2-Case4b-fast/config.json
+++ b/etc/peer/models/Set2-Case4b-fast/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set2-Case4b-fast",
-  "surfaceSpacing": 0.1,
-  "ruptureFloating": "TRIANGULAR",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "NONE",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set2-Case4b-fast",
+    "surfaceSpacing": 0.1,
+    "ruptureFloating": "TRIANGULAR",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "NONE",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set2-Case4b/config.json b/etc/peer/models/Set2-Case4b/config.json
index 72aadafc9..5bab654ef 100644
--- a/etc/peer/models/Set2-Case4b/config.json
+++ b/etc/peer/models/Set2-Case4b/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set2-Case4b",
-  "surfaceSpacing": 0.05,
-  "ruptureFloating": "TRIANGULAR",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "NONE",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  "model": {
+    "name": "PEER Test: Set2-Case4b",
+    "surfaceSpacing": 0.05,
+    "ruptureFloating": "TRIANGULAR",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "NONE",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.7, 0.8, 0.9, 1.0]
+  }
 }
diff --git a/etc/peer/models/Set2-Case5a/config.json b/etc/peer/models/Set2-Case5a/config.json
index 355c8f657..e8ef193af 100644
--- a/etc/peer/models/Set2-Case5a/config.json
+++ b/etc/peer/models/Set2-Case5a/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set2-Case5a",
-  "surfaceSpacing": 0.05,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "PEER_MIXTURE_REFERENCE",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.2, 0.4, 0.6, 0.8, 1.0, 1.25, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0, 6.0, 7.0]
+  "model": {
+    "name": "PEER Test: Set2-Case5a",
+    "surfaceSpacing": 0.05,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "PEER_MIXTURE_REFERENCE",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.2, 0.4, 0.6, 0.8, 1.0, 1.25, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0, 6.0, 7.0]
+  }
 }
diff --git a/etc/peer/models/Set2-Case5b/config.json b/etc/peer/models/Set2-Case5b/config.json
index 6c59f064f..7de164e4d 100644
--- a/etc/peer/models/Set2-Case5b/config.json
+++ b/etc/peer/models/Set2-Case5b/config.json
@@ -1,12 +1,15 @@
 {
-  "name": "PEER Test: Set2-Case5b",
-  "surfaceSpacing": 0.05,
-  "ruptureFloating": "ON",
-  "ruptureVariability": false,
-  "pointSourceType": "FINITE",
-  "areaGridScaling": "UNIFORM_0P01",
-
-  "exceedanceModel": "PEER_MIXTURE_MODEL",
-  "imts": ["PGA"],
-  "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.2, 0.4, 0.6, 0.8, 1.0, 1.25, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0, 6.0, 7.0]
+  "model": {
+    "name": "PEER Test: Set2-Case5b",
+    "surfaceSpacing": 0.05,
+    "ruptureFloating": "ON",
+    "ruptureVariability": false,
+    "pointSourceType": "FINITE",
+    "areaGridScaling": "UNIFORM_0P01"
+  },
+  "curve": {
+    "exceedanceModel": "PEER_MIXTURE_MODEL",
+    "imts": ["PGA"],
+    "defaultImls": [0.001, 0.01, 0.05, 0.1, 0.2, 0.4, 0.6, 0.8, 1.0, 1.25, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0, 6.0, 7.0]
+  }
 }
-- 
GitLab


From 11d2c851e8a264a044a233e4e30419e443318127 Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Thu, 24 Mar 2016 10:50:31 -0600
Subject: [PATCH 09/17] output of source and gmm curveTypes now processed by
 results from config

---
 etc/examples/6-enhanced-output/config.json |  2 +-
 src/org/opensha2/calc/CalcConfig.java      |  2 +-
 src/org/opensha2/calc/Results.java         | 73 ++++++++++++----------
 3 files changed, 42 insertions(+), 35 deletions(-)

diff --git a/etc/examples/6-enhanced-output/config.json b/etc/examples/6-enhanced-output/config.json
index b42a3f733..a16d67dd9 100644
--- a/etc/examples/6-enhanced-output/config.json
+++ b/etc/examples/6-enhanced-output/config.json
@@ -1,5 +1,5 @@
 {
   "output": {
-    "curveTypes": ["TOTAL", "GMM", "SOURCE"]
+    "curveTypes": ["TOTAL", "GMM"]
   }
 }
diff --git a/src/org/opensha2/calc/CalcConfig.java b/src/org/opensha2/calc/CalcConfig.java
index 147166799..3b5cd571d 100644
--- a/src/org/opensha2/calc/CalcConfig.java
+++ b/src/org/opensha2/calc/CalcConfig.java
@@ -600,7 +600,7 @@ public final class CalcConfig {
 
 	@Override
 	public String toString() {
-		return new StringBuilder(padEnd("Calc Configuration:", VALUE_INDENT_SIZE, ' '))
+		return new StringBuilder("Calc Configuration: ")
 			.append(resource.isPresent()
 				? resource.get().toAbsolutePath().normalize()
 				: "(from defaults)")
diff --git a/src/org/opensha2/calc/Results.java b/src/org/opensha2/calc/Results.java
index 1b94ad464..13e0e9901 100644
--- a/src/org/opensha2/calc/Results.java
+++ b/src/org/opensha2/calc/Results.java
@@ -146,10 +146,11 @@ public class Results {
 		Hazard demo = batch.get(0);
 		boolean newFile = options.length == 0;
 		boolean namedSites = demo.site.name != Site.NO_NAME;
-		boolean detailed = false; //demo.config.hazardFormat.equals(HazardFormat.DETAILED);
+		boolean gmmCurves = demo.config.output.curveTypes.contains(CurveType.GMM);
+		boolean sourceCurves = demo.config.output.curveTypes.contains(CurveType.SOURCE);
 
 		Map<Imt, List<String>> totalLineMap = Maps.newEnumMap(Imt.class);
-		Map<Imt, Map<SourceType, List<String>>> typeLineMap = Maps.newEnumMap(Imt.class);
+		Map<Imt, Map<SourceType, List<String>>> sourceLineMap = Maps.newEnumMap(Imt.class);
 		Map<Imt, Map<Gmm, List<String>>> gmmLineMap = Maps.newEnumMap(Imt.class);
 
 		/* Initialize line maps for all types and gmms referenced by a model */
@@ -163,14 +164,15 @@ public class Results {
 			}
 			totalLineMap.put(imt, lines);
 
-			if (detailed) {
-
+			if (sourceCurves) {
 				Map<SourceType, List<String>> typeLines = Maps.newEnumMap(SourceType.class);
 				for (SourceType type : demo.model.types()) {
 					typeLines.put(type, Lists.newArrayList(lines));
 				}
-				typeLineMap.put(imt, typeLines);
+				sourceLineMap.put(imt, typeLines);
+			}
 
+			if (gmmCurves) {
 				Map<Gmm, List<String>> gmmLines = Maps.newEnumMap(Gmm.class);
 				for (Gmm gmm : gmmSet(demo.model)) {
 					gmmLines.put(gmm, Lists.newArrayList(lines));
@@ -188,10 +190,9 @@ public class Results {
 				String.format("%.5f", hazard.site.location.lon()),
 				String.format("%.5f", hazard.site.location.lat()));
 
-			Map<Imt, Map<SourceType, XySequence>> curvesByType = detailed ?
-				curvesByType(hazard) : null;
-			Map<Imt, Map<Gmm, XySequence>> curvesByGmm = detailed ?
-				curvesByGmm(hazard) : null;
+			Map<Imt, Map<SourceType, XySequence>> curvesBySource =
+				sourceCurves ? curvesBySource(hazard) : null;
+			Map<Imt, Map<Gmm, XySequence>> curvesByGmm = gmmCurves ? curvesByGmm(hazard) : null;
 
 			for (Entry<Imt, XySequence> imtEntry : hazard.totalCurves.entrySet()) {
 				Imt imt = imtEntry.getKey();
@@ -205,24 +206,25 @@ public class Results {
 					imtEntry.getValue().yValues(),
 					formatter));
 
-				if (detailed) {
-
-					Map<SourceType, XySequence> typeCurves = curvesByType.get(imt);
-					for (Entry<SourceType, List<String>> typeEntry : typeLineMap.get(imt)
+				if (sourceCurves) {
+					Map<SourceType, XySequence> sourceCurveMap = curvesBySource.get(imt);
+					for (Entry<SourceType, List<String>> typeEntry : sourceLineMap.get(imt)
 						.entrySet()) {
 						SourceType type = typeEntry.getKey();
-						String typeLine = typeCurves.containsKey(type) ?
-							toLine(locData, typeCurves.get(type).yValues(), formatter) :
-							emptyLine;
+						String typeLine = sourceCurveMap.containsKey(type)
+							? toLine(locData, sourceCurveMap.get(type).yValues(), formatter)
+							: emptyLine;
 						typeEntry.getValue().add(typeLine);
 					}
+				}
 
-					Map<Gmm, XySequence> gmmCurves = curvesByGmm.get(imt);
+				if (gmmCurves) {
+					Map<Gmm, XySequence> gmmCurveMap = curvesByGmm.get(imt);
 					for (Entry<Gmm, List<String>> gmmEntry : gmmLineMap.get(imt).entrySet()) {
 						Gmm gmm = gmmEntry.getKey();
-						String gmmLine = gmmCurves.containsKey(gmm) ?
-							toLine(locData, gmmCurves.get(gmm).yValues(), formatter) :
-							emptyLine;
+						String gmmLine = gmmCurveMap.containsKey(gmm)
+							? toLine(locData, gmmCurveMap.get(gmm).yValues(), formatter)
+							: emptyLine;
 						gmmEntry.getValue().add(gmmLine);
 					}
 				}
@@ -238,16 +240,18 @@ public class Results {
 			Path totalFile = imtDir.resolve("total" + CURVE_FILE_SUFFIX);
 			Files.write(totalFile, totalEntry.getValue(), US_ASCII, options);
 
-			if (detailed) {
-
-				Path typeDir = imtDir.resolve("type");
+			if (sourceCurves) {
+				Path typeDir = imtDir.resolve("source");
 				Files.createDirectories(typeDir);
-				for (Entry<SourceType, List<String>> typeEntry : typeLineMap.get(imt).entrySet()) {
+				for (Entry<SourceType, List<String>> typeEntry : sourceLineMap.get(imt)
+					.entrySet()) {
 					Path typeFile = typeDir.resolve(
 						typeEntry.getKey().toString() + CURVE_FILE_SUFFIX);
 					Files.write(typeFile, typeEntry.getValue(), US_ASCII, options);
 				}
+			}
 
+			if (gmmCurves) {
 				Path gmmDir = imtDir.resolve("gmm");
 				Files.createDirectories(gmmDir);
 				for (Entry<Gmm, List<String>> gmmEntry : gmmLineMap.get(imt).entrySet()) {
@@ -272,7 +276,7 @@ public class Results {
 	 * Derive maps of curves by source type for each Imt in a {@code Hazard}
 	 * result.
 	 */
-	public static Map<Imt, Map<SourceType, XySequence>> curvesByType(Hazard hazard) {
+	public static Map<Imt, Map<SourceType, XySequence>> curvesBySource(Hazard hazard) {
 
 		EnumMap<Imt, Map<SourceType, XySequence>> imtMap = Maps.newEnumMap(Imt.class);
 
@@ -324,30 +328,33 @@ public class Results {
 		return Sets.immutableEnumSet(
 			FluentIterable.from(sourceSets).transformAndConcat(
 				new Function<SourceSet<? extends Source>, Set<Gmm>>() {
-					@Override public Set<Gmm> apply(SourceSet<? extends Source> sourceSet) {
+					@Override
+					public Set<Gmm> apply(SourceSet<? extends Source> sourceSet) {
 						return sourceSet.groundMotionModels().gmms();
 					}
-				})
-			);
+				}));
 	}
 
-	/* Initalize a map of curves, one entry for each of the supplied enum keys. */
+	/*
+	 * Initalize a map of curves, one entry for each of the supplied enum keys.
+	 */
 	private static <K extends Enum<K>> Map<K, XySequence> initCurves(
 			final Set<K> keys,
 			final XySequence model) {
 		return Maps.immutableEnumMap(
 			FluentIterable.from(keys).toMap(
 				new Function<K, XySequence>() {
-					@Override public XySequence apply(K key) {
+					@Override
+					public XySequence apply(K key) {
 						return emptyCopyOf(model);
 					}
-				})
-			);
+				}));
 	}
 
 	private static final Function<HazardCurveSet, SourceSet<? extends Source>> CURVE_SET_TO_SOURCE_SET =
 		new Function<HazardCurveSet, SourceSet<? extends Source>>() {
-			@Override public SourceSet<? extends Source> apply(HazardCurveSet curves) {
+			@Override
+			public SourceSet<? extends Source> apply(HazardCurveSet curves) {
 				return curves.sourceSet;
 			}
 		};
-- 
GitLab


From 0aab4a93c411193829b1c782c9fbfeade854fff1 Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Thu, 24 Mar 2016 11:53:13 -0600
Subject: [PATCH 10/17] site toString improvements

---
 src/org/opensha2/calc/Site.java  |  2 +-
 src/org/opensha2/calc/Sites.java | 19 +++++++++++++------
 2 files changed, 14 insertions(+), 7 deletions(-)

diff --git a/src/org/opensha2/calc/Site.java b/src/org/opensha2/calc/Site.java
index 49cd9ea75..6a49dc975 100644
--- a/src/org/opensha2/calc/Site.java
+++ b/src/org/opensha2/calc/Site.java
@@ -125,7 +125,7 @@ public class Site implements Named {
 
 	@Override
 	public String toString() {
-		return new StringBuilder(Strings.padEnd(name, 16, ' '))
+		return new StringBuilder(Strings.padEnd(name, 24, ' '))
 			.append(String.format("%.3f %.3f Vs30=%s ", location.lon(), location.lat(), vs30))
 			.append(vsInferred ? "inferred " : "measured ")
 			.append(String.format("Z1.0=%s Z2.5=%s", z1p0, z2p5))
diff --git a/src/org/opensha2/calc/Sites.java b/src/org/opensha2/calc/Sites.java
index f8cb483bf..4afb4a40c 100644
--- a/src/org/opensha2/calc/Sites.java
+++ b/src/org/opensha2/calc/Sites.java
@@ -2,13 +2,14 @@ package org.opensha2.calc;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.base.Strings.padStart;
 import static com.google.common.base.Strings.repeat;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.opensha2.geo.BorderType.MERCATOR_LINEAR;
 import static org.opensha2.util.GeoJson.validateProperty;
 import static org.opensha2.util.Parsing.splitToList;
 import static org.opensha2.util.TextUtils.ALIGN_COL;
-import static org.opensha2.util.TextUtils.format;
+import static org.opensha2.util.TextUtils.*;
 
 import java.io.IOException;
 import java.io.Reader;
@@ -33,6 +34,7 @@ import org.opensha2.util.Parsing.Delimiter;
 import org.opensha2.util.TextUtils;
 
 import com.google.common.base.Optional;
+import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.reflect.TypeToken;
@@ -191,13 +193,15 @@ public final class Sites {
 		return new ListIterable(ImmutableList.of(b.build()));
 	}
 
+	private static final int TO_STRING_LIMIT = 5;
+	private static final int SITE_INDENT_SIZE = 8;
+	private static final String SITE_INDENT = Strings.repeat(" ", SITE_INDENT_SIZE);
+
 	/*
 	 * Parent class for deserialization of different GeoJSON site file formats
 	 */
 	private abstract static class SiteIterable implements Iterable<Site> {
 
-		static final int TO_STRING_LIMIT = 5;
-
 		@Override
 		public String toString() {
 			boolean region = this instanceof RegionIterable;
@@ -207,12 +211,15 @@ public final class Sites {
 				.append(" [size=").append(size).append("]");
 			if (!region) {
 				for (Site site : Iterables.limit(this, TO_STRING_LIMIT)) {
-					sb.append(format("Site")).append(site);
+					sb.append(NEWLINE)
+						.append(SITE_INDENT)
+						.append("Site: ")
+						.append(site);
 				}
 				if (size > TO_STRING_LIMIT) {
 					int delta = size - TO_STRING_LIMIT;
-					sb.append(TextUtils.NEWLINE)
-						.append(repeat(" ", ALIGN_COL + 2))
+					sb.append(NEWLINE)
+						.append(SITE_INDENT)
 						.append("... and ").append(delta).append(" more ...");
 				}
 			}
-- 
GitLab


From 00c6c9955a61c031b09d943f831b25e538eeed95 Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Thu, 24 Mar 2016 13:01:43 -0600
Subject: [PATCH 11/17] TextUtil cleanout and log formatting updates

---
 src/org/opensha2/calc/CalcConfig.java      | 57 ++++++----------------
 src/org/opensha2/calc/Deaggregation.java   | 17 ++++---
 src/org/opensha2/calc/Site.java            |  2 +-
 src/org/opensha2/calc/Sites.java           | 22 +++------
 src/org/opensha2/eq/model/ModelConfig.java | 30 +++++++-----
 src/org/opensha2/util/TextUtils.java       | 27 ++--------
 6 files changed, 56 insertions(+), 99 deletions(-)

diff --git a/src/org/opensha2/calc/CalcConfig.java b/src/org/opensha2/calc/CalcConfig.java
index 3b5cd571d..2e3c65f66 100644
--- a/src/org/opensha2/calc/CalcConfig.java
+++ b/src/org/opensha2/calc/CalcConfig.java
@@ -10,10 +10,10 @@ import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.opensha2.data.XySequence.create;
 import static org.opensha2.data.XySequence.immutableCopyOf;
 import static org.opensha2.util.Parsing.enumsToString;
+import static org.opensha2.util.TextUtils.LOG_INDENT;
+import static org.opensha2.util.TextUtils.LOG_VALUE_COLUMN;
 import static org.opensha2.util.TextUtils.NEWLINE;
 
-// import static org.opensha2.util.TextUtils.*;
-
 import java.io.IOException;
 import java.io.Reader;
 import java.lang.reflect.Type;
@@ -31,7 +31,6 @@ import org.opensha2.data.XySequence;
 import org.opensha2.eq.model.SourceType;
 import org.opensha2.gmm.Gmm;
 import org.opensha2.gmm.Imt;
-import org.opensha2.util.TextUtils;
 
 import com.google.common.base.Optional;
 import com.google.common.collect.Maps;
@@ -200,7 +199,7 @@ public final class CalcConfig {
 				}
 			}
 			return new StringBuilder()
-				.append(formatGroup("Curve"))
+				.append(LOG_INDENT).append("Curve")
 				.append(formatEntry(Key.EXCEEDANCE_MODEL, exceedanceModel))
 				.append(formatEntry(Key.TRUNCATION_LEVEL, truncationLevel))
 				.append(formatEntry(Key.IMTS, enumsToString(imts, Imt.class)))
@@ -355,7 +354,7 @@ public final class CalcConfig {
 
 		private StringBuilder asString() {
 			return new StringBuilder()
-				.append(formatGroup("Performance"))
+				.append(LOG_INDENT).append("Performance")
 				.append(formatEntry(Key.OPTIMIZE_GRIDS, optimizeGrids))
 				.append(formatEntry(Key.COLLAPSE_MFDS, collapseMfds))
 				.append(formatEntry(Key.SYSTEM_PARTITION, systemPartition))
@@ -453,7 +452,7 @@ public final class CalcConfig {
 
 		private StringBuilder asString() {
 			return new StringBuilder()
-				.append(formatGroup("Output"))
+				.append(LOG_INDENT).append("Output")
 				.append(formatEntry(Key.DIRECTORY, directory.toAbsolutePath().normalize()))
 				.append(formatEntry(Key.CURVE_TYPES, enumsToString(curveTypes, CurveType.class)))
 				.append(formatEntry(Key.FLUSH_LIMIT, flushLimit));
@@ -548,7 +547,7 @@ public final class CalcConfig {
 
 		private StringBuilder asString() {
 			return new StringBuilder()
-				.append(formatGroup("Deaggregation"))
+				.append(LOG_INDENT).append("Deaggregation")
 				.append(formatEntry("R"))
 				.append("min=").append(rMin).append(", ")
 				.append("max=").append(rMax).append(", ")
@@ -600,7 +599,7 @@ public final class CalcConfig {
 
 	@Override
 	public String toString() {
-		return new StringBuilder("Calc Configuration: ")
+		return new StringBuilder("Calc Config: ")
 			.append(resource.isPresent()
 				? resource.get().toAbsolutePath().normalize()
 				: "(from defaults)")
@@ -611,38 +610,25 @@ public final class CalcConfig {
 			.toString();
 	}
 
-	// public static <E extends Enum<E>> String format(E id) {
-	// return format(id.toString());
-	// }
-
-	private static final int GROUP_INDENT_SIZE = 8;
-	private static final int KEY_INDENT_SIZE = 10;
-	private static final int VALUE_INDENT_SIZE = 28;
 	private static final int MAX_COL = 100;
-	private static final int VALUE_WIDTH = MAX_COL - VALUE_INDENT_SIZE;
-	private static final String S = " ";
-	private static final String GROUP_INDENT = repeat(S, GROUP_INDENT_SIZE);
-	private static final String KEY_INDENT = repeat(S, KEY_INDENT_SIZE);
-	private static final String VALUE_INDENT = repeat(S, VALUE_INDENT_SIZE);
-
-	private static String formatGroup(String group) {
-		return TextUtils.NEWLINE + GROUP_INDENT + group;
-	}
+	private static final int VALUE_WIDTH = MAX_COL - LOG_VALUE_COLUMN;
+	private static final String KEY_INDENT = LOG_INDENT + "  ";
+	private static final String VALUE_INDENT = NEWLINE + repeat(" ", LOG_VALUE_COLUMN);
 
 	private static String formatEntry(String key) {
-		return NEWLINE + padEnd(KEY_INDENT + '.' + key + ':', VALUE_INDENT_SIZE, ' ');
+		return padEnd(KEY_INDENT + '.' + key + ':', LOG_VALUE_COLUMN, ' ');
 	}
 
 	private static <E extends Enum<E>> String formatEntry(E key, Object value) {
-		return NEWLINE + padEnd(KEY_INDENT + '.' + key + ':', VALUE_INDENT_SIZE, ' ') + value;
+		return padEnd(KEY_INDENT + '.' + key + ':', LOG_VALUE_COLUMN, ' ') + value;
 	}
 
 	/* wrap a commma-delimited string */
 	private static String wrap(String s, boolean pad) {
-		if (s.length() <= VALUE_WIDTH) return pad ? NEWLINE + VALUE_INDENT + s : s;
+		if (s.length() <= VALUE_WIDTH) return pad ? VALUE_INDENT + s : s;
 		StringBuilder sb = new StringBuilder();
 		int lastCommaIndex = s.substring(0, VALUE_WIDTH).lastIndexOf(',') + 1;
-		if (pad) sb.append(NEWLINE).append(VALUE_INDENT);
+		if (pad) sb.append(VALUE_INDENT);
 		sb.append(s.substring(0, lastCommaIndex));
 		sb.append(wrap(s.substring(lastCommaIndex).trim(), true));
 		return sb.toString();
@@ -660,21 +646,6 @@ public final class CalcConfig {
 		})
 		.create();
 
-	// TODO clean
-	public static void main(String[] args) throws IOException {
-
-		// CalcConfig cc =
-		// Builder.fromFile(Paths.get("etc/examples/5-complex-model/config-sites.json")).build();
-		// System.out.println(cc);
-
-		CalcConfig cc = Builder
-			.withDefaults()
-			// .extend(Builder.fromFile(Paths.get("etc/examples/6-enhanced-output/config.json")))
-			.extend(Builder.fromFile(Paths.get("etc/examples/2-custom-config/config.json")))
-			.build();
-		System.out.println(cc);
-	}
-
 	/**
 	 * A builder of configuration instances.
 	 */
diff --git a/src/org/opensha2/calc/Deaggregation.java b/src/org/opensha2/calc/Deaggregation.java
index 0a34f6591..dced2dfc3 100644
--- a/src/org/opensha2/calc/Deaggregation.java
+++ b/src/org/opensha2/calc/Deaggregation.java
@@ -4,7 +4,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 import static org.opensha2.data.Data.checkInRange;
 import static org.opensha2.util.TextUtils.NEWLINE;
-import static org.opensha2.util.TextUtils.format;
+import static org.opensha2.util.TextUtils.LOG_INDENT;
 import static org.opensha2.data.Data.multiply;
 import static com.google.common.primitives.Doubles.toArray;
 
@@ -207,11 +207,16 @@ public final class Deaggregation {
 
 		@Override public String toString() {
 			return new StringBuilder("Deagg config:")
-				.append(format("imt")).append(imt.name()).append(" [").append(imt).append("]")
-				.append(format("iml")).append(iml).append(" ").append(imt.units())
-				.append(format("rate")).append(rate).append(" yr⁻¹")
-				.append(format("returnPeriod")).append(returnPeriod).append(" yrs")
-				.append(format("probabilityModel")).append(probabilityModel)
+				.append(LOG_INDENT)
+				.append("imt: ").append(imt.name()).append(" [").append(imt).append("]")
+				.append(LOG_INDENT)
+				.append("iml: ").append(iml).append(" ").append(imt.units())
+				.append(LOG_INDENT)
+				.append("rate: ").append(rate).append(" yr⁻¹")
+				.append(LOG_INDENT)
+				.append("returnPeriod: ").append(returnPeriod).append(" yrs")
+				.append(LOG_INDENT)
+				.append("probabilityModel: ").append(probabilityModel)
 				.append(" [trunc = ").append(truncation).append("]")
 				.toString();
 		}
diff --git a/src/org/opensha2/calc/Site.java b/src/org/opensha2/calc/Site.java
index 6a49dc975..1fb8ba1f3 100644
--- a/src/org/opensha2/calc/Site.java
+++ b/src/org/opensha2/calc/Site.java
@@ -125,7 +125,7 @@ public class Site implements Named {
 
 	@Override
 	public String toString() {
-		return new StringBuilder(Strings.padEnd(name, 24, ' '))
+		return new StringBuilder(Strings.padEnd(name, 20, ' '))
 			.append(String.format("%.3f %.3f Vs30=%s ", location.lon(), location.lat(), vs30))
 			.append(vsInferred ? "inferred " : "measured ")
 			.append(String.format("Z1.0=%s Z2.5=%s", z1p0, z2p5))
diff --git a/src/org/opensha2/calc/Sites.java b/src/org/opensha2/calc/Sites.java
index 4afb4a40c..c3f869afb 100644
--- a/src/org/opensha2/calc/Sites.java
+++ b/src/org/opensha2/calc/Sites.java
@@ -2,14 +2,13 @@ package org.opensha2.calc;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.base.Strings.padStart;
-import static com.google.common.base.Strings.repeat;
+import static com.google.common.base.Strings.padEnd;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.opensha2.geo.BorderType.MERCATOR_LINEAR;
 import static org.opensha2.util.GeoJson.validateProperty;
 import static org.opensha2.util.Parsing.splitToList;
-import static org.opensha2.util.TextUtils.ALIGN_COL;
-import static org.opensha2.util.TextUtils.*;
+import static org.opensha2.util.TextUtils.LOG_INDENT;
+import static org.opensha2.util.TextUtils.LOG_VALUE_COLUMN;
 
 import java.io.IOException;
 import java.io.Reader;
@@ -31,10 +30,8 @@ import org.opensha2.geo.Regions;
 import org.opensha2.util.GeoJson;
 import org.opensha2.util.Parsing;
 import org.opensha2.util.Parsing.Delimiter;
-import org.opensha2.util.TextUtils;
 
 import com.google.common.base.Optional;
-import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.reflect.TypeToken;
@@ -194,8 +191,8 @@ public final class Sites {
 	}
 
 	private static final int TO_STRING_LIMIT = 5;
-	private static final int SITE_INDENT_SIZE = 8;
-	private static final String SITE_INDENT = Strings.repeat(" ", SITE_INDENT_SIZE);
+	private static final String SITE_INDENT = LOG_INDENT + "       ";
+//	private static final String SITE_STRING = padEnd(SITE_INDENT + "Site:", LOG_VALUE_COLUMN, ' ');
 
 	/*
 	 * Parent class for deserialization of different GeoJSON site file formats
@@ -211,16 +208,11 @@ public final class Sites {
 				.append(" [size=").append(size).append("]");
 			if (!region) {
 				for (Site site : Iterables.limit(this, TO_STRING_LIMIT)) {
-					sb.append(NEWLINE)
-						.append(SITE_INDENT)
-						.append("Site: ")
-						.append(site);
+					sb.append(SITE_INDENT).append(site);
 				}
 				if (size > TO_STRING_LIMIT) {
 					int delta = size - TO_STRING_LIMIT;
-					sb.append(NEWLINE)
-						.append(SITE_INDENT)
-						.append("... and ").append(delta).append(" more ...");
+					sb.append(SITE_INDENT).append("... and ").append(delta).append(" more ...");
 				}
 			}
 			return sb.toString();
diff --git a/src/org/opensha2/eq/model/ModelConfig.java b/src/org/opensha2/eq/model/ModelConfig.java
index edd4f00af..b263e55c1 100644
--- a/src/org/opensha2/eq/model/ModelConfig.java
+++ b/src/org/opensha2/eq/model/ModelConfig.java
@@ -4,15 +4,16 @@ import static com.google.common.base.CaseFormat.LOWER_CAMEL;
 import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.base.Strings.padEnd;
 import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.opensha2.util.TextUtils.format;
+import static org.opensha2.util.TextUtils.LOG_INDENT;
+import static org.opensha2.util.TextUtils.LOG_VALUE_COLUMN;
 
 import java.io.IOException;
 import java.io.Reader;
 import java.nio.file.Files;
 import java.nio.file.Path;
 
-import org.opensha2.calc.CalcConfig;
 import org.opensha2.eq.fault.surface.RuptureFloating;
 import org.opensha2.eq.model.AreaSource.GridScaling;
 
@@ -20,7 +21,6 @@ import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonParser;
-import com.google.gson.JsonStreamParser;
 
 /**
  * Model and calculation configuration class. No defaults; 'config.json' must be
@@ -87,20 +87,26 @@ final class ModelConfig {
 
 	@Override
 	public String toString() {
-		return new StringBuilder("Model Configuration:")
-			.append(format(Key.NAME)).append(name)
-			.append(format(Key.RESOURCE)).append(resource.toAbsolutePath().normalize())
-			.append(format(Key.SURFACE_SPACING)).append(surfaceSpacing)
-			.append(format(Key.RUPTURE_FLOATING)).append(ruptureFloating)
-			.append(format(Key.RUPTURE_VARIABILITY)).append(ruptureVariability)
-			.append(format(Key.POINT_SOURCE_TYPE)).append(pointSourceType)
-			.append(format(Key.AREA_GRID_SCALING)).append(areaGridScaling)
+		return new StringBuilder("Model Config: ")
+			.append(resource.toAbsolutePath().normalize())
+			.append(LOG_INDENT).append("Model")
+			.append(formatEntry(Key.NAME, name))
+			.append(formatEntry(Key.SURFACE_SPACING, surfaceSpacing))
+			.append(formatEntry(Key.RUPTURE_FLOATING, ruptureFloating))
+			.append(formatEntry(Key.RUPTURE_VARIABILITY, ruptureVariability))
+			.append(formatEntry(Key.POINT_SOURCE_TYPE, pointSourceType))
+			.append(formatEntry(Key.AREA_GRID_SCALING, areaGridScaling))
 			.toString();
 	}
 
+	private static final String KEY_INDENT = LOG_INDENT + "  ";
+
+	private static <E extends Enum<E>> String formatEntry(E key, Object value) {
+		return padEnd(KEY_INDENT + '.' + key + ':', LOG_VALUE_COLUMN, ' ') + value;
+	}
+
 	final static class Builder {
 
-		private static final String ID = "ModelConfig.Builder";
 		private boolean built = false;
 
 		private String name;
diff --git a/src/org/opensha2/util/TextUtils.java b/src/org/opensha2/util/TextUtils.java
index 28e243ef0..33be0461f 100644
--- a/src/org/opensha2/util/TextUtils.java
+++ b/src/org/opensha2/util/TextUtils.java
@@ -1,12 +1,7 @@
 package org.opensha2.util;
 
 import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Strings.padStart;
-import static com.google.common.base.Strings.repeat;
 
-import java.util.Map;
-
-import com.google.common.base.Joiner;
 import com.google.common.base.StandardSystemProperty;
 import com.google.common.base.Strings;
 
@@ -19,26 +14,14 @@ public class TextUtils {
 
 	/** System specific newline string. */
 	public static final String NEWLINE = StandardSystemProperty.LINE_SEPARATOR.value();
-	
-	public static final int ALIGN_COL = 24;
-	private static final int MAX_COL = 100;
-	private static final int DELTA_COL = MAX_COL - ALIGN_COL - 2;
-	private static final String INDENT = NEWLINE + repeat(" ", ALIGN_COL + 2);
-	
-	
-	private static final Joiner.MapJoiner MAP_JOIN = Joiner.on(", ").withKeyValueSeparator(": ");
 
-	private String toString(Map<?, ?> map) {
-		return Parsing.addBrackets(MAP_JOIN.join(map));
-	}
+	/** The column on which to align values in a log entry. */
+	public static final int LOG_VALUE_COLUMN = 32;
 	
-	public static <E extends Enum<E>> String format(E id) {
-		return format(id.toString());
-	}
+	private static final int LOG_INDENT_SIZE = 8;
 
-	public static String format(String s) {
-		return NEWLINE + padStart(s, ALIGN_COL, ' ') + ": ";
-	}
+	/** A newline plus the number of spaces to indent multiline log entries. */
+	public static final String LOG_INDENT = NEWLINE + Strings.repeat(" ", LOG_INDENT_SIZE);
 	
 	/**
 	 * Verifies that the supplied {@code String} is neither {@code null} or
-- 
GitLab


From 3e901237a0b3133d0c7cc9720641efe808a87ea3 Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Thu, 24 Mar 2016 13:25:47 -0600
Subject: [PATCH 12/17] threadCount now picked up from config

---
 etc/examples/6-enhanced-output/config.json |  2 +-
 src/org/opensha2/calc/Calcs.java           |  2 +-
 src/org/opensha2/calc/ThreadCount.java     | 45 ++++++++++++++++++----
 src/org/opensha2/programs/DeaggCalc.java   | 15 ++------
 src/org/opensha2/programs/HazardCalc.java  | 45 ++++++++++------------
 5 files changed, 64 insertions(+), 45 deletions(-)

diff --git a/etc/examples/6-enhanced-output/config.json b/etc/examples/6-enhanced-output/config.json
index a16d67dd9..b42a3f733 100644
--- a/etc/examples/6-enhanced-output/config.json
+++ b/etc/examples/6-enhanced-output/config.json
@@ -1,5 +1,5 @@
 {
   "output": {
-    "curveTypes": ["TOTAL", "GMM"]
+    "curveTypes": ["TOTAL", "GMM", "SOURCE"]
   }
 }
diff --git a/src/org/opensha2/calc/Calcs.java b/src/org/opensha2/calc/Calcs.java
index df2a65388..7ddecbd88 100644
--- a/src/org/opensha2/calc/Calcs.java
+++ b/src/org/opensha2/calc/Calcs.java
@@ -107,7 +107,7 @@ public class Calcs {
 
 	/**
 	 * Compute probabilistic seismic hazard, possibly using an {@link Optional}
-	 * {@link Executor}. If no {@code Executor} is supplied, the calculation
+	 * {@link Executor}. If no executor is supplied, the calculation
 	 * will run on the current thread.
 	 * 
 	 * @param model to use
diff --git a/src/org/opensha2/calc/ThreadCount.java b/src/org/opensha2/calc/ThreadCount.java
index cbae63135..f1dc3a94b 100644
--- a/src/org/opensha2/calc/ThreadCount.java
+++ b/src/org/opensha2/calc/ThreadCount.java
@@ -2,6 +2,8 @@ package org.opensha2.calc;
 
 import java.util.concurrent.ExecutorService;
 
+import com.google.common.util.concurrent.MoreExecutors;
+
 /**
  * The number of threads with which to intialize thread pools. Values reference
  * the number of non-competing threads that could be supported on a particular
@@ -12,28 +14,57 @@ import java.util.concurrent.ExecutorService;
 public enum ThreadCount {
 
 	/**
-	 * A single thread. Use of a single thread will generally prevent an
-	 * {@link ExecutorService} from being used, and all calculations will be run
+	 * A single thread. This identifier will typically cause a program to
+	 * either use Guava's {@link MoreExecutors#directExecutor()} or skip using
+	 * an {@link ExecutorService} altogether, and all calculations will be run
 	 * on the thread from which a program was called. This is useful for
 	 * debugging.
 	 */
-	ONE,
+	ONE {
+		@Override
+		public int value() {
+			return 1;
+		}
+	},
 
 	/**
 	 * Half of {@code ALL}.
 	 */
-	HALF,
+	HALF {
+		@Override
+		public int value() {
+			return Math.max(1, CORES / 2);
+		}
+	},
 
 	/**
-	 * Two less than {@code ALL}. So as to not commandeer all available
+	 * Two less than {@code ALL}, so as to not commandeer all available
 	 * resources.
 	 */
-	N_MINUS_2,
+	N_MINUS_2 {
+		@Override
+		public int value() {
+			return Math.max(1, CORES - 2);
+		}
+	},
 
 	/**
 	 * All possible non-competing threads. The number of threads will equal the
 	 * number of available processors.
 	 */
-	ALL;
+	ALL {
+		@Override
+		public int value() {
+			return CORES;
+		}
+	};
+
+	private static final int CORES = Runtime.getRuntime().availableProcessors();
+
+	/**
+	 * The number of threads relative to the number of available processors on a
+	 * system. The value returned will never be less than one.
+	 */
+	public abstract int value();
 
 }
diff --git a/src/org/opensha2/programs/DeaggCalc.java b/src/org/opensha2/programs/DeaggCalc.java
index 2e07692f1..d9a9d37df 100644
--- a/src/org/opensha2/programs/DeaggCalc.java
+++ b/src/org/opensha2/programs/DeaggCalc.java
@@ -57,8 +57,9 @@ public class DeaggCalc {
 	/**
 	 * Perform a hazard deaggregation at a {@code site} for a {@code model},
 	 * {@code config}, and return period. If an {@code executor} is supplied, it
-	 * will be used to distribute hazard calculation tasks; otherwise, one will
-	 * be created.
+	 * will be used to distribute tasks; otherwise, the calculation will run on
+	 * the current thread. Be sure to shutdown any supplied executor after a
+	 * calculation completes.
 	 * 
 	 * <p><b>Note:</b> any model initialization settings in {@code config} will
 	 * be ignored as the supplied model will already have been initialized.</p>
@@ -76,12 +77,8 @@ public class DeaggCalc {
 			Site site,
 			double returnPeriod,
 			Optional<Executor> executor) {
-
-		Optional<Executor> execLocal = executor.or(Optional.of(createExecutor()));
-
 		try {
-			Hazard result = Calcs.hazard(model, config, site, execLocal);
-			if (!executor.isPresent()) ((ExecutorService) executor).shutdown();
+			Hazard result = Calcs.hazard(model, config, site, executor);
 			return Calcs.deaggregation(result, returnPeriod);
 		} catch (ExecutionException | InterruptedException e) {
 			Throwables.propagate(e);
@@ -89,8 +86,4 @@ public class DeaggCalc {
 		}
 	}
 
-	private static ExecutorService createExecutor() {
-		return newFixedThreadPool(getRuntime().availableProcessors());
-	}
-
 }
diff --git a/src/org/opensha2/programs/HazardCalc.java b/src/org/opensha2/programs/HazardCalc.java
index eb05b0344..aff44addd 100644
--- a/src/org/opensha2/programs/HazardCalc.java
+++ b/src/org/opensha2/programs/HazardCalc.java
@@ -1,6 +1,5 @@
 package org.opensha2.programs;
 
-import static java.lang.Runtime.getRuntime;
 import static java.nio.file.StandardOpenOption.APPEND;
 import static java.util.concurrent.Executors.newFixedThreadPool;
 import static org.opensha2.util.TextUtils.NEWLINE;
@@ -24,11 +23,11 @@ import org.opensha2.calc.Hazard;
 import org.opensha2.calc.Results;
 import org.opensha2.calc.Site;
 import org.opensha2.calc.Sites;
+import org.opensha2.calc.ThreadCount;
 import org.opensha2.eq.model.HazardModel;
 import org.opensha2.util.Logging;
 
 import com.google.common.base.Optional;
-import com.google.common.base.StandardSystemProperty;
 import com.google.common.base.Stopwatch;
 import com.google.common.base.Throwables;
 
@@ -134,7 +133,8 @@ public class HazardCalc {
 		} catch (Exception e) {
 			throw new IllegalArgumentException(
 				"'sites' [" + arg + "] must either be a 3 to 7 argument, comma-delimited string " +
-					"or specify a path to a *.csv or *.geojson file", e);
+					"or specify a path to a *.csv or *.geojson file",
+				e);
 		}
 	}
 
@@ -150,10 +150,15 @@ public class HazardCalc {
 			Iterable<Site> sites,
 			Logger log) throws IOException {
 
-		ExecutorService execSvc = createExecutor();
-		int threadCount = ((ThreadPoolExecutor) execSvc).getCorePoolSize();
-		log.info("Threads: " + threadCount);
-		Optional<Executor> executor = Optional.<Executor> of(execSvc);
+		ExecutorService execSvc = null;
+		ThreadCount threadCount = config.performance.threadCount;
+		if (threadCount != ThreadCount.ONE) {
+			execSvc = newFixedThreadPool(threadCount.value());
+			log.info("Threads: " + ((ThreadPoolExecutor) execSvc).getCorePoolSize());
+		} else {
+			log.info("Threads: Running on calling thread");
+		}
+		Optional<Executor> executor = Optional.<Executor> fromNullable(execSvc);
 
 		log.info(PROGRAM + ": calculating ...");
 		Stopwatch batchWatch = Stopwatch.createStarted();
@@ -184,13 +189,17 @@ public class HazardCalc {
 		}
 		log.info(PROGRAM + ": " + count + " complete " + totalWatch);
 
-		execSvc.shutdown();
+		if (threadCount != ThreadCount.ONE) {
+			execSvc.shutdown();
+		}
 	}
 
 	/**
 	 * Compute hazard curves at a {@code site} for a {@code model} and
 	 * {@code config}. If an {@code executor} is supplied, it will be used to
-	 * distribute tasks; otherwise, one will be created.
+	 * distribute tasks; otherwise, the calculation will run on the current
+	 * thread. Be sure to shutdown any supplied executor after a calculation
+	 * completes.
 	 * 
 	 * <p><b>Note:</b> any model initialization settings in {@code config} will
 	 * be ignored as the supplied model will already have been initialized.</p>
@@ -206,28 +215,14 @@ public class HazardCalc {
 			CalcConfig config,
 			Site site,
 			Optional<Executor> executor) {
-
-		// TODO not sure why we're mandating an executor here.
-		// legacy from refactoring?
-		Optional<Executor> execLocal = executor.or(Optional.of(createExecutor()));
-
 		try {
-			Hazard result = Calcs.hazard(model, config, site, execLocal);
-			// Shut down the locally created executor if none was supplied
-			if (!executor.isPresent()) {
-				((ExecutorService) execLocal.get()).shutdown();
-			}
-			return result;
+			return Calcs.hazard(model, config, site, executor);
 		} catch (ExecutionException | InterruptedException e) {
 			Throwables.propagate(e);
 			return null;
 		}
 	}
-
-	private static ExecutorService createExecutor() {
-		return newFixedThreadPool(getRuntime().availableProcessors());
-	}
-
+	
 	private static final String PROGRAM = HazardCalc.class.getSimpleName();
 	private static final String USAGE_COMMAND =
 		"java -cp nshmp-haz.jar org.opensha2.programs.HazardCalc model sites [config]";
-- 
GitLab


From 9e1647bfbc8dd86adbac60cc4e522c2f63f430e0 Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Thu, 24 Mar 2016 13:42:44 -0600
Subject: [PATCH 13/17] flushLimit (aka batchSize) now implemented in
 hazardCalc

---
 src/org/opensha2/calc/CalcConfig.java | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/org/opensha2/calc/CalcConfig.java b/src/org/opensha2/calc/CalcConfig.java
index 2e3c65f66..18cc7770b 100644
--- a/src/org/opensha2/calc/CalcConfig.java
+++ b/src/org/opensha2/calc/CalcConfig.java
@@ -445,8 +445,9 @@ public final class CalcConfig {
 				int flushLimit) {
 
 			this.directory = directory;
-			curveTypes.add(CurveType.TOTAL);
-			this.curveTypes = Sets.immutableEnumSet(curveTypes);
+			this.curveTypes = Sets.immutableEnumSet(
+				CurveType.TOTAL,
+				curveTypes.toArray(new CurveType[curveTypes.size()]));
 			this.flushLimit = flushLimit;
 		}
 
-- 
GitLab


From 877100b367916d4eeaa2ffd24cd46378eb3b5bee Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Thu, 24 Mar 2016 14:36:18 -0600
Subject: [PATCH 14/17] added output directory incrementing to hazardCalc

---
 src/org/opensha2/programs/HazardCalc.java | 20 +++++++++++++++++---
 src/org/opensha2/util/Logging.java        |  5 ++---
 2 files changed, 19 insertions(+), 6 deletions(-)

diff --git a/src/org/opensha2/programs/HazardCalc.java b/src/org/opensha2/programs/HazardCalc.java
index aff44addd..571f45193 100644
--- a/src/org/opensha2/programs/HazardCalc.java
+++ b/src/org/opensha2/programs/HazardCalc.java
@@ -5,6 +5,7 @@ import static java.util.concurrent.Executors.newFixedThreadPool;
 import static org.opensha2.util.TextUtils.NEWLINE;
 
 import java.io.IOException;
+import java.nio.file.Files;
 import java.nio.file.OpenOption;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -168,13 +169,15 @@ public class HazardCalc {
 		List<Hazard> results = new ArrayList<>();
 		boolean firstBatch = true;
 
+		Path outDir = createOutputDir(config.output.directory);
+
 		for (Site site : sites) {
 			Hazard result = calc(model, config, site, executor);
 			results.add(result);
 			if (results.size() == config.output.flushLimit) {
 				OpenOption[] opts = firstBatch ? WRITE_OPTIONS : APPEND_OPTIONS;
 				firstBatch = false;
-				Results.writeResults(config.output.directory, results, opts);
+				Results.writeResults(outDir, results, opts);
 				log.info("     batch: " + (count + 1) + "  " + batchWatch +
 					"  total: " + totalWatch);
 				results.clear();
@@ -185,7 +188,7 @@ public class HazardCalc {
 		// write final batch
 		if (!results.isEmpty()) {
 			OpenOption[] opts = firstBatch ? WRITE_OPTIONS : APPEND_OPTIONS;
-			Results.writeResults(config.output.directory, results, opts);
+			Results.writeResults(outDir, results, opts);
 		}
 		log.info(PROGRAM + ": " + count + " complete " + totalWatch);
 
@@ -194,6 +197,17 @@ public class HazardCalc {
 		}
 	}
 
+	/* avoid clobbering exsting result directories via incrementing */
+	private static Path createOutputDir(Path dir) {
+		int i = 1;
+		Path dirIncrement = dir;
+		while (Files.exists(dirIncrement)) {
+			dirIncrement = dirIncrement.resolveSibling(dir.getFileName() + "-" + i);
+			i++;
+		}
+		return dirIncrement;
+	}
+
 	/**
 	 * Compute hazard curves at a {@code site} for a {@code model} and
 	 * {@code config}. If an {@code executor} is supplied, it will be used to
@@ -222,7 +236,7 @@ public class HazardCalc {
 			return null;
 		}
 	}
-	
+
 	private static final String PROGRAM = HazardCalc.class.getSimpleName();
 	private static final String USAGE_COMMAND =
 		"java -cp nshmp-haz.jar org.opensha2.programs.HazardCalc model sites [config]";
diff --git a/src/org/opensha2/util/Logging.java b/src/org/opensha2/util/Logging.java
index 1b09aede6..66f6abfb8 100644
--- a/src/org/opensha2/util/Logging.java
+++ b/src/org/opensha2/util/Logging.java
@@ -62,8 +62,8 @@ public class Logging {
 	 */
 	public final static class ConsoleFormatter extends Formatter {
 
-		@Override public String format(LogRecord record) {
-			// @formatter:off
+		@Override
+		public String format(LogRecord record) {
 			StringBuilder b = new StringBuilder();
 			Level l = record.getLevel();
 			b.append(Strings.padStart(l.toString(), 7, ' '));
@@ -92,7 +92,6 @@ public class Logging {
 			}
 			b.append(LF);
 			return b.toString();
-			// @formatter:on
 		}
 
 	}
-- 
GitLab


From 879e09b67289271c76c263c7d2e7b04b74d9d070 Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Thu, 24 Mar 2016 15:08:49 -0600
Subject: [PATCH 15/17] hazard Results now handles annRate vs poissProb output
 from config

---
 etc/examples/2-custom-config/README.md   |   1 +
 etc/examples/2-custom-config/config.json |   1 +
 src/org/opensha2/calc/Results.java       | 111 +++++++----------------
 3 files changed, 33 insertions(+), 80 deletions(-)

diff --git a/etc/examples/2-custom-config/README.md b/etc/examples/2-custom-config/README.md
index cff4d28ec..10b3a22ac 100644
--- a/etc/examples/2-custom-config/README.md
+++ b/etc/examples/2-custom-config/README.md
@@ -12,6 +12,7 @@ hazard ../../peer/models/Set1-Case1 "San Francisco,-122.40,37.75" config.json
 In this example we've overridden the configuration supplied by the model. Specifically:
 
 * The upper end of each hazard curve has been truncated at 3 standard deviations.
+* Hazard curves have been saved as poisson probability instead of annual rate.
 * Hazard curves have been calculated for 3 `imts` ([intensity measures](http://usgs.github.io/nshmp-haz/javadoc/index.html?org/opensha2/gmm/Imt.html), or spectral periods).
 * The `imls` (intensity measure levels or x-values) of the resultant curves have been explicitely defined for each `imt`.
 
diff --git a/etc/examples/2-custom-config/config.json b/etc/examples/2-custom-config/config.json
index 36b538061..81bf35969 100644
--- a/etc/examples/2-custom-config/config.json
+++ b/etc/examples/2-custom-config/config.json
@@ -3,6 +3,7 @@
     "exceedanceModel": "TRUNCATION_UPPER_ONLY",
     "truncationLevel": 3.0,
     "imts": ["PGA", "SA0P2", "SA1P0"],
+    "valueType": "POISSON_PROBABILITY",
     "customImls": {
       "PGA":   [0.0050, 0.0070, 0.0098, 0.0137, 0.0192, 0.0269, 0.0376, 0.0527, 0.0738, 0.103, 0.145, 0.203, 0.284, 0.397, 0.556, 0.778, 1.09, 1.52, 2.2, 3.3],
       "SA0P2": [0.0050, 0.0075, 0.0113, 0.0169, 0.0253, 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],
diff --git a/src/org/opensha2/calc/Results.java b/src/org/opensha2/calc/Results.java
index 13e0e9901..782f62aae 100644
--- a/src/org/opensha2/calc/Results.java
+++ b/src/org/opensha2/calc/Results.java
@@ -19,13 +19,14 @@ import org.opensha2.data.XySequence;
 import org.opensha2.eq.model.Source;
 import org.opensha2.eq.model.SourceSet;
 import org.opensha2.eq.model.SourceType;
-import org.opensha2.geo.Location;
 import org.opensha2.gmm.Gmm;
 import org.opensha2.gmm.Imt;
+import org.opensha2.mfd.Mfds;
 import org.opensha2.util.Parsing;
 import org.opensha2.util.Parsing.Delimiter;
 
 import com.google.common.base.Function;
+import com.google.common.base.Functions;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
@@ -43,6 +44,28 @@ public class Results {
 	private static final String CURVE_FILE_SUFFIX = ".csv";
 	private static final String RATE_FMT = "%.8e";
 
+	/*
+	 * Individual Hazard results only contain data relevant to the site of
+	 * interest (e.g. for the NSHM WUS models, hazard in San Fancisco is
+	 * influenced by slab sources whereas hazard in Los Angeles is not because
+	 * it is too far away). For consistency when outputting batches of results,
+	 * files are written for all source types and ground motion models supported
+	 * by the HazardModel being used. This yields curve sets that are consistent
+	 * across all locations in a batch, however, some of the curves may be
+	 * empty. Depending on the extents of a map or list of sites, some curve
+	 * sets may consist exclusively of zero-valued curves.
+	 */
+
+	/*
+	 * TODO There is no good reason for this class to be public. It would be
+	 * better as a ResultsWriter, an instance of which would be obtained and
+	 * configured on a per-calculation basis to receive batches of results.
+	 * Although problems are unlikely, we're repeating a number of configuration
+	 * steps below and relying on the same config file coming with the first
+	 * result in each batch (below). We also wouldn't have to pass around
+	 * OpenOptions which are mildly confusing.
+	 */
+
 	/**
 	 * Write a {@code batch} of {@code HazardResult}s to files in the specified
 	 * directory, one for each {@link Imt} in the {@code batch}. See
@@ -59,96 +82,24 @@ public class Results {
 	 * @throws IOException if a problem is encountered
 	 * @see Files#write(Path, Iterable, java.nio.charset.Charset, OpenOption...)
 	 */
-	@Deprecated
-	public static void writeResultsOLD(Path dir, List<Hazard> batch, OpenOption... options)
-			throws IOException {
-
-		Function<Double, String> locFmtFunc = Parsing.formatDoubleFunction("%.5f");
-		Function<Double, String> rateFmtFunc = Parsing.formatDoubleFunction(RATE_FMT);
-
-		Hazard demo = batch.get(0);
-		boolean newFile = options.length == 0;
-		boolean namedSites = demo.site.name != Site.NO_NAME;
-
-		Map<Imt, List<String>> lineMap = new EnumMap<>(Imt.class);
-		for (Imt imt : demo.totalCurves.keySet()) {
-			List<String> lineList = new ArrayList<>();
-			// write header
-			if (newFile) {
-				List<String> headings = new ArrayList<>();
-				if (namedSites) headings.add("name");
-				headings.add("lon");
-				headings.add("lat");
-				Iterable<?> header = Iterables.concat(
-					headings,
-					demo.config.curve.modelCurves().get(imt).xValues());
-				lineList.add(Parsing.join(header, Delimiter.COMMA));
-			}
-			lineMap.put(imt, lineList);
-		}
-
-		for (Hazard result : batch) {
-			Iterable<String> locData = Iterables.transform(
-				Lists.newArrayList(
-					result.site.location.lon(),
-					result.site.location.lat()),
-				locFmtFunc);
-			String name = result.site.name;
-			for (Entry<Imt, XySequence> entry : result.totalCurves.entrySet()) {
-
-				// enable to output poisson probability - used when running
-				// PEER test cases - TODO should be configurable
-				// Function<Double, String> valueFunction = Functions.compose(
-				// rateFmtFunc,
-				// Mfds.rateToProbConverter());
-
-				// enable to output annual rate
-				Function<Double, String> valueFunction = rateFmtFunc;
-
-				Iterable<String> lineData = Iterables.concat(
-					locData,
-					Iterables.transform(
-						entry.getValue().yValues(),
-						valueFunction));
-
-				String line = Parsing.join(lineData, Delimiter.COMMA);
-				if (namedSites) line = name + "," + line;
-				lineMap.get(entry.getKey()).add(line);
-			}
-		}
-
-		for (Entry<Imt, List<String>> entry : lineMap.entrySet()) {
-			String filename = entry.getKey().name() + CURVE_FILE_SUFFIX;
-			Path file = dir.resolve(filename);
-			Files.write(file, entry.getValue(), US_ASCII, options);
-		}
-	}
-
-	/*
-	 * Individual Hazard results only contain data relevant to the site of
-	 * interest (e.g. for the NSHM WUS models, hazard in San Fancisco is
-	 * influenced by slab sources whereas hazard in Los Angeles is not because
-	 * it is too far away). For consistency when outputting batches of results,
-	 * files are written for all source types and ground motion models supported
-	 * by the HazardModel being used. This yields curve sets that are consistent
-	 * across all locations in a batch, however, some of the curves may be
-	 * empty. Depending on the extents of a map or list of sites, some curve
-	 * sets may consist exclusively of zero-valued curves.
-	 */
-
 	public static void writeResults(
 			Path dir,
 			List<Hazard> batch,
 			OpenOption... options) throws IOException {
 
-		Function<Double, String> formatter = Parsing.formatDoubleFunction(RATE_FMT);
-
 		Hazard demo = batch.get(0);
 		boolean newFile = options.length == 0;
 		boolean namedSites = demo.site.name != Site.NO_NAME;
 		boolean gmmCurves = demo.config.output.curveTypes.contains(CurveType.GMM);
 		boolean sourceCurves = demo.config.output.curveTypes.contains(CurveType.SOURCE);
 
+		Function<Double, String> formatter = Parsing.formatDoubleFunction(RATE_FMT);
+		if (demo.config.curve.valueType == CurveValue.POISSON_PROBABILITY) {
+			formatter = Functions.compose(
+				formatter,
+				Mfds.annualRateToProbabilityConverter());
+		}
+
 		Map<Imt, List<String>> totalLineMap = Maps.newEnumMap(Imt.class);
 		Map<Imt, Map<SourceType, List<String>>> sourceLineMap = Maps.newEnumMap(Imt.class);
 		Map<Imt, Map<Gmm, List<String>>> gmmLineMap = Maps.newEnumMap(Imt.class);
-- 
GitLab


From 39af53d06a3177e73d51c5715d70f1ecdcd9b46b Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Thu, 24 Mar 2016 16:44:07 -0600
Subject: [PATCH 16/17] calc log now written to temp file and copied to results
 directory upon completion

---
 lib/logging.properties                    |  7 ++--
 src/org/opensha2/eq/model/Loader.java     |  1 -
 src/org/opensha2/programs/HazardCalc.java | 47 +++++++++++++++++------
 src/org/opensha2/util/Logging.java        |  1 -
 4 files changed, 39 insertions(+), 17 deletions(-)

diff --git a/lib/logging.properties b/lib/logging.properties
index ab912df4f..41a45128d 100644
--- a/lib/logging.properties
+++ b/lib/logging.properties
@@ -37,10 +37,11 @@ handlers = java.util.logging.ConsoleHandler
 java.util.logging.FileHandler.pattern = %h/java%u.log
 java.util.logging.FileHandler.limit = 50000
 java.util.logging.FileHandler.count = 1
-java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
+#java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
+java.util.logging.FileHandler.formatter = org.opensha2.util.Logging$ConsoleFormatter
 
 # Limit the message that are printed on the console to INFO and above.
-java.util.logging.ConsoleHandler.level = ALL
+java.util.logging.ConsoleHandler.level = INFO
 #java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
 java.util.logging.ConsoleHandler.formatter = org.opensha2.util.Logging$ConsoleFormatter
 
@@ -57,4 +58,4 @@ org.opensha2.eq.model.IndexedFaultParser.level = INFO
 org.opensha2.eq.model.GridParser.level = INFO
 org.opensha2.eq.model.InterfaceParser.level = INFO
 org.opensha2.eq.model.GmmParser.level = INFO
-org.opensha2.programs.HazardCurve = ALL
+org.opensha2.programs.HazardCalc = ALL
diff --git a/src/org/opensha2/eq/model/Loader.java b/src/org/opensha2/eq/model/Loader.java
index 6b950ccee..9ace19ec3 100644
--- a/src/org/opensha2/eq/model/Loader.java
+++ b/src/org/opensha2/eq/model/Loader.java
@@ -1,7 +1,6 @@
 package org.opensha2.eq.model;
 
 import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.base.StandardSystemProperty.LINE_SEPARATOR;
 import static java.nio.file.Files.newDirectoryStream;
diff --git a/src/org/opensha2/programs/HazardCalc.java b/src/org/opensha2/programs/HazardCalc.java
index 571f45193..2a6f358b4 100644
--- a/src/org/opensha2/programs/HazardCalc.java
+++ b/src/org/opensha2/programs/HazardCalc.java
@@ -16,6 +16,7 @@ import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.ThreadPoolExecutor;
+import java.util.logging.FileHandler;
 import java.util.logging.Logger;
 
 import org.opensha2.calc.CalcConfig;
@@ -85,10 +86,14 @@ public class HazardCalc {
 			return Optional.of(USAGE);
 		}
 
-		Logging.init();
-		Logger log = Logger.getLogger(HazardCalc.class.getName());
-
 		try {
+			Logging.init();
+			Logger log = Logger.getLogger(HazardCalc.class.getName());
+			Path tempLog = createTempLog();
+			FileHandler fh = new FileHandler(tempLog.getFileName().toString());
+			fh.setFormatter(new Logging.ConsoleFormatter());
+			log.getParent().addHandler(fh);
+
 			log.info(PROGRAM + ": initializing...");
 			Path modelPath = Paths.get(args[0]);
 			HazardModel model = HazardModel.load(modelPath);
@@ -106,7 +111,9 @@ public class HazardCalc {
 			log.info("");
 			log.info("Sites: " + sites);
 
-			calc(model, config, sites, log);
+			Path out = calc(model, config, sites, log);
+			Files.move(tempLog, out.resolve(PROGRAM + ".log"));
+			
 			log.info(PROGRAM + ": finished");
 			return Optional.absent();
 
@@ -143,9 +150,10 @@ public class HazardCalc {
 	private static final OpenOption[] APPEND_OPTIONS = new OpenOption[] { APPEND };
 
 	/*
-	 * Compute hazard curves using the supplied model, config, and sites.
+	 * Compute hazard curves using the supplied model, config, and sites. Method
+	 * returns the path to the directory where results were written.
 	 */
-	private static void calc(
+	private static Path calc(
 			HazardModel model,
 			CalcConfig config,
 			Iterable<Site> sites,
@@ -191,21 +199,36 @@ public class HazardCalc {
 			Results.writeResults(outDir, results, opts);
 		}
 		log.info(PROGRAM + ": " + count + " complete " + totalWatch);
-
+		
 		if (threadCount != ThreadCount.ONE) {
 			execSvc.shutdown();
 		}
+		
+		return outDir;
 	}
 
-	/* avoid clobbering exsting result directories via incrementing */
+	/* Avoid clobbering exsting result directories via incrementing */
 	private static Path createOutputDir(Path dir) {
 		int i = 1;
-		Path dirIncrement = dir;
-		while (Files.exists(dirIncrement)) {
-			dirIncrement = dirIncrement.resolveSibling(dir.getFileName() + "-" + i);
+		Path dirIncr = dir;
+		while (Files.exists(dirIncr)) {
+			dirIncr = dirIncr.resolveSibling(dir.getFileName() + "-" + i);
+			i++;
+		}
+		return dirIncr;
+	}
+
+	private static final String TMP_LOG = "nshmp-haz-log";
+
+	private static Path createTempLog() {
+		Path logBase = Paths.get(".");
+		Path logIncr = logBase.resolve(TMP_LOG);
+		int i = 1;
+		while (Files.exists(logIncr)) {
+			logIncr = logBase.resolve(TMP_LOG + "-" + i);
 			i++;
 		}
-		return dirIncrement;
+		return logIncr;
 	}
 
 	/**
diff --git a/src/org/opensha2/util/Logging.java b/src/org/opensha2/util/Logging.java
index 66f6abfb8..0e6e9b87c 100644
--- a/src/org/opensha2/util/Logging.java
+++ b/src/org/opensha2/util/Logging.java
@@ -58,7 +58,6 @@ public class Logging {
 
 	/**
 	 * Custom console formatter.
-	 * @author Peter Powers
 	 */
 	public final static class ConsoleFormatter extends Formatter {
 
-- 
GitLab


From f5c78c3f3824711dd1d47cb81605a004e0678cec Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Thu, 24 Mar 2016 22:21:55 -0600
Subject: [PATCH 17/17] a copy of the calcConfig is now written to the results
 directory

---
 etc/examples/1-hazard-curve/README.md     |  2 +-
 src/org/opensha2/calc/CalcConfig.java     | 36 +++++++++++++++++++++--
 src/org/opensha2/programs/HazardCalc.java |  3 ++
 3 files changed, 37 insertions(+), 4 deletions(-)

diff --git a/etc/examples/1-hazard-curve/README.md b/etc/examples/1-hazard-curve/README.md
index 3647eee34..b4a39bc3c 100644
--- a/etc/examples/1-hazard-curve/README.md
+++ b/etc/examples/1-hazard-curve/README.md
@@ -15,7 +15,7 @@ The result of this calculation should be available as a single comma-delimited f
 
 Note that not all [calculation configuration](https://github.com/usgs/nshmp-haz/wiki/Configuration) parameters need be supplied; see the [configuration file](../../peer/models/Set1-Case1/config.json) for this example model.
 
-Also note that all output is written to the current working directory, but the ouput destination can be specified via the [`outputDir`](https://github.com/usgs/nshmp-haz/wiki/Configuration) parameter.
+Also note that all output is written to a `curves` directory by default, but the ouput destination can be specified via the [`outputDir`](https://github.com/usgs/nshmp-haz/wiki/Configuration) parameter. In addition to hazard curves, the calculation configuration and a log of the calculation are also saved.
 
 In the next example, we'll override the model supplied configuration with a custom file.
 
diff --git a/src/org/opensha2/calc/CalcConfig.java b/src/org/opensha2/calc/CalcConfig.java
index 18cc7770b..e6aa3f5cb 100644
--- a/src/org/opensha2/calc/CalcConfig.java
+++ b/src/org/opensha2/calc/CalcConfig.java
@@ -16,7 +16,9 @@ import static org.opensha2.util.TextUtils.NEWLINE;
 
 import java.io.IOException;
 import java.io.Reader;
+import java.io.Writer;
 import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -41,6 +43,9 @@ import com.google.gson.JsonDeserializationContext;
 import com.google.gson.JsonDeserializer;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
 
 /**
  * Calculation configuration.
@@ -60,7 +65,7 @@ public final class CalcConfig {
 	 * the field of the extending resource. If this configuration was built only
 	 * from {@link Builder#withDefaults()}, this field will be empty.
 	 */
-	public final Optional<Path> resource;
+	public final transient Optional<Path> resource;
 
 	/** Hazard curve calculation settings */
 	public final Curve curve;
@@ -140,8 +145,9 @@ public final class CalcConfig {
 		private final double[] defaultImls;
 		private final Map<Imt, double[]> customImls;
 
-		private final Map<Imt, XySequence> modelCurves;
-		private final Map<Imt, XySequence> logModelCurves;
+		/* Do not serialize to JSON */
+		private final transient Map<Imt, XySequence> modelCurves;
+		private final transient Map<Imt, XySequence> logModelCurves;
 
 		private Curve(
 				ExceedanceModel exceedanceModel,
@@ -636,6 +642,8 @@ public final class CalcConfig {
 	}
 
 	private static final Gson GSON = new GsonBuilder()
+		.setPrettyPrinting()
+		.enableComplexMapKeySerialization()
 		.registerTypeAdapter(Path.class, new JsonDeserializer<Path>() {
 			@Override
 			public Path deserialize(
@@ -645,8 +653,30 @@ public final class CalcConfig {
 				return Paths.get(json.getAsString());
 			}
 		})
+		.registerTypeAdapter(Path.class, new JsonSerializer<Path>() {
+			@Override
+			public JsonElement serialize(
+					Path path,
+					Type type,
+					JsonSerializationContext context) {
+				return new JsonPrimitive(path.toAbsolutePath().normalize().toString());
+			}
+		})
 		.create();
 
+	/**
+	 * Save this config in JSON format to the speciifed directory.
+	 * 
+	 * @param dir the directory to write to
+	 * @throws IOException if there is a problem writing the file
+	 */
+	public void write(Path dir) throws IOException {
+		Path file = dir.resolve(FILE_NAME);
+		Writer writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8);
+		GSON.toJson(this, writer);
+		writer.close();
+	}
+
 	/**
 	 * A builder of configuration instances.
 	 */
diff --git a/src/org/opensha2/programs/HazardCalc.java b/src/org/opensha2/programs/HazardCalc.java
index 2a6f358b4..8451d1c9e 100644
--- a/src/org/opensha2/programs/HazardCalc.java
+++ b/src/org/opensha2/programs/HazardCalc.java
@@ -112,7 +112,10 @@ public class HazardCalc {
 			log.info("Sites: " + sites);
 
 			Path out = calc(model, config, sites, log);
+			
+			// transfer log and write config
 			Files.move(tempLog, out.resolve(PROGRAM + ".log"));
+			config.write(out);
 			
 			log.info(PROGRAM + ": finished");
 			return Optional.absent();
-- 
GitLab