package nl.fontys.sofa.limo.simulation.task; import gnu.trove.map.TObjectDoubleMap; import gnu.trove.map.hash.TObjectDoubleHashMap; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Random; import nl.fontys.sofa.limo.domain.component.Component; import nl.fontys.sofa.limo.domain.component.Node; import nl.fontys.sofa.limo.domain.component.SupplyChain; import nl.fontys.sofa.limo.domain.component.event.Event; import nl.fontys.sofa.limo.domain.component.event.ExecutionState; import nl.fontys.sofa.limo.domain.component.hub.Hub; import nl.fontys.sofa.limo.domain.component.leg.Leg; import nl.fontys.sofa.limo.domain.component.leg.MultiModeLeg; import nl.fontys.sofa.limo.domain.component.leg.ScheduledLeg; import nl.fontys.sofa.limo.simulation.result.TestCaseResult; /** * * @author Dominik Kaisers {@literal <d.kaisers@student.fontys.nl>} */ public class TestCase implements Runnable { private static Random random; private final SupplyChain supplyChain; private double totalCosts; private double totalLeadTimes; private double totalDelays; private double totalExtraCosts; private TObjectDoubleMap<String> costsByCategory; private TObjectDoubleMap<String> leadTimesByCategory; private TObjectDoubleMap<String> delaysByCategory; private TObjectDoubleMap<String> extraCostsByCategory; private TObjectDoubleMap<String> costsByNode; private TObjectDoubleMap<String> leadTimesByNode; private TObjectDoubleMap<String> delaysByNode; private TObjectDoubleMap<String> extraCostsByNode; private List<Event> executedEvents; private double totalCO2; private TObjectDoubleMap<String> co2ByCategory; private TObjectDoubleMap<String> co2ByNode; private long lastDelay; private TestCaseResult result; public TestCase(SupplyChain supplyChain) { this.supplyChain = supplyChain; } public TestCaseResult getResult() { return result; } @Override public void run() { totalCosts = 0; totalLeadTimes = 0; totalDelays = 0; totalExtraCosts = 0; totalCO2 = 0; costsByCategory = new TObjectDoubleHashMap<>(12); leadTimesByCategory = new TObjectDoubleHashMap<>(12); delaysByCategory = new TObjectDoubleHashMap<>(12); extraCostsByCategory = new TObjectDoubleHashMap<>(12); costsByNode = new TObjectDoubleHashMap<>(12); leadTimesByNode = new TObjectDoubleHashMap<>(12); delaysByNode = new TObjectDoubleHashMap<>(12); extraCostsByNode = new TObjectDoubleHashMap<>(12); co2ByCategory = new TObjectDoubleHashMap<>(12); co2ByNode = new TObjectDoubleHashMap<>(12); executedEvents = new ArrayList<>(); lastDelay = 0; Node currentNode = supplyChain.getStartHub(); while (currentNode != null) { Node calcNode = currentNode; if (currentNode instanceof ScheduledLeg) { calcNode = calculateScheduledLeg(currentNode, calcNode); } else if (currentNode instanceof MultiModeLeg) { calcNode = calculateMultiModeLeg(currentNode); } calculateProcedures(calcNode); // Calc events lastDelay = 0; calculateEvents(currentNode.getName(), calcNode, calcNode.getEvents()); currentNode = currentNode.getNext(); } result = new TestCaseResult(supplyChain, totalCosts, totalLeadTimes, totalDelays, totalExtraCosts, totalCO2, costsByCategory, leadTimesByCategory, delaysByCategory, extraCostsByCategory, co2ByCategory, costsByNode, leadTimesByNode, delaysByNode, extraCostsByNode, co2ByNode, executedEvents); } private Node calculateScheduledLeg(Node currentNode, Node calcNode) { // calcNode -> best leg of scheduled leg ScheduledLeg sl = (ScheduledLeg) currentNode; long acceptTime = lastDelay + sl.getExpectedTime(); // Get first possible acceptance time int index = sl.getAcceptanceTimes().size(); while (index > 0 && sl.getAcceptanceTimes().get(index - 1) > acceptTime) { index--; } // No acceptance times, too long wait, times not met -> alternative used if (sl.getAcceptanceTimes().isEmpty() || (index == sl.getAcceptanceTimes().size()) || (sl.getAcceptanceTimes().get(index) - acceptTime > sl.getWaitingTimeLimit())) { calcNode = sl.getAlternative(); } else { // Calculate the time that has to be waited until acceptance and add to lead times double leadTime = sl.getAcceptanceTimes().get(index) - acceptTime; totalLeadTimes += leadTime; if (!leadTimesByCategory.containsKey(ScheduledLeg.WAIT_CATEGORY)) { leadTimesByCategory.put(ScheduledLeg.WAIT_CATEGORY, leadTime); } else { double lt = leadTimesByCategory.get(ScheduledLeg.WAIT_CATEGORY) + leadTime; leadTimesByCategory.put(ScheduledLeg.WAIT_CATEGORY, lt); } if (!leadTimesByNode.containsKey(ScheduledLeg.WAIT_CATEGORY)) { leadTimesByNode.put(ScheduledLeg.WAIT_CATEGORY, leadTime); } else { double lt = leadTimesByNode.get(ScheduledLeg.WAIT_CATEGORY) + leadTime; leadTimesByNode.put(ScheduledLeg.WAIT_CATEGORY, lt); } } return calcNode; } private Node calculateMultiModeLeg(Node currentNode) { Node calcNode; // calcNode -> one node based on probability MultiModeLeg mml = (MultiModeLeg) currentNode; double totalSum = mml.getTotalWeight(); Random rand = new Random(); double index = rand.nextDouble() * totalSum; double sum = 0; int i = 0; List<Map.Entry<Leg, Double>> legs = new ArrayList<>(mml.getLegs().entrySet()); while (sum < index) { sum += legs.get(i++).getValue(); } calcNode = legs.get(i - 1).getKey(); return calcNode; } private void calculateProcedures(Node calcNode) { calcNode.getProcedures().stream().map((procedure) -> { double pCost; if (procedure.getCost() == null) { pCost = 0; } else { pCost = procedure.getCost().getValue(); } double pLeadTime; if (procedure.getTimeType() == null || procedure.getTime() == null) { pLeadTime = 0; } else { pLeadTime = procedure.getTimeType().getMinutes(procedure.getTime().getValue()); } double pCO2; if (procedure.getCotwo() == null) { pCO2 = 0; } else { pCO2 = procedure.getCotwo().getValue(); } totalCosts += pCost; totalLeadTimes += pLeadTime; totalCO2 += pCO2; if (!co2ByCategory.containsKey(procedure.getCategory())) { co2ByCategory.put(procedure.getCategory(), 0d); } double newCategoryCO2 = co2ByCategory.get(procedure.getCategory()) + pCO2; co2ByCategory.put(procedure.getCategory(), newCategoryCO2); if (!co2ByNode.containsKey(calcNode.getName())) { co2ByNode.put(calcNode.getName(), 0d); } double newNodeCO2 = co2ByNode.get(calcNode.getName()) + pCO2; co2ByNode.put(calcNode.getName(), newNodeCO2); if (!costsByCategory.containsKey(procedure.getCategory())) { costsByCategory.put(procedure.getCategory(), 0d); } double newCategoryCost = costsByCategory.get(procedure.getCategory()) + pCost; costsByCategory.put(procedure.getCategory(), newCategoryCost); if (!costsByNode.containsKey(calcNode.getName())) { costsByNode.put(calcNode.getName(), 0d); } double newNodeCost = costsByNode.get(calcNode.getName()) + pCost; costsByNode.put(calcNode.getName(), newNodeCost); if (!leadTimesByCategory.containsKey(procedure.getCategory())) { leadTimesByCategory.put(procedure.getCategory(), 0d); } double newCategoryLeadTime = leadTimesByCategory.get(procedure.getCategory()) + pLeadTime; leadTimesByCategory.put(procedure.getCategory(), newCategoryLeadTime); if (!leadTimesByNode.containsKey(calcNode.getName())) { leadTimesByNode.put(calcNode.getName(), 0d); } double newNodeLeadTime = leadTimesByCategory.get(calcNode.getName()) + pLeadTime; return newNodeLeadTime; }).forEach((newNodeLeadTime) -> { leadTimesByNode.put(calcNode.getName(), newNodeLeadTime); }); } private void calculateEvents(String nodeKey, Component parent, List<Event> events) { events.stream().forEach((Event event) -> { Event parentEvent = parent instanceof Event ? (Event) parent : null; // See if should be executed if (event.getDependency() == ExecutionState.INDEPENDENT || parent instanceof Hub || parent instanceof Leg || (parentEvent != null && parentEvent.getExecutionState() == event.getDependency())) { // Check if event should be executed and set state boolean executed = randomDouble() <= event.getProbability().getProbability(); event.setExecutionState(ExecutionState.stateFromBool(executed)); // If executed, add costs and times to extra costs and delays from procedures of event if (executed) { event.getProcedures().stream().map((procedure) -> { double pExtraCost; if (procedure.getCost() == null) { pExtraCost = 0; } else { pExtraCost = procedure.getCost().getValue(); } double pDelay; if (procedure.getTimeType() == null || procedure.getTime() == null) { pDelay = 0; } else { pDelay = procedure.getTimeType().getMinutes(procedure.getTime().getValue()); } double pCO2; if (procedure.getCotwo() == null) { pCO2 = 0; } else { pCO2 = procedure.getCotwo().getValue(); } totalExtraCosts += pExtraCost; totalDelays += pDelay; lastDelay += pDelay; totalCO2 += pCO2; if (!co2ByCategory.containsKey(procedure.getCategory())) { co2ByCategory.put(procedure.getCategory(), 0d); } double newCategoryCO2 = co2ByCategory.get(procedure.getCategory()) + pCO2; co2ByCategory.put(procedure.getCategory(), newCategoryCO2); if (!co2ByNode.containsKey(nodeKey)) { co2ByNode.put(nodeKey, 0d); } double newNodeCO2 = co2ByNode.get(nodeKey) + pCO2; co2ByNode.put(nodeKey, newNodeCO2); if (!extraCostsByCategory.containsKey(procedure.getCategory())) { extraCostsByCategory.put(procedure.getCategory(), 0d); } if (!extraCostsByNode.containsKey(nodeKey)) { extraCostsByNode.put(nodeKey, 0d); } double newCost = extraCostsByCategory.get(procedure.getCategory()) + pExtraCost; extraCostsByCategory.put(procedure.getCategory(), newCost); extraCostsByNode.put(nodeKey, newCost); if (!delaysByCategory.containsKey(procedure.getCategory())) { delaysByCategory.put(procedure.getCategory(), 0d); } double newCategoryDelay = delaysByCategory.get(procedure.getCategory()) + pDelay; delaysByCategory.put(procedure.getCategory(), newCategoryDelay); if (!delaysByNode.containsKey(nodeKey)) { delaysByNode.put(nodeKey, 0d); } double newNodeDelay = delaysByCategory.get(nodeKey) + pDelay; return newNodeDelay; }).map((newNodeDelay) -> { delaysByNode.put(nodeKey, newNodeDelay); return newNodeDelay; }).forEach((_item) -> { executedEvents.add(event); }); } // Recursive call to all subevents calculateEvents(nodeKey, event, event.getEvents()); } }); } /** * Get a random double between 0 and 1 to check if event should be exeuted. * * @return Random double between 0 and 1. */ private static double randomDouble() { if (random == null) { random = new Random(); } return random.nextDouble(); } }