package org.opensha2; import static java.util.concurrent.Executors.newFixedThreadPool; import static org.opensha2.internal.TextUtils.NEWLINE; import org.opensha2.calc.CalcConfig; import org.opensha2.calc.EqRate; import org.opensha2.calc.EqRateExport; import org.opensha2.calc.Site; import org.opensha2.calc.Sites; import org.opensha2.calc.ThreadCount; import org.opensha2.eq.model.HazardModel; import org.opensha2.internal.Logging; import com.google.common.base.Optional; import com.google.common.base.Throwables; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.logging.FileHandler; import java.util.logging.Logger; /** * Compute earthquake rates or Poisson probabilities from a {@link HazardModel}. * * @author Peter Powers */ public class RateCalc { /** * Entry point for the calculation of earthquake rates and probabilities. * * <p>Computing earthquake rates requires at least 2, and at most 3, * arguments. At a minimum, the path to a model zip file or directory and the * site(s) at which to perform calculations must be specified. Under the * 2-argument scenario, model initialization and calculation configuration * settings are drawn from the config file that <i>must</i> reside at the root * of the model directory. Sites may be defined as a string, a CSV file, or a * GeoJSON file. * * <p>To override any default or calculation configuration settings included * with the model, supply the path to another configuration file as a third * argument. * * <p>Please refer to the nshmp-haz <a * href="https://github.com/usgs/nshmp-haz/wiki" target="_top">wiki</a> for * comprehensive descriptions of source models, configuration files, site * files, and earthquake rate calculations. * * @see <a href="https://github.com/usgs/nshmp-haz/wiki/Building-&-Running" * target="_top"> nshmp-haz wiki</a> * @see <a href="https://github.com/usgs/nshmp-haz/tree/master/etc/examples" * target="_top"> example calculations</a> */ public static void main(String[] args) { /* Delegate to run which has a return value for testing. */ Optional<String> status = run(args); if (status.isPresent()) { System.err.print(status.get()); System.exit(1); } System.exit(0); } static Optional<String> run(String[] args) { int argCount = args.length; if (argCount < 2 || argCount > 3) { return Optional.of(USAGE); } try { Logging.init(); Logger log = Logger.getLogger(HazardCalc.class.getName()); Path tempLog = HazardCalc.createTempLog(); FileHandler fh = new FileHandler(tempLog.getFileName().toString()); fh.setFormatter(new Logging.ConsoleFormatter()); log.getParent().addHandler(fh); log.info(PROGRAM + ": " + HazardCalc.VERSION); Path modelPath = Paths.get(args[0]); HazardModel model = HazardModel.load(modelPath); CalcConfig config = model.config(); if (argCount == 3) { Path userConfigPath = Paths.get(args[2]); config = CalcConfig.Builder.copyOf(model.config()) .extend(CalcConfig.Builder.fromFile(userConfigPath)) .build(); } log.info(config.toString()); log.info(""); Sites sites = HazardCalc.readSites(args[1], config, log); log.info("Sites: " + sites); Path out = calc(model, config, sites, log); log.info(PROGRAM + ": finished"); /* Transfer log and write config, windows requires fh.close() */ fh.close(); Files.move(tempLog, out.resolve(PROGRAM + ".log")); config.write(out); return Optional.absent(); } catch (Exception e) { StringBuilder sb = new StringBuilder() .append(NEWLINE) .append(PROGRAM + ": error").append(NEWLINE) .append(" Arguments: ").append(Arrays.toString(args)).append(NEWLINE) .append(NEWLINE) .append(Throwables.getStackTraceAsString(e)) .append(USAGE); return Optional.of(sb.toString()); } } /* * Compute earthquake rates or probabilities using the supplied model, config, * and sites. Method returns the path to the directory where results were * written. * * Unlike hazard calculations, which spread work out over multiple threads for * a single calculation, rate calculations are single threaded. Concurrent * calculations for multiple sites are handled below. */ private static Path calc( HazardModel model, CalcConfig config, Sites sites, Logger log) throws IOException, ExecutionException, InterruptedException { ThreadCount threadCount = config.performance.threadCount; EqRateExport export = null; if (threadCount != ThreadCount.ONE) { ExecutorService poolExecutor = newFixedThreadPool(threadCount.value()); ListeningExecutorService executor = MoreExecutors.listeningDecorator(poolExecutor); log.info("Threads: " + ((ThreadPoolExecutor) poolExecutor).getCorePoolSize()); log.info(PROGRAM + ": calculating ..."); export = concurrentCalc(model, config, sites, log, executor); executor.shutdown(); } else { log.info("Threads: Running on calling thread"); log.info(PROGRAM + ": calculating ..."); export = EqRateExport.create(config, sites, log); for (Site site : sites) { EqRate rate = calc(model, config, site); export.add(rate); } } export.expire(); log.info(String.format( PROGRAM + ": %s sites completed in %s", export.resultsProcessed(), export.elapsedTime())); return export.outputDir(); } private static EqRateExport concurrentCalc( HazardModel model, CalcConfig config, Sites sites, Logger log, ListeningExecutorService executor) throws InterruptedException, ExecutionException, IOException { EqRateExport export = EqRateExport.create(config, sites, log); int batchSize = config.output.flushLimit; int submitted = 0; List<ListenableFuture<EqRate>> rateFutures = new ArrayList<>(batchSize); /* * Although the approach below may not fully leverage all processors if * there are one or more longer-running calcs in the batch, processing * batches of locations to a List preserves submission order; as opposed to * using FutureCallbacks, which will reorder sites on export. */ for (Site site : sites) { Callable<EqRate> task = EqRate.callable(model, config, site); rateFutures.add(executor.submit(task)); submitted++; if (submitted == batchSize) { List<EqRate> rateList = Futures.allAsList(rateFutures).get(); export.addAll(rateList); submitted = 0; rateFutures.clear(); } } List<EqRate> lastBatch = Futures.allAsList(rateFutures).get(); export.addAll(lastBatch); return export; } /** * Compute earthquake rates at a {@code site} for a {@code model} and * {@code config}. * * <p><b>Note:</b> any model initialization settings in {@code config} will be * ignored as the supplied model will already have been initialized. * * @param model to use * @param config calculation configuration * @param site of interest */ public static EqRate calc( HazardModel model, CalcConfig config, Site site) { return EqRate.create(model, config, site); } private static final String PROGRAM = RateCalc.class.getSimpleName(); private static final String USAGE_COMMAND = "java -cp nshmp-haz.jar org.opensha2.RateCalc model sites [config]"; private static final String USAGE_URL1 = "https://github.com/usgs/nshmp-haz/wiki"; private static final String USAGE_URL2 = "https://github.com/usgs/nshmp-haz/tree/master/etc"; private static final String SITE_STRING = "name,lon,lat"; private static final String USAGE = new StringBuilder() .append(NEWLINE) .append(PROGRAM).append(" [").append(HazardCalc.VERSION).append("]").append(NEWLINE) .append(NEWLINE) .append("Usage:").append(NEWLINE) .append(" ").append(USAGE_COMMAND).append(NEWLINE) .append(NEWLINE) .append("Where:").append(NEWLINE) .append(" 'model' is a model zip file or directory") .append(NEWLINE) .append(" 'sites' is either:") .append(NEWLINE) .append(" - a string, e.g. ").append(SITE_STRING) .append(NEWLINE) .append(" (escape any spaces or enclose string in double-quotes)") .append(NEWLINE) .append(" - or a *.csv file or *.geojson file of site data") .append(NEWLINE) .append(" 'config' (optional) supplies a calculation configuration") .append(NEWLINE) .append(NEWLINE) .append("For more information, see:").append(NEWLINE) .append(" ").append(USAGE_URL1).append(NEWLINE) .append(" ").append(USAGE_URL2).append(NEWLINE) .append(NEWLINE) .toString(); }