Skip to content
Snippets Groups Projects
CalcConfig.java 22 KiB
Newer Older
  • Learn to ignore specific revisions
  • package org.opensha2.calc;
    
    Powers, Peter M.'s avatar
    Powers, Peter M. committed
    
    
    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;
    
    Powers, Peter M.'s avatar
    Powers, Peter M. committed
    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.NEWLINE;
    
    // import static org.opensha2.util.TextUtils.*;
    
    Powers, Peter M.'s avatar
    Powers, Peter M. committed
    import java.io.IOException;
    import java.io.Reader;
    
    import java.lang.reflect.Type;
    
    Powers, Peter M.'s avatar
    Powers, Peter M. committed
    import java.nio.file.Files;
    import java.nio.file.Path;
    
    import java.nio.file.Paths;
    
    Powers, Peter M.'s avatar
    Powers, Peter M. committed
    import java.util.Arrays;
    
    Powers, Peter M.'s avatar
    Powers, Peter M. committed
    import java.util.EnumSet;
    
    Powers, Peter M.'s avatar
    Powers, Peter M. committed
    import java.util.Map;
    import java.util.Map.Entry;
    
    Powers, Peter M.'s avatar
    Powers, Peter M. committed
    import java.util.Set;
    
    import org.opensha2.data.Data;
    
    Powers, Peter M.'s avatar
    Powers, Peter M. committed
    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;
    
    Powers, Peter M.'s avatar
    Powers, Peter M. committed
    
    
    import com.google.common.base.Optional;
    
    Powers, Peter M.'s avatar
    Powers, Peter M. committed
    import com.google.common.collect.Maps;
    
    import com.google.common.collect.Sets;
    
    Powers, Peter M.'s avatar
    Powers, Peter M. committed
    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;
    
    Powers, Peter M.'s avatar
    Powers, Peter M. committed
    
    /**
     * Calculation configuration.
    
    Powers, Peter M.'s avatar
    Powers, Peter M. committed
     * @author Peter Powers
     */
    
    public final class CalcConfig {
    
    Powers, Peter M.'s avatar
    Powers, Peter M. committed
    	static final String FILE_NAME = "config.json";
    
    	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";
    
    Powers, Peter M.'s avatar
    Powers, Peter M. committed
    
    
    	 * 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 Optional<Path> resource;
    
    	/** Hazard curve calculation settings */
    	public final Curve curve;
    
    	/** Performance and optimization settings. */
    	public final Performance performance;
    
    	/** Output configuration. */
    	public final Output output;
    
    	/** Deaggregation configuration. */
    	public final Deagg deagg;
    
    	private CalcConfig(
    			Optional<Path> resource,
    			Curve curve,
    			Performance performance,
    			Output output,
    			Deagg deagg) {
    
    		this.resource = resource;
    		this.curve = curve;
    		this.performance = performance;
    		this.output = output;
    		this.deagg = deagg;
    	}
    
    	 * Hazard curve calculation configuration.
    
    	public final static class Curve {
    
    		static final String ID = CalcConfig.ID + "." + Curve.class.getSimpleName();
    
    		/**
    		 * 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
    
    		/**
    		 * 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;
    
    		/**
    		 * 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;
    
    		/**
    		 * 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;
    
    		/**
    		 * 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;
    		}
    
    		/**
    		 * An empty linear curve for the requested {@code Imt}.
    		 * @param imt to get curve for
    		 */
    		public XySequence modelCurve(Imt imt) {
    			return modelCurves.get(imt);
    
    		/**
    		 * An immutable map of model curves where x-values are in linear space.
    		 */
    		public Map<Imt, XySequence> modelCurves() {
    			return modelCurves;
    
    		/**
    		 * An immutable map of model curves where x-values are in natural-log
    		 * space.
    		 */
    		public Map<Imt, XySequence> logModelCurves() {
    			return logModelCurves;
    		}
    
    		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));
    				}
    
    Powers, Peter M.'s avatar
    Powers, Peter M. committed
    			}
    
    			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);
    
    Powers, Peter M.'s avatar
    Powers, Peter M. committed
    		}
    
    		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());
    			}
    
    			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;
    			}
    		}
    
    	 * Performance and optimization settings.
    
    	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);
    			}
    		}
    
    	 * Hazard curve and file output settings.
    
    	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.
    	 */
    
    	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;
    
    		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;
    
    		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;
    
    		public final double Δε;
    
    
    			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("Calc Configuration: ")
    
    			.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()
    		.registerTypeAdapter(Path.class, new JsonDeserializer<Path>() {
    			@Override
    			public Path deserialize(
    					JsonElement json,
    					Type type,
    					JsonDeserializationContext context) throws JsonParseException {
    				return Paths.get(json.getAsString());
    			}
    		})
    		.create();
    
    	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
    
    			// .extend(Builder.fromFile(Paths.get("etc/examples/6-enhanced-output/config.json")))
    			.extend(Builder.fromFile(Paths.get("etc/examples/2-custom-config/config.json")))
    
    	 * A builder of configuration instances.
    
    	 */
    	public static final class Builder {
    
    		private boolean built = false;
    
    
    Powers, Peter M.'s avatar
    Powers, Peter M. committed
    		private Path resource;
    
    		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 values copied from the supplied config.
    
    		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;
    
    		 * 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 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;
    
    		/**
    		 * Extend {@code this} builder to match {@code that} builder. Fields in
    		 * that builder take precedence unless they are not set.
    		 */
    
    		public Builder extend(final Builder that) {
    			checkNotNull(that);
    
    			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;
    		}
    
    
    		/**
    		 * Set the IMTs for which results should be calculated.
    		 */
    
    		public Builder imts(Set<Imt> imts) {
    
    			this.curve.imts = checkNotNull(imts);
    
    		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");
    
    		/**
    		 * Build a new calculation configuration.
    		 */
    
    		public CalcConfig build() {
    
    			return new CalcConfig(
    
    				Optional.fromNullable(resource),
    				curve.build(),
    				performance.build(),
    				output.build(),
    				deagg);
    
    Powers, Peter M.'s avatar
    Powers, Peter M. committed
    }