package org.opensha2.calc;
import static com.google.common.base.Preconditions.checkState;
import static java.nio.charset.StandardCharsets.US_ASCII;
import org.opensha2.data.XySequence;
import org.opensha2.eq.model.SourceType;
import org.opensha2.geo.Location;
import org.opensha2.internal.Parsing;
import org.opensha2.internal.Parsing.Delimiter;
import com.google.common.base.Function;
import com.google.common.base.Stopwatch;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.primitives.Doubles;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;
/**
* Earthquake rate and probability exporter.
*
* @author Peter Powers
*/
public final class EqRateExport {
private final Logger log;
private final Path dir;
private final CalcConfig config;
private final boolean exportSource;
private final Stopwatch batchWatch;
private final Stopwatch totalWatch;
private int batchCount = 0;
private int resultCount = 0;
private final boolean namedSites;
private boolean firstBatch = true;
private boolean used = false;
private final List<EqRate> rates;
private EqRateExport(CalcConfig config, Sites sites, Logger log) throws IOException {
this.log = log;
this.dir = HazardExport.createOutputDir(improvedOutputDirectory(config));
this.config = config;
this.exportSource = config.output.dataTypes.contains(DataType.SOURCE);
this.rates = new ArrayList<>();
Site demoSite = sites.iterator().next();
this.namedSites = demoSite.name() != Site.NO_NAME;
this.batchWatch = Stopwatch.createStarted();
this.totalWatch = Stopwatch.createStarted();
}
/*
* If output from config is 'curves', change to the more appropriate 'eq-rate'
* or 'eq-prob'.
*/
static Path improvedOutputDirectory(CalcConfig config) {
Path out = config.output.directory;
if (out.toString().equals(CalcConfig.DEFAULT_OUT)) {
return (config.rate.valueFormat == ValueFormat.POISSON_PROBABILITY) ? Paths.get("eq-prob")
: Paths.get("eq-rate");
}
return out;
}
/**
* Create a new results handler.
*
* @param config that specifies output options and formats
* @param sites reference to the sites to be processed (not retained)
* @param log shared logging instance from calling class
* @throws IllegalStateException if binary output has been specified in the
* {@code config} but the {@code sites} container does not specify map
* extents.
*/
public static EqRateExport create(
CalcConfig config,
Sites sites,
Logger log) throws IOException {
return new EqRateExport(config, sites, log);
}
/**
* Add the supplied {@code EqRate}s to this exporter.
*
* @param rates data containers to add
*/
public void addAll(Collection<EqRate> rates) throws IOException {
for (EqRate rate : rates) {
add(rate);
}
}
/**
* Add an {@code EqRate} to this exporter.
*
* @param rate data container to add
*/
public void add(EqRate rate) throws IOException {
checkState(!used, "This result handler is expired");
resultCount++;
rates.add(rate);
if (rates.size() == config.output.flushLimit) {
flush();
batchCount++;
log.info(String.format(
" batch: %s in %s – %s sites in %s",
batchCount, batchWatch, resultCount, totalWatch));
batchWatch.reset().start();
}
}
/**
* Flushes any remaining results, stops all timers and sets the state of this
* exporter to 'used'; no more results may be added.
*/
public void expire() throws IOException {
flush();
batchWatch.stop();
totalWatch.stop();
used = true;
}
/*
* Flush any stored Hazard and Deaggregation results to file, clearing
*/
private void flush() throws IOException {
if (!rates.isEmpty()) {
writeRates();
rates.clear();
firstBatch = false;
}
}
/**
* The number of hazard [and deagg] results passed to this handler thus far.
*/
public int resultsProcessed() {
return resultCount;
}
/**
* The number of {@code Hazard} results this handler is currently storing.
*/
public int size() {
return rates.size();
}
/**
* A string representation of the time duration that this result handler has
* been running.
*/
public String elapsedTime() {
return totalWatch.toString();
}
/**
* The target output directory established by this handler.
*/
public Path outputDir() {
return dir;
}
/*
* Write the current list of {@code EqRate}s to file.
*/
private void writeRates() throws IOException {
EqRate demo = rates.get(0);
Iterable<Double> emptyValues = Doubles.asList(new double[demo.totalMfd.size()]);
OpenOption[] options = firstBatch ? HazardExport.WRITE : HazardExport.APPEND;
Function<Double, String> formatter = Parsing.formatDoubleFunction(HazardExport.RATE_FMT);
/* Line maps for ascii output; may or may not be used */
List<String> totalLines = new ArrayList<>();
Map<SourceType, List<String>> typeLines = Maps.newEnumMap(SourceType.class);
if (firstBatch) {
Iterable<?> header = Iterables.concat(
Lists.newArrayList(namedSites ? "name" : null, "lon", "lat"),
demo.totalMfd.xValues());
totalLines.add(Parsing.join(header, Delimiter.COMMA));
}
if (exportSource) {
// TODO get source types from model
for (SourceType type : SourceType.values()) {
typeLines.put(type, Lists.newArrayList(totalLines));
}
}
/* Process batch */
for (EqRate rate : rates) {
String name = namedSites ? rate.site.name : null;
Location location = rate.site.location;
List<String> locData = Lists.newArrayList(
name,
String.format("%.5f", location.lon()),
String.format("%.5f", location.lat()));
String line = toLine(locData, rate.totalMfd.yValues(), formatter);
totalLines.add(line);
String emptyLine = toLine(locData, emptyValues, formatter);
if (exportSource) {
for (Entry<SourceType, List<String>> entry : typeLines.entrySet()) {
SourceType type = entry.getKey();
String typeLine = emptyLine;
if (rate.typeMfds.containsKey(type)) {
XySequence typeRate = rate.typeMfds.get(type);
typeLine = toLine(locData, typeRate.yValues(), formatter);
}
entry.getValue().add(typeLine);
}
}
/* write/append */
Path totalFile = dir.resolve("total" + HazardExport.TEXT_SUFFIX);
Files.write(totalFile, totalLines, US_ASCII, options);
if (exportSource) {
Path typeDir = dir.resolve("source");
Files.createDirectories(typeDir);
for (Entry<SourceType, List<String>> typeEntry : typeLines.entrySet()) {
SourceType type = typeEntry.getKey();
String filename = type.toString();
Path typeFile = typeDir.resolve(filename + HazardExport.TEXT_SUFFIX);
Files.write(typeFile, typeEntry.getValue(), US_ASCII, options);
}
}
}
}
private static String toLine(
Iterable<String> location,
Iterable<Double> values,
Function<Double, String> formatter) {
return Parsing.join(
FluentIterable.from(location).append(Iterables.transform(values, formatter)),
Delimiter.COMMA);
}
}