package org.openlca.core.results;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Stack;
import org.openlca.core.matrix.LongPair;
import org.openlca.core.matrix.TechIndex;
import org.openlca.core.model.ModelType;
import org.openlca.core.model.descriptors.BaseDescriptor;
import org.openlca.core.model.descriptors.FlowDescriptor;
import org.openlca.core.model.descriptors.ImpactCategoryDescriptor;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
class UpstreamTreeCalculator {
private FullResult result;
private LinkContributions linkContributions;
private Multimap<LongPair, LongPair> links;
private boolean skipNegatives = false;
private boolean skipNulls = false;
public UpstreamTreeCalculator(FullResult result) {
this.result = result;
this.linkContributions = result.linkContributions;
this.links = makeLinks(result.productIndex);
}
private Multimap<LongPair, LongPair> makeLinks(TechIndex index) {
Multimap<LongPair, LongPair> links = ArrayListMultimap.create();
for (LongPair exchange : index.getLinkedExchanges()) {
long processId = exchange.getFirst();
LongPair provider = index.getLinkedProvider(exchange);
for (LongPair recipient : index.getProviders(processId)) {
links.put(recipient, provider);
}
}
return links;
}
public void skipNegativeValues(boolean skipNegatives) {
this.skipNegatives = skipNegatives;
}
public void skipNullValues(boolean skipNulls) {
this.skipNulls = skipNulls;
}
public UpstreamTree calculate(FlowDescriptor flow) {
FlowResultFetch fn = new FlowResultFetch(flow);
return calculate(fn);
}
public UpstreamTree calculate(ImpactCategoryDescriptor impact) {
ImpactResultFetch fn = new ImpactResultFetch(impact);
return calculate(fn);
}
public UpstreamTree calculateCosts() {
CostResultFetch fn = new CostResultFetch();
return calculate(fn);
}
private UpstreamTree calculate(ResultFetch fn) {
UpstreamTree tree = new UpstreamTree();
tree.setReference(fn.getReference());
UpstreamTreeNode root = new UpstreamTreeNode();
LongPair refProduct = result.productIndex.getRefFlow();
root.setShare(1d);
root.setProcessProduct(refProduct);
root.setAmount(fn.getTotalAmount(refProduct));
tree.setRoot(root);
NodeSorter sorter = new NodeSorter();
Stack<UpstreamTreeNode> stack = new Stack<>();
stack.push(root);
HashSet<LongPair> handled = new HashSet<>();
handled.add(refProduct);
while (!stack.isEmpty()) {
UpstreamTreeNode node = stack.pop();
List<UpstreamTreeNode> childs = createChildNodes(node, fn);
Collections.sort(childs, sorter);
node.getChildren().addAll(childs);
for (int i = childs.size() - 1; i >= 0; i--) {
// push in reverse order, so that the highest contribution is
// on the top
UpstreamTreeNode child = childs.get(i);
if (!handled.contains(child.getProcessProduct())) {
stack.push(child);
handled.add(child.getProcessProduct());
}
}
}
return tree;
}
private List<UpstreamTreeNode> createChildNodes(UpstreamTreeNode parent,
ResultFetch fn) {
List<UpstreamTreeNode> childNodes = new ArrayList<>();
LongPair recipient = parent.getProcessProduct();
for (LongPair provider : links.get(recipient)) {
double share = linkContributions.getShare(provider, recipient)
* parent.getShare();
double amount = share * fn.getTotalAmount(provider);
if (amount == 0 && skipNulls)
continue;
if (amount < 0 && skipNegatives)
continue;
UpstreamTreeNode node = new UpstreamTreeNode();
node.setShare(share);
node.setAmount(amount);
node.setProcessProduct(provider);
childNodes.add(node);
}
return childNodes;
}
private interface ResultFetch {
double getTotalAmount(LongPair product);
BaseDescriptor getReference();
}
private class FlowResultFetch implements ResultFetch {
private final FlowDescriptor flow;
private final long flowId;
private final boolean isInput;
public FlowResultFetch(FlowDescriptor flow) {
this.flow = flow;
this.flowId = flow.getId();
this.isInput = result.flowIndex.isInput(flowId);
}
@Override
public double getTotalAmount(LongPair product) {
double val = result.getUpstreamFlowResult(product, flowId);
if (val == 0)
return 0; // avoid -0 in results
return isInput ? -val : val;
}
@Override
public BaseDescriptor getReference() {
return flow;
}
}
private class ImpactResultFetch implements ResultFetch {
private final ImpactCategoryDescriptor impact;
private final long impactId;
public ImpactResultFetch(ImpactCategoryDescriptor impact) {
this.impact = impact;
this.impactId = impact.getId();
}
@Override
public double getTotalAmount(LongPair processProduct) {
return result.getUpstreamImpactResult(processProduct, impactId);
}
@Override
public BaseDescriptor getReference() {
return impact;
}
}
private class CostResultFetch implements ResultFetch {
@Override
public double getTotalAmount(LongPair product) {
return result.getUpstreamCostResult(product);
}
@Override
public BaseDescriptor getReference() {
BaseDescriptor d = new BaseDescriptor();
d.setId(0);
d.setType(ModelType.CURRENCY);
return d;
}
}
private class NodeSorter implements Comparator<UpstreamTreeNode> {
@Override
public int compare(UpstreamTreeNode node1, UpstreamTreeNode node2) {
return Double.compare(node2.getAmount(), node1.getAmount());
}
}
}