Commit 78672061 authored by Sebastian Vollbrecht's avatar Sebastian Vollbrecht

Adapted FeasibleMinIIProperty to the recent changes.

Also removed SCC-related classes as they have become obsolete.
parent 9354d739
......@@ -17,13 +17,10 @@
package graphgen.generator.components.properties.minII.feasible;
import graphgen.datastructures.Pair;
import graphgen.enums.ModuloSchedulingFormulation;
import graphgen.generator.GraphGenerator;
import graphgen.generator.components.properties.Property;
import graphgen.generator.components.properties.minII.MinIICycleInspector;
import graphgen.generator.components.properties.util.PlannedEdge;
import graphgen.generator.components.properties.util.SCC;
import graphgen.generator.components.properties.minII.util.PlannedEdge;
import graphgen.generator.exceptions.MinIIImpossibleException;
import graphgen.graph.ResourceNode;
import graphgen.util.GraphUtils;
......@@ -31,20 +28,13 @@ import graphgen.util.JavaUtils;
import graphgen.util.SchedulingUtils;
import modsched.Edge;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* This property ensures that the specified MinII of the generated graphs is going to be feasible.
* This property ensures that the specified MinII of generated graphs is going to be feasible.
*
* @author Sebastian Vollbrecht
*/
......@@ -62,18 +52,8 @@ public class FeasibleMinIIProperty extends Property {
*/
private final int backedgeDistance;
/**
* The maximum inner delay of the RecMinII-ensuring SCC (i.e. the maximum delay of a path through it consisting of
* forward edges only). This must not be exceeded, as otherwise the RecMinII of the SCC would exceed the specified
* MinII of the property.
*/
private final int maxInnerDelay;
private final Set<ResourceNode> enclosedNodes;
private final ModuloSchedulingFormulation formulation;
private SCC recMinIISCC;
/**
* Creates a new feasible MinII property which ensures the specified MinII during graph creation. The modulo
* scheduling formulation will be used to determine edge and backedge validity. Uses 1 for the delay and distance
......@@ -99,21 +79,20 @@ public class FeasibleMinIIProperty extends Property {
public FeasibleMinIIProperty(int minII, int backedgeDelay, int backedgeDistance,
ModuloSchedulingFormulation formulation) {
if (backedgeDelay < 0)
if (minII <= 0) {
throw new IllegalArgumentException("The MinII must be greater than zero.");
}
if (backedgeDelay < 0) {
throw new IllegalArgumentException("The backedge delay must be greater than or equal to zero.");
if (backedgeDistance <= 0)
}
if (backedgeDistance <= 0) {
throw new IllegalArgumentException("The backedge delay must be greater than zero.");
}
this.minII = minII;
this.backedgeDelay = backedgeDelay;
this.backedgeDistance = backedgeDistance;
// TODO: allow inner delays >= minInnerDelay and <= maxInnerDelay
this.maxInnerDelay = MinIICycleInspector.maxInnerDelay(minII, backedgeDelay, backedgeDistance);
this.enclosedNodes = new HashSet<>();
this.formulation = formulation;
this.recMinIISCC = null;
}
......@@ -121,80 +100,36 @@ public class FeasibleMinIIProperty extends Property {
public boolean isEdgeValid(ResourceNode src, ResourceNode dst, int delay) {
/*
* An edge is always valid if any of the following 3 cases apply.
*
* 1) There is no RecMinII-ensuring SCC, i.e. the MinII is ensured through means
* of the nodes' ResMinII.
* An edge is invalid if adding it to the graph would result in a RecMinII
* exceeding the specified MinII.
*/
if (recMinIISCC == null)
return true;
Edge<ResourceNode> tmpEdge = new Edge<>(src, dst, delay, 0);
/*
* 2) If the edge-to-be's source node is not reachable from the destination node
* of the RecMinII-ensuring SCC's backedge, it is not enclosed in the SCC. The
* SCC can thus simply be 'pushed away' (ASAP-wise) from the edge-to-be's source
* node during scheduling, without affecting its RecMinII.
*/
if (!recMinIISCC.getDelaysFromPreviousDstNode().containsKey(src)) {
return true;
}
Set<Edge<ResourceNode>> tmpEdges = new HashSet<>(edgeCreator.edgeView());
tmpEdges.add(tmpEdge);
/*
* 3) If the source node of the RecMinII-ensuring SCC's backedge cannot be
* reached from the edge-to-be's destination node, the SCC's RecMinII cannot be
* increased with the new edge in any way. The edge-to-be's destination node can
* thus be freely scheduled.
* We only need to consider the sub graph consisting of the union of all cycles
* which would be present in the graph. All other nodes and edges do not affect
* the RecMinII or the feasibility of the graph.
*/
if (!recMinIISCC.getDelaysToNextSrcNode().containsKey(dst)) {
return true;
}
Set<ResourceNode> enclosedNodes = findAllEnclosedNodes(tmpEdges);
/*
* If these cases do not apply, both the edge-to-be's source node and
* destination node would be enclosed in the RecMinII-ensuring SCC. Therefore,
* we must check whether the edge-to-be would violate the RecMinII of the SCC.
* This is done by summing up:
*
* 1) The delay from the backedge's destination node to the edge-to-be's source
* node.
*
* 2) The delay from the edge-to-be's source node to the edge-to-be's
* destination node.
*
* 3) The delay from the edge-to-be's destination node to the backedge's source
* node.
*/
Edge<ResourceNode> recMinIIBackedge = recMinIISCC.getBackedges().get(0);
int delayFromBackedgeDst = recMinIISCC.getDelaysFromPreviousDstNode().get(src);
int delayInBetween = src.getDelay() + delay;
int delayToBackedgeSrc = recMinIISCC.getDelaysToNextSrcNode().get(dst);
tmpEdges.retainAll(GraphUtils.asReducedEdgeSet(tmpEdges, enclosedNodes));
int resultingPathDelay = delayFromBackedgeDst + delayInBetween + delayToBackedgeSrc;
if (enclosedNodes.isEmpty() || tmpEdges.isEmpty()) {
return true;
}
if (resultingPathDelay + recMinIIBackedge.getSrc().getDelay() > maxInnerDelay) {
if (SchedulingUtils.getRecMinII(enclosedNodes, tmpEdges) != minII) {
return false;
}
/*
* If the RecMinII would not be violated, we need to check whether the SCC would
* still be feasible with the new edge. For this, we need to recompute which
* nodes would be enclosed by the updated SCC. Afterwards, we simply have to
* schedule the enclosed nodes with all edges among them to check whether the
* graph would still be feasible.
* If the RecMinII would not exceed the MinII, we need to check whether the graph
* would still remain feasible. Here, we have no choice but to actually schedule
* the sub graph induced by the currently existing edges (including the new one).
*/
Edge<ResourceNode> tmpEdge = new Edge<>(src, dst, delay, 0);
Map<ResourceNode, Set<Edge<ResourceNode>>> tmpIncomingEdges = new HashMap<>(edgeCreator.incomingEdgesView());
tmpIncomingEdges.put(dst, new HashSet<>(tmpIncomingEdges.get(dst)));
tmpIncomingEdges.get(dst).add(tmpEdge);
Set<Edge<ResourceNode>> tmpEdges = new HashSet<>(edgeCreator.edgeView());
tmpEdges.add(tmpEdge);
Set<ResourceNode> enclosedNodes = findAllEnclosedNodes(tmpEdges, tmpIncomingEdges);
tmpEdges.retainAll(GraphUtils.asReducedEdgeSet(tmpEdges, enclosedNodes));
return SchedulingUtils.isFeasible(enclosedNodes, tmpEdges, formulation, minII, minII);
}
......@@ -202,58 +137,56 @@ public class FeasibleMinIIProperty extends Property {
@Override
public boolean isBackedgeValid(ResourceNode src, ResourceNode dst, int delay, int distance) {
/*
* Backedge validity checks are similar to edge validity checks. The only difference to
* be aware of is that there now could be several cycles present.
*/
Set<Edge<ResourceNode>> tmpEdges = new HashSet<>(edgeCreator.edgeView());
tmpEdges.add(new Edge<>(src, dst, delay, distance));
if (SchedulingUtils.getRecMinII(nodes, tmpEdges) > minII)
return false;
Set<ResourceNode> tmpEnclosedNodes = findAllEnclosedNodes(tmpEdges);
if (tmpEnclosedNodes.isEmpty()) {
return true;
}
/*
* If the enclosed nodes encompass all graph nodes already, we can skip the
* computation of enclosed nodes entirely and immediately begin scheduling.
* The source node must be included in the enclosed nodes, otherwise two
* distinct cycles might need to be scheduled at time slot zero, thus resulting
* in a faulty infeasible schedule (e.g. if both cycles' start nodes use the
* same resource with limit 1), although they wouldn't necessarily even need to
* be scheduled in the same slot.
*/
if (!enclosedNodes.containsAll(nodes)) {
Set<ResourceNode> tmpEnclosedNodes = findAllEnclosedNodes(tmpEdges, edgeCreator.incomingEdgesView());
if (tmpEnclosedNodes.isEmpty())
return true;
tmpEnclosedNodes.add(GraphGenerator.SOURCE);
/*
* The source node must be included in the enclosed nodes, otherwise two
* distinct cycles might need to be scheduled at time slot zero, thus resulting
* in a faulty infeasible schedule (e.g. if both cycles' start nodes use the
* same resource with limit 1), although they wouldn't necessarily even need to
* be scheduled in the same slot.
*/
tmpEnclosedNodes.add(GraphGenerator.SOURCE);
tmpEdges.retainAll(GraphUtils.asReducedEdgeSet(tmpEdges, tmpEnclosedNodes));
Map<ResourceNode, Set<Edge<ResourceNode>>> incomingEdges = edgeCreator.incomingEdgesView();
tmpEdges.retainAll(GraphUtils.asReducedEdgeSet(tmpEdges, tmpEnclosedNodes));
if (SchedulingUtils.getRecMinII(nodes, tmpEdges) > minII) {
return false;
}
/*
* Add a temporary edge connecting from the graph's source node to all enclosed
* nodes if no other enclosing node connects to it already (this could happen
* for the cycle's start nodes, for example). This must be done as otherwise the
* reduced graph would not represent the entire graph anymore due to there being
* more than one source node. Source nodes must be scheduled in timestep 0,
* possibly resulting in resource conflicts which would not necessarily arise in
* the unmodified graph.
*/
for (ResourceNode enclosedNode : tmpEnclosedNodes) {
/*
* Add a temporary edge connecting from the graph's source node to all enclosed
* nodes if no other enclosing node connects to it already (this could happen
* for the cycles' start nodes, for example). This must be done due to there
* being more than one source node, which must be scheduled in timeslot 0,
* possibly resulting in resource conflicts which would not necessarily arise in
* the unmodified graph.
*/
Map<ResourceNode, Set<Edge<ResourceNode>>> incomingEdges = edgeCreator.incomingEdgesView();
if (GraphGenerator.SOURCE == enclosedNode)
continue;
for (ResourceNode enclosedNode : tmpEnclosedNodes) {
if (incomingEdges.get(enclosedNode).stream().map(Edge::getSrc).noneMatch(tmpEnclosedNodes::contains)) {
tmpEdges.add(new Edge<>(GraphGenerator.SOURCE, enclosedNode, 0, 0));
}
if (GraphGenerator.SOURCE == enclosedNode) {
continue;
}
return SchedulingUtils.isFeasible(tmpEnclosedNodes, tmpEdges, formulation, minII, minII);
if (incomingEdges.get(enclosedNode).stream().map(Edge::getSrc).noneMatch(tmpEnclosedNodes::contains)) {
tmpEdges.add(new Edge<>(GraphGenerator.SOURCE, enclosedNode, 0, 0));
}
}
return SchedulingUtils.isFeasible(enclosedNodes, tmpEdges, formulation, minII, minII);
return SchedulingUtils.isFeasible(tmpEnclosedNodes, tmpEdges, formulation, minII, minII);
}
......@@ -262,598 +195,171 @@ public class FeasibleMinIIProperty extends Property {
int resMinII = SchedulingUtils.getResMinII(nodes);
if (resMinII > minII)
if (resMinII > minII) {
throw new MinIIImpossibleException(resMinII, minII);
else if (resMinII == minII)
} else if (resMinII == minII) {
return;
/*
* Determine all possible initial nodes. Two distinct nodes are considered
* initial if the sum of their delays is less than or equal to the needed path
* delay (= RecMinII check) and if a path consisting of both nodes only with an
* edge delay of 0 would not render the path infeasible (= infeasibility check).
*/
Map<Integer, Set<ResourceNode>> nodesByDelay = new HashMap<>();
for (ResourceNode node : nodes) {
nodesByDelay.putIfAbsent(node.getDelay(), new HashSet<>());
nodesByDelay.get(node.getDelay()).add(node);
}
Set<Pair<ResourceNode, ResourceNode>> initialPairs = getPossibleInitialPairs(nodesByDelay);
if (initialPairs.isEmpty()) {
throw new MinIIImpossibleException();
}
Edge<ResourceNode> theBackedge = createMaxInnerDelayPath(initialPairs, nodesByDelay);
recMinIISCC = new SCC(Arrays.asList(theBackedge), layers, edgeCreator);
enclosedNodes.addAll(recMinIISCC.update(minII));
PathConfiguration pathCfg = choosePathConfiguration();
createNeededEdges(pathCfg);
}
@Override
public void reset() {
recMinIISCC = null;
enclosedNodes.clear();
// Do nothing.
}
@Override
public void notify(Edge<ResourceNode> edge) {
if (!edge.isBackedge() && recMinIISCC != null) {
enclosedNodes.addAll(recMinIISCC.update(minII));
} else if (edge.isBackedge()) {
enclosedNodes.addAll(findAllEnclosedNodes(edgeCreator.edgeView(), edgeCreator.incomingEdgesView()));
}
// Do nothing.
}
private final class PlannedPath {
private final Deque<PlannedEdge> path;
private Integer lowestLayer;
private Integer highestLayer;
private boolean isStartNodeImmutable = false;
private boolean isEndNodeImmutable = false;
private PlannedPath() {
this.path = new ArrayDeque<>();
}
private void prependEdge(PlannedEdge edge) {
if (highestLayer == null)
highestLayer = layers.getDepth(edge.dst);
if (!isEndNodeImmutable && highestLayer == layers.getHeight() - 1)
isEndNodeImmutable = true;
if (lowestLayer == null)
lowestLayer = layers.getDepth(edge.src);
else
lowestLayer--;
if (!isStartNodeImmutable && lowestLayer == 0)
isStartNodeImmutable = true;
path.addFirst(edge);
}
private void appendEdge(PlannedEdge edge) {
if (lowestLayer == null)
lowestLayer = layers.getDepth(edge.src);
if (!isStartNodeImmutable && lowestLayer == 0)
isStartNodeImmutable = true;
if (highestLayer == null)
highestLayer = layers.getDepth(edge.dst);
else
highestLayer++;
if (!isEndNodeImmutable && highestLayer == layers.getHeight() - 1)
isEndNodeImmutable = true;
path.addLast(edge);
}
private PathConfiguration choosePathConfiguration() {
private ResourceNode getStartNode() {
return path.getFirst().src;
}
private ResourceNode getEndNode() {
return path.getLast().dst;
}
@Override
public String toString() {
return path.toString();
}
}
private Set<Pair<ResourceNode, ResourceNode>> getPossibleInitialPairs(
Map<Integer, Set<ResourceNode>> nodesByDelay) {
Set<Pair<ResourceNode, ResourceNode>> possiblePairs = new HashSet<>();
for (Entry<Integer, Set<ResourceNode>> firstDelayToNodesEntry : nodesByDelay.entrySet()) {
int firstNodeDelay = firstDelayToNodesEntry.getKey();
Set<ResourceNode> nodesWithFirstDelay = firstDelayToNodesEntry.getValue();
for (ResourceNode firstNode : nodesWithFirstDelay) {
for (Entry<Integer, Set<ResourceNode>> secondDelayToNodesEntry : nodesByDelay.entrySet()) {
int secondNodeDelay = secondDelayToNodesEntry.getKey();
Set<ResourceNode> nodesWithSecondDelay = secondDelayToNodesEntry.getValue();
/*
* RecMinII check.
*/
if (firstNodeDelay + secondNodeDelay > maxInnerDelay)
continue;
for (ResourceNode secondNode : nodesWithSecondDelay) {
if (firstNode == secondNode)
continue;
/*
* Infeasibility check.
*/
if (secondNode.resource == firstNode.resource) {
if (firstNode.resource.limit == 1) {
if (maxInnerDelay - (2 * firstNode.resource.delay) == 0) {
continue;
}
}
}
possiblePairs.add(Pair.of(firstNode, secondNode));
}
}
}
}
return possiblePairs;
}
/**
* First, two initial nodes must be determined between which an edge can be put without violating the RecMinII and
* the feasibility. Then, the nodes must be placed in neighboring layers in order to ensure that a minimal RecMinII
* path can be created by all means. At the end, the path can be extended by appending nodes at the front or at the
* end, depending on whether the remaining path delay allows for it or not.
*/
private Edge<ResourceNode> createMaxInnerDelayPath(Set<Pair<ResourceNode, ResourceNode>> initialPairs,
Map<Integer, Set<ResourceNode>> nodesByDelay) {
PlannedPath path = new PlannedPath();
Pair<ResourceNode, ResourceNode> initialPair = JavaUtils.pickRandomElement(initialPairs, rng);
ResourceNode firstSrc = initialPair.first;
ResourceNode firstDst = initialPair.second;
FeasibleCycleInspector inspector = new FeasibleCycleInspector(layers, nodes);
/*
* These two nodes are not available anymore, so we remove them from the delays
* map. Later on, when appending additional nodes to the path, the map is
* required to contain nothing but available nodes.
* Determine initial nodes for every possible inner delay.
*/
nodesByDelay.get(firstSrc.getDelay()).remove(firstSrc);
nodesByDelay.get(firstDst.getDelay()).remove(firstDst);
int minInnerDelay = inspector.getMinInnerDelay(minII, backedgeDelay, backedgeDistance);
int maxInnerDelay = inspector.getMaxInnerDelay(minII, backedgeDelay, backedgeDistance);
/*
* Move the nodes to neighboring layers if needed. When moving, the current
* locations are kept as intact as possible.
*/
boolean isSourceInLowerLayer = moveNodesToNeighbouringLayersIfNeeded(firstSrc, firstDst);
Map<Integer, Set<ResourceNode>> initialNodesByInnerDelay = new HashMap<>();
if (!isSourceInLowerLayer) {
ResourceNode tmp = firstSrc;
firstSrc = firstDst;
firstDst = tmp;
for (int innerDelay = minInnerDelay; innerDelay <= maxInnerDelay; innerDelay++) {
Set<ResourceNode> possibleInitialNodes = inspector.getPossibleInitialNodes(maxInnerDelay);
if (!possibleInitialNodes.isEmpty()) {
initialNodesByInnerDelay.put(innerDelay, possibleInitialNodes);
}
}
/*
* Extend the path if possible by appending edges at the front or at the end of
* the current path, as long as there is enough path delay remaining to do so
* and if the path boundaries allow for it.
*/
int remainingPathDelay = maxInnerDelay - firstSrc.getDelay() - firstDst.getDelay();
int edgeDelay = edgeCreator.computeEdgeDelay(firstSrc, firstDst);
if (initialNodesByInnerDelay.isEmpty()) {
throw new MinIIImpossibleException();
}
/*
* Feasibility check.
* Begin creating the cycle by determining an initial node, to which additional nodes
* can then be prepended/appended to create the cycle's RecMinII-ensuring path.
*/
if (firstSrc.resource == firstDst.resource && firstSrc.resource.limit == 1 && edgeDelay == 0)
edgeDelay = 1;
/*
* RecMinII check.
*/
else if (edgeDelay > remainingPathDelay)
edgeDelay = remainingPathDelay;
remainingPathDelay -= edgeDelay;
int innerDelay = JavaUtils.pickRandomElement(initialNodesByInnerDelay.keySet(), rng);
ResourceNode initialNode = JavaUtils.pickRandomElement(initialNodesByInnerDelay.get(innerDelay), rng);
path.appendEdge(new PlannedEdge(firstSrc, firstDst, edgeDelay));
PathConfiguration pathCfg = new PathConfiguration(layers, nodeTable, innerDelay, initialNode);
/*
* Pick the probability to stop appending more nodes with zero delay to the path
* if the remaining delay is zero already.
*/
double pToAppendMoreZeroNodes = rng.nextDouble();
while (remainingPathDelay >= 0 && (!path.isEndNodeImmutable || !path.isStartNodeImmutable)) {
if (remainingPathDelay == 0 && rng.nextDouble() > pToAppendMoreZeroNodes)
break;
remainingPathDelay = appendNodeToPath(remainingPathDelay, nodesByDelay, path);
}
PlannedEdge.distributeDelay(path.path, remainingPathDelay, rng);
path.path.forEach(e -> edgeCreator.createEdge(e.src, e.dst, e.delay));
return edgeCreator.createBackedge(path.getEndNode(), path.getStartNode(), backedgeDelay, backedgeDistance);
}
private boolean moveNodesToNeighbouringLayersIfNeeded(ResourceNode n1, ResourceNode n2) {
int firstNodeDepth = layers.getDepth(n1);
int secondNodeDepth = layers.getDepth(n2);
/*
* Only move the nodes if they are not currently located in neighbouring layers
* already.
*/
if (Math.abs(firstNodeDepth - secondNodeDepth) != 1) {
ResourceNode nodeToSwap;
int layerToSwapWith;
int otherNodeDepth;
if (rng.nextDouble() < 0.5) {
nodeToSwap = n1;
otherNodeDepth = secondNodeDepth;
} else {
nodeToSwap = n2;
otherNodeDepth = firstNodeDepth;
}
if (otherNodeDepth == 0)
layerToSwapWith = 1;
else if (otherNodeDepth == layers.getHeight() - 1)
layerToSwapWith = otherNodeDepth - 1;
else
layerToSwapWith = otherNodeDepth + (rng.nextDouble() < 0.5 ? 1 : -1);
int swapID = JavaUtils.pickRandomElement(layers.getLayer(layerToSwapWith), rng);
layers.swapIDs(nodeToSwap.getId(), swapID);
}
return layers.getDepth(n1) < layers.getDepth(n2);
}
private int appendNodeToPath(int remainingPathDelay, Map<Integer, Set<ResourceNode>> nodesByDelay,
PlannedPath path) {
double pToAddMoreZeroNodes = rng.nextDouble();
/*
* Determine all possible nodes which can still be appended to the path by
* iterating through the map of remaining node delays. A node is considered a
* candidate if its delay is less than or equal to the remaining path delay and
* if appending it keeps the path feasible.
*
* Use the path boundaries to determine whether any of these nodes could be used
* immediately without swapping nodes around. A node can be used immediately if
* it is located either in the layer below the currently lowest layer
* (predecessor of the path's start node), or if it is located in the layer
* above the currently highest layer (successor of the path's end node).
*/
Set<ResourceNode> candidates = new HashSet<>();
Set<ResourceNode> succs = new HashSet<>();
Set<ResourceNode> preds = new HashSet<>();
int remainingPathDelay = pathCfg.getRemainingInnerDelay();
/*
* Keeps track of nodes to which an edge with delay > 0 must be laid in order to
* keep the path feasible.
* Append as many nodes as possible to the path to keep the edge delay minimal.
*/
Set<ResourceNode> criticalNodes = new HashSet<>();
for (Entry<Integer, Set<ResourceNode>> entry : nodesByDelay.entrySet()) {
if (entry.getKey() > remainingPathDelay)
continue;
for (ResourceNode candidate : entry.getValue()) {
boolean canBeNewSucc = false;
boolean canBeNewPred = false;
/*
* An immutable end node means that we are not allowed to append any nodes to
* the path's end node.
*/
if (!path.isEndNodeImmutable) {
if (candidate.resource.isUnlimited() || candidate.getDelay() > 0) {
canBeNewSucc = true;
} else if (wouldPathBeInfeasible(path.path, candidate, true) && remainingPathDelay > 0) {
criticalNodes.add(candidate);
canBeNewSucc = true;
}
}
/*
* An immutable start node means that we are not allowed to prepend any nodes to
* the path's start node.
*/
if (!path.isStartNodeImmutable) {
if (candidate.resource.isUnlimited() || candidate.getDelay() > 0) {
canBeNewPred = true;
} else if (wouldPathBeInfeasible(path.path, candidate, false) && remainingPathDelay > 0) {
criticalNodes.add(candidate);
canBeNewPred = true;
}
}
if (!canBeNewSucc && !canBeNewPred)
continue;
candidates.add(candidate);
int candidateDepth = layers.getDepth(candidate);
canBeNewSucc &= candidateDepth == path.highestLayer + 1;
canBeNewPred &= candidateDepth == path.lowestLayer - 1;
FeasibleNodePlacer placer = new FeasibleNodePlacer(pathCfg, rng);
int oldPathLength;