Files
Chamilo/vendor/graphp/algorithms/src/MinimumCostFlow/SuccessiveShortestPath.php
2025-04-10 12:24:57 +02:00

155 lines
5.5 KiB
PHP

<?php
namespace Graphp\Algorithms\MinimumCostFlow;
use Fhaculty\Graph\Edge\Directed as EdgeDirected;
use Fhaculty\Graph\Exception\UnderflowException;
use Fhaculty\Graph\Exception\UnexpectedValueException;
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Set\Edges;
use Fhaculty\Graph\Vertex;
use Graphp\Algorithms\ResidualGraph;
use Graphp\Algorithms\ShortestPath\MooreBellmanFord as SpMooreBellmanFord;
use Graphp\Algorithms\Search\BreadthFirst as SearchBreadthFirst;
class SuccessiveShortestPath extends Base
{
/**
* @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
assert($newflow !== null);
$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;
}
/**
* @param Graph $graph
* @return Vertex a source vertex in the given graph
* @throws UnderflowException if there is no left source vertex
*/
private function getVertexSource(Graph $graph)
{
foreach ($graph->getVertices()->getMap() as $vid => $vertex) {
if ($this->graph->getVertex($vid)->getBalance() - $vertex->getBalance() > 0) {
return $vertex;
}
}
throw new UnderflowException('No source vertex found in graph');
}
/**
* @param Vertex $source
* @return Vertex a sink-vertex that is reachable from the source
* @throws UnderflowException if there is no reachable sink vertex
* @uses BreadthFirst::getVertices()
*/
private function getVertexSink(Vertex $source)
{
// search for reachable Vertices
$algBFS = new SearchBreadthFirst($source);
foreach ($algBFS->getVertices()->getMap() as $vid => $vertex) {
if ($this->graph->getVertex($vid)->getBalance() - $vertex->getBalance() < 0) {
return $vertex;
}
}
throw new UnderflowException('No sink vertex connected to given source vertex found');
}
private function addBalance(Vertex $vertex, $balance)
{
$vertex->setBalance($vertex->getBalance() + $balance);
}
}