Commit 4a46bdea authored by Sebastian Vollbrecht's avatar Sebastian Vollbrecht

Further refactoring of the edge creation phase.

parent c98932fd
......@@ -290,8 +290,9 @@ public class FeasibleMinIIProperty extends Property {
Set<Pair<ResourceNode, ResourceNode>> initialPairs = getPossibleInitialPairs(nodesByDelay);
if (initialPairs.isEmpty())
throw new MinIIImpossibleException(nodes);
if (initialPairs.isEmpty()) {
throw new MinIIImpossibleException();
}
Edge<ResourceNode> theBackedge = createMaxInnerDelayPath(initialPairs, nodesByDelay);
......@@ -507,7 +508,7 @@ public class FeasibleMinIIProperty extends Property {
remainingPathDelay = appendNodeToPath(remainingPathDelay, nodesByDelay, path);
}
PlannedEdge.distributeRemainingDelay(path.path, remainingPathDelay, rng);
PlannedEdge.distributeDelay(path.path, remainingPathDelay, rng);
path.path.forEach(e -> edgeCreator.createEdge(e.src, e.dst, e.delay));
......
......@@ -17,27 +17,19 @@
package graphgen.generator.components.properties.infeasibleMinII;
import graphgen.datastructures.SeededRandom;
import graphgen.generator.components.properties.infeasibleMinII.configuration.InfeasibleConfiguration;
import graphgen.generator.components.properties.infeasibleMinII.configuration.StartConfiguration;
import graphgen.generator.components.properties.infeasibleMinII.configuration.SuffixConfiguration;
import graphgen.generator.components.properties.util.PlannedEdge;
import graphgen.graph.LayerStructure;
import graphgen.graph.Resource;
import graphgen.graph.ResourceNode;
import graphgen.util.JavaUtils;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class InfeasibilityInspector {
......@@ -323,600 +315,24 @@ public class InfeasibilityInspector {
return remainingDelays;
}
public static Set<Integer> getPotentialLayers(ResourceNode problematicNode, InfeasibleConfiguration infeasibleCfg) {
int remainingZeroNodes = infeasibleCfg.getRemainingZeroNodes().size();
Set<Integer> possibleLayers = new HashSet<>();
LayerStructure layers = infeasibleCfg.getLayers();
if (Objects.requireNonNull(problematicNode).getDelay() == 0) {
remainingZeroNodes--;
}
boolean isDelayZero = problematicNode.getDelay() == 0;
int firstLayer = Objects.requireNonNull(infeasibleCfg).getStartConfiguration().getDestinationNodeLayer() + 1;
/*
* Iterate through all possible layers to determine which layers the problematic
* node can be placed in, using the many special cases to exit the loop early.
*/
for (int currentLayer = firstLayer; currentLayer < layers.getHeight(); currentLayer++) {
/*
* This set stores all nodes located in the current layer which have not yet
* been fixed.
*/
Set<ResourceNode> looseNodes = infeasibleCfg.getLooseNodesAtLayer(currentLayer);
/*
* Denotes whether the current layer contains a zero node already.
*/
boolean layerContainsZeroNodeAlready = infeasibleCfg.getZeroNodesByLayer().containsKey(currentLayer);
/*
* If the layer does not contain any loose nodes, we cannot place the
* problematic node in the current layer at all. We can furthermore only
* continue with our search for possible layers if the current layer contains a
* zero node already. Otherwise, access to subsequent layers is blocked and we
* must stop.
*/
if (looseNodes.isEmpty()) {
if (layerContainsZeroNodeAlready) {
continue;
} else {
break;
}
}
/*
* Now we can check whether the node can be placed in the current layer. This is
* only possible if the remaining problematic nodes can still be placed somewhere as well.
*/
Optional<Integer> lastLayerNeeded = getLastLayerNeeded(problematicNode, currentLayer, infeasibleCfg);
boolean canFitRest = lastLayerNeeded.isPresent();
if (canFitRest) {
/*
* If the problematic resource's delay is zero and if the destination node is
* problematic as well, an additional subsequent end node is needed which the
* RecMinII-ensuring backedge can then extend from, as the path does not have
* any delay so far (the destination node and the other problematic nodes all
* share the same time slot).
*
* If no additional end node can be appended (this is the case if the last
* needed layer is the very last layer), a maximum inner delay greater than 0
* will be impossible to ensure and the current layer and all its subsequent
* layers are thus invalid.
*/
boolean isDstNodeProblematic = infeasibleCfg.getProblematicNodes()
.contains(infeasibleCfg.getStartConfiguration().getDestinationNode());
if (isDelayZero && isDstNodeProblematic && lastLayerNeeded.get() == layers.getHeight() - 1) {
if (infeasibleCfg.getStartConfiguration().getMaxInnerDelay() > 0) {
break;
}
}
possibleLayers.add(currentLayer);
}
/*
* Before checking the next layer, we should make sure that we still have zero
* nodes remaining which would need to be placed in the current layer if we
* wanted to place the problematic node in the next layer.
*
* If no such zero nodes are available anymore, we must stop right here.
*/
if (!layerContainsZeroNodeAlready) {
if (remainingZeroNodes == 0) {
break;
} else {
remainingZeroNodes--;
}
}
}
return possibleLayers;
}
/**
* Computes the last layer which the problematic nodes would need to be placed in if the given node were placed in
* the specified layer.
*
* @param nodeToPlace the node to be placed in the target layer
* @param targetLayer the layer to place the node in
* @param infeasibleCfg the infeasible configuration to use
* @return an optional containing the last layer or an empty optional if: <ul><li>the target layer cannot be reached
* at all</li><li>it would then be impossible to arrange the remaining problematic nodes in an infeasible
* fashion</li></ul>
* @throws IllegalArgumentException if any of the following conditions apply:
* <ul><li>the node has been fixed already</li><li>the target layer does not
* have any empty spots remaining</li><li>the target layer's depth is not greater
* than the destination node layer's depth</li><li>the layer structure does not
* contain the target layer</li></ul>
*/
private static Optional<Integer> getLastLayerNeeded(ResourceNode nodeToPlace, int targetLayer,
InfeasibleConfiguration infeasibleCfg) {
if (Objects.requireNonNull(infeasibleCfg).getFixedNodes().contains(Objects.requireNonNull(nodeToPlace))) {
throw new IllegalArgumentException("Node " + nodeToPlace + " has been fixed already.");
}
if (targetLayer < 0 || targetLayer >= infeasibleCfg.getLayers().getHeight()) {
throw new IllegalArgumentException("Specified layer out of bounds.");
} else if (targetLayer <= infeasibleCfg.getStartConfiguration().getDestinationNodeLayer()) {
throw new IllegalArgumentException("Target layer must be greater than the destination node layer.");
}
int emptySpots = 0;
int remainingZeroNodes = 0;
for (ResourceNode otherNode : JavaUtils
.asSet(infeasibleCfg.getNodeTable().values().stream().filter(n -> n.getDelay() == 0))) {
if (otherNode != nodeToPlace && !infeasibleCfg.getFixedNodes().contains(otherNode)) {
remainingZeroNodes++;
}
}
boolean isDelayZero = nodeToPlace.getDelay() == 0;
int leftToPlace = infeasibleCfg.howManyLeftToPlace();
int firstLayer = infeasibleCfg.getStartConfiguration().getDestinationNodeLayer() + 1;
for (int currentLayer = firstLayer; currentLayer < infeasibleCfg.getLayers().getHeight(); currentLayer++) {
int spotsAvailable = infeasibleCfg.getLooseNodesAtLayer(currentLayer).size();
boolean containsZeroNodeAlready = infeasibleCfg.getZeroNodesByLayer().containsKey(currentLayer);
if (targetLayer == currentLayer) {
if (spotsAvailable == 0) {
throw new IllegalArgumentException("There exists no empty spot in the target layer.");
} else {
spotsAvailable--;
leftToPlace--;
if (isDelayZero) {
containsZeroNodeAlready = true;
}
}
}
if (currentLayer >= targetLayer && emptySpots + spotsAvailable >= leftToPlace) {
if (infeasibleCfg.getProblematicNodesAtLayer().isEmpty()) {
return Optional.of(currentLayer);
} else {
int currentLastLayer = Collections.max(infeasibleCfg.getProblematicNodesAtLayer().keySet());
return Optional.of(Math.max(currentLayer, currentLastLayer));
}
}
if (!containsZeroNodeAlready) {
if (spotsAvailable > 0 && remainingZeroNodes > 0) {
spotsAvailable--;
remainingZeroNodes--;
if (isDelayZero) {
leftToPlace--;
}
} else {
return Optional.empty();
}
}
emptySpots += spotsAvailable;
}
return Optional.empty();
}
public static int placeNode(ResourceNode problematicNode, InfeasibleConfiguration infeasibleCfg, SeededRandom rng) {
Set<Integer> possibleLayers = InfeasibilityInspector.getPotentialLayers(problematicNode, infeasibleCfg);
boolean useCurrentLayer = possibleLayers.contains(infeasibleCfg.getLayers().getDepth(problematicNode.getId()));
/*
* Determine the problematic node's target layer, based on our previous
* findings. Also move the node to its target layer if needed.
*/
int targetLayer;
if (!useCurrentLayer) {
targetLayer = JavaUtils.pickRandomElement(possibleLayers, rng);
} else {
targetLayer = infeasibleCfg.getLayers().getDepth(problematicNode.getId());
}
if (!useCurrentLayer) {
moveNodeToLayer(problematicNode, targetLayer, infeasibleCfg, rng);
}
infeasibleCfg.fixateNode(problematicNode);
return targetLayer;
}
public static void placeZeroNodesInBetween(int targetLayer, InfeasibleConfiguration infeasibleCfg,
SeededRandom rng) {
int firstLayer = infeasibleCfg.getStartConfiguration().getDestinationNodeLayer() + 1;
Set<ResourceNode> remainingZeroNodes = infeasibleCfg.getRemainingZeroNodes();
for (int layer = firstLayer; layer < targetLayer; layer++) {
if (infeasibleCfg.getZeroNodesByLayer().containsKey(layer))
continue;
Set<ResourceNode> presentNodesWithZeroDelay = new HashSet<>();
for (int id : infeasibleCfg.getLayers().getLayer(layer)) {
ResourceNode node = infeasibleCfg.getNodeTable().get(id);
if (node.getDelay() == 0) {
presentNodesWithZeroDelay.add(node);
}
}
ResourceNode zeroNode;
if (!presentNodesWithZeroDelay.isEmpty()) {
zeroNode = JavaUtils.removeRandomElement(presentNodesWithZeroDelay, rng);
remainingZeroNodes.remove(zeroNode);
} else {
zeroNode = JavaUtils.removeRandomElement(remainingZeroNodes, rng);
moveNodeToLayer(zeroNode, layer, infeasibleCfg, rng);
}
infeasibleCfg.fixateNode(zeroNode);
}
}
private static void moveNodeToLayer(ResourceNode node, int layer, InfeasibleConfiguration infeasibleCfg,
SeededRandom rng) {
Set<Integer> swappableIDs = new HashSet<>();
for (int id : infeasibleCfg.getLayers().getLayer(layer)) {
if (infeasibleCfg.getFixedNodes().contains(infeasibleCfg.getNodeTable().get(id))) {
swappableIDs.add(id);
}
}
infeasibleCfg.getLayers().swapIDs(node.getId(), JavaUtils.pickRandomElement(swappableIDs, rng));
}
public static Set<Integer> getProblematicTimeslots(StartConfiguration startCfg,
InfeasibleConfiguration infeasibleCfg,
SuffixConfiguration suffixCfg) {
Set<Integer> possibleTimeSlots = new HashSet<>();
ResourceNode destinationNode = startCfg.getDestinationNode();
ResourceNode destinationNode = Objects.requireNonNull(startCfg).getDestinationNode();
if (infeasibleCfg.getProblematicNodes().contains(destinationNode)) {
if (Objects.requireNonNull(infeasibleCfg).getProblematicNodes().contains(destinationNode)) {
possibleTimeSlots.add(0);
return possibleTimeSlots;
}
int firstSlot = destinationNode.getDelay();
int lastSlot = firstSlot + infeasibleCfg.getProblematicResource().delay + suffixCfg.getRemainingInnerDelay();
int lastSlot = firstSlot + Objects.requireNonNull(suffixCfg).getRemainingInnerDelay();
return JavaUtils.asSet(IntStream.rangeClosed(firstSlot, lastSlot).boxed());
}
public static Set<PlannedEdge> getNeededEdges(StartConfiguration startCfg, InfeasibleConfiguration infeasibleCfg,
SuffixConfiguration suffixCfg, SeededRandom rng) {
Set<PlannedEdge> plannedEdges = new HashSet<>();
LayerStructure layers = startCfg.getLayers();
/*
* Preparations for the distribution of the remaining inner delay over all
* suffix edges and the edges extending from the destination nodes. All other
* edges (i.e. the edges between the problematic nodes and the zero nodes) must
* have a delay of zero to not destroy the infeasibility.
*/
Map<ResourceNode, PlannedEdge> nodesToIncomingEdges = new HashMap<>();
for (int i = 0; i < suffixCfg.getSuffixNodes().size() - 1; i++) {
ResourceNode src = suffixCfg.getSuffixNodes().get(i);
ResourceNode dst = suffixCfg.getSuffixNodes().get(i + 1);
PlannedEdge edge = new PlannedEdge(src, dst, 0, 0);
plannedEdges.add(edge);
nodesToIncomingEdges.put(dst, edge);
}
/*
* Add one additional edge representing the edges extending from the destination
* node. If this edge's delay is increased, all similar edges must be assigned
* the exact same delay as well to ensure that all problematic nodes remain in
* the same time step. Only do this if the destination node is not problematic
* itself, as then these edges must have a delay of zero. Furthermore, do this
* for every edge connecting to the first suffix node as well.
*/
PlannedEdge firstEdgeRepresentative = null;
PlannedEdge lastEdgeRepresentative = null;
/*
* Stores the edges extending from the destination node.
*/
Set<PlannedEdge> firstEdges = new HashSet<>();
if (!infeasibleCfg.getProblematicNodes().contains(startCfg.getDestinationNode())) {
ResourceNode src = startCfg.getDestinationNode();
int firstProblematicLayer = startCfg.getDestinationNodeLayer() + 1;
for (ResourceNode dst : infeasibleCfg.getProblematicNodes()) {
if (layers.getDepth(dst.getId()) == firstProblematicLayer) {
PlannedEdge firstEdge = new PlannedEdge(src, dst, 0, 0);
firstEdges.add(firstEdge);
nodesToIncomingEdges.put(dst, firstEdge);
}
}
if (infeasibleCfg.getZeroNodesByLayer().containsKey(firstProblematicLayer)) {
for (ResourceNode dst : infeasibleCfg.getZeroNodesByLayer().get(firstProblematicLayer)) {
PlannedEdge firstEdge = new PlannedEdge(src, dst, 0, 0);
firstEdges.add(firstEdge);
nodesToIncomingEdges.put(dst, firstEdge);
}
}
firstEdgeRepresentative = firstEdges.iterator().next();
plannedEdges.add(firstEdgeRepresentative);
}
Map<Integer, List<ResourceNode>> nodesByDepth = infeasibleCfg.getProblematicNodes().stream()
.collect(Collectors.groupingBy(n -> layers.getDepth(n.getId())));
Set<ResourceNode> lowestProblematicNodes = new HashSet<>(
nodesByDepth.get(Collections.max(nodesByDepth.keySet())));
/*
* Stores the edges going to the first suffix node.
*/
Set<PlannedEdge> lastEdges = new HashSet<>();
if (!suffixCfg.getSuffixNodes().isEmpty()) {
for (ResourceNode src : lowestProblematicNodes) {
PlannedEdge lastEdge = new PlannedEdge(src, suffixCfg.getSuffixNodes().get(0), 0, 0);
lastEdges.add(lastEdge);
nodesToIncomingEdges.put(lastEdge.dst, lastEdge);
}
lastEdgeRepresentative = lastEdges.iterator().next();
plannedEdges.add(lastEdgeRepresentative);
}
PlannedEdge.distributeRemainingDelay(plannedEdges, suffixCfg.getRemainingInnerDelay(), rng);
/*
* Copy the delay of the first edges' representative to the other first edges
* (and deal with the last edges analogously).
*/
for (PlannedEdge firstEdge : firstEdges) {
firstEdge.delay = firstEdgeRepresentative.delay;
}
for (PlannedEdge lastEdge : lastEdges) {
lastEdge.delay = lastEdgeRepresentative.delay;
}
if (!suffixCfg.getSuffixNodes().isEmpty()) {
suffixCfg.getSuffixOffsets().put(suffixCfg.getSuffixNodes().get(0), lastEdgeRepresentative.delay);
}
plannedEdges.addAll(firstEdges);
plannedEdges.addAll(lastEdges);
/*
* Adjust the suffix nodes' offsets (i.e. ASAP-wise from the last problematic
* nodes) based on the freshly distributed delay of the edges among them.
*/
for (int i = 0; i < suffixCfg.getSuffixNodes().size() - 1; i++) {
ResourceNode src = suffixCfg.getSuffixNodes().get(i);
ResourceNode dst = suffixCfg.getSuffixNodes().get(i + 1);
PlannedEdge edge = nodesToIncomingEdges.get(dst);
suffixCfg.getSuffixOffsets().put(dst, suffixCfg.getSuffixOffsets().get(src) + src.getDelay() + edge.delay);
}
return plannedEdges;
}
public static void addIncomingProblematicEdges(Set<PlannedEdge> problematicEdges, StartConfiguration startCfg,
InfeasibleConfiguration infeasibleCfg, SuffixConfiguration suffixCfg,
SeededRandom rng) {
/*
* We need to create an incoming edge for every fixed node. The source node can
* either be the destination node or a zero node of the previous layer.
*
* The edge's delay must be chosen carefully so as not to exceed the RecMinII:
* If the source node is not the destination node, the edge's delay must always
* be zero
*
* TODO: varying delay from destination node to very first zero nodes (this can
* then be distributed over the edges extending from the zero node without
* affecting the infeasibility)
*/
for (ResourceNode fixedNode : infeasibleCfg.getFixedNodes()) {
/*
* The destination node doesn't need an incoming edge (at least during property
* creation).
*/
if (fixedNode == startCfg.getDestinationNode()) {
continue;
}
/*
* Skip the node if there exists a planned outgoing edge for it already.
*/
if (findAnyIncomingForwardEdge(problematicEdges, fixedNode).isPresent()) {
continue;
}
int dstLayer = startCfg.getLayers().getDepth(fixedNode.getId());
ResourceNode src;
if (dstLayer == startCfg.getDestinationNodeLayer() + 1) {
src = startCfg.getDestinationNode();
} else {
src = JavaUtils.pickRandomElement(infeasibleCfg.getZeroNodesByLayer().get(dstLayer - 1), rng);
}
problematicEdges.add(new PlannedEdge(src, fixedNode, 0, 0));
}
}
public static Set<PlannedEdge> getOutgoingProblematicEdges(StartConfiguration startCfg,
InfeasibleConfiguration infeasibleCfg,
SuffixConfiguration suffixCfg, SeededRandom rng,
BiFunction<ResourceNode, ResourceNode, Integer> delayComputer) {
Set<PlannedEdge> problematicEdges = new HashSet<>();
/*
* Determine the ASAP slot of the problematic nodes by pathing backwards to the
* destination node. It is sufficient to compute any problematic node's ASAP
* slot (relative to the destination node), as all problematic nodes are
* guaranteed to share the same slot (otherwise they wouldn't be problematic).
*/
int problematicASAPSlot = startCfg.getDestinationNode().getDelay();
Optional<PlannedEdge> incomingEdge = findAnyIncomingForwardEdge(problematicEdges,
infeasibleCfg.getProblematicNodes().iterator()
.next()
);
while (incomingEdge.isPresent() && incomingEdge.get().src != startCfg.getDestinationNode()) {
problematicASAPSlot += incomingEdge.get().delay + incomingEdge.get().src.getDelay();
incomingEdge = findAnyIncomingForwardEdge(problematicEdges, incomingEdge.get().src);
}
int earliestSuffixSlot = problematicASAPSlot + startCfg.getProblematicResource().delay;
/*
* Create an outgoing edge for every fixed node. This can either be an edge
* connecting to other fixed nodes/suffix nodes, or it can be a backedge
* connecting to the destination node, thereby reducing the fixed node's slack
* to zero.
*/
for (ResourceNode fixedNode : infeasibleCfg.getFixedNodes()) {
/*
* No need to connect problematic nodes which have been connected to other nodes
* already (this can happen if their delay is 0 for example) or which will be
* used as backedge sources.
*/
if (findAnyOutgoingForwardEdge(problematicEdges, fixedNode).isPresent() || suffixCfg.getBackedgeSources()
.contains(fixedNode)) {
continue;
}
Map<ResourceNode, Integer> possibleDstsWithDelay = new HashMap<>();
/*
* A problematic node may only connect to other problematic nodes if both the
* edge's delay and the problematic resource's delay are zero. Otherwise, the
* destination node's ASAP time would increase and it would not share the
* problematic slot with the other nodes anymore.
*/
if (startCfg.getProblematicResource().delay == 0) {
int sourceNodeDepth = startCfg.getLayers().getDepth(fixedNode.getId());
for (ResourceNode dst : infeasibleCfg.getFixedNodes()) {
if (dst == fixedNode || startCfg.getLayers().getDepth(dst.getId()) <= sourceNodeDepth) {
continue;
}
if (delayComputer.apply(fixedNode, dst) == 0) {
possibleDstsWithDelay.put(dst, 0);
}
}
}
/*
* A problematic node may only connect to suffix nodes if the edge delay of the
* resulting edge would reduce the problematic node's slack to zero.
*/
for (ResourceNode suffixNode : suffixCfg.getSuffixNodes()) {
int suffixNodeSlot = earliestSuffixSlot + suffixCfg.getSuffixOffsets().get(suffixNode);
int edgeDelay = delayComputer.apply(fixedNode, suffixNode);
int neededEdgeDelay = suffixNodeSlot - fixedNode.getDelay() - problematicASAPSlot;
if (edgeDelay == neededEdgeDelay) {
possibleDstsWithDelay.put(suffixNode, edgeDelay);
}
}
/*
* If no possible destination node has been found yet, connect the problematic
* node either to the first suffix node (if present) to keep the edge delay
* minimal. If no suffix has been created, the node must become a backedge
* source to still be contained in the cycle.
*/
if (possibleDstsWithDelay.isEmpty()) {
if (!suffixCfg.getSuffixNodes().isEmpty()) {
ResourceNode firstSuffixNode = suffixCfg.getSuffixNodes().get(0);
int suffixNodeSlot = earliestSuffixSlot + suffixCfg.getSuffixOffsets().get(firstSuffixNode);
int edgeDelay = suffixNodeSlot - fixedNode.getDelay() - problematicASAPSlot;
problematicEdges.add(new PlannedEdge(fixedNode, firstSuffixNode, edgeDelay, 0));
} else {
suffixCfg.getBackedgeSources().add(fixedNode);
}
}
/*
* Otherwise, if possible destination nodes have been determined, pick one of
* them randomly and create the resulting edge.
*/
else {
ResourceNode dst = JavaUtils.pickRandomElement(possibleDstsWithDelay.keySet(), rng);
problematicEdges.add(new PlannedEdge(fixedNode, dst, possibleDstsWithDelay.get(dst), 0));
}
}
return problematicEdges;
}
private static Optional<PlannedEdge> findAnyIncomingForwardEdge(Set<PlannedEdge> edges, ResourceNode dst) {
return edges.stream().filter(e -> e.dst == dst && e.distance == 0).findAny();
}
private static Optional<PlannedEdge> findAnyOutgoingForwardEdge(Set<PlannedEdge> edges, ResourceNode src) {
return edges.stream().filter(e -> e.src == src && e.distance == 0).findAny();
}
}