using HeuristicLab.BioBoost.Data; using HeuristicLab.BioBoost.ProblemDescription; using HeuristicLab.Common; using HeuristicLab.Core; using HeuristicLab.Data; using HeuristicLab.Persistence.Default.CompositeSerializers.Storable; using System; using System.Collections.Generic; using System.Linq; using HeuristicLab.BioBoost.Representation; using HeuristicLab.BioBoost.Utils; namespace HeuristicLab.BioBoost.Evaluators { [StorableClass] public class LogisticCostEvaluator : BioBoostEvaluator { private class Variant : IComparable { public double HandlingCost; public double TransportCost; public double Penalty; public double Distance; public Logistic Logistic; public double Amount; public int CompareTo(Variant other) { return (int)((HandlingCost + TransportCost) - (other.HandlingCost + other.TransportCost)); } } #region fields private DoubleArray invest, maint, fuel, labor, other; #endregion #region Construction & Cloning [StorableConstructor] protected LogisticCostEvaluator(bool isDeserializing) : base(isDeserializing) {} protected LogisticCostEvaluator(LogisticCostEvaluator original, Cloner cloner) : base(original, cloner) {} public LogisticCostEvaluator() { } public override IDeepCloneable Clone(Cloner cloner) { return new LogisticCostEvaluator(this, cloner); } #endregion public override IOperation Apply() { var logistics = ProblemData.Logistics; var distances = new Dictionary(); var emissions = new Dictionary(); invest = ProblemData.InvestmentCostFactors; maint = ProblemData.MaintenanceCostFactors; fuel = ProblemData.FuelCostFactors; labor = ProblemData.LaborCostFactors; other = ProblemData.OtherCostFactors; var prices = ProblemData.Products.ToDictionary(p => p.Label, p => p.Price); // TODO: regional scaling for supplies and side-products? foreach (var product in logistics.Select(l => l.Key)) { var amountsAtSource = GetFromScope(LayerDescriptor.AmountsAtSource.NameWithPrefix(product)); var costsAtSource = GetFromScope(LayerDescriptor.TotalCostsAtSource.NameWithPrefix(product)); var transportTargets = GetFromScope(LayerDescriptor.TransportTargets.NameWithPrefix(product)); if (amountsAtSource == null || transportTargets == null) continue; var length = amountsAtSource.Length; var sources = new ItemArray>(length); var amountsAtTarget = new DoubleArray(length); var chosenVariants = new List(new Variant[length]); var costsAtTarget = new DoubleArray(length); var totalProductHandlingCost = 0d; var totalProductTransportCost = 0d; var totalPenalty = 0d; for (int i = 0; i < length; i++) { var amount = amountsAtSource[i]; if (amount <= 0) continue; var j = transportTargets[i]; if (j == -1) { amountsAtSource[i] = 0; // no transport -> no feedstock collected continue; } var chosenVariant = ChooseVariant(logistics[product], amount, i, j, distances); if (chosenVariant != null) { amountsAtTarget[j] += Math.Min(chosenVariant.Amount, amount); // cannot have more than before costsAtTarget[j] += costsAtSource[i] + chosenVariant.HandlingCost + chosenVariant.TransportCost + chosenVariant.Logistic.Transport.Emissions.TotalCost(prices, null)*chosenVariant.Amount*chosenVariant.Distance + chosenVariant.Logistic.Handling.Emissions.TotalCost(prices, null)*chosenVariant.Amount; totalProductHandlingCost += chosenVariant.HandlingCost; totalProductTransportCost += chosenVariant.TransportCost; totalPenalty += chosenVariant.Penalty; AddEmissions(emissions, chosenVariant.Logistic.Handling.Emissions, amount, i, j, length); AddEmissions(emissions, chosenVariant.Logistic.Transport.Emissions, amount*chosenVariant.Distance, i, j, length); chosenVariants[i] = chosenVariant; if (sources[j] != null) { sources[j].Add(new IntValue(i)); } else { sources[j] = new ItemList(new [] { new IntValue(i) }); } } else { amountsAtSource[i] = 0; // will be AmountsTransportedFromSource } } RenameInScope(LayerDescriptor.AmountsAtSource.NameWithPrefix(product), LayerDescriptor.AmountsTransportedFromSource.NameWithPrefix(product)); PutInScope(LayerDescriptor.TransportModes.NameWithPrefix(product), chosenVariants.Select("", v => v.Logistic.Mode).ToStringArray(), false); PutInScope(LayerDescriptor.TransportCosts.NameWithPrefix(product), chosenVariants.Select(0, v => v.TransportCost).ToDoubleArray(), false); PutInScope(LayerDescriptor.TransportCostsPerT.NameWithPrefix(product), chosenVariants.Select(0, v => v.Amount == 0 ? 0 : v.TransportCost / v.Amount).ToDoubleArray(), false); PutInScope(LayerDescriptor.TransportCostsPerTkm.NameWithPrefix(product), chosenVariants.Select(0, v => v.Amount == 0 || v.Distance == 0 ? 0 : v.TransportCost / v.Amount / v.Distance).ToDoubleArray(), false); PutInScope(LayerDescriptor.HandlingCosts.NameWithPrefix(product), chosenVariants.Select(0, v => v.HandlingCost).ToDoubleArray(), false); PutInScope(LayerDescriptor.RelativeHandlingCosts.NameWithPrefix(product), chosenVariants.Select(0, v => v.Amount == 0 ? 0 : v.HandlingCost / v.Amount).ToDoubleArray(), false); AddInScope(LayerDescriptor.AmountsAtTarget.NameWithPrefix(product), amountsAtTarget, false); AddInScope(LayerDescriptor.TotalCostsAtTarget.NameWithPrefix(product), costsAtTarget, false); PutInScope(LayerDescriptor.RelativeCostAtTarget.NameWithPrefix(product), costsAtTarget.Zip(amountsAtTarget, (c,a) => a == 0 ? 0 : c/a).ToDoubleArray(), false); PutInScope(LayerDescriptor.TransportDistance.NameWithPrefix(product), chosenVariants.Select(0, v => v.Distance).ToDoubleArray(), false); PutInScope(LayerDescriptor.Tkm.NameWithPrefix(product), chosenVariants.Select(0, v => v.Distance*v.Amount).ToDoubleArray(), false); var transportPenalties = chosenVariants.Select(0, v => v.Penalty).ToDoubleArray(); PutInScope(LayerDescriptor.ExceedingTransportDistancePenalty.NameWithPrefix(product), transportPenalties, false); AddInScope(LayerDescriptor.TotalPenalty.Name, (DoubleArray)transportPenalties.Clone(), false); PutInScope(LayerDescriptor.TotalLogisticCosts.NameWithPrefix(product), chosenVariants.Select(0, v => v.TransportCost + v.HandlingCost).ToDoubleArray(), false); PutInScope(LayerDescriptor.LogisticCostsPerTon.NameWithPrefix(product), chosenVariants.Select(0, v => v.Amount == 0 ? 0 : (v.TransportCost + v.HandlingCost)/v.Amount).ToDoubleArray(), false); //PutInScope(LayerDescriptor.LogisticCostsPerTonKm.NameWithPrefix(product), chosenVariants.Select(0, v => v.Amount * v.Distance == 0 ? 0 : (v.TransportCost + v.HandlingCost)/v.Amount/v.Distance).ToDoubleArray(), false); AddInScope(LayerDescriptor.LocalAddedValue.Name, chosenVariants.Select(0, v => v.HandlingCost + v.TransportCost).ToDoubleArray(), false); PutInScope(LayerDescriptor.Sources.NameWithPrefix(product), sources, false); AddCost(LayerDescriptor.HandlingCosts.NameWithPrefix(product), totalProductHandlingCost); AddCost(LayerDescriptor.TransportCosts.NameWithPrefix(product), totalProductTransportCost); AddCost(LayerDescriptor.ExceedingTransportDistancePenalty.NameWithPrefix(product), totalPenalty); } foreach (var e in emissions) { AddInScope(LayerDescriptor.AmountsAtSource.NameWithPrefix(e.Key), e.Value, false); AddInScope(LayerDescriptor.DischargeCosts.NameWithPrefix(e.Key), e.Value.Select(x => !prices.ContainsKey(e.Key) ? 0 : (prices[e.Key] * x)).ToDoubleArray(), false); } return base.Apply(); } private Variant ChooseVariant(IEnumerable logistics, double amount, int i, int j, IDictionary distances) { var variants = new List(); foreach (var l in logistics) { DistanceMatrix distanceMatrix = null; if (!distances.TryGetValue(l.Distances, out distanceMatrix)) { distanceMatrix = GetFromProblemData(l.Distances + "DistanceMatrix"); if (distanceMatrix != null) distances[l.Distances] = distanceMatrix; else throw new InvalidOperationException("Distance Matrix \"" + l.Distances + "\" for logistic action \"" + l.Mode + "\" of product \"" + l.Product + "\" could not be loaded"); } if (distanceMatrix == null) continue; var distance = distanceMatrix[i, j]; var actualAmount = Math.Min(Math.Max(amount, l.MinAmount), l.MaxAmount); var v = new Variant { Amount = actualAmount, Logistic = l, HandlingCost = GetLogisticActionCost(l.Handling, actualAmount*(1-l.WaterContent), i, j), TransportCost = GetLogisticActionCost(l.Transport, actualAmount*distance*(1-l.WaterContent), i, j), Distance = distance }; if (distance > l.MaxDistance) v.Penalty = Penalty(distance, l.MaxDistance, distanceMatrix.Max); variants.Add(v); } variants.Sort(); return variants.FirstOrDefault(); } public static double Penalty(double distance, double maxAllowedDistance, double maxPossibleDistance) { return (distance - maxAllowedDistance)/maxPossibleDistance; } private double GetLogisticActionCost(LogisticAction a, double amount, int i, int j) { return amount * ( a.Investment *(invest[i] + invest[j]) + a.Maintenance *( maint[i] + maint[j]) + a.Fuel *( fuel[i] + fuel[j]) + a.Labor *( labor[i] + labor[j]) + a.Other *( other[i] + other[j]))/2; } private void AddEmissions(Dictionary totalEmissions, IEnumerable emissions, double amount, int i, int j, int length) { foreach (var e in emissions) { DoubleArray values = null; if (!totalEmissions.TryGetValue(e.Name, out values)) { totalEmissions.Add(e.Name, values = new DoubleArray(length)); } values[i] += ((DoubleValue)e.Value).Value*amount/2; values[j] += ((DoubleValue)e.Value).Value*amount/2; } } } }