package nl.fontys.sofa.limo.simulation.result; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; import nl.fontys.sofa.limo.domain.component.SupplyChain; import nl.fontys.sofa.limo.domain.component.event.Event; import nl.fontys.sofa.limo.simulation.util.MathUtil; /** * A SimulationResult encapsulates the data resulting from a simulation of a * single supply chain. There is data on the single test cases and accumulated * data (min, max, avg) on the whole simulation. * * @author Dominik Kaisers {@literal <d.kaisers@student.fontys.nl>} */ public class SimulationResult { private final SupplyChain supplyChain; private DataEntry totalCosts; private DataEntry totalLeadTimes; private DataEntry totalDelays; private DataEntry totalExtraCosts; private DataEntry totalCO2; private final Map<String, DataEntry> costsByCategory; private final Map<String, DataEntry> leadTimesByCategory; private final Map<String, DataEntry> delaysByCategory; private final Map<String, DataEntry> extraCostsByCategory; private final Map<String, DataEntry> co2ByCategory; private final Map<String, DataEntry> costsByNode; private final Map<String, DataEntry> leadTimesByNode; private final Map<String, DataEntry> delaysByNode; private final Map<String, DataEntry> co2ByNode; private final Map<String, DataEntry> extraCostsByNode; private final Map<String, Double> eventExecutionRate; private final Map<String, Event> executedEvents; private final AtomicInteger testCaseCount; private final List<TestCaseResult> results; public SimulationResult(SupplyChain supplyChain) { this.supplyChain = supplyChain; this.totalCosts = new DataEntry(); this.totalLeadTimes = new DataEntry(); this.totalDelays = new DataEntry(); this.totalExtraCosts = new DataEntry(); this.totalCO2 = new DataEntry(); this.costsByCategory = new ConcurrentHashMap<>(); this.leadTimesByCategory = new ConcurrentHashMap<>(); this.delaysByCategory = new ConcurrentHashMap<>(); this.extraCostsByCategory = new ConcurrentHashMap<>(); this.costsByNode = new ConcurrentHashMap<>(); this.leadTimesByNode = new ConcurrentHashMap<>(); this.delaysByNode = new ConcurrentHashMap<>(); this.extraCostsByNode = new ConcurrentHashMap<>(); this.co2ByCategory = new ConcurrentHashMap<>(); this.co2ByNode = new ConcurrentHashMap<>(); this.eventExecutionRate = new ConcurrentHashMap<>(); this.executedEvents = new ConcurrentHashMap<>(); this.testCaseCount = new AtomicInteger(); this.results = new CopyOnWriteArrayList<>(); } public List<TestCaseResult> getResults() { return results; } public SupplyChain getSupplyChain() { return supplyChain; } public DataEntry getTotalCosts() { return totalCosts; } public DataEntry getTotalCO2() { return totalCO2; } public DataEntry getTotalLeadTimes() { return totalLeadTimes; } public DataEntry getTotalDelays() { return totalDelays; } public DataEntry getTotalExtraCosts() { return totalExtraCosts; } public Map<String, DataEntry> getCo2ByCategory() { return co2ByCategory; } public Map<String, DataEntry> getCo2ByNode() { return co2ByNode; } public Map<String, Event> getExecutedEvents() { return executedEvents; } public Map<String, DataEntry> getCostsByCategory() { return Collections.unmodifiableMap(costsByCategory); } public Map<String, DataEntry> getLeadTimesByCategory() { return Collections.unmodifiableMap(leadTimesByCategory); } public Map<String, DataEntry> getDelaysByCategory() { return Collections.unmodifiableMap(delaysByCategory); } public Map<String, DataEntry> getExtraCostsByCategory() { return extraCostsByCategory; } public Map<String, DataEntry> getCostsByNode() { return costsByNode; } public Map<String, DataEntry> getLeadTimesByNode() { return leadTimesByNode; } public Map<String, DataEntry> getDelaysByNode() { return delaysByNode; } public Map<String, DataEntry> getExtraCostsByNode() { return extraCostsByNode; } public Map<String, Double> getEventExecutionRate() { return eventExecutionRate; } public int getTestCaseCount() { return testCaseCount.get(); } private double mean = 0; /** * Adds a test case result to this simulation result. * * @param tcr Test case result to add. */ public void addTestCaseResult(TestCaseResult tcr) { if (tcr == null) { return; } final int size = testCaseCount.get(); // Recalculate totals by adding the new value to the existing data entry. this.totalCosts = recalculateDataEntry(totalCosts, size, tcr.getTotalCosts()); this.totalLeadTimes = recalculateDataEntry(totalLeadTimes, size, tcr.getTotalLeadTimes()); this.totalDelays = recalculateDataEntry(totalDelays, size, tcr.getTotalDelays()); this.totalExtraCosts = recalculateDataEntry(totalExtraCosts, size, tcr.getTotalExtraCosts()); this.totalCO2 = recalculateDataEntry(totalCO2, size, tcr.getTotalCO2()); // BY CATEGORY tcr.getCostsByCategory().forEachEntry((String key, double value) -> { if (key == null) { return true; } DataEntry old = costsByCategory.get(key); costsByCategory.put(key, recalculateDataEntry(old, size, value)); return true; }); tcr.getLeadTimesByCategory().forEachEntry((String key, double value) -> { if (key == null) { return true; } DataEntry old = leadTimesByCategory.get(key); leadTimesByCategory.put(key, recalculateDataEntry(old, size, value)); return true; }); tcr.getDelaysByCategory().forEachEntry((String key, double value) -> { if (key == null) { return true; } DataEntry old = delaysByCategory.get(key); delaysByCategory.put(key, recalculateDataEntry(old, size, value)); return true; }); tcr.getExtraCostsByCategory().forEachEntry((String key, double value) -> { if (key == null) { return true; } DataEntry old = extraCostsByCategory.get(key); extraCostsByCategory.put(key, recalculateDataEntry(old, size, value)); return true; }); tcr.getCo2ByCategory().forEachEntry((String key, double value) -> { if (key == null) { return true; } DataEntry old = co2ByCategory.get(key); co2ByCategory.put(key, recalculateDataEntry(old, size, value)); return true; }); // BY NODE tcr.getCostsByNode().forEachEntry((String key, double value) -> { if (key == null) { return true; } DataEntry old = costsByNode.get(key); costsByNode.put(key, recalculateDataEntry(old, size, value)); return true; }); tcr.getLeadTimesByNode().forEachEntry((String key, double value) -> { if (key == null) { return true; } DataEntry old = leadTimesByNode.get(key); leadTimesByNode.put(key, recalculateDataEntry(old, size, value)); return true; }); tcr.getDelaysByNode().forEachEntry((String key, double value) -> { if (key == null) { return true; } DataEntry old = delaysByNode.get(key); delaysByNode.put(key, recalculateDataEntry(old, size, value)); return true; }); tcr.getExtraCostsByNode().forEachEntry((String key, double value) -> { if (key == null) { return true; } DataEntry old = extraCostsByNode.get(key); extraCostsByNode.put(key, recalculateDataEntry(old, size, value)); return true; }); tcr.getCo2ByNode().forEachEntry((String key, double value) -> { if (key == null) { return true; } DataEntry old = co2ByNode.get(key); co2ByNode.put(key, recalculateDataEntry(old, size, value)); return true; }); // ADD Events tcr.getExecutedEvents().stream().forEach((event) -> { if (event == null) { return; } if (!eventExecutionRate.containsKey(event.getUniqueIdentifier())) { this.eventExecutionRate.put(event.getUniqueIdentifier(), 1.0 / (size + 1)); this.executedEvents.put(event.getUniqueIdentifier(), event); } else { double newAvg = MathUtil.getCumulativeMovingAverage(this.eventExecutionRate.get(event.getUniqueIdentifier()), size, 1); this.eventExecutionRate.put(event.getUniqueIdentifier(), newAvg); } }); if (results.size() < 1000 || tcr.getExecutedEvents().size() > mean) { if (results.size() < 1000) { this.results.add(tcr); } else { this.results.remove(results.size() / 2); results.add(tcr); } Collections.sort(results, (TestCaseResult o1, TestCaseResult o2) -> { if (o1 == null) { return 1; } if (o2 == null) { return -1; } if (o1.getExecutedEvents().size() == o2.getExecutedEvents().size()) { if (o1.getTotalDelays() <= o2.getTotalDelays()) { return 1; } else { return -1; } } if (o1.getExecutedEvents().size() < o2.getExecutedEvents().size()) { return 1; } else { return -1; } }); int value = 0; value = results.stream().map((d) -> d.getExecutedEvents().size()).reduce(value, Integer::sum); mean = value / results.size(); } this.testCaseCount.incrementAndGet(); } /** * Recalculate an data entry with new data. * * @param old Old data entry. * @param oldCount Old count used to create average. * @param newValue New value to add to data entry. * @return Recalculated data entry. */ protected DataEntry recalculateDataEntry(DataEntry old, int oldCount, double newValue) { if (old == null) { return new DataEntry(newValue, newValue, newValue); } double min = old.getMin() != null && old.getMin() < newValue ? old.getMin() : newValue; double max = old.getMax() != null && old.getMax() > newValue ? old.getMax() : newValue; double avg = MathUtil.getCumulativeMovingAverage(old.getAvg(), oldCount, newValue); return new DataEntry(min, max, avg); } }