package org.openlca.core.matrix.product.index;
import java.util.ArrayList;
import java.util.Collections;
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.matrix.cache.ProcessTable;
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 ProductIndexBuilder implements IProductIndexBuilder {
private Logger log = LoggerFactory.getLogger(getClass());
private ProcessType preferredType = ProcessType.LCI_RESULT;
private MatrixCache cache;
private ProcessTable processTable;
private ProductSystem system;
public ProductIndexBuilder(MatrixCache cache, ProductSystem system) {
this.cache = cache;
this.system = system;
this.processTable = cache.getProcessTable();
}
@Override
public void setPreferredType(ProcessType preferredType) {
this.preferredType = preferredType;
}
@Override
public TechIndex build(LongPair refProduct) {
return build(refProduct, 1.0);
}
@Override
public TechIndex build(LongPair refFlow, double demand) {
log.trace("build product index for {}", refFlow);
TechIndex index = new TechIndex(refFlow);
index.setDemand(demand);
addSystemLinks(index);
List<LongPair> block = new ArrayList<>();
block.add(refFlow);
HashSet<LongPair> handled = new HashSet<>();
while (!block.isEmpty()) {
List<LongPair> nextBlock = new ArrayList<>();
log.trace("fetch next block with {} entries", block.size());
Map<Long, List<CalcExchange>> exchanges = fetchExchanges(block);
for (LongPair recipient : block) {
handled.add(recipient);
List<CalcExchange> allExchanges = exchanges.get(recipient
.getFirst());
List<CalcExchange> productInputs = getProductInputs(
allExchanges);
for (CalcExchange productInput : productInputs) {
LongPair provider = findProvider(productInput);
if (provider == null)
continue;
LongPair exchange = new LongPair(recipient.getFirst(),
productInput.exchangeId);
index.putLink(exchange, provider);
if (!handled.contains(provider) && !nextBlock.contains(provider))
nextBlock.add(provider);
}
}
block = nextBlock;
}
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 List<CalcExchange> getProductInputs(
List<CalcExchange> processExchanges) {
if (processExchanges == null || processExchanges.isEmpty())
return Collections.emptyList();
List<CalcExchange> productInputs = new ArrayList<>();
for (CalcExchange exchange : processExchanges) {
if (!exchange.input)
continue;
if (exchange.flowType == FlowType.ELEMENTARY_FLOW)
continue;
productInputs.add(exchange);
}
return productInputs;
}
private Map<Long, List<CalcExchange>> fetchExchanges(List<LongPair> block) {
if (block.isEmpty())
return Collections.emptyMap();
Set<Long> processIds = new HashSet<>();
for (LongPair pair : block)
processIds.add(pair.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();
}
}
private LongPair findProvider(CalcExchange productInput) {
if (productInput == null)
return null;
long productId = productInput.flowId;
long[] processIds = processTable.getProductProviders(productId);
if (processIds == null)
return null;
LongPair candidate = null;
for (long processId : processIds) {
LongPair newOption = LongPair.of(processId, productId);
if (isBetter(productInput, candidate, newOption))
candidate = newOption;
}
return candidate;
}
private boolean isBetter(CalcExchange inputLink, LongPair candidate,
LongPair newOption) {
if (candidate == null)
return true;
if (newOption == null)
return false;
if (candidate.getFirst() == inputLink.defaultProviderId)
return false;
if (newOption.getFirst() == inputLink.defaultProviderId)
return true;
ProcessType candidateType = processTable.getType(candidate.getFirst());
ProcessType newOptionType = processTable.getType(newOption.getFirst());
if (candidateType == preferredType && newOptionType != preferredType)
return false;
return candidateType != preferredType && newOptionType == preferredType;
}
}