package org.opensha2.calc;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.util.concurrent.Futures.allAsList;
import static com.google.common.util.concurrent.Futures.immediateFuture;
import static com.google.common.util.concurrent.Futures.transform;
import static org.opensha2.calc.CalcFactory.clustersToCurves;
import static org.opensha2.calc.CalcFactory.sourcesToCurves;
import static org.opensha2.calc.CalcFactory.systemToCurves;
import static org.opensha2.calc.CalcFactory.toHazardResult;
import static org.opensha2.data.Data.checkInRange;
import static org.opensha2.eq.model.PointSourceType.FIXED_STRIKE;
import org.opensha2.calc.Transforms.SourceToInputs;
import org.opensha2.eq.model.ClusterSourceSet;
import org.opensha2.eq.model.GridSourceSet;
import org.opensha2.eq.model.HazardModel;
import org.opensha2.eq.model.Source;
import org.opensha2.eq.model.SourceSet;
import org.opensha2.eq.model.SystemSourceSet;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Stopwatch;
import com.google.common.collect.Range;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Static probabilistic seismic hazard analysis calculators.
*
* @author Peter Powers
*/
public class HazardCalcs {
/*
* Developer notes:
*
* -------------------------------------------------------------------------
* Method argument order in this class, CalcFactory, and Transforms follow the
* general rule of model (or model derived objects), calc config, site, and
* then any others.
* -------------------------------------------------------------------------
* All calculations are done in log space, only on export are x-values
* returned to linear space.
* -------------------------------------------------------------------------
* The ability to run a full calculation on the current thread facilitates
* debugging.
* -------------------------------------------------------------------------
* Single threaded calcs monitor and log calculation duration of each
* SourceSet.
* -------------------------------------------------------------------------
* Although NPEs will be thrown once any method argument in this class is
* used, because multiple arguments may be passed on the same line thereby
* complicating identification of a null value, preemtive checks are
* performed.
* -------------------------------------------------------------------------
* Notes on empty objects: In the default calculation pipeline, calculation
* tasks are initiated when SourceSet.iterableForLocation() yields relevant
* sources for a site. Similarly, optimized (table-based) grid sources, once
* created for a site, will not yield sources if the mfd table is empty.
* System source sets, on the other hand, create a list of inputs on a
* separate thread using means other than iterableForLocation. They are
* therefore short-circuited returning an empty HazardCurveSet if no sources
* are within range of a site. Cluster source sets have similar issue.
*
* TODO follow HazardCurveSet.Builder.empty() calls because it seems like
* clusters shouldn't have to use it as the iterator in clustersToInputs
* should just not be submitting any sources in any event if they are all too
* far away.
* -------------------------------------------------------------------------
*/
/**
* Return a {@code Function} that converts a {@code Source} along with an
* initially supplied {@code Site} to a list of ground motion model inputs.
*
* @param site to initialize function with
* @see InputList
*/
public static Function<Source, InputList> sourceToInputs(Site site) {
return new SourceToInputs(site);
}
private static Range<Double> rpRange = Range.closed(1.0, 20000.0);
/**
* Perform a deaggregation of probabilisitic seismic hazard.
*
* @param hazard to deaggregate
* @param returnPeriod at which to deaggregate
*/
public static Deaggregation deaggregation(
Hazard hazard,
double returnPeriod) {
checkNotNull(hazard);
checkInRange(rpRange, "Return period", returnPeriod);
return Deaggregation.atReturnPeriod(hazard, returnPeriod);
}
/**
* Compute probabilistic seismic hazard, possibly using an {@link Optional}
* {@link Executor}. If no executor is supplied, the calculation will run on
* the current thread.
*
* @param model to use
* @param config calculation properties
* @param site of interest
* @param ex optional {@code Executor} to use in calculation
* @throws InterruptedException if an {@code Executor} was supplied and the
* calculation is interrupted
* @throws ExecutionException if an {@code Executor} was supplied and a
* problem arises during the calculation
*/
public static Hazard hazard(
HazardModel model,
CalcConfig config,
Site site,
Optional<Executor> ex)
throws InterruptedException, ExecutionException {
checkNotNull(model);
checkNotNull(config);
checkNotNull(site);
checkNotNull(ex);
if (ex.isPresent()) {
return asyncHazardCurve(model, config, site, ex.get());
}
Logger log = Logger.getLogger(HazardCalcs.class.getName());
return hazardCurve(model, config, site, log);
}
/*
* Run a hazard curve calculation in parallel.
*/
private static Hazard asyncHazardCurve(
HazardModel model,
CalcConfig config,
Site site,
Executor ex) throws InterruptedException, ExecutionException {
AsyncList<HazardCurveSet> curveSets = AsyncList.createWithCapacity(model.size());
AsyncList<SourceSet<? extends Source>> gridTables = AsyncList.create();
for (SourceSet<? extends Source> sourceSet : model) {
switch (sourceSet.type()) {
case GRID:
GridSourceSet gss = (GridSourceSet) sourceSet;
if (config.performance.optimizeGrids && gss.sourceType() != FIXED_STRIKE &&
gss.optimizable()) {
gridTables.add(transform(immediateFuture(gss),
GridSourceSet.optimizer(site.location), ex));
break;
}
curveSets.add(sourcesToCurves(sourceSet, config, site, ex));
break;
case CLUSTER:
curveSets.add(clustersToCurves((ClusterSourceSet) sourceSet, config, site, ex));
break;
case SYSTEM:
curveSets.add(systemToCurves((SystemSourceSet) sourceSet, config, site, ex));
break;
default:
curveSets.add(sourcesToCurves(sourceSet, config, site, ex));
break;
}
}
/*
* If grid optimization is enabled, grid calculations were deferred (above)
* while table based source sets were initialized. Submit once all other
* source types have been submitted.
*/
for (SourceSet<? extends Source> sourceSet : allAsList(gridTables).get()) {
curveSets.add(sourcesToCurves(sourceSet, config, site, ex));
}
return toHazardResult(model, config, site, curveSets, ex);
}
/*
* Run a hazard curve calculation on the current thread.
*/
private static Hazard hazardCurve(
HazardModel model,
CalcConfig config,
Site site,
Logger log) {
List<HazardCurveSet> curveSets = new ArrayList<>(model.size());
log.info("HazardCurve: (single-threaded)");
Stopwatch swTotal = Stopwatch.createStarted();
Stopwatch swSource = Stopwatch.createStarted();
for (SourceSet<? extends Source> sourceSet : model) {
switch (sourceSet.type()) {
case GRID:
GridSourceSet gss = (GridSourceSet) sourceSet;
if (config.performance.optimizeGrids && gss.sourceType() != FIXED_STRIKE) {
sourceSet = GridSourceSet.optimizer(site.location).apply(gss);
log(log, MSSG_GRID_INIT, sourceSet.name(), duration(swSource));
}
curveSets.add(sourcesToCurves(sourceSet, config, site));
log(log, MSSG_COMPLETED, sourceSet.name(), duration(swSource));
break;
case CLUSTER:
curveSets.add(clustersToCurves((ClusterSourceSet) sourceSet, config, site));
log(log, MSSG_COMPLETED, sourceSet.name(), duration(swSource));
break;
case SYSTEM:
curveSets.add(systemToCurves((SystemSourceSet) sourceSet, config, site));
log(log, MSSG_COMPLETED, sourceSet.name(), duration(swSource));
break;
default:
curveSets.add(sourcesToCurves(sourceSet, config, site));
log(log, MSSG_COMPLETED, sourceSet.name(), duration(swSource));
break;
}
}
log.log(Level.INFO, String.format(" %s: %s", MSSG_DURATION, duration(swTotal)));
swTotal.stop();
swSource.stop();
return toHazardResult(model, config, site, curveSets);
}
/*
* Support methods and fields for single-threaded calculations with more
* verbose output.
*/
private static String duration(Stopwatch sw) {
String duration = sw.toString();
sw.reset().start();
return duration;
}
private static final String MSSG_GRID_INIT = "Init grid table";
private static final String MSSG_COMPLETED = " Completed";
private static final String MSSG_DURATION = " Total time";
private static void log(Logger log, String leader, String source, String duration) {
log.log(Level.INFO, String.format(" %s: %s %s", leader, duration, source));
}
}