docs: @Override
  	public void postVisit(PlanNode node) {
  		try {
  			// --------- check special cases for which we handle post visit differently ----------
  			
  			// skip data source node (they have no inputs)
  			// also, do nothing for union nodes, we connect them later when gathering the inputs for a task
  			// solution sets have no input. the initial solution set input is connected when the iteration node is in its postVisit
  			if (node instanceof SourcePlanNode || node instanceof NAryUnionPlanNode || node instanceof SolutionSetPlanNode) {
  				return;
  			}
  			
  			// check if we have an iteration. in that case, translate the step function now
  			if (node instanceof IterationPlanNode) {
  				// prevent nested iterations
  				if (node.isOnDynamicPath()) {
  					throw new CompilerException("Nested Iterations are not possible at the moment!");
  				}
  				
  				// if we recursively go into an iteration (because the constant path of one iteration contains
  				// another one), we push the current one onto the stack
  				if (this.currentIteration != null) {
  					this.iterationStack.add(this.currentIteration);
  				}
  				
  				this.currentIteration = (IterationPlanNode) node;
  				this.currentIteration.acceptForStepFunction(this);
  				
  				// pop the current iteration from the stack
  				if (this.iterationStack.isEmpty()) {
  					this.currentIteration = null;
  				} else {
  					this.currentIteration = this.iterationStack.remove(this.iterationStack.size() - 1);
  				}
  				
  				// inputs for initial bulk partial solution or initial workset are already connected to the iteration head in the head's post visit.
  				// connect the initial solution set now.
  				if (node instanceof WorksetIterationPlanNode) {
  					// connect the initial solution set
  					WorksetIterationPlanNode wsNode = (WorksetIterationPlanNode) node;
  					JobVertex headVertex = this.iterations.get(wsNode).getHeadTask();
  					TaskConfig headConfig = new TaskConfig(headVertex.getConfiguration());
  					int inputIndex = headConfig.getDriverStrategy().getNumInputs();
  					headConfig.setIterationHeadSolutionSetInputIndex(inputIndex);
  					translateChannel(wsNode.getInitialSolutionSetInput(), inputIndex, headVertex, headConfig, false);
  				}
  				
  				return;
  			}
  			
  			final JobVertex targetVertex = this.vertices.get(node);
  			
  			
  			// --------- Main Path: Translation of channels ----------
  			// 
  			// There are two paths of translation: One for chained tasks (or merged tasks in general),
  			// which do not have their own task vertex. The other for tasks that have their own vertex,
  			// or are the primary task in a vertex (to which the others are chained).
  			
  			// check whether this node has its own task, or is merged with another one
  			if (targetVertex == null) {
  				// node's task is merged with another task. it is either chained, of a merged head vertex
  				// from an iteration
  				final TaskInChain chainedTask;
  				if ((chainedTask = this.chainedTasks.get(node)) != null) {
  					// Chained Task. Sanity check first...
  					final Iterator<Channel> inConns = node.getInputs().iterator();
  					if (!inConns.hasNext()) {
  						throw new CompilerException("Bug: Found chained task with no input.");
  					}
  					final Channel inConn = inConns.next();
  					
  					if (inConns.hasNext()) {
  						throw new CompilerException("Bug: Found a chained task with more than one input!");
  					}
  					if (inConn.getLocalStrategy() != null && inConn.getLocalStrategy() != LocalStrategy.NONE) {
  						throw new CompilerException("Bug: Found a chained task with an input local strategy.");
  					}
  					if (inConn.getShipStrategy() != null && inConn.getShipStrategy() != ShipStrategyType.FORWARD) {
  						throw new CompilerException("Bug: Found a chained task with an input ship strategy other than FORWARD.");
  					}
  	
  					JobVertex container = chainedTask.getContainingVertex();
  					
  					if (container == null) {
  						final PlanNode sourceNode = inConn.getSource();
  						container = this.vertices.get(sourceNode);
  						if (container == null) {
  							// predecessor is itself chained
  							container = this.chainedTasks.get(sourceNode).getContainingVertex();
  							if (container == null) {
  								throw new IllegalStateException("Bug: Chained task predecessor has not been assigned its containing vertex.");
  							}
  						} else {
  							// predecessor is a proper task job vertex and this is the first chained task. add a forward connection entry.
  							new TaskConfig(container.getConfiguration()).addOutputShipStrategy(ShipStrategyType.FORWARD);
  						}
  						chainedTask.setContainingVertex(container);
  					}
  					
  					// add info about the input serializer type
  					chainedTask.getTaskConfig().setInputSerializer(inConn.getSerializer(), 0);
  					
  					// update name of container task
  					String containerTaskName = container.getName();
  					if (containerTaskName.startsWith("CHAIN ")) {
  						container.setName(containerTaskName + " -> " + chainedTask.getTaskName());
  					} else {
  						container.setName("CHAIN " + containerTaskName + " -> " + chainedTask.getTaskName());
  					}
  
  					//update resource of container task
  					container.setResources(container.getMinResources().merge(node.getMinResources()),
  							container.getPreferredResources().merge(node.getPreferredResources()));
  					
  					this.chainedTasksInSequence.add(chainedTask);
  					return;
  				}
  				else if (node instanceof BulkPartialSolutionPlanNode ||
  						node instanceof WorksetPlanNode)
  				{
  					// merged iteration head task. the task that the head is merged with will take care of it
  					return;
  				} else {
  					throw new CompilerException("Bug: Unrecognized merged task vertex.");
  				}
  			}
  			
  			// -------- Here, we translate non-chained tasks -------------
  			
  			
  			if (this.currentIteration != null) {
  				JobVertex head = this.iterations.get(this.currentIteration).getHeadTask();
  				// Exclude static code paths from the co-location constraint, because otherwise
  				// their execution determines the deployment slots of the co-location group
  				if (node.isOnDynamicPath()) {
  					targetVertex.setStrictlyCoLocatedWith(head);
  				}
  			}
  			
  			
  			// create the config that will contain all the description of the inputs
  			final TaskConfig targetVertexConfig = new TaskConfig(targetVertex.getConfiguration());
  						
  			// get the inputs. if this node is the head of an iteration, we obtain the inputs from the
  			// enclosing iteration node, because the inputs are the initial inputs to the iteration.
  			final Iterator<Channel> inConns;
  			if (node instanceof BulkPartialSolutionPlanNode) {
  				inConns = ((BulkPartialSolutionPlanNode) node).getContainingIterationNode().getInputs().iterator();
  				// because the partial solution has its own vertex, is has only one (logical) input.
  				// note this in the task configuration
  				targetVertexConfig.setIterationHeadPartialSolutionOrWorksetInputIndex(0);
  			} else if (node instanceof WorksetPlanNode) {
  				WorksetPlanNode wspn = (WorksetPlanNode) node;
  				// input that is the initial workset
  				inConns = Collections.singleton(wspn.getContainingIterationNode().getInput2()).iterator();
  				
  				// because we have a stand-alone (non-merged) workset iteration head, the initial workset will
  				// be input 0 and the solution set will be input 1
  				targetVertexConfig.setIterationHeadPartialSolutionOrWorksetInputIndex(0);
  				targetVertexConfig.setIterationHeadSolutionSetInputIndex(1);
  			} else {
  				inConns = node.getInputs().iterator();
  			}
  			if (!inConns.hasNext()) {
  				throw new CompilerException("Bug: Found a non-source task with no input.");
  			}
  			
  			int inputIndex = 0;
  			while (inConns.hasNext()) {
  				Channel input = inConns.next();
  				inputIndex += translateChannel(input, inputIndex, targetVertex, targetVertexConfig, false);
  			}
  			// broadcast variables
  			int broadcastInputIndex = 0;
  			for (NamedChannel broadcastInput: node.getBroadcastInputs()) {
  				int broadcastInputIndexDelta = translateChannel(broadcastInput, broadcastInputIndex, targetVertex, targetVertexConfig, true);
  				targetVertexConfig.setBroadcastInputName(broadcastInput.getName(), broadcastInputIndex);
  				targetVertexConfig.setBroadcastInputSerializer(broadcastInput.getSerializer(), broadcastInputIndex);
  				broadcastInputIndex += broadcastInputIndexDelta;
  			}
  		} catch (Exception e) {
  			throw new CompilerException(
  				"An error occurred while translating the optimized plan to a JobGraph: " + e.getMessage(), e);
  		}
  	}