public function createGraph() { $this->checkBalance(); // create resulting graph with supersource and supersink $resultGraph = $this->graph->createGraphClone(); $superSource = $resultGraph->createVertex()->setLayoutAttribute('label', 's*'); $superSink = $resultGraph->createVertex()->setLayoutAttribute('label', 't*'); $sumBalance = 0; // connect supersource s* and supersink t* with all "normal" sources and sinks foreach ($resultGraph->getVertices() as $vertex) { $balance = $vertex->getBalance(); if ($balance > 0) { // positive balance => source capacity $superSource->createEdgeTo($vertex)->setCapacity($balance); $sumBalance += $balance; } elseif ($balance < 0) { // negative balance => sink capacity (positive) $vertex->createEdgeTo($superSink)->setCapacity(-$balance); } } // calculate (s*, t*)-flow $algMaxFlow = new MaxFlowEdmondsKarp($superSource, $superSink); $flowMax = $algMaxFlow->getFlowMax(); if ($flowMax !== $sumBalance) { throw new UnexpectedValueException('Network does not support required flow of ' . $sumBalance . ' (maximum possible flow limited to ' . $flowMax . ')'); } $resultGraph = $algMaxFlow->createGraph(); while (true) { // create residual graph $algRG = new ResidualGraph($resultGraph); $residualGraph = $algRG->createGraph(); // get negative cycle $alg = new DetectNegativeCycle($residualGraph); try { $clonedEdges = $alg->getCycleNegative()->getEdges(); } catch (UnderflowException $ignore) { // no negative cycle found => end algorithm break; } // calculate maximal possible flow = minimum capacity remaining for all edges $newFlow = $clonedEdges->getEdgeOrder(Edges::ORDER_CAPACITY_REMAINING)->getCapacityRemaining(); // set flow on original graph $this->addFlow($resultGraph, $clonedEdges, $newFlow); } // destroy temporary supersource and supersink again $resultGraph->getVertex($superSink->getId())->destroy(); $resultGraph->getVertex($superSource->getId())->destroy(); return $resultGraph; }
/** * Returns max flow graph * * @return Graph */ public function createGraph() { $graphResult = $this->startVertex->getGraph()->createGraphClone(); // initialize null flow and check edges foreach ($graphResult->getEdges() as $edge) { if (!$edge instanceof EdgeDirected) { throw new UnexpectedValueException('Undirected edges not supported for edmonds karp'); } $edge->setFlow(0); } $idA = $this->startVertex->getId(); $idB = $this->destinationVertex->getId(); do { // Generate new residual graph and repeat $residualAlgorithm = new ResidualGraph($graphResult); $graphResidual = $residualAlgorithm->createGraph(); // 1. Search _shortest_ (number of hops and cheapest) path from s -> t $alg = new BreadthFirst($graphResidual->getVertex($idA)); try { $pathFlow = $alg->getWalkTo($graphResidual->getVertex($idB)); } catch (OutOfBoundsException $e) { $pathFlow = NULL; } // If path exists add the new flow to graph if ($pathFlow) { // 2. get max flow from path $maxFlowValue = $pathFlow->getEdges()->getEdgeOrder(Edges::ORDER_CAPACITY)->getCapacity(); // 3. add flow to path foreach ($pathFlow->getEdges() as $edge) { // try to look for forward edge to increase flow try { $originalEdge = $graphResult->getEdgeClone($edge); $originalEdge->setFlow($originalEdge->getFlow() + $maxFlowValue); // forward edge not found, look for back edge to decrease flow } catch (UnderflowException $e) { $originalEdge = $graphResult->getEdgeCloneInverted($edge); $originalEdge->setFlow($originalEdge->getFlow() - $maxFlowValue); } } } // repeat while we still finds paths with residual capacity to add flow to } while ($pathFlow); return $graphResult; }
/** * expect exception for edges with no capacity * @expectedException UnexpectedValueException */ public function testInvalidNoCapacity() { $graph = new Graph(); $graph->createVertex()->createEdgeTo($graph->createVertex())->setFlow(1); $alg = new ResidualGraph($graph); $alg->createGraph(); }
/** * @uses Graph::createGraphClone() * @uses ResidualGraph::createGraph() * @uses SpMooreBellmanFord::getEdgesTo(Vertex $targetVertex) * @see Base::createGraph() */ public function createGraph() { $this->checkBalance(); $resultGraph = $this->graph->createGraphClone(); // initial balance to 0 $vertices = $resultGraph->getVertices(); foreach ($vertices as $vertex) { $vertex->setBalance(0); } // initial flow of edges $edges = $resultGraph->getEdges(); foreach ($edges as $edge) { if (!$edge instanceof EdgeDirected) { throw new UnexpectedValueException('Undirected edges are not supported for SuccessiveShortestPath'); } // 0 if weight of edge is positive $flow = 0; // maximal flow if weight of edge is negative if ($edge->getWeight() < 0) { $flow = $edge->getCapacity(); $startVertex = $edge->getVertexStart(); $endVertex = $edge->getVertexEnd(); // add balance to start- and end-vertex $this->addBalance($startVertex, $flow); $this->addBalance($endVertex, -$flow); } $edge->setFlow($flow); } // return or Exception inside this while while (true) { // create residual graph $algRG = new ResidualGraph($resultGraph); $residualGraph = $algRG->createGraph(); // search for a source try { $sourceVertex = $this->getVertexSource($residualGraph); } catch (UnderflowException $ignore) { // no source is found => minimum-cost flow is found break; } // search for reachable target sink from this source try { $targetVertex = $this->getVertexSink($sourceVertex); } catch (UnderflowException $e) { // no target found => network does not have enough capacity throw new UnexpectedValueException('The graph has not enough capacity for the minimum-cost flow', 0, $e); } // calculate shortest path between source- and target-vertex $algSP = new SpMooreBellmanFord($sourceVertex); $edgesOnFlow = $algSP->getEdgesTo($targetVertex); // calculate the maximal possible flow // new flow is the maximal possible flow for this path $newflow = $this->graph->getVertex($sourceVertex->getId())->getBalance() - $sourceVertex->getBalance(); $targetFlow = -($this->graph->getVertex($targetVertex->getId())->getBalance() - $targetVertex->getBalance()); // get minimum of source and target if ($targetFlow < $newflow) { $newflow = $targetFlow; } // get minimum of capacity remaining on path $minCapacity = $edgesOnFlow->getEdgeOrder(Edges::ORDER_CAPACITY_REMAINING)->getCapacityRemaining(); if ($minCapacity < $newflow) { $newflow = $minCapacity; } // add the new flow to the path $this->addFlow($resultGraph, $edgesOnFlow, $newflow); // add balance to source and remove for the target sink $oriSourceVertex = $resultGraph->getVertex($sourceVertex->getId()); $oriTargetVertex = $resultGraph->getVertex($targetVertex->getId()); $this->addBalance($oriSourceVertex, $newflow); $this->addBalance($oriTargetVertex, -$newflow); } return $resultGraph; }