package org.openlca.app.results.analysis.sankey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.function.ToDoubleFunction;
import org.openlca.app.util.CostResultDescriptor;
import org.openlca.core.matrix.LongIndex;
import org.openlca.core.model.ProcessLink;
import org.openlca.core.model.ProductSystem;
import org.openlca.core.model.descriptors.FlowDescriptor;
import org.openlca.core.model.descriptors.ImpactCategoryDescriptor;
import org.openlca.core.model.descriptors.ProcessDescriptor;
import org.openlca.core.results.FullResultProvider;
import org.openlca.util.Doubles;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class SankeyResult {
private Logger log = LoggerFactory.getLogger(getClass());
private ProductSystem system;
private FullResultProvider results;
private LongIndex processIndex;
private ProcessDescriptor[] processes;
private double[] upstreamResults;
private double[] upstreamContributions;
private double[] directResults;
private double[] directContributions;
public SankeyResult(ProductSystem system, FullResultProvider results) {
this.system = system;
this.results = results;
}
public double getDirectResult(long processId) {
return fetchVal(processId, directResults);
}
public double getDirectContribution(long processId) {
return fetchVal(processId, directContributions);
}
public double getUpstreamResult(long processId) {
return fetchVal(processId, upstreamResults);
}
public double getUpstreamContribution(long processId) {
return fetchVal(processId, upstreamContributions);
}
private double fetchVal(long processId, double[] values) {
if (values == null || processIndex == null)
return Double.NaN;
int idx = processIndex.getIndex(processId);
if (idx < 0 || idx >= values.length)
return Double.NaN;
return values[idx];
}
public double findCutoff(int maxProcessesCount) {
if (upstreamContributions.length == 0
|| maxProcessesCount >= upstreamContributions.length)
return 0;
int length = upstreamContributions.length;
double[] contributions = new double[length];
System.arraycopy(upstreamContributions, 0, contributions, 0, length);
Arrays.sort(contributions);
return Math.abs(contributions[length - maxProcessesCount]);
}
public List<Long> getProcesseIdsAboveCutoff(double cutoff) {
List<Long> processes = new ArrayList<>();
for (Long process : system.getProcesses()) {
double contr = getUpstreamContribution(process);
if (Math.abs(contr) >= cutoff)
processes.add(process);
}
return processes;
}
public double getLinkContribution(ProcessLink link) {
if (link == null || results == null)
return 0;
double totalContr = getUpstreamContribution(link.providerId);
double linkShare = results.getLinkShare(link);
return totalContr * linkShare;
}
public void calculate(Object selection) {
log.trace("Calculate Sankey result for selection {}", selection);
buildProcessIndex();
if (selection instanceof FlowDescriptor) {
FlowDescriptor f = (FlowDescriptor) selection;
upstreamResults = vec(p -> results.getUpstreamFlowResult(p, f).value);
directResults = vec(p -> results.getSingleFlowResult(p, f).value);
} else if (selection instanceof ImpactCategoryDescriptor) {
ImpactCategoryDescriptor i = (ImpactCategoryDescriptor) selection;
upstreamResults = vec(p -> results.getUpstreamImpactResult(p, i).value);
directResults = vec(p -> results.getSingleImpactResult(p, i).value);
} else if (selection instanceof CostResultDescriptor) {
CostResultDescriptor c = (CostResultDescriptor) selection;
upstreamResults = vec(p -> {
double v = results.getUpstreamCostResult(p);
return c.forAddedValue && v != 0 ? -v : v;
});
directResults = vec(p -> {
double v = results.getSingleCostResult(p);
return c.forAddedValue && v != 0 ? -v : v;
});
} else {
directResults = upstreamResults = new double[processIndex.size()];
}
upstreamContributions = calcContributions(upstreamResults);
directContributions = calcContributions(directResults);
log.trace("Calculation done");
}
private double[] calcContributions(double[] values) {
if (values == null || values.length == 0)
return new double[0];
double min = Doubles.min(values);
double max = Doubles.max(values);
double ref = Math.max(Math.abs(min), Math.abs(max));
double[] contributions = new double[values.length];
if (ref == 0)
return contributions;
for (int i = 0; i < contributions.length; i++) {
double val = values[i];
contributions[i] = val / ref;
}
return contributions;
}
private double[] vec(ToDoubleFunction<ProcessDescriptor> fn) {
double[] vector = new double[processIndex.size()];
for (int i = 0; i < processIndex.size(); i++) {
ProcessDescriptor process = processes[i];
double result = fn.applyAsDouble(process);
vector[i] = result;
}
return vector;
}
private void buildProcessIndex() {
processIndex = new LongIndex();
Set<ProcessDescriptor> processSet = results.getProcessDescriptors();
processes = new ProcessDescriptor[processSet.size()];
for (ProcessDescriptor process : processSet) {
int i = processIndex.put(process.getId());
processes[i] = process;
}
}
}