package org.openlca.core.matrix.product.index;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.openlca.core.matrix.CalcExchange;
import org.openlca.core.matrix.LongPair;
import org.openlca.core.matrix.TechIndex;
import org.openlca.core.matrix.cache.MatrixCache;
import org.openlca.core.model.FlowType;
import org.openlca.core.model.ProcessLink;
import org.openlca.core.model.ProcessType;
import org.openlca.core.model.ProductSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ProductIndexCutoffBuilder implements IProductIndexBuilder {
private Logger log = LoggerFactory.getLogger(getClass());
private MatrixCache cache;
private ProviderSearch providerSearch;
private ProductSystem system;
private double cutoff;
public ProductIndexCutoffBuilder(MatrixCache cache, ProductSystem system, double cutoff) {
this.cache = cache;
this.cutoff = cutoff;
this.system = system;
this.providerSearch = new ProviderSearch(cache.getProcessTable(),
ProcessType.LCI_RESULT);
}
@Override
public void setPreferredType(ProcessType type) {
providerSearch.setPreferredType(type);
}
@Override
public TechIndex build(LongPair refProduct) {
return build(refProduct, 1.0);
}
@Override
public TechIndex build(LongPair refProduct, double demand) {
log.trace("build product index for {} with cutoff=", refProduct,
cutoff);
TechIndex index = new TechIndex(refProduct);
index.setDemand(demand);
addSystemLinks(index);
Graph g = new Graph(refProduct, demand);
while (!g.next.isEmpty())
g.handleNext();
fillIndex(g, index);
log.trace("created the index with {} products", index.size());
return index;
}
private void addSystemLinks(TechIndex index) {
if (system == null)
return;
for (ProcessLink link : system.getProcessLinks()) {
LongPair provider = new LongPair(link.providerId, link.flowId);
LongPair exchange = new LongPair(link.processId, link.exchangeId);
index.putLink(exchange, provider);
}
}
private void fillIndex(Graph g, TechIndex index) {
for (Node node : g.nodes.values()) {
if (node.state != NodeState.FOLLOWED)
continue;
for (Link link : node.inputLinks) {
if (link.demand < cutoff)
continue;
Node provider = link.provider;
LongPair exchange = LongPair.of(node.product.getFirst(),
link.exchangeId);
index.putLink(exchange, provider.product);
}
}
}
private class Graph {
Node root;
List<Node> next = new ArrayList<>();
HashMap<LongPair, Node> nodes = new HashMap<>();
Graph(LongPair refProduct, double demand) {
this.root = new Node(refProduct, demand);
root.product = refProduct;
root.state = NodeState.WAITING;
nodes.put(refProduct, root);
next.add(root);
}
void handleNext() {
log.trace("handle next layer with {} product nodes", next.size());
// to minimize the re-scale calls we first sort the nodes by their
// demands (see the compareTo method in Node)
Collections.sort(next);
Map<Long, List<CalcExchange>> nextExchanges = fetchNextExchanges();
List<Node> nextLayer = new ArrayList<>();
for (Node node : next) {
node.state = NodeState.PROGRESS;
List<CalcExchange> exchanges = nextExchanges.get(
node.product.getFirst());
CalcExchange output = getOutput(node, exchanges);
if (output == null)
continue;
node.outputAmount = amount(output);
node.scalingFactor = node.demand / node.outputAmount;
followInputs(node, exchanges, nextLayer);
node.state = NodeState.FOLLOWED;
}
next.clear();
next.addAll(nextLayer);
}
private void followInputs(Node node, List<CalcExchange> exchanges,
List<Node> nextLayer) {
for (CalcExchange input : getInputs(node, exchanges)) {
LongPair inputProduct = providerSearch.find(input);
if (inputProduct == null)
continue;
double inputAmount = amount(input);
double inputDemand = node.scalingFactor * inputAmount;
Node provider = nodes.get(inputProduct);
if (provider != null)
checkSubGraph(inputDemand, provider, nextLayer, false);
else {
provider = createNode(inputDemand, inputProduct,
nextLayer);
}
Link link = new Link(provider, input.exchangeId,
inputAmount, inputDemand);
node.inputLinks.add(link);
}
}
private Node createNode(double inputDemand, LongPair product,
List<Node> nextLayer) {
Node node = new Node(product, inputDemand);
nodes.put(product, node);
if (inputDemand < cutoff)
node.state = NodeState.EXCLUDED;
else {
node.state = NodeState.WAITING;
nextLayer.add(node);
}
return node;
}
private void checkSubGraph(double demand, Node provider,
List<Node> nextLayer, boolean recursion) {
if (demand <= provider.demand || demand < cutoff)
return;
provider.demand = demand;
if (provider.state == NodeState.EXCLUDED) {
provider.state = NodeState.WAITING;
nextLayer.add(provider);
}
if (provider.state == NodeState.FOLLOWED) {
provider.state = NodeState.RESCALED;
rescaleSubGraph(provider, nextLayer);
if (!recursion) {
log.trace("rescaled a sub graph");
unsetScaleState();
}
}
}
private void rescaleSubGraph(Node start, List<Node> nextLayer) {
start.scalingFactor = start.demand / start.outputAmount;
for (Link link : start.inputLinks) {
double inputDemand = link.inputAmount * start.scalingFactor;
link.demand = inputDemand;
Node provider = link.provider;
checkSubGraph(inputDemand, provider, nextLayer, true);
}
}
private void unsetScaleState() {
for (Node node : nodes.values()) {
if (node.state == NodeState.RESCALED) {
node.state = NodeState.FOLLOWED;
}
}
}
private CalcExchange getOutput(Node node, List<CalcExchange> all) {
for (CalcExchange e : all) {
if (e.input
|| e.flowType != FlowType.PRODUCT_FLOW
|| e.flowId != node.product.getSecond())
continue;
return e;
}
return null;
}
private List<CalcExchange> getInputs(Node node,
List<CalcExchange> all) {
List<CalcExchange> inputs = new ArrayList<>();
for (CalcExchange e : all) {
if (e.input && e.flowType == FlowType.PRODUCT_FLOW)
inputs.add(e);
}
return inputs;
}
private double amount(CalcExchange e) {
if (e == null)
return 0;
return e.amount * e.conversionFactor;
}
private Map<Long, List<CalcExchange>> fetchNextExchanges() {
if (next.isEmpty())
return Collections.emptyMap();
Set<Long> processIds = new HashSet<>();
for (Node node : next)
processIds.add(node.product.getFirst());
try {
return cache.getExchangeCache().getAll(processIds);
} catch (Exception e) {
Logger log = LoggerFactory.getLogger(getClass());
log.error("failed to load exchanges from cache", e);
return Collections.emptyMap();
}
}
}
}