package org.openlca.app.editors.processes;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.openlca.core.model.AllocationFactor;
import org.openlca.core.model.AllocationMethod;
import org.openlca.core.model.Exchange;
import org.openlca.core.model.Flow;
import org.openlca.core.model.FlowProperty;
import org.openlca.core.model.FlowPropertyFactor;
import org.openlca.core.model.FlowPropertyType;
import org.openlca.core.model.Process;
class AllocationSync {
private final Process process;
private boolean firstInit;
private AllocationSync(Process process) {
this.process = process;
}
/**
* Adds or removes allocation factors if required. New allocation factors
* are initialized with a default value. Values of existing allocation
* factors are not changed.
*/
public static void updateFactors(Process process) {
if (process == null)
return;
new AllocationSync(process).doUpdate();
}
/**
* Calculates default allocation factors from the flow properties or costs
* of the exchanges. It also calls updateFactors internally.
*/
public static void calculateDefaults(Process process) {
if (process == null)
return;
new AllocationSync(process).doCalc();
}
private void doCalc() {
doUpdate();
List<Exchange> products = Processes.getOutputProducts(process);
if (products.size() < 2)
return;
List<F> physFactors = calcFactors(AllocationMethod.PHYSICAL, products);
List<F> ecoFactors;
if (canCalculateFromCosts(products))
ecoFactors = calculateFromCosts(products);
else
ecoFactors = calcFactors(AllocationMethod.ECONOMIC, products);
setNewValues(physFactors, AllocationMethod.PHYSICAL);
setNewValues(ecoFactors, AllocationMethod.ECONOMIC);
setNewCausalValues(physFactors);
}
private void setNewValues(List<F> factors, AllocationMethod method) {
for (F f : factors) {
AllocationFactor factor = getFactor(f.product, method);
if (factor == null)
continue;
factor.setValue(f.value);
}
}
private void setNewCausalValues(List<F> factors) {
for (F f : factors) {
for (Exchange e : Processes.getNonOutputProducts(process)) {
AllocationFactor factor = getCausalFactor(f.product, e);
if (factor == null)
continue;
factor.setValue(f.value);
}
}
}
private void doUpdate() {
List<Exchange> products = Processes.getOutputProducts(process);
if (products.size() < 2) {
process.getAllocationFactors().clear();
return;
}
firstInit = process.getAllocationFactors().isEmpty();
removeUnusedFactors(products);
addNewFactors(products);
}
private void removeUnusedFactors(List<Exchange> products) {
List<AllocationFactor> removals = new ArrayList<>();
for (AllocationFactor factor : process.getAllocationFactors()) {
long productId = factor.getProductId();
boolean remove = true;
for (Exchange product : products) {
if (productId == product.getFlow().getId()) {
remove = false;
break;
}
}
if (remove)
removals.add(factor);
}
process.getAllocationFactors().removeAll(removals);
}
private void addNewFactors(List<Exchange> products) {
for (Exchange product : products) {
createIfAbsent(product, AllocationMethod.PHYSICAL);
createIfAbsent(product, AllocationMethod.ECONOMIC);
for (Exchange exchange : Processes.getNonOutputProducts(process)) {
createCausalIfAbsent(product, exchange);
}
}
}
/** For physical and economic allocation. */
private void createIfAbsent(Exchange product, AllocationMethod method) {
AllocationFactor factor = getFactor(product, method);
if (factor != null)
return;
factor = new AllocationFactor();
factor.setAllocationType(method);
factor.setProductId(product.getFlow().getId());
factor.setValue(getInitialValue(product));
process.getAllocationFactors().add(factor);
}
private double getInitialValue(Exchange product) {
if (firstInit && Objects.equals(product, process.getQuantitativeReference()))
return 1;
else
return 0d;
}
/** For physical and economic allocation. */
private AllocationFactor getFactor(Exchange product, AllocationMethod method) {
for (AllocationFactor factor : process.getAllocationFactors()) {
if (factor.getAllocationType() != method)
continue;
if (factor.getProductId() == product.getFlow().getId())
return factor;
}
return null;
}
/** For causal allocation. */
private void createCausalIfAbsent(Exchange product, Exchange exchange) {
AllocationFactor factor = getCausalFactor(product, exchange);
if (factor != null)
return;
factor = new AllocationFactor();
factor.setAllocationType(AllocationMethod.CAUSAL);
factor.setExchange(exchange);
factor.setProductId(product.getFlow().getId());
factor.setValue(getInitialValue(product));
process.getAllocationFactors().add(factor);
}
private AllocationFactor getCausalFactor(Exchange product, Exchange exchange) {
for (AllocationFactor factor : process.getAllocationFactors()) {
if (factor.getAllocationType() != AllocationMethod.CAUSAL)
continue;
if (factor.getProductId() == product.getFlow().getId()
&& Objects.equals(exchange, factor.getExchange()))
return factor;
}
return null;
}
private List<F> calcFactors(AllocationMethod method, List<Exchange> products) {
FlowProperty commonProp = getCommonProperty(products, method);
if (commonProp == null && method != AllocationMethod.PHYSICAL)
commonProp = getCommonProperty(products, AllocationMethod.PHYSICAL);
List<F> factors = new ArrayList<>();
double totalAmount = 0;
for (Exchange product : products) {
double refAmount = getRefAmount(product);
double amount = 0;
if (commonProp != null) {
Flow flow = product.getFlow();
FlowPropertyFactor factor = flow.getFactor(commonProp);
if (factor != null)
amount = refAmount * factor.getConversionFactor();
}
totalAmount += amount;
factors.add(new F(product, amount));
}
if (totalAmount == 0)
return factors;
for (F f : factors)
f.value = f.value / totalAmount;
return factors;
}
private double getRefAmount(Exchange exchange) {
if (exchange.getUnit() == null
|| exchange.getFlowPropertyFactor() == null)
return 0;
double amount = exchange.getAmountValue();
double unitFactor = exchange.getUnit().getConversionFactor();
double propFactor = exchange.getFlowPropertyFactor()
.getConversionFactor();
if (propFactor == 0)
return 0;
return amount * unitFactor / propFactor;
}
private FlowProperty getCommonProperty(List<Exchange> products,
AllocationMethod method) {
List<FlowProperty> candidates = null;
for (Exchange product : products) {
Flow flow = product.getFlow();
List<FlowProperty> props = getProperties(flow, method);
if (candidates == null)
candidates = props;
else
candidates.retainAll(props);
}
if (candidates == null || candidates.isEmpty())
return null;
return candidates.get(0);
}
private List<FlowProperty> getProperties(Flow flow, AllocationMethod method) {
List<FlowProperty> properties = new ArrayList<>();
for (FlowPropertyFactor factor : flow.getFlowPropertyFactors()) {
FlowProperty prop = factor.getFlowProperty();
if (match(prop.getFlowPropertyType(), method))
properties.add(prop);
}
return properties;
}
private boolean match(FlowPropertyType propertyType, AllocationMethod method) {
if (propertyType == null || method == null)
return false;
else if (propertyType == FlowPropertyType.ECONOMIC
&& method == AllocationMethod.ECONOMIC)
return true;
else if (propertyType == FlowPropertyType.PHYSICAL
&& method == AllocationMethod.PHYSICAL)
return true;
else
return false;
}
private boolean canCalculateFromCosts(List<Exchange> products) {
for (Exchange product : products) {
if (product.costValue == null)
return false;
}
return true;
}
private List<F> calculateFromCosts(List<Exchange> products) {
List<F> factors = new ArrayList<>();
double total = 0;
for (Exchange product : products) {
double val = product.costValue == null ? 0 : product.costValue;
if (product.currency != null) {
val *= product.currency.conversionFactor;
}
factors.add(new F(product, val));
total += val;
}
if (total == 0)
return factors;
for (F f : factors)
f.value = f.value / total;
return factors;
}
/** Simple internal class that represents an allocation factor. */
private class F {
final Exchange product;
double value;
F(Exchange product, double value) {
this.product = product;
this.value = value;
}
}
}