package org.opensha2.calc; import static com.google.common.base.Preconditions.checkState; import static org.opensha2.eq.model.SourceType.SYSTEM; import static org.opensha2.internal.TextUtils.NEWLINE; import org.opensha2.calc.DeaggExport.ContributionFilter; import org.opensha2.data.Data; import org.opensha2.data.IntervalArray; import org.opensha2.data.XySequence; import org.opensha2.eq.model.ClusterSource; import org.opensha2.eq.model.Rupture; 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.internal.Parsing; import org.opensha2.util.Maths; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Ordering; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; /** * Skeletal class and implementations used for contribution to hazard in a * deaggregation. A contribution may be keyed to a {@code SourceSet}, an * individual {@code Source}, or some other representation, e.g. optimized * (table-based) {@code GridSource}s or {@code SystemSource} fault sections. * * @author Peter Powers */ abstract class DeaggContributor { final double rate; final double residual; final double rScaled; final double mScaled; final double εScaled; DeaggContributor( double rate, double residual, double rScaled, double mScaled, double εScaled) { this.rate = rate; this.residual = residual; this.rScaled = rScaled; this.mScaled = mScaled; this.εScaled = εScaled; } double total() { return rate + residual; } /* Convenience array for use when combining children. */ private double[] values() { return new double[] { rate, residual, rScaled, mScaled, εScaled }; } abstract StringBuilder appendTo( StringBuilder sb, String indent, ContributionFilter filter); abstract List<JsonContributor> toJson(ContributionFilter filter); abstract static class Builder { double rate; double residual; double rScaled; double mScaled; double εScaled; /* Only for use by inner concrete subtypes. */ private void add( double rate, double residual, double rScaled, double mScaled, double εScaled) { this.rate += rate; this.residual += residual; this.rScaled += rScaled; this.mScaled += mScaled; this.εScaled += εScaled; } /* Only for use by inner concrete subtypes. */ private void add(double[] values) { this.rate += values[0]; this.residual += values[1]; this.rScaled += values[2]; this.mScaled += values[3]; this.εScaled += values[4]; } /* * Optional, single-call operation. Some implementations may throw an * UnsupportedOperationException. May only be called once with subsequent * calls to addChild() throwing an IllegalStateException. */ abstract Builder multiply(double scale); /* * Optional operation; some implementations may throw an * UnsupportedOperationException. */ abstract Builder addChild(DeaggContributor.Builder contributor); /* * Build implementations should compile rate and statistic values for this * from any children instead of allowing them to be add()-ed directly. */ abstract DeaggContributor build(); } /* * Implementations. * * NOTE Once logic-tree branches are better supported, we'll likely require * contributors keyed to branches. For example, GR and CH branches are * currently separated into separate source sets, but this won't be the case * in the future. * * TODO There is no verification as yet on build calls that builders have all * the necessary data to proceed */ /* SourceSet contributor with references to child contributors. */ static final class SourceSetContributor extends DeaggContributor { final SourceSet<? extends Source> sourceSet; final List<DeaggContributor> children; private SourceSetContributor( SourceSet<? extends Source> sourceSet, List<DeaggContributor> children, double rate, double residual, double rScaled, double mScaled, double εScaled) { super(rate, residual, rScaled, mScaled, εScaled); this.sourceSet = sourceSet; this.children = children; } @Override public String toString() { return "SourceSet contributor: " + sourceSet.name(); } @Override public StringBuilder appendTo( StringBuilder sb, String indent, ContributionFilter filter) { double contribution = filter.toPercent(total()); sb.append(String.format( DeaggExport.CONTRIB_SOURCE_SET_FMT, sourceSet.name(), sourceSet.type(), contribution)); sb.append(NEWLINE); for (DeaggContributor child : children) { if (filter.apply(child)) { child.appendTo(sb, " ", filter); continue; } break; } return sb; } @Override List<JsonContributor> toJson(ContributionFilter filter) { ArrayList<JsonContributor> jsonList = new ArrayList<>(); JsonContributor jc = JsonContributor.createMulti( sourceSet.name(), sourceSet.type(), Maths.round(filter.toPercent(total()), 2)); jsonList.add(jc); for (DeaggContributor child : children) { if (filter.apply(child)) { jsonList.addAll(child.toJson(filter)); continue; } break; } return jsonList; } static final class Builder extends DeaggContributor.Builder { SourceSet<? extends Source> sourceSet; ArrayList<DeaggContributor.Builder> children = new ArrayList<>(); Builder sourceSet(SourceSet<? extends Source> sourceSet) { this.sourceSet = sourceSet; return this; } @Override Builder addChild(DeaggContributor.Builder contributor) { children.add(contributor); return this; } @Override Builder multiply(double scale) { throw new UnsupportedOperationException(); } @Override SourceSetContributor build() { List<DeaggContributor> sortedChildren = buildAndSort(children); super.add(sumChildValues(sortedChildren)); return new SourceSetContributor( sourceSet, sortedChildren, rate, residual, rScaled, mScaled, εScaled); } } } /* * TODO Builders should have better state checking. e.g. once the source has * been set for a source contributor, it may not be set again. Likewise, add() * can not be called unless source is set. */ /* Generic Source contributor. Location and azimuth are site-specific. */ static class SourceContributor extends DeaggContributor { final Source source; final Location location; final double azimuth; private SourceContributor( Source source, Location location, double azimuth, double rate, double residual, double rScaled, double mScaled, double εScaled) { super(rate, residual, rScaled, mScaled, εScaled); this.source = source; this.location = location; this.azimuth = azimuth; } @Override public String toString() { return "Source contributor: " + source.name(); } @Override StringBuilder appendTo( StringBuilder sb, String indent, ContributionFilter filter) { double total = total(); double rBar = rScaled / total; double mBar = mScaled / total; double εBar = εScaled / total; sb.append(String.format( DeaggExport.CONTRIB_SOURCE_FMT, indent + source.name(), rBar, mBar, εBar, location.lon(), location.lat(), azimuth, filter.toPercent(total))); sb.append(NEWLINE); return sb; } @Override List<JsonContributor> toJson(ContributionFilter filter) { double total = total(); double rBar = rScaled / total; double mBar = mScaled / total; double εBar = εScaled / total; JsonContributor jc = JsonContributor.createSingle( source.name(), source.type(), Maths.round(filter.toPercent(total()), 2), -1, rBar, mBar, εBar, azimuth, location.lat(), location.lon()); return ImmutableList.of(jc); } static final class Builder extends DeaggContributor.Builder { Source source; Location location; double azimuth; boolean scaled = false; Builder source(Source source, Location location, double azimuth) { this.source = source; this.location = location; this.azimuth = azimuth; return this; } Builder add( double rate, double residual, double rScaled, double mScaled, double εScaled) { checkState(!scaled); super.add(rate, residual, rScaled, mScaled, εScaled); return this; } @Override Builder multiply(double scale) { checkState(!scaled); rate *= scale; residual *= scale; rScaled *= scale; mScaled *= scale; εScaled *= scale; scaled = true; return this; } @Override Builder addChild(DeaggContributor.Builder contributor) { throw new UnsupportedOperationException(); } @Override SourceContributor build() { return new SourceContributor( source, location, azimuth, rate, residual, rScaled, mScaled, εScaled); } } } /* ClusterSource contributor. */ static final class ClusterContributor extends DeaggContributor { final ClusterSource cluster; final List<DeaggContributor> faults; final Location location; final double azimuth; ClusterContributor( ClusterSource cluster, List<DeaggContributor> faults, Location location, double azimuth, double rate, double residual, double rScaled, double mScaled, double εScaled) { super(rate, residual, rScaled, mScaled, εScaled); this.cluster = cluster; this.faults = faults; this.location = location; this.azimuth = azimuth; } @Override public String toString() { return "Cluster contributor: " + cluster.name(); } @Override StringBuilder appendTo( StringBuilder sb, String indent, ContributionFilter filter) { double total = total(); double rBar = rScaled / total; double mBar = mScaled / total; double εBar = εScaled / total; sb.append(String.format( DeaggExport.CONTRIB_SOURCE_FMT, indent + cluster.name(), rBar, mBar, εBar, location.lon(), location.lat(), azimuth, filter.toPercent(total))); sb.append(NEWLINE); for (DeaggContributor fault : faults) { if (filter.apply(fault)) { fault.appendTo(sb, " ", filter); continue; } break; } return sb; } @Override List<JsonContributor> toJson(ContributionFilter filter) { double total = total(); double rBar = rScaled / total; double mBar = mScaled / total; double εBar = εScaled / total; JsonContributor jc = JsonContributor.createSingle( cluster.name(), cluster.type(), Maths.round(filter.toPercent(total()), 2), -2, rBar, mBar, εBar, azimuth, location.lat(), location.lon()); return ImmutableList.of(jc); } static final class Builder extends DeaggContributor.Builder { ClusterSource cluster; ArrayList<DeaggContributor.Builder> faults = new ArrayList<>(); Location location; double azimuth; Builder cluster(ClusterSource cluster, Location location, double azimuth) { this.cluster = cluster; this.location = location; this.azimuth = azimuth; return this; } @Override Builder multiply(double scale) { for (DeaggContributor.Builder fault : faults) { fault.multiply(scale); } return this; } @Override Builder addChild(DeaggContributor.Builder contributor) { faults.add(contributor); return this; } @Override ClusterContributor build() { List<DeaggContributor> sortedFaults = buildAndSort(faults); super.add(sumChildValues(sortedFaults)); return new ClusterContributor( cluster, sortedFaults, location, azimuth, rate, residual, rScaled, mScaled, εScaled); } } } private static double[] sumChildValues(Collection<DeaggContributor> contributors) { double[] sum = new double[5]; for (DeaggContributor contributor : contributors) { Data.add(sum, contributor.values()); } return sum; } static final class SystemContributor extends DeaggContributor { final SectionSource section; final Location location; final double azimuth; final IntervalArray mfd; SystemContributor( SectionSource section, Location location, double azimuth, IntervalArray mfd, double rate, double residual, double rScaled, double mScaled, double εScaled) { super(rate, residual, rScaled, mScaled, εScaled); this.section = section; this.location = location; this.azimuth = azimuth; this.mfd = mfd; } @Override public String toString() { return "System contributor: Section " + section.index; } @Override StringBuilder appendTo( StringBuilder sb, String indent, ContributionFilter filter) { double total = total(); double rBar = rScaled / total; double mBar = mScaled / total; double εBar = εScaled / total; sb.append(String.format( DeaggExport.CONTRIB_SOURCE_FMT, indent + section.name(), rBar, mBar, εBar, location.lon(), location.lat(), azimuth, filter.toPercent(total))); sb.append(NEWLINE); return sb; } @Override List<JsonContributor> toJson(ContributionFilter filter) { double total = total(); double rBar = rScaled / total; double mBar = mScaled / total; double εBar = εScaled / total; JsonContributor jc = JsonContributor.createSingle( section.name(), section.type(), Maths.round(filter.toPercent(total()), 2), -3, rBar, mBar, εBar, azimuth, location.lat(), location.lon()); return ImmutableList.of(jc); } StringBuilder appendMfd(StringBuilder sb) { sb.append(String.format(DeaggExport.SYSTEM_MFD_FORMAT, section.index, section.name())); sb.append(Parsing.toString(mfd.values().yValues(), "%9.3g", ",", false, false)); sb.append(NEWLINE); return sb; } static final class Builder extends DeaggContributor.Builder { SectionSource section; Location location; double azimuth; IntervalArray.Builder mfd; Builder section( SectionSource section, Location location, double azimuth, IntervalArray.Builder mfd) { this.section = section; this.location = location; this.azimuth = azimuth; this.mfd = mfd; return this; } Builder add( double rate, double residual, double rScaled, double mScaled, double εScaled) { super.add(rate, residual, rScaled, mScaled, εScaled); return this; } /* * The mfd data could be backed out on every call to add, but this likely * requires a bin index lookup to be repeated across all relevant Gmms. */ Builder addToMfd(int index, double rate) { mfd.add(index, rate); return this; } Builder addMfd(IntervalArray mfd) { this.mfd.add(mfd); return this; } @Override Builder multiply(double scale) { throw new UnsupportedOperationException(); } @Override Builder addChild(DeaggContributor.Builder contributor) { throw new UnsupportedOperationException(); } @Override DeaggContributor build() { return new SystemContributor( section, location, azimuth, mfd.build(), rate, residual, rScaled, mScaled, εScaled); } } } /* * Custom source that encapsulates the fact that relevant data in the * deaggregation of SystemSourceSet is associated with individual fault * sections rather than sources/ruptures. */ static final class SectionSource implements Source { final int index; final String name; SectionSource(int index, String name) { this.index = index; this.name = name; } @Override public String name() { return name; } @Override public int size() { // TODO perhaps this should return the number of participating ruptures return 1; } @Override public int id() { return index; } @Override public SourceType type() { return SYSTEM; } @Override public Location location(Location site) { return null; // unused } @Override public List<XySequence> mfds() { return null; // TODO this needs to return something, not sure what yet } @Override public Iterator<Rupture> iterator() { throw new UnsupportedOperationException(); } } /* Wrapper of contributor data suitable for JSON serialization. */ static final class JsonContributor { String name; String source; JsonContributorType type; Double contribution; Integer id; Double r; Double m; Double ε; Double azimuth; Double latitude; Double longitude; /* Use for SourceSets. */ static JsonContributor createMulti( String name, SourceType source, double contribution) { JsonContributor jc = new JsonContributor(); jc.name = name; jc.source = source.toString(); jc.type = JsonContributorType.SET; jc.contribution = contribution; return jc; } /* Use for more detailed individual sources. */ static JsonContributor createSingle( String name, SourceType source, double contribution, int id, double r, double m, double ε, double azimuth, double latitude, double longitude) { JsonContributor jc = new JsonContributor(); jc.name = name; jc.source = source.toString(); jc.type = JsonContributorType.SINGLE; jc.contribution = contribution; jc.id = id; jc.r = r; jc.m = m; jc.ε = ε; jc.azimuth = azimuth; jc.latitude = latitude; jc.longitude = longitude; return jc; } } private static enum JsonContributorType { SET, SINGLE; } /* Convert a builder list to immutable sorted contributor list. */ private static List<DeaggContributor> buildAndSort( Collection<DeaggContributor.Builder> builders) { return SORTER.immutableSortedCopy(Iterables.transform(builders, BUILDER)); } static final Function<DeaggContributor.Builder, DeaggContributor> BUILDER = new Function<DeaggContributor.Builder, DeaggContributor>() { @Override public DeaggContributor apply(Builder builder) { return builder.build(); } }; static final Ordering<DeaggContributor> SORTER = new Ordering<DeaggContributor>() { @Override public int compare(DeaggContributor left, DeaggContributor right) { return Double.compare(right.total(), left.total()); } }; }