Repository: graphp/algorithms Branch: 0.9.x Commit: 269389305c94 Files: 92 Total size: 238.7 KB Directory structure: gitextract_dqkgq7nx/ ├── .gitattributes ├── .github/ │ └── workflows/ │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml.dist ├── phpunit.xml.legacy ├── src/ │ ├── Base.php │ ├── BaseDual.php │ ├── BaseGraph.php │ ├── BaseVertex.php │ ├── Bipartit.php │ ├── Complete.php │ ├── ConnectedComponents.php │ ├── Degree.php │ ├── DetectNegativeCycle.php │ ├── Directed.php │ ├── Eulerian.php │ ├── Flow.php │ ├── Groups.php │ ├── Loop.php │ ├── MaxFlow/ │ │ └── EdmondsKarp.php │ ├── MaximumMatching/ │ │ ├── Base.php │ │ └── Flow.php │ ├── MinimumCostFlow/ │ │ ├── Base.php │ │ ├── CycleCanceling.php │ │ └── SuccessiveShortestPath.php │ ├── MinimumSpanningTree/ │ │ ├── Base.php │ │ ├── Kruskal.php │ │ └── Prim.php │ ├── Parallel.php │ ├── Property/ │ │ ├── GraphProperty.php │ │ └── WalkProperty.php │ ├── ResidualGraph.php │ ├── Search/ │ │ ├── Base.php │ │ ├── BreadthFirst.php │ │ └── DepthFirst.php │ ├── ShortestPath/ │ │ ├── Base.php │ │ ├── BreadthFirst.php │ │ ├── Dijkstra.php │ │ └── MooreBellmanFord.php │ ├── Symmetric.php │ ├── TopologicalSort.php │ ├── TransposeGraph.php │ ├── TravelingSalesmanProblem/ │ │ ├── Base.php │ │ ├── Bruteforce.php │ │ ├── MinimumSpanningTree.php │ │ └── NearestNeighbor.php │ ├── Tree/ │ │ ├── Base.php │ │ ├── BaseDirected.php │ │ ├── InTree.php │ │ ├── OutTree.php │ │ └── Undirected.php │ └── Weight.php └── tests/ ├── BipartitTest.php ├── CompleteTest.php ├── ConnectedComponentsTest.php ├── DegreeTest.php ├── DetectNegativeCycleTest.php ├── DirectedTest.php ├── EulerianTest.php ├── FlowTest.php ├── GroupsTest.php ├── LoopTest.php ├── MaxFlow/ │ └── EdmondsKarpTest.php ├── MaximumMatching/ │ └── FlowTest.php ├── MinimumCostFlow/ │ ├── BaseMcfTest.php │ ├── CycleCancellingTest.php │ └── SuccessiveShortestPathTest.php ├── MinimumSpanningTree/ │ ├── BaseMstTest.php │ ├── KruskalTest.php │ └── PrimTest.php ├── ParallelTest.php ├── Property/ │ ├── PropertyGraphTest.php │ └── WalkPropertyTest.php ├── ResidualGraphTest.php ├── Search/ │ └── BreadthFirstTest.php ├── ShortestPath/ │ ├── BaseShortestPathTest.php │ ├── BreadthFirstTest.php │ ├── DijkstraTest.php │ └── MooreBellmanFordTest.php ├── SymmetricTest.php ├── TestCase.php ├── TopologicalSortTest.php ├── TravelingSalesmanProblem/ │ └── BruteforceTest.php ├── Tree/ │ ├── BaseDirectedTest.php │ ├── InTreeTest.php │ ├── OutTreeTest.php │ └── UndirectedTest.php └── WeightTest.php ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ /.gitattributes export-ignore /.github/workflows/ export-ignore /.gitignore export-ignore /phpunit.xml.dist export-ignore /phpunit.xml.legacy export-ignore /tests export-ignore ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: push: pull_request: jobs: PHPUnit: runs-on: ubuntu-20.04 strategy: matrix: php: - 7.4 - 7.3 - 7.2 - 7.1 - 7.0 - 5.6 - 5.5 - 5.4 - 5.3 steps: - uses: actions/checkout@v2 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - run: composer install - run: vendor/bin/phpunit --coverage-text if: ${{ matrix.php >= 7.3 }} - run: vendor/bin/phpunit --coverage-text -c phpunit.xml.legacy if: ${{ matrix.php < 7.3 }} ================================================ FILE: .gitignore ================================================ /vendor /composer.lock ================================================ FILE: CHANGELOG.md ================================================ # Changelog ## 0.8.2 (2020-02-20) * Feature: Add max depth parameter to breadth first search. (#27 by @phyrwork) * Feature: Replace recursive topological sort with iterative algorithm. (#25 by @phyrwork) * Fix: Fix option to merge parallel edges when creating residual graph. (#39 by @clue) * Fix: Fix setting upper limit for TSP bruteforce via MST algorithm. (#36 by @clue) * Minor code style improvements to make PHPStan happy, clean up dead code for depth first search and automated native_function_invocation fixes. (#35 and #40 by @clue and #37 by @draco2003) * Improve test suite to support PHPUnit 6 and PHPUnit 5 and support running on legacy PHP 5.3 through PHP 7.2 and HHVM. (#32 by @clue) ## 0.8.1 (2015-03-08) * Support graph v0.9 (while keeping BC) ([#16](https://github.com/graphp/algorithms/pull/16)) * Deprecate internal algorithm base classes ([#15](https://github.com/graphp/algorithms/pull/15)) ## 0.8.0 (2015-02-25) * First tagged release, split off from [clue/graph](https://github.com/clue/graph) v0.8.0 ([#1](https://github.com/graphp/algorithms/issues/1)) ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015 Christian Lück Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # graphp/algorithms [![CI status](https://github.com/graphp/algorithms/workflows/CI/badge.svg)](https://github.com/graphp/algorithms/actions) Common mathematical graph algorithms implemented in PHP > **Development version:** This branch contains the code for the upcoming > version 0.9. For the code of the current version 0.8, check out the > [`0.8.x` branch](https://github.com/graphp/algorithms/tree/0.8.x). > > The upcoming version 0.9 will be the way forward for this package. However, > we will still actively support version 0.8 for those not yet on the latest > version. See also [installation instructions](#install) for more details. ## Install The recommended way to install this library is [through Composer](https://getcomposer.org/). [New to Composer?](https://getcomposer.org/doc/00-intro.md) Once released, this project will follow [SemVer](https://semver.org/). At the moment, this will install the latest development version: ```bash composer require graphp/algorithms:^0.9@dev ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. This project aims to run on any platform and thus does not require any PHP extensions and supports running on legacy PHP 5.3 through current PHP 7+. It's *highly recommended to use PHP 7+* for this project. ## Tests To run the test suite, you first need to clone this repo and then install all dependencies [through Composer](https://getcomposer.org/): ```bash composer install ``` To run the test suite, go to the project root and run: ```bash vendor/bin/phpunit ``` ## License This project is released under the permissive [MIT license](LICENSE). ================================================ FILE: composer.json ================================================ { "name": "graphp/algorithms", "description": "Common mathematical graph algorithms implemented in PHP", "keywords": ["Graph algorithms", "shortest path", "dijkstra", "moore-bellman-ford", "minimum spanning tree", "kruskal", "prim"], "homepage": "https://github.com/graphp/algorithms", "license": "MIT", "authors": [ { "name": "Christian Lück", "email": "christian@clue.engineering" } ], "require": { "php": ">=5.3", "graphp/graph": "1.x-dev#fb198e4 as 1.0.0" }, "require-dev": { "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" }, "autoload": { "psr-4": {"Graphp\\Algorithms\\": "src/"} }, "autoload-dev": { "psr-4": { "Graphp\\Tests\\Algorithms\\": "tests/" } } } ================================================ FILE: phpunit.xml.dist ================================================ ./tests/ ./src/ ================================================ FILE: phpunit.xml.legacy ================================================ ./tests/ ./src/ ================================================ FILE: src/Base.php ================================================ set = $graphOrWalk; } } ================================================ FILE: src/BaseGraph.php ================================================ graph = $graph; } } ================================================ FILE: src/BaseVertex.php ================================================ vertex = $vertex; } } ================================================ FILE: src/Bipartit.php ================================================ getColors(); return true; } catch (UnexpectedValueException $ignore) { } return false; } /** * checks whether the input graph's vertex groups are a valid bipartition * * @return bool * @uses AlgorithmGroups::isBipartit() */ public function isBipartitGroups() { $alg = new Groups($this->graph); return $alg->isBipartit(); } /** * get map of vertex ID to vertex color * * @return int[] * @throws UnexpectedValueException if graph is not bipartit * @uses AlgorithmBipartit::checkVertex() for every vertex not already colored */ public function getColors() { $colors = array(); // get color for each vertex foreach ($this->graph->getVertices()->getMap() as $vid => $startVertex) { if (!isset($colors[$vid])) { $queue = array($startVertex); // initialize each components color $colors[$vid] = 0; // breadth search all vertices in same component do { // next vertex in color $vertex = \array_shift($queue); $color = $colors[$vertex->getId()]; $nextColor = 1-$color; // scan all vertices connected to this vertex foreach ($vertex->getVerticesEdge()->getMap() as $vid => $nextVertex) { // color unknown, so expect next color for this vertex if (!isset($colors[$vid])) { $colors[$vid] = $nextColor; $queue[] = $nextVertex; // color is known but differs => can not be bipartit } elseif ($colors[$vid] !== $nextColor) { throw new UnexpectedValueException('Graph is not bipartit'); } } } while ($queue); } } return $colors; } /** * get groups of vertices per color * * @return array[] array of arrays of vertices */ public function getColorVertices() { $colors = $this->getColors(); $ret = array(0 => array(), 1 => array()); foreach ($this->graph->getVertices()->getMap() as $vid => $vertex) { $ret[$colors[$vid]][$vid] = $vertex; } return $ret; } /** * create new graph with valid groups set according to bipartition colors * * @return Graph * @throws UnexpectedValueException if graph is not bipartit * @uses AlgorithmBipartit::getColors() * @uses Graph::createGraphClone() * @uses Vertex::setGroup() */ public function createGraphGroups() { $colors = $this->getColors(); $graph = $this->graph->createGraphClone(); foreach ($graph->getVertices()->getMap() as $vid => $vertex) { $vertex->setGroup($colors[$vid]); } return $graph; } } ================================================ FILE: src/Complete.php ================================================ graph->getVertices()->getVector(); // from each vertex foreach ($vertices as $vertex) { // to each vertex foreach ($c as $other) { // missing edge => fail if ($other !== $vertex && !$vertex->hasEdgeTo($other)) { return false; } } } return true; } } ================================================ FILE: src/ConnectedComponents.php ================================================ getGraph() !== $this->graph) { throw new InvalidArgumentException('This graph does not contain the given vertex'); } return $this->graph->createGraphCloneVertices($this->createSearch($vertex)->getVertices()); } /** * * @param Vertex $vertex * @return SearchBreadthFirst */ private function createSearch(Vertex $vertex) { $alg = new SearchBreadthFirst($vertex); // follow into both directions (loosely connected) return $alg->setDirection(SearchBreadthFirst::DIRECTION_BOTH); } /** * check whether this graph consists of only a single component * * If a Graph consists of only a single component, it is said to be a * connected Graph, otherwise it's called a disconnected Graph. * * This method returns exactly the same result as checking *
($this->getNumberOfComponents() === 1)
. However, using this * method is faster than calling getNumberOfComponents(), as it only has to * count all vertices in one component to see if the graph consists of only * a single component. * * As such, a null Graph (a Graph with no vertices) is not considered * connected here. * * @return bool * @see self::getNumberOfComponents() */ public function isSingle() { try { $vertex = $this->graph->getVertices()->getVertexFirst(); } catch (UnderflowException $e) { // no first vertex => empty graph => has zero components return false; } $alg = $this->createSearch($vertex); return (\count($this->graph->getVertices()) === \count($alg->getVertices())); } /** * count number of connected components * * A null Graph (a Graph with no vertices) will return 0 components. * * @return int number of components * @uses Graph::getVertices() * @uses AlgorithmSearchBreadthFirst::getVertices() */ public function getNumberOfComponents() { $visitedVertices = array(); $components = 0; // for each vertices foreach ($this->graph->getVertices()->getMap() as $vid => $vertex) { // did I visit this vertex before? if (!isset($visitedVertices[$vid])) { // get all vertices of this component $newVertices = $this->createSearch($vertex)->getVertices()->getIds(); ++$components; // mark the vertices of this component as visited foreach ($newVertices as $vid) { $visitedVertices[$vid] = true; } } } // return number of components return $components; } /** * separate input graph into separate independant and unconnected graphs * * @return Graph[] * @uses Graph::getVertices() * @uses AlgorithmSearchBreadthFirst::getVertices() */ public function createGraphsComponents() { $visitedVertices = array(); $graphs = array(); // for each vertices foreach ($this->graph->getVertices()->getMap() as $vid => $vertex) { // did I visit this vertex before? if (!isset($visitedVertices[$vid])) { $alg = $this->createSearch($vertex); // get all vertices of this component $newVertices = $alg->getVertices(); // mark the vertices of this component as visited foreach ($newVertices->getIds() as $vid) { $visitedVertices[$vid] = true; } $graphs[] = $this->graph->createGraphCloneVertices($newVertices); } } return $graphs; } } ================================================ FILE: src/Degree.php ================================================ getDegreeVertex($this->graph->getVertices()->getVertexFirst()); foreach ($this->graph->getVertices() as $vertex) { assert($vertex instanceof Vertex); $i = $this->getDegreeVertex($vertex); if ($i !== $degree) { throw new UnexpectedValueException('Graph is not k-regular (vertex degrees differ)'); } } return $degree; } /** * get minimum degree of vertices * * @return int * @throws UnderflowException if graph is empty * @uses Vertices::getVertexOrder() * @uses self::getDegreeVertex() */ public function getDegreeMin() { return $this->getDegreeVertex($this->graph->getVertices()->getVertexOrder(array($this, 'getDegreeVertex'))); } /** * get maximum degree of vertices * * @return int * @throws UnderflowException if graph is empty * @uses Vertices::getVertexOrder() * @uses self::getDegreeVertex() */ public function getDegreeMax() { return $this->getDegreeVertex($this->graph->getVertices()->getVertexOrder(array($this, 'getDegreeVertex'), true)); } /** * checks whether this graph is regular, i.e. each vertex has the same indegree/outdegree * * @return bool * @uses self::getDegree() */ public function isRegular() { // an empty graph is considered regular if ($this->graph->getVertices()->isEmpty()) { return true; } try { $this->getDegree(); return true; } catch (UnexpectedValueException $ignore) { } return false; } /** * checks whether the indegree of every vertex equals its outdegree * * @return bool * @uses self::getDegreeInVertex() * @uses self::getDegreeOutVertex() */ public function isBalanced() { foreach ($this->graph->getVertices() as $vertex) { if ($this->getDegreeInVertex($vertex) !== $this->getDegreeOutVertex($vertex)) { return false; } } return true; } /** * checks whether this vertex is a source, i.e. its indegree is zero * * @param Vertex $vertex * @return bool * @uses Edge::hasVertexTarget() * @see self::getDegreeInVertex() */ public function isVertexSource(Vertex $vertex) { foreach ($vertex->getEdges() as $edge) { if ($edge->hasVertexTarget($vertex)) { return false; } } // reach this point: no edge to this vertex return true; } /** * checks whether this vertex is a sink, i.e. its outdegree is zero * * @param Vertex $vertex * @return bool * @uses Edge::hasVertexStart() * @see self::getDegreeOutVertex() */ public function isVertexSink(Vertex $vertex) { foreach ($vertex->getEdges() as $edge) { if ($edge->hasVertexStart($vertex)) { return false; } } // reach this point: no edge away from this vertex return true; } /** * get degree of this vertex (total number of edges) * * vertex degree counts the total number of edges attached to this vertex * regardless of whether they're directed or not. loop edges are counted * twice as both start and end form a 'line' to the same vertex. * * @param Vertex $vertex * @return int * @see self::getDegreeInVertex() * @see self::getDegreeOutVertex() */ public function getDegreeVertex(Vertex $vertex) { return \count($vertex->getEdges()); } /** * check whether this vertex is isolated (i.e. has no edges attached) * * @param Vertex $vertex * @return bool */ public function isVertexIsolated(Vertex $vertex) { return $vertex->getEdges()->isEmpty(); } /** * get indegree of this vertex (number of edges TO this vertex) * * @param Vertex $vertex * @return int * @uses Edge::hasVertexTarget() * @see self::getDegreeVertex() */ public function getDegreeInVertex($vertex) { $n = 0; foreach ($vertex->getEdges() as $edge) { if ($edge->hasVertexTarget($vertex)) { ++$n; } } return $n; } /** * get outdegree of this vertex (number of edges FROM this vertex TO other vertices) * * @param Vertex $vertex * @return int * @uses Edge::hasVertexStart() * @see self::getDegreeVertex() */ public function getDegreeOutVertex(Vertex $vertex) { $n = 0; foreach ($vertex->getEdges() as $edge) { if ($edge->hasVertexStart($vertex)) { ++$n; } } return $n; } } ================================================ FILE: src/DetectNegativeCycle.php ================================================ getCycleNegative(); // cycle was found => okay return true; // no cycle found } catch (UnderflowException $ignore) {} return false; } /** * Searches all vertices for the first negative cycle * * @return Walk * @throws UnderflowException if there's no negative cycle * @uses AlgorithmSpMooreBellmanFord::getVertices() */ public function getCycleNegative() { // remember vertices already visited, as they can not lead to a new cycle $verticesVisited = array(); // check for all vertices foreach ($this->graph->getVertices()->getMap() as $vid => $vertex) { // skip vertices already visited if (!isset($verticesVisited[$vid])) { // start MBF algorithm on current vertex $alg = new SpMooreBellmanFord($vertex); try { // try to get all connected vertices (or throw new cycle) foreach ($alg->getVertices()->getIds() as $vid) { // getting connected vertices succeeded, so skip over all of them $verticesVisited[$vid] = true; // no cycle found, check next vertex... } // yey, negative cycle encountered => return } catch (NegativeCycleException $e) { return $e->getCycle(); } } // no more vertices to check => abort } throw new UnderflowException('No negative cycle found'); } /** * create new graph clone with only vertices and edges in negative cycle * * @return Graph * @throws UnderflowException if there's no negative cycle * @uses AlgorithmDetectNegativeCycle::getCycleNegative() * @uses Walk::createGraph() */ public function createGraph() { return $this->getCycleNegative()->createGraph(); } } ================================================ FILE: src/Directed.php ================================================ set->getEdges() as $edge) { if ($edge instanceof EdgeDirected) { return true; } } return false; } /** * checks whether the graph has any undirected edges * * This method is intentionally not named "isUndirected()", * because that might be misleading in regards to empty and/or mixed graphs. * * @return bool */ public function hasUndirected() { foreach ($this->set->getEdges() as $edge) { if ($edge instanceof EdgeUndirected) { return true; } } return false; } /** * checks whether this is a mixed graph (contains both directed and undirected edges) * * @return bool * @uses self::hasDirected() * @uses self::hasUndirected() */ public function isMixed() { return ($this->hasDirected() && $this->hasUndirected()); } } ================================================ FILE: src/Eulerian.php ================================================ graph); if ($components->isSingle()) { $alg = new Degree($this->graph); foreach ($this->graph->getVertices() as $vertex) { // uneven degree => fail if ($alg->getDegreeVertex($vertex) & 1) { return false; } } return true; } return false; } } ================================================ FILE: src/Flow.php ================================================ set->getEdges() as $edge) { if ($edge->getFlow() !== NULL) { return true; } } return false; } /** * Calculates the flow for this Vertex: sum(outflow) - sum(inflow) * * Usually, vertices should have a resulting flow of 0: The sum of flows * entering a vertex must equal the sum of flows leaving a vertex. If the * resulting flow is < 0, this vertex is considered a sink (i.e. there's * more flow into this vertex). If the resulting flow is > 0, this vertex * is considered a "source" (i.e. there's more flow leaving this vertex). * * @param Vertex $vertex * @return float * @throws UnexpectedValueException if they are undirected edges * @see Vertex::getBalance() * @uses Vertex::getEdges() * @uses Edge::getFlow() */ public function getFlowVertex(Vertex $vertex) { $sumOfFlow = 0; foreach ($vertex->getEdges() as $edge) { if (!($edge instanceof EdgeDirected)) { throw new UnexpectedValueException("TODO: undirected edges not suported yet"); } // edge is an outgoing edge of this vertex if ($edge->hasVertexStart($vertex)) { // flowing out (flow is "pointing away") $sumOfFlow += $edge->getFlow(); // this is an ingoing edge } else { // flowing in $sumOfFlow -= $edge->getFlow(); } } return $sumOfFlow; } public function getBalance() { $balance = 0; // Sum for all vertices of value foreach ($this->set->getVertices() as $vertex) { $balance += $vertex->getBalance(); } return $balance; } /** * check if the current flow is balanced (aka "balanced flow" or "b-flow") * * a flow is considered balanced if each edge's current flow does not exceed its * maximum capacity (which is always guaranteed due to the implementation * of Edge::setFlow()) and each vertices' flow (i.e. outflow-inflow) equals * its balance. * * checking whether the FLOW is balanced is not to be confused with checking * whether the GRAPH is balanced (see Graph::isBalanced() instead) * * @return bool * @see Degree::isBalanced() if you merely want to check indegree=outdegree * @uses self::getFlowVertex() * @uses Vertex::getBalance() */ public function isBalancedFlow() { // no need to check for each edge: flow <= capacity (setters already check that) // check for each vertex: outflow-inflow = balance foreach ($this->set->getVertices() as $vertex) { if ($this->getFlowVertex($vertex) !== $vertex->getBalance()) { return false; } } return true; } } ================================================ FILE: src/Groups.php ================================================ getGroups()); } /** * checks whether the input graph's vertex groups are a valid bipartition * * @return bool * @see AlgorithmBipartit() if you do NOT want to take vertex groups into consideration * @uses AlgorithmGroups::getNumberOfGroups() * @uses Vertex::getGroup() */ public function isBipartit() { // graph has to contain exactly 2 groups if ($this->getNumberOfGroups() !== 2) { return false; } // for each vertex foreach ($this->graph->getVertices() as $vertex) { // get current group $group = $vertex->getGroup(); // for every neighbor vertex foreach ($vertex->getVerticesEdge() as $vertexNeighbor) { // vertex group must be other group if ($vertexNeighbor->getGroup() === $group) { return false; } } } return true; } /** * get vector of all group numbers * * @return int[] * @uses Vertex::getGroup() */ public function getGroups() { $groups = array(); foreach ($this->graph->getVertices() as $vertex) { assert($vertex instanceof Vertex); $groups[$vertex->getGroup()] = true; } return \array_keys($groups); } /** * get set of all Vertices in the given group * * @param int $group * @return Vertices * @uses Vertex::getGroup() */ public function getVerticesGroup($group) { $vertices = array(); foreach ($this->graph->getVertices()->getMap() as $vid => $vertex) { if ($vertex->getGroup() === $group) { $vertices[$vid] = $vertex; } } return new Vertices($vertices); } } ================================================ FILE: src/Loop.php ================================================ set->getEdges() as $edge) { if ($edge->isLoop()) { return true; } } return false; } /** * checks whether this vertex has a loop (edge to itself) * * @return bool * @uses Edge::isLoop() */ public function hasLoopVertex(Vertex $vertex) { foreach ($vertex->getEdges() as $edge) { if ($edge->isLoop()) { return true; } } return false; } } ================================================ FILE: src/MaxFlow/EdmondsKarp.php ================================================ getGraph() !== $destinationVertex->getGraph()) { throw new InvalidArgumentException('Start and target vertex have to be in the same graph instance'); } $this->startVertex = $startVertex; $this->destinationVertex = $destinationVertex; } /** * Returns max flow graph * * @return Graph * @throws UnexpectedValueException for undirected edges */ 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; } /** * Returns max flow value * * @return float */ public function getFlowMax() { $resultGraph = $this->createGraph(); $start = $resultGraph->getVertex($this->startVertex->getId()); $maxFlow = 0; foreach ($start->getEdgesOut() as $edge) { $maxFlow = $maxFlow + $edge->getFlow(); } return $maxFlow; } } ================================================ FILE: src/MaximumMatching/Base.php ================================================ getEdges()); } /** * create new resulting graph with only edges from maximum matching * * @return Graph * @uses Base::getEdges() * @uses Graph::createGraphCloneEdges() */ public function createGraph() { return $this->graph->createGraphCloneEdges($this->getEdges()); } /** * create new resulting graph with minimum-cost flow on edges * * @return Edges */ abstract public function getEdges(); } ================================================ FILE: src/MaximumMatching/Flow.php ================================================ graph); if ($alg->hasDirected()) { throw new UnexpectedValueException('Input graph contains directed edges'); } $alg = new Groups($this->graph); if (!$alg->isBipartit()) { throw new UnexpectedValueException('Input graph does not have bipartit groups assigned to each vertex. Consider Using "AlgorithmBipartit::createGraph()" first'); } // create temporary flow graph with supersource and supersink $graphFlow = $this->graph->createGraphCloneEdgeless(); $superSource = $graphFlow->createVertex(); $superSink = $graphFlow->createVertex(); $groups = $alg->getGroups(); $groupA = $groups[0]; // connect supersource s* to set A and supersink t* to set B foreach ($graphFlow->getVertices() as $vertex) { assert($vertex instanceof Vertex); // we want to skip over supersource & supersink as they do not have a partition assigned if ($vertex === $superSource || $vertex === $superSink) continue; $group = $vertex->getGroup(); if ($group === $groupA) { // group A: source $graphFlow->createEdgeDirected($superSource, $vertex)->setCapacity(1)->setFlow(0); // temporarily create edges from A->B for flow graph $originalVertex = $this->graph->getVertex($vertex->getId()); foreach ($originalVertex->getVerticesEdgeTo() as $vertexTarget) { $graphFlow->createEdgeDirected($vertex, $graphFlow->getVertex($vertexTarget->getId()))->setCapacity(1)->setFlow(0); } } else { // group B: sink $graphFlow->createEdgeDirected($vertex, $superSink)->setCapacity(1)->setFlow(0); } } // visualize($resultGraph); // calculate (s*, t*)-flow $algMaxFlow = new MaxFlowEdmondsKarp($superSource, $superSink); $resultGraph = $algMaxFlow->createGraph(); // destroy temporary supersource and supersink again $resultGraph->getVertex($superSink->getId())->destroy(); $resultGraph->getVertex($superSource->getId())->destroy(); $returnEdges = array(); foreach ($resultGraph->getEdges() as $edge) { // only keep matched edges if ($edge->getFlow() > 0) { $originalEdge = $this->graph->getEdgeClone($edge); $returnEdges[] = $originalEdge; } } return new Edges($returnEdges); } } ================================================ FILE: src/MinimumCostFlow/Base.php ================================================ graph); $balance = $alg->getBalance(); $tolerance = 0.000001; if ($balance >= $tolerance || $balance <= -$tolerance) { throw new UnexpectedValueException('The given graph is not balanced value is: ' . $balance); } return $this; } /** * helper used to add $newFlow to original edges of $clonedEdges in graph $resultGraph * * @param Graph $resultGraph graph to look for original edges * @param Edges $clonedEdges set of cloned edges to be modified * @param number $newFlow flow to add * @uses Graph::getEdgeClone() * @uses Graph::getEdgeCloneInverted() * @uses Edge::getFlow() * @uses Edge::setFlow() */ protected function addFlow(Graph $resultGraph, Edges $clonedEdges, $newFlow) { foreach ($clonedEdges as $clonedEdge) { try { // get edge from clone $edge = $resultGraph->getEdgeClone($clonedEdge); // add flow $edge->setFlow($edge->getFlow() + $newFlow); } catch (UnderflowException $ignore) { // if the edge doesn't exist => use the residual edge $edge = $resultGraph->getEdgeCloneInverted($clonedEdge); // remove flow $edge->setFlow($edge->getFlow() - $newFlow); } } } /** * calculate total weight along minimum-cost flow * * @return float * @uses self::createGraph() * @uses AlgorithmWeight::getWeightFlow() */ public function getWeightFlow() { $alg = new AlgorithmWeight($this->createGraph()); return $alg->getWeightFlow(); } /** * create new resulting graph with minimum-cost flow on edges * * @return Graph * @throws UnexpectedValueException for undirected edges * @throws UnexpectedValueException if the graph has not enough capacity for the minimum-cost flow */ abstract public function createGraph(); } ================================================ FILE: src/MinimumCostFlow/CycleCanceling.php ================================================ checkBalance(); // create resulting graph with supersource and supersink $resultGraph = $this->graph->createGraphClone(); $superSource = $resultGraph->createVertex(); $superSink = $resultGraph->createVertex(); $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 $resultGraph->createEdgeDirected($superSource, $vertex)->setCapacity($balance); $sumBalance += $balance; } elseif ($balance < 0) { // negative balance => sink capacity (positive) $resultGraph->createEdgeDirected($vertex, $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 assert($newFlow !== null); $this->addFlow($resultGraph, $clonedEdges, $newFlow); } // destroy temporary supersource and supersink again $resultGraph->getVertex($superSink->getId())->destroy(); $resultGraph->getVertex($superSource->getId())->destroy(); return $resultGraph; } } ================================================ FILE: src/MinimumCostFlow/SuccessiveShortestPath.php ================================================ 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); } } ================================================ FILE: src/MinimumSpanningTree/Base.php ================================================ getGraph()->createGraphCloneEdges($this->getEdges()); } /** * get all edges on minimum spanning tree * * @return Edges */ abstract public function getEdges(); /** * return reference to current Graph * * @return Graph */ abstract protected function getGraph(); /** * get total weight of minimum spanning tree * * @return float */ public function getWeight() { return $this->getEdges()->getSumCallback(function (Edge $edge) { return $edge->getWeight(); }); } /** * helper method to add a set of Edges to the given set of sorted edges * * @param Edges $edges * @param SplPriorityQueue $sortedEdges */ protected function addEdgesSorted(Edges $edges, SplPriorityQueue $sortedEdges) { // For all edges foreach ($edges as $edge) { assert($edge instanceof Edge); // ignore loops (a->a) if (!$edge->isLoop()) { // Add edges with negative weight because of order in stl $sortedEdges->insert($edge, -$edge->getWeight()); } } } } ================================================ FILE: src/MinimumSpanningTree/Kruskal.php ================================================ graph = $inputGraph; } protected function getGraph() { return $this->graph; } /** * @return Edges */ public function getEdges() { // Sortiere Kanten im Graphen $sortedEdges = new SplPriorityQueue(); // For all edges $this->addEdgesSorted($this->graph->getEdges(), $sortedEdges); $returnEdges = array(); // next color to assign $colorNext = 0; // array(color1 => array(vid1, vid2, ...), color2=>...) $colorVertices = array(); // array(vid1 => color1, vid2 => color1, ...) $colorOfVertices = array(); // Füge billigste Kanten zu neuen Graphen hinzu und verschmelze teilgragen wenn es nötig ist (keine Kreise) // solange ich mehr als einen Graphen habe mit weniger als n-1 kanten (bei n knoten im original) foreach ($sortedEdges as $edge) { assert($edge instanceof Edge); // Gucke Kante an: $vertices = $edge->getVertices()->getIds(); $aId = $vertices[0]; $bId = $vertices[1]; $aColor = isset($colorOfVertices[$aId]) ? $colorOfVertices[$aId] : NULL; $bColor = isset($colorOfVertices[$bId]) ? $colorOfVertices[$bId] : NULL; // 1. weder start noch end gehört zu einem graphen // => neuer Graph mit kanten if ($aColor === NULL && $bColor === NULL) { $colorOfVertices[$aId] = $colorNext; $colorOfVertices[$bId] = $colorNext; $colorVertices[$colorNext] = array($aId, $bId); ++$colorNext; // connect both vertices $returnEdges[] = $edge; } // 4. start xor end gehören zu einem graphen // => erweitere diesesn Graphen // Only b has color else if ($aColor === NULL && $bColor !== NULL) { // paint a in b's color $colorOfVertices[$aId] = $bColor; $colorVertices[$bColor][]=$aId; $returnEdges[] = $edge; // Only a has color } elseif ($aColor !== NULL && $bColor === NULL) { // paint b in a's color $colorOfVertices[$bId] = $aColor; $colorVertices[$aColor][]=$bId; $returnEdges[] = $edge; } // 3. start und end gehören zu unterschiedlichen graphen // => vereinigung // Different color else if ($aColor !== $bColor) { $betterColor = $aColor; $worseColor = $bColor; // more vertices with color a => paint all in b in a's color if (\count($colorVertices[$bColor]) > \count($colorVertices[$aColor])) { $betterColor = $bColor; $worseColor = $aColor; } // search all vertices with color b foreach ($colorVertices[$worseColor] as $vid) { $colorOfVertices[$vid] = $betterColor; // repaint in a's color $colorVertices[$betterColor][]=$vid; } // delete old color unset($colorVertices[$worseColor]); $returnEdges[] = $edge; } // 2. start und end gehören zum gleichen graphen => zirkel // => nichts machen } // definition of spanning tree: number of edges = number of vertices - 1 // above algorithm does not check isolated edges or may otherwise return multiple connected components => force check if (\count($returnEdges) !== (\count($this->graph->getVertices()) - 1)) { throw new UnexpectedValueException('Graph is not connected'); } return new Edges($returnEdges); } } ================================================ FILE: src/MinimumSpanningTree/Prim.php ================================================ startVertex = $startVertex; } /** * @return Edges */ public function getEdges() { // Initialize algorithm $edgeQueue = new SplPriorityQueue(); $vertexCurrent = $this->startVertex; $markInserted = array(); $returnEdges = array(); // iterate n-1 times (per definition, resulting MST MUST have n-1 edges) for ($i = 0, $n = \count($this->startVertex->getGraph()->getVertices()) - 1; $i < $n; ++$i) { $markInserted[$vertexCurrent->getId()] = true; // get unvisited vertex of the edge and add edges from new vertex // Add all edges from $currentVertex to priority queue $this->addEdgesSorted($vertexCurrent->getEdges(), $edgeQueue); do { if ($edgeQueue->isEmpty()) { throw new UnexpectedValueException('Graph has more than one component'); } // Get next cheapest edge $cheapestEdge = $edgeQueue->extract(); assert($cheapestEdge instanceof Edge); // Check if edge is between unmarked and marked edge $vertices = $cheapestEdge->getVertices(); $vertexA = $vertices->getVertexFirst(); $vertexB = $vertices->getVertexLast(); } while (!(isset($markInserted[$vertexA->getId()]) XOR isset($markInserted[$vertexB->getId()]))); // Cheapest Edge found, add edge to returnGraph $returnEdges[] = $cheapestEdge; // set current vertex for next iteration in order to add its edges to queue if (isset($markInserted[$vertexA->getId()])) { $vertexCurrent = $vertexB; } else { $vertexCurrent = $vertexA; } } return new Edges($returnEdges); } protected function getGraph() { return $this->startVertex->getGraph(); } } ================================================ FILE: src/Parallel.php ================================================ graph->getEdges() as $edge) { if ($this->hasEdgeParallelEdge($edge)) { return true; } } return false; } /** * checks whether this edge has any parallel edges * * @return bool * @uses Edge::getEdgesParallel() */ public function hasEdgeParallelEdge(Edge $edge) { return !$this->getEdgesParallelEdge($edge)->isEmpty(); } /** * get set of all Edges parallel to this edge (excluding self) * * @param Edge $edge * @return Edges */ public function getEdgesParallelEdge(Edge $edge) { if ($edge instanceof EdgeDirected) { // get all edges between this edge's endpoints $edges = $edge->getVertexStart()->getEdgesTo($edge->getVertexEnd())->getVector(); } else { // edge points into both directions (undirected/bidirectional edge) // also get all edges in other direction $ends = $edge->getVertices(); $edges = $ends->getVertexFirst()->getEdges()->getEdgesIntersection($ends->getVertexLast()->getEdges())->getVector(); } $pos = \array_search($edge, $edges, true); assert($pos !== false); // exclude current edge from parallel edges unset($edges[$pos]); return new Edges(\array_values($edges)); } } ================================================ FILE: src/Property/GraphProperty.php ================================================ graph->getEdges()->isEmpty(); } /** * checks whether this graph is a null graph (no vertex - and thus no edges) * * Each Edge is incident to two Vertices, or in case of an loop Edge, * incident to the same Vertex twice. As such an Edge can not exist when * no Vertices exist. So if we check we have no Vertices, we can also be * sure that no Edges exist either. * * @return bool */ public function isNull() { return $this->graph->getVertices()->isEmpty(); } /** * checks whether this graph is trivial (one vertex and no edges) * * @return bool */ public function isTrivial() { return ($this->graph->getEdges()->isEmpty() && \count($this->graph->getVertices()) === 1); } } ================================================ FILE: src/Property/WalkProperty.php ================================================ walk = $walk; } /** * checks whether walk is a cycle (i.e. source vertex = target vertex) * * A cycle is also known as a closed path, a walk that is NOT a cycle is * also known as an open path. * * A walk with no edges is not considered a cycle. The shortest possible * cycle is a single loop edge: * * 1--\ * ^ | * \--/ * * The following Walk is also considered a valid cycle: * * /->3--\ * | | * 1 -> 2 -\ | * ^ ^ | | * | \--/ | * | | * \----------/ * * @return bool * @link http://en.wikipedia.org/wiki/Cycle_%28graph_theory%29 * @see self::isCircuit() * @see self::isLoop() */ public function isCycle() { $vertices = $this->walk->getVertices(); return ($vertices->getVertexFirst() === $vertices->getVertexLast() && !$this->walk->getEdges()->isEmpty()); } /** * checks whether this walk is a circuit (i.e. a cycle with no duplicate edges) * * A circuit is also known as a closed (=cycle) trail (=path), that has at * least one edge. * * The following Walk is considered both a valid cycle and a valid circuit: * * 1 -> 2 -> 3 -\ * ^ | * | | * \------------/ * * The following Walk is also considered both a valid cycle and a valid circuit: * * /->3--\ * | | * 1 -> 2 -\ | * ^ ^ | | * | \--/ | * | | * \----------/ * * The later circuit walk can be expressed by its Vertex IDs as * "1, 2, 2, 3, 1". If however, the inner loop would be "walked along" * several times, the resulting walk would be expressed as * "1, 2, 2, 2, 3, 1", which would still be a valid cycle, but NOT a valid * circuit anymore. * * @return bool * @link http://www.proofwiki.org/wiki/Definition:Circuit * @uses self::isCycle() * @uses self::isPath() */ public function isCircuit() { return ($this->isCycle() && $this->isPath()); } /** * checks whether walk is a path (i.e. does not contain any duplicate edges) * * A path Walk is also known as a trail. * * @return bool * @uses self::hasArrayDuplicates() * @link http://www.proofwiki.org/wiki/Definition:Trail */ public function isPath() { return !$this->hasArrayDuplicates($this->walk->getEdges()->getVector()); } /** * checks whether walk contains a cycle (i.e. contains a duplicate vertex) * * A walk that CONTAINS a cycle does not neccessarily have to BE a cycle. * Conversely, a Walk that *is* a cycle, automatically always *contains* a * cycle. * * The following Walk is NOT a cycle, but it *contains* a valid cycle: * * /->4 * | * 1 -> 2 -> 3 -\ * ^ | * \-------/ * * @return bool * @uses self::hasArrayDuplicates() * @see self::isCycle() */ public function hasCycle() { return $this->hasArrayDuplicates($this->walk->getVertices()->getVector()); } /** * checks whether this walk IS a loop (single edge connecting vertex A with vertex A again) * * A loop is the simplest possible cycle. As such, each loop is also a * cycle. Accordingly, every Walk that *is* a loop, automatically also *is* * a cycle and automatically *contains* a loop and automatically *contains* * a cycle. * * The following Walk represents a simple (directed) loop: * * 1--\ * ^ | * \--/ * * @return bool * @uses self::isCycle() * @see self::hasLoop() */ public function isLoop() { return (\count($this->walk->getEdges()) === 1 && $this->isCycle()); } /** * checks whether this walk HAS a loop (single edge connecting vertex A with vertex A again) * * The following Walk is NOT a valid loop, but it contains a valid loop: * * /->3 * | * 1 -> 2 -\ * ^ | * \--/ * * @return bool * @uses AlgorithmLoop::hasLoop() * @see self::isLoop() */ public function hasLoop() { $alg = new AlgorithmLoop($this->walk); return $alg->hasLoop(); } /** * checks whether this walk is a digon (a pair of parallel edges in a multigraph or a pair of antiparallel edges in a digraph) * * A digon is a cycle connecting exactly two distinct vertices with exactly * two distinct edges. * * The following Graph represents a digon in an undirected Graph: * * /--\ * 1 2 * \--/ * * The following Graph represents a digon as a set of antiparallel directed * Edges in a directed Graph: * * 1 -> 2 * ^ | * | | * \----/ * * @return bool * @uses self::hasArrayDuplicates() * @uses self::isCycle() */ public function isDigon() { // exactly 2 edges return (\count($this->walk->getEdges()) === 2 && // no duplicate edges !$this->hasArrayDuplicates($this->walk->getEdges()->getVector()) && // exactly two distinct vertices \count($this->walk->getVertices()->getVerticesDistinct()) === 2 && // this is actually a cycle $this->isCycle()); } /** * checks whether this walk is a triangle (a simple cycle with exactly three distinct vertices) * * The following Graph is a valid directed triangle: * * 1->2->3 * ^ | * \-----/ * * @return bool * @uses self::isCycle() */ public function isTriangle() { // exactly 3 (implicitly distinct) edges return (\count($this->walk->getEdges()) === 3 && // exactly three distinct vertices \count($this->walk->getVertices()->getVerticesDistinct()) === 3 && // this is actually a cycle $this->isCycle()); } /** * check whether this walk is simple * * contains no duplicate/repeated vertices (and thus no duplicate edges either) * other than the starting and ending vertices of cycles. * * A simple Walk is also known as a chain. * * The term "simple walk" is somewhat related to a walk with no cycles. If * a Walk has a cycle, it is not simple - with one single exception: a Walk * that IS a cycle automatically also contains a cycle, but if it contains * no "further" additional cycles, it is considered a simple cycle. * * The following Graph represents a (very) simple Walk: * * 1 -- 2 * * The following Graph IS a cycle and is simple: * * 1 -> 2 * ^ | * \----/ * * The following Graph contains a cycle and is NOT simple: * * /->4 * | * 1 -> 2 -> 3 -\ * ^ | * \-------/ * * The following Graph IS a cycle and thus automatically contains a cycle. * Due to the additional "inner" cycle (loop at vertex 2), it is NOT simple: * * /->3--\ * | | * 1 -> 2 -\ | * ^ ^ | | * | \--/ | * | | * \----------/ * * @return bool * @uses self::isCycle() * @uses self::hasArrayDuplicates() * @see self::hasCycle() */ public function isSimple() { $vertices = $this->walk->getVertices()->getVector(); // ignore starting vertex for cycles as it's always the same as ending vertex if ($this->isCycle()) { unset($vertices[0]); } return !$this->hasArrayDuplicates($vertices); } /** * checks whether walk is hamiltonian (i.e. walk over ALL VERTICES of the graph) * * A hamiltonian Walk is also known as a spanning walk. * * @return bool * @see self::isEulerian() if you want to check for all EDGES instead of VERTICES * @uses self::isArrayContentsEqual() * @link http://en.wikipedia.org/wiki/Hamiltonian_path */ public function isHamiltonian() { $vertices = $this->walk->getVertices()->getVector(); // ignore starting vertex for cycles as it's always the same as ending vertex if ($this->isCycle()) { unset($vertices[0]); } return $this->isArrayContentsEqual($vertices, $this->walk->getGraph()->getVertices()->getVector()); } /** * checks whether walk is eulerian (i.e. a walk over ALL EDGES of the graph) * * @return bool * @see self::isHamiltonian() if you want to check for all VERTICES instead of EDGES * @uses self::isArrayContentsEqual() * @link http://en.wikipedia.org/wiki/Eulerian_path */ public function isEulerian() { return $this->isArrayContentsEqual($this->walk->getEdges()->getVector(), $this->walk->getGraph()->getEdges()->getVector()); } /** * checks whether ths given array contains duplicate identical entries * * @param array $array * @return bool */ private function hasArrayDuplicates($array) { $compare = array(); foreach ($array as $element) { // duplicate element found if (\in_array($element, $compare, true)) { return true; } else { // add element to temporary array to check for duplicates $compare [] = $element; } } return false; } /** * checks whether the contents of array a equals those of array b (ignore keys and order but otherwise strict check) * * @param array $a * @param array $b * @return bool */ private function isArrayContentsEqual($a, $b) { foreach ($b as $one) { $pos = \array_search($one, $a, true); if ($pos === false) { return false; } else { unset($a[$pos]); } } return $a ? false : true; } } ================================================ FILE: src/ResidualGraph.php ================================================ keepNullCapacity = !!$toggle; return $this; } public function setMergeParallelEdges($toggle) { $this->mergeParallelEdges = !!$toggle; return $this; } /** * create residual graph * * @throws UnexpectedValueException if input graph has undirected edges or flow/capacity is not set * @return Graph * @uses Graph::createGraphCloneEdgeless() * @uses Graph::createEdgeClone() * @uses Graph::createEdgeCloneInverted() */ public function createGraph() { $newgraph = $this->graph->createGraphCloneEdgeless(); foreach ($this->graph->getEdges() as $edge) { if (!($edge instanceof EdgeDirected)) { throw new UnexpectedValueException('Edge is undirected'); } $flow = $edge->getFlow(); if ($flow === NULL) { throw new UnexpectedValueException('Flow not set'); } $capacity = $edge->getCapacity(); if ($capacity === NULL) { throw new UnexpectedValueException('Capacity not set'); } // capacity is still available, clone remaining capacity into new edge if ($this->keepNullCapacity || $flow < $capacity) { $newEdge = $newgraph->createEdgeClone($edge)->setFlow(0)->setCapacity($capacity - $flow); if ($this->mergeParallelEdges) { $this->mergeParallelEdges($newEdge); } } // flow is set, clone current flow as capacity for back-flow into new inverted edge (opposite direction) if ($this->keepNullCapacity || $flow > 0) { $newEdge = $newgraph->createEdgeCloneInverted($edge)->setFlow(0)->setCapacity($flow); // if weight is set, use negative weight for back-edges if ($newEdge->getWeight() !== NULL) { $newEdge->setWeight(-$newEdge->getWeight()); } if ($this->mergeParallelEdges) { $this->mergeParallelEdges($newEdge); } } } return $newgraph; } /** * Will merge all edges that are parallel to to given edge * * @param Edge $newEdge */ private function mergeParallelEdges(Edge $newEdge) { $alg = new Parallel($this->graph); $parallelEdges = $alg->getEdgesParallelEdge($newEdge)->getVector(); if (!$parallelEdges) { return; } $mergedCapacity = 0; foreach ($parallelEdges as $parallelEdge) { $mergedCapacity += $parallelEdge->getCapacity(); } $newEdge->setCapacity($newEdge->getCapacity() + $mergedCapacity); foreach ($parallelEdges as $parallelEdge) { $parallelEdge->destroy(); } } } ================================================ FILE: src/Search/Base.php ================================================ direction = $direction; return $this; } protected function getVerticesAdjacent(Vertex $vertex) { if ($this->direction === self::DIRECTION_FORWARD) { return $vertex->getVerticesEdgeTo(); } elseif ($this->direction === self::DIRECTION_REVERSE) { return $vertex->getVerticesEdgeFrom(); } else { return $vertex->getVerticesEdge(); } } /** * get set of all Vertices that can be reached from start vertex * * @return Vertices */ abstract public function getVertices(); } ================================================ FILE: src/Search/BreadthFirst.php ================================================ vertex); // to not add vertices twice in array visited $mark = array($this->vertex->getId() => true); // visited vertices $visited = array(); // keep track of depth $currentDepth = 0; $nodesThisLevel = 1; $nodesNextLevel = 0; do { // get first from queue $t = \array_shift($queue); // save as visited $visited[$t->getId()] = $t; // get next vertices $children = $this->getVerticesAdjacent($t); // track depth $nodesNextLevel = $children->count(); if (--$nodesThisLevel === 0) { if (++$currentDepth > $maxDepth) { return new Vertices($visited); } $nodesThisLevel = $nodesNextLevel; $nodesNextLevel = 0; } // process next vertices foreach ($children->getMap() as $id => $vertex) { // if not "touched" before if (!isset($mark[$id])) { // add to queue $queue[] = $vertex; // and mark $mark[$id] = true; } } // until queue is empty } while ($queue); return new Vertices($visited); } } ================================================ FILE: src/Search/DepthFirst.php ================================================ vertex); while ($vertex = \array_shift($todo)) { if (!isset($visited[$vertex->getId()])) { $visited[$vertex->getId()] = $vertex; foreach (\array_reverse($this->getVerticesAdjacent($vertex)->getMap(), true) as $nextVertex) { $todo[] = $nextVertex; } } } return new Vertices($visited); } } ================================================ FILE: src/ShortestPath/Base.php ================================================ C->D->B" * with a distance (total weight) of 6. * * In graph theory, it is usually assumed that a path to an unreachable vertex * has infinite distance. In the above pictured graph, there's no way path * from A to F, i.e. vertex F is unreachable from vertex A because of the * directed edge "E <- F" pointing in the opposite direction. This library * considers this an Exception instead. So if you're asking for the distance * between A and F, you'll receive an OutOfBoundsException instead. * * In graph theory, it is usually assumed that each vertex has a (pseudo-)path * to itself with a distance of 0. In order to produce reliable, consistent * results, this library considers this (pseudo-)path to be non-existant, i.e. * there's NO "magic" path between A and A. So if you're asking for the distance * between A and A, you'll receive an OutOfBoundsException instead. This allows * us to check hether there's a real path between A and A (cycle via other * vertices) as well as working with loop edges. * * @link http://en.wikipedia.org/wiki/Shortest_path_problem * @link http://en.wikipedia.org/wiki/Tree_%28data_structure%29 * @see ShortestPath\Dijkstra * @see ShortestPath\MooreBellmanFord which also supports negative Edge weights * @see ShortestPath\BreadthFirst with does not consider Edge weights, but only the number of hops */ abstract class Base extends BaseVertex { /** * get walk (path) from start vertex to given end vertex * * @param Vertex $endVertex * @return Walk * @throws OutOfBoundsException if there's no path to the given end vertex * @uses self::getEdgesTo() * @uses Walk::factoryFromEdges() */ public function getWalkTo(Vertex $endVertex) { return Walk::factoryFromEdges($this->getEdgesTo($endVertex), $this->vertex); } /** * get array of edges (path) from start vertex to given end vertex * * @param Vertex $endVertex * @throws OutOfBoundsException if there's no path to the given end vertex * @return Edges * @uses self::getEdges() * @uses self::getEdgesToInternal() */ public function getEdgesTo(Vertex $endVertex) { return $this->getEdgesToInternal($endVertex, $this->getEdges()); } /** * get array of edges (path) from start vertex to given end vertex * * @param Vertex $endVertex * @param Edges|Edge[] $edges set or array of all input edges to operate on * @throws OutOfBoundsException if there's no path to the given vertex * @return Edges * @uses self::getEdges() if no edges were given */ protected function getEdgesToInternal(Vertex $endVertex, $edges) { $currentVertex = $endVertex; $path = array(); do { $pre = NULL; // check all edges to search for edge that points TO current vertex foreach ($edges as $edge) { try { // get start point of this edge (fails if current vertex is not its end point) $pre = $edge->getVertexFromTo($currentVertex); $path[] = $edge; $currentVertex = $pre; break; } catch (InvalidArgumentException $ignore) { } // ignore: this edge does not point TO current vertex } if ($pre === NULL) { throw new OutOfBoundsException('No edge leading to vertex'); } } while ($currentVertex !== $this->vertex); return new Edges(\array_reverse($path)); } /** * get sum of weight of given edges * * @param Edges $edges * @return float * @uses Edge::getWeight() */ private function sumEdges(Edges $edges) { $sum = 0; foreach ($edges as $edge) { $sum += $edge->getWeight(); } return $sum; } /** * get set of all Vertices the given start vertex has a path to * * @return Vertices * @uses self::getDistanceMap() */ public function getVertices() { $vertices = array(); $map = $this->getDistanceMap(); foreach ($this->vertex->getGraph()->getVertices()->getMap() as $vid => $vertex) { if (isset($map[$vid])) { $vertices[$vid] = $vertex; } } return new Vertices($vertices); } /** * checks whether there's a path from this start vertex to given end vertex * * @param Vertex $vertex * @return bool * @uses self::getEdgesTo() */ public function hasVertex(Vertex $vertex) { try { $this->getEdgesTo($vertex); } catch (OutOfBoundsException $e) { return false; } return true; } /** * get map of vertex IDs to distance * * @return float[] * @uses self::getEdges() * @uses self::getEdgesToInternal() * @uses self::sumEdges() */ public function getDistanceMap() { $edges = $this->getEdges(); $ret = array(); foreach ($this->vertex->getGraph()->getVertices()->getMap() as $vid => $vertex) { try { $ret[$vid] = $this->sumEdges($this->getEdgesToInternal($vertex, $edges)); } catch (OutOfBoundsException $ignore) { } // ignore vertices that can not be reached } return $ret; } /** * get distance (sum of weights) between start vertex and given end vertex * * @param Vertex $endVertex * @return float * @throws OutOfBoundsException if there's no path to the given end vertex * @uses self::getEdgesTo() * @uses self::sumEdges() */ public function getDistance(Vertex $endVertex) { return $this->sumEdges($this->getEdgesTo($endVertex)); } /** * create new resulting graph with only edges on shortest path * * The resulting Graph will always represent a tree with the start vertex * being the root vertex. * * For example considering the following input Graph with equal weights on * each edge: * * A----->F * / \ ^ * / \ / * / \ / * | E * | \ * | \ * B--->C<---D * * The resulting shortest path tree Graph will look like this: * * A----->F * / \ * / \ * / \ * | E * | \ * | \ * B--->C D * * Or by just arranging the Vertices slightly different: * * A * /|\ * / | \ * B E \->F * / | * C<-/ D * * @return Graph * @uses self::getEdges() * @uses Graph::createGraphCloneEdges() */ public function createGraph() { return $this->vertex->getGraph()->createGraphCloneEdges($this->getEdges()); } /** * get cheapest edges (lowest weight) for given map of vertex predecessors * * @param Vertex[] $predecessor * @return Edges * @uses Graph::getVertices() * @uses Vertex::getEdgesTo() * @uses Edges::getEdgeOrder() */ protected function getEdgesCheapestPredecesor(array $predecessor) { $vertices = $this->vertex->getGraph()->getVertices()->getMap(); $edges = array(); foreach ($vertices as $vid => $vertex) { if (isset($predecessor[$vid])) { // get predecor $predecesVertex = $predecessor[$vid]; // get cheapest edge $edges[] = $predecesVertex->getEdgesTo($vertex)->getEdgeOrder(Edges::ORDER_WEIGHT); } } return new Edges($edges); } /** * get all edges on shortest path for this vertex * * @return Edges */ abstract public function getEdges(); } ================================================ FILE: src/ShortestPath/BreadthFirst.php ================================================ getEdgesTo($endVertex)); } /** * get array of edges on the walk for each vertex (vertex ID => array of walk edges) * * @return array[] */ public function getEdgesMap() { $vertexQueue = array(); $edges = array(); // $edges[$this->vertex->getId()] = array(); $vertexCurrent = $this->vertex; $edgesCurrent = array(); do { foreach ($vertexCurrent->getEdgesOut() as $edge) { $vertexTarget = $edge->getVertexToFrom($vertexCurrent); $vid = $vertexTarget->getId(); if (!isset($edges[$vid])) { $vertexQueue[] = $vertexTarget; $edges[$vid] = \array_merge($edgesCurrent, array($edge)); } } // get next from queue $vertexCurrent = \array_shift($vertexQueue); if ($vertexCurrent) { $edgesCurrent = $edges[$vertexCurrent->getId()]; } // untill queue is empty } while ($vertexCurrent); return $edges; } public function getEdgesTo(Vertex $endVertex) { if ($endVertex->getGraph() === $this->vertex->getGraph()) { $map = $this->getEdgesMap(); if (isset($map[$endVertex->getId()])) { return new Edges($map[$endVertex->getId()]); } } throw new OutOfBoundsException('Given target vertex can not be reached from start vertex'); } /** * get map of vertex IDs to distance * * @return float[] * @uses Vertex::hasLoop() */ public function getDistanceMap() { $ret = array(); foreach ($this->getEdgesMap() as $vid => $edges) { $ret[$vid] = (float)\count($edges); } return $ret; } /** * get array of all target vertices this vertex has a path to * * @return Vertices * @uses self::getEdgesMap() */ public function getVertices() { $ret = array(); $graph = $this->vertex->getGraph(); foreach (\array_keys($this->getEdgesMap()) as $vid) { $ret[$vid] = $graph->getVertex($vid); } return new Vertices($ret); } public function getEdges() { $ret = array(); foreach ($this->getEdgesMap() as $edges) { foreach ($edges as $edge) { if (!\in_array($edge, $ret, true)) { $ret[] = $edge; } } } return new Edges($ret); } } ================================================ FILE: src/ShortestPath/Dijkstra.php ================================================ vertex->getId()] = INF; // just to get the cheapest vertex in the correct order $cheapestVertex = new SplPriorityQueue(); $cheapestVertex->insert($this->vertex, 0); // predecessor $predecesVertexOfCheapestPathTo = Array(); $predecesVertexOfCheapestPathTo[$this->vertex->getId()] = $this->vertex; // mark vertices when their cheapest path has been found $usedVertices = Array(); $isFirst = true; // Repeat until all vertices have been marked $totalCountOfVertices = \count($this->vertex->getGraph()->getVertices()); for ($i = 0; $i < $totalCountOfVertices; ++$i) { $currentVertex = NULL; $currentVertexId = NULL; $isEmpty = false; do { // if the priority queue is empty there are isolated vertices, but the algorithm visited all other vertices if ($cheapestVertex->isEmpty()) { $isEmpty = true; break; } // Get cheapest unmarked vertex $currentVertex = $cheapestVertex->extract(); $currentVertexId = $currentVertex->getId(); // Vertices can be in the priority queue multiple times, with different path costs (if vertex is already marked, this is an old unvalid entry) } while (isset($usedVertices[$currentVertexId])); // catch "algorithm ends" condition if ($isEmpty) { break; } if ($isFirst) { $isFirst = false; } else { // mark this vertex $usedVertices[$currentVertexId] = true; } // check for all edges of current vertex if there is a cheaper path (or IN OTHER WORDS: Add reachable nodes from currently added node and refresh the current possible distances) foreach ($currentVertex->getEdgesOut() as $edge) { $weight = $edge->getWeight(); if ($weight < 0) { throw new UnexpectedValueException('Djkstra not supported for negative weights - Consider using MooreBellmanFord'); } $targetVertex = $edge->getVertexToFrom($currentVertex); $targetVertexId = $targetVertex->getId(); // if the targetVertex is marked, the cheapest path for this vertex has already been found (no negative edges) { if (!isset($usedVertices[$targetVertexId])) { // calculate new cost to vertex $newCostsToTargetVertex = $totalCostOfCheapestPathTo[$currentVertexId] + $weight; if (\is_infinite($newCostsToTargetVertex)) { $newCostsToTargetVertex = $weight; } if ((!isset($predecesVertexOfCheapestPathTo[$targetVertexId])) // is the new path cheaper? || $totalCostOfCheapestPathTo[$targetVertexId] > $newCostsToTargetVertex){ // Not an update, just an new insert with lower cost $cheapestVertex->insert($targetVertex, - $newCostsToTargetVertex); // so the lowest cost will be extraced first // and higher cost will be skipped during extraction // update/set costs found with the new connection $totalCostOfCheapestPathTo[$targetVertexId] = $newCostsToTargetVertex; // update/set predecessor vertex from the new connection $predecesVertexOfCheapestPathTo[$targetVertexId] = $currentVertex; } } } } if ($totalCostOfCheapestPathTo[$this->vertex->getId()] === INF) { unset($predecesVertexOfCheapestPathTo[$this->vertex->getId()]); } // algorithm is done, return resulting edges return $this->getEdgesCheapestPredecesor($predecesVertexOfCheapestPathTo); } } ================================================ FILE: src/ShortestPath/MooreBellmanFord.php ================================================ getVerticesTarget() as $toVertex) { $fromVertex = $edge->getVertexFromTo($toVertex); // If the fromVertex already has a path if (isset($totalCostOfCheapestPathTo[$fromVertex->getId()])) { // New possible costs of this path $newCost = $totalCostOfCheapestPathTo[$fromVertex->getId()] + $edge->getWeight(); if (\is_infinite($newCost)) { $newCost = $edge->getWeight() + 0; } // No path has been found yet if (!isset($totalCostOfCheapestPathTo[$toVertex->getId()]) // OR this path is cheaper than the old path || $totalCostOfCheapestPathTo[$toVertex->getId()] > $newCost){ $changed = $toVertex; $totalCostOfCheapestPathTo[$toVertex->getId()] = $newCost; $predecessorVertexOfCheapestPathTo[$toVertex->getId()] = $fromVertex; } } } } return $changed; } /** * Calculate the Moore-Bellman-Ford-Algorithm and get all edges on shortest path for this vertex * * @return Edges * @throws NegativeCycleException if there is a negative cycle */ public function getEdges() { // start node distance, add placeholder weight $totalCostOfCheapestPathTo = array($this->vertex->getId() => INF); // predecessor $predecessorVertexOfCheapestPathTo = array($this->vertex->getId() => $this->vertex); // the usal algorithm says we repeat (n-1) times. // but because we also want to check for loop edges on the start vertex, // we have to add an additional step: $numSteps = \count($this->vertex->getGraph()->getVertices()); $edges = $this->vertex->getGraph()->getEdges(); $changed = true; for ($i = 0; $i < $numSteps && $changed; ++$i) { $changed = $this->bigStep($edges, $totalCostOfCheapestPathTo, $predecessorVertexOfCheapestPathTo); } // no cheaper edge to start vertex found => remove placeholder weight if ($totalCostOfCheapestPathTo[$this->vertex->getId()] === INF) { unset($predecessorVertexOfCheapestPathTo[$this->vertex->getId()]); } // algorithm is done, build graph $returnEdges = $this->getEdgesCheapestPredecesor($predecessorVertexOfCheapestPathTo); // Check for negative cycles (only if last step didn't already finish anyway) // something is still changing... if ($changed && $changed = $this->bigStep($edges, $totalCostOfCheapestPathTo, $predecessorVertexOfCheapestPathTo)) { $cycle = Walk::factoryCycleFromPredecessorMap($predecessorVertexOfCheapestPathTo, $changed, Edges::ORDER_WEIGHT); throw new NegativeCycleException('Negative cycle found', 0, NULL, $cycle); } return $returnEdges; } /** * get negative cycle * * @return Walk * @throws UnderflowException if there's no negative cycle */ public function getCycleNegative() { try { $this->getEdges(); } catch (NegativeCycleException $e) { return $e->getCycle(); } throw new UnderflowException('No cycle found'); } } ================================================ FILE: src/Symmetric.php ================================================ b there's also an edge b->a) * * @return bool * @uses Graph::getEdges() * @uses EdgeDirected::getVertexStart() * @uses EdgeDirected::getVertedEnd() * @uses Vertex::hasEdgeTo() */ public function isSymmetric() { // check all edges foreach ($this->graph->getEdges() as $edge) { // only check directed edges (undirected ones are symmetric by definition) if ($edge instanceof EdgeDirected) { // check if end also has an edge to start if (!$edge->getVertexEnd()->hasEdgeTo($edge->getVertexStart())) { return false; } } } return true; } } ================================================ FILE: src/TopologicalSort.php ================================================ graph->getVertices()->getVector()) as $top) { assert($top instanceof Vertex); $tid = $top->getId(); if (!isset($visited[$tid])) { // don't examine if already found \array_push($stack, $top); } while ($stack) { $node = \end($stack); assert($node instanceof Vertex); $nid = $node->getId(); $visited[$nid] = false; // temporary mark $found = false; // new children found during visit to this node // find the next node to visit foreach (\array_reverse($node->getVerticesEdgeTo()->getVector()) as $child) { assert($child instanceof Vertex); $cid = $child->getId(); if (!isset($visited[$cid])) { // found a new node - push it onto the stack \array_push($stack, $child); $found = true; // move onto the new node break; } else if ($visited[$cid] === false) { // temporary mark => not a DAG throw new UnexpectedValueException('Not a DAG'); } } if (!$found) { \array_pop($stack); // no new children found - we're done with this node $visited[$nid] = true; // mark as visited \array_push($output, $node); // add to results } } } return new Vertices(\array_reverse($output, true)); } } ================================================ FILE: src/TransposeGraph.php ================================================ graph->createGraphCloneEdgeless(); foreach ($this->graph->getEdges() as $edge) { if (!($edge instanceof EdgeDirected)) { throw new UnexpectedValueException('Edge is undirected'); } $newgraph->createEdgeCloneInverted($edge); } return $newgraph; } } ================================================ FILE: src/TravelingSalesmanProblem/Base.php ================================================ getGraph()->createGraphCloneEdges($this->getEdges()); } /** * get graph this algorithm operates on * * @return Graph */ abstract protected function getGraph(); /** * get start vertex this algorithm starts on * * @return Vertex */ abstract protected function getVertexStart(); /** * get (first) best circle connecting all vertices * * @return Walk * @uses self::getEdges() * @uses self::getVertexStart() * @uses Walk::factoryCycleFromEdges() */ public function getCycle() { return Walk::factoryCycleFromEdges($this->getEdges(), $this->getVertexStart()); } public function getWeight() { $weight = 0; foreach ($this->getEdges() as $edge) { $weight += $edge->getWeight(); } return $weight; } /** * get array of edges connecting all vertices in a circle * * @return Edges */ abstract public function getEdges(); } ================================================ FILE: src/TravelingSalesmanProblem/Bruteforce.php ================================================ graph = $graph; } /** * explicitly set upper limit to use for branch-and-bound * * this method can be used to optimize the algorithm by providing an upper * bound of when to stop branching any further. * * @param float $limit * @return self $this (chainable) */ public function setUpperLimit($limit) { $this->upperLimit = $limit; return $this; } /** * automatically sets upper limit to use for branch-and-bound from the MST heuristic * * @return self $this (chainable) * @uses AlgorithmTspMst */ public function setUpperLimitMst() { $alg = new AlgorithmTspMst($this->graph); $this->upperLimit = $alg->getWeight(); return $this; } protected function getVertexStart() { // actual start doesn't really matter as we're only considering complete graphs here return $this->graph->getVertices()->getVertexFirst(); } protected function getGraph() { return $this->graph; } /** * get resulting (first) best circle of edges connecting all vertices * * @throws \Exception on error * @return Edges */ public function getEdges() { $this->numEdges = \count($this->graph->getVertices()); if ($this->numEdges < 3) { throw new UnderflowException('Needs at least 3 vertices'); } // numEdges 3-12 should work $this->bestWeight = $this->upperLimit; $this->startVertex = $this->getVertexStart(); $result = $this->step($this->startVertex, 0, array(), array() ); if ($result === NULL) { throw new UnexpectedValueException('No resulting solution for TSP found'); } return new Edges($result); } /** * * @param Vertex $vertex current point-of-view * @param number $totalWeight total weight (so far) * @param bool[] $visitedVertices * @param Edge[] $visitedEdges * @return Edge[]|null */ private function step(Vertex $vertex, $totalWeight, array $visitedVertices, array $visitedEdges) { // stop recursion if best result is exceeded (branch and bound) if ($this->branchAndBound && $this->bestWeight !== NULL && $totalWeight >= $this->bestWeight) { return NULL; } // kreis geschlossen am Ende if ($vertex === $this->startVertex && \count($visitedEdges) === $this->numEdges) { // new best result $this->bestWeight = $totalWeight; return $visitedEdges; } // only visit each vertex once if (isset($visitedVertices[$vertex->getId()])) { return NULL; } $visitedVertices[$vertex->getId()] = true; $bestResult = NULL; // weiter verzweigen in alle vertices foreach ($vertex->getEdgesOut() as $edge) { // get target vertex of this edge $target = $edge->getVertexToFrom($vertex); $weight = $edge->getWeight(); if ($weight < 0) { throw new UnexpectedValueException('Edge with negative weight "' . $weight . '" not supported'); } $result = $this->step($target, $totalWeight + $weight, $visitedVertices, \array_merge($visitedEdges, array($edge)) ); // new result found if ($result !== NULL) { // branch and bound enabled (default): returned result MUST be the new best result if($this->branchAndBound || // this is the first result, just use it anyway $bestResult === NULL || // this is the new best result $this->sumEdges($result) < $this->sumEdges($bestResult)){ $bestResult = $result; } } } return $bestResult; } /** * get sum of weight of given edges * * no need to optimize this further, as it's only evaluated if branchAndBound is disabled and * there's no valid reason why anybody would want to do so. * * @param Edge[] $edges * @return float */ private function sumEdges(array $edges) { $sum = 0; foreach ($edges as $edge) { $sum += $edge->getWeight(); } return $sum; } } ================================================ FILE: src/TravelingSalesmanProblem/MinimumSpanningTree.php ================================================ graph = $inputGraph; } protected function getVertexStart() { return $this->graph->getVertices()->getVertexFirst(); } protected function getGraph() { return $this->graph; } /** * @return Edges */ public function getEdges() { $returnEdges = array(); // Create minimum spanning tree $minimumSpanningTreeAlgorithm = new MstKruskal($this->graph); $minimumSpanningTree = $minimumSpanningTreeAlgorithm->createGraph(); $alg = new SearchDepthFirst($minimumSpanningTree->getVertices()->getVertexFirst()); // Depth first search in minmum spanning tree (for the eulerian path) $startVertex = NULL; $oldVertex = NULL; // connect vertices in order of the depth first search foreach ($alg->getVertices() as $vertex) { // get vertex from the original graph (not from the depth first search) $vertex = $this->graph->getVertex($vertex->getId()); // need to clone the edge from the original graph, therefore i need the original edge if ($startVertex === NULL) { $startVertex = $vertex; } else { // get edge(s) to clone, multiple edges are possible (returns an array if undirected edge) assert($oldVertex !== null); $returnEdges[] = $oldVertex->getEdgesTo($vertex)->getEdgeFirst(); } $oldVertex = $vertex; } // connect last vertex with start vertex // multiple edges are possible (returns an array if undirected edge) assert($startVertex !== null && $oldVertex !== null); $returnEdges[] = $oldVertex->getEdgesTo($startVertex)->getEdgeFirst(); return new Edges($returnEdges); } } ================================================ FILE: src/TravelingSalesmanProblem/NearestNeighbor.php ================================================ vertex = $startVertex; } protected function getVertexStart() { return $this->vertex; } protected function getGraph() { return $this->vertex->getGraph(); } /** * @return Edges */ public function getEdges() { $returnEdges = array(); $n = \count($this->vertex->getGraph()->getVertices()); $vertex = $nextVertex = $this->vertex; $visitedVertices = array($vertex->getId() => true); for ($i = 0; $i < $n - 1; ++$i, // n-1 steps (spanning tree) $vertex = $nextVertex) { // get all edges from the aktuel vertex $edges = $vertex->getEdgesOut(); $sortedEdges = new SplPriorityQueue(); // sort the edges foreach ($edges as $edge) { $sortedEdges->insert($edge, - $edge->getWeight()); } // Untill first is found: get cheepest edge foreach ($sortedEdges as $edge) { // Get EndVertex of this edge $nextVertex = $edge->getVertexToFrom($vertex); // is unvisited if (!isset($visitedVertices[$nextVertex->getId()])) { break; } } // check if there is a way i can use if (isset($visitedVertices[$nextVertex->getId()])) { throw new UnexpectedValueException('Graph is not complete - can\'t find an edge to unconnected vertex'); } $visitedVertices[$nextVertex->getId()] = TRUE; // clone edge in new Graph assert(isset($edge)); $returnEdges[] = $edge; } // check if there is a way from end edge to start edge // get first connecting edge // connect the last vertex with the start vertex $returnEdges[] = $vertex->getEdgesTo($this->vertex)->getEdgeFirst(); return new Edges($returnEdges); } } ================================================ FILE: src/Tree/Base.php ================================================ degree = new Degree($graph); } /** * checks whether the given graph is actually a tree * * @return bool */ abstract public function isTree(); /** * checks if the given $vertex is a leaf (outermost vertext) * * leaf vertex is also known as leaf node, external node or terminal node * * @param Vertex $vertex * @return bool */ abstract public function isVertexLeaf(Vertex $vertex); /** * checks if the given $vertex is an internal vertex (somewhere in the "middle" of the tree) * * internal vertex is also known as inner node (inode) or branch node * * @param Vertex $vertex * @return bool */ abstract public function isVertexInternal(Vertex $vertex); /** * get array of leaf vertices (outermost vertices with no children) * * @return Vertices * @uses Graph::getVertices() * @uses self::isVertexLeaf() */ public function getVerticesLeaf() { return $this->graph->getVertices()->getVerticesMatch(array($this, 'isVertexLeaf')); } /** * get array of internal vertices * * @return Vertices * @uses Graph::getVertices() * @uses self::isVertexInternal() */ public function getVerticesInternal() { return $this->graph->getVertices()->getVerticesMatch(array($this, 'isVertexInternal')); } } ================================================ FILE: src/Tree/BaseDirected.php ================================================ B * \ * \--> C * * - Alternative InTree implementation where Edges "point towards" root Vertex * * ROOT * ^ ^ * / \ * A B * ^ * \ * C * * It's your choice on how to direct the edges, but make sure they all point in * the "same direction", or it will not be a valid tree anymore. However your * decision may be, in the above example, ROOT is always the root Vertex, * B is the parent of "C" and A, B are the children of ROOT. * * For performance reasons, except for `isTree()`, none of the below methods * check if the given Graph is actually a valid tree. So make sure to verify * `isTree()` returns `true` before relying on any of the methods. * * @link http://en.wikipedia.org/wiki/Arborescence_%28graph_theory%29 * @link http://en.wikipedia.org/wiki/Spaghetti_stack * @see OutTree usual implementation where Edges "point away" from root vertex * @see InTree alternative implementation where Edges "point towards" root vertex */ abstract class BaseDirected extends Tree { /** * get root vertex for this in-tree * * @return Vertex * @throws UnderflowException if given graph is empty or no possible root candidate was found (check isTree()!) * @uses Graph::getVertices() to iterate over each Vertex * @uses self::isVertexPossibleRoot() to check if any Vertex is a possible root candidate */ public function getVertexRoot() { foreach ($this->graph->getVertices() as $vertex) { if ($this->isVertexPossibleRoot($vertex)) { return $vertex; } } throw new UnderflowException('No possible root found. Either empty graph or no Vertex with proper degree found.'); } /** * checks if this is a tree * * @return bool * @uses self::getVertexRoot() to get root Vertex to start search from * @uses self::getVerticesSubtree() to count number of vertices connected to root */ public function isTree() { try { $root = $this->getVertexRoot(); } catch (UnderflowException $e) { return false; } catch (UnexpectedValueException $e) { return false; } try { $num = \count($this->getVerticesSubtree($root)); } catch (UnexpectedValueException $e) { return false; } // check number of vertices reachable from root should match total number of vertices return ($num === \count($this->graph->getVertices())); } /** * get parent vertex for given $vertex * * @param Vertex $vertex * @throws UnderflowException if vertex has no parent (is a root vertex) * @throws UnexpectedValueException if vertex has more than one possible parent (check isTree()!) * @return Vertex * @uses self::getVerticesParents() to get array of parent vertices */ public function getVertexParent(Vertex $vertex) { $parents = $this->getVerticesParent($vertex); if (\count($parents) !== 1) { if ($parents->isEmpty()) { throw new UnderflowException('No parents for given vertex found'); } else { throw new UnexpectedValueException('More than one parent'); } } return $parents->getVertexFirst(); } /** * get array of child vertices for given $vertex * * @param Vertex $vertex * @return Vertices * @throws UnexpectedValueException if the given $vertex contains invalid / parallel edges (check isTree()!) */ abstract public function getVerticesChildren(Vertex $vertex); /** * internal helper to get all parents vertices * * a valid tree vertex only ever has a single parent, except for the root, * which has none. * * @param Vertex $vertex * @return Vertices * @throws UnexpectedValueException if the given $vertex contains invalid / parallel edges (check isTree()!) */ abstract protected function getVerticesParent(Vertex $vertex); /** * check if given vertex is a possible root (i.e. has no parent) * * @param Vertex $vertex * @return bool * @uses self::getVerticesParent() */ protected function isVertexPossibleRoot(Vertex $vertex) { return (\count($this->getVerticesParent($vertex)) === 0); } /** * checks if the given $vertex is a leaf (outermost vertex with no children) * * @param Vertex $vertex * @return bool * @uses self::getVerticesChildren() to check given vertex has no children */ public function isVertexLeaf(Vertex $vertex) { return (\count($this->getVerticesChildren($vertex)) === 0); } /** * checks if the given $vertex is an internal vertex (has children and is not root) * * @param Vertex $vertex * @return bool * @uses self::getVerticesParent() to check given vertex has a parent (is not root) * @uses self::getVerticesChildren() to check given vertex has children (is not a leaf) * @see \Graphp\Algorithms\Tree\Base::isVertexInternal() for more information */ public function isVertexInternal(Vertex $vertex) { return (!$this->getVerticesParent($vertex)->isEmpty() && !$this->getVerticesChildren($vertex)->isEmpty()); } /** * get degree of tree (maximum number of children) * * @return int * @throws UnderflowException for empty graphs * @uses Graph::getVertices() * @uses self::getVerticesChildren() */ public function getDegree() { $max = null; foreach ($this->graph->getVertices() as $vertex) { $num = \count($this->getVerticesChildren($vertex)); if ($max === null || $num > $max) { $max = $num; } } if ($max === null) { throw new UnderflowException('No vertices found'); } return $max; } /** * get depth of given $vertex (number of edges between root vertex) * * root has depth zero * * @param Vertex $vertex * @return int * @throws UnderflowException for empty graphs * @throws UnexpectedValueException if there's no path to root node (check isTree()!) * @uses self::getVertexRoot() * @uses self::getVertexParent() for each step */ public function getDepthVertex(Vertex $vertex) { $root = $this->getVertexRoot(); $depth = 0; while ($vertex !== $root) { $vertex = $this->getVertexParent($vertex); ++$depth; } return $depth; } /** * get height of this tree (longest downward path to a leaf) * * a single vertex graph has height zero * * @return int * @throws UnderflowException for empty graph * @uses self::getVertexRoot() * @uses self::getHeightVertex() */ public function getHeight() { return $this->getHeightVertex($this->getVertexRoot()); } /** * get height of given vertex (longest downward path to a leaf) * * leafs has height zero * * @param Vertex $vertex * @return int * @uses self::getVerticesChildren() to get children of given vertex * @uses self::getHeightVertex() to recurse into sub-children */ public function getHeightVertex(Vertex $vertex) { $max = 0; foreach ($this->getVerticesChildren($vertex) as $vertex) { $height = $this->getHeightVertex($vertex) + 1; if ($height > $max) { $max = $height; } } return $max; } /** * get all vertices that are in the subtree of the given $vertex (which IS included) * * root vertex will return the whole tree, leaf vertices will only return themselves * * @param Vertex $vertex * @throws UnexpectedValueException if there are invalid edges (check isTree()!) * @return Vertices * @uses self::getVerticesSubtreeRecursive() * @uses self::getVerticesSubtree() */ public function getVerticesSubtree(Vertex $vertex) { $vertices = array(); $this->getVerticesSubtreeRecursive($vertex, $vertices); return new Vertices($vertices); } /** * helper method to get recursively get subtree for given $vertex * * @param Vertex $vertex * @param Vertex[] $vertices * @throws UnexpectedValueException if multiple links were found to the given edge (check isTree()!) * @uses self::getVerticesChildren() * @uses self::getVerticesSubtreeRecursive() to recurse into subtrees */ private function getVerticesSubtreeRecursive(Vertex $vertex, array &$vertices) { $vid = $vertex->getId(); if (isset($vertices[$vid])) { throw new UnexpectedValueException('Multiple links found'); } $vertices[$vid] = $vertex; foreach ($this->getVerticesChildren($vertex) as $vertexChild) { $this->getVerticesSubtreeRecursive($vertexChild, $vertices); } } /** * get all vertices below the given $vertex (which is NOT included) * * think of this as the recursive version of getVerticesChildren() * * @param Vertex $vertex * @return Vertices * @throws UnexpectedValueException if there are invalid edges (check isTree()!) * @uses self::getVerticesSubtree() */ public function getVerticesDescendant(Vertex $vertex) { $vertices = $this->getVerticesSubtree($vertex)->getMap(); unset($vertices[$vertex->getId()]); return new Vertices($vertices); } } ================================================ FILE: src/Tree/InTree.php ================================================ getVerticesEdgeFrom(); if ($vertices->hasDuplicates()) { throw new UnexpectedValueException(); } return $vertices; } protected function getVerticesParent(Vertex $vertex) { $vertices = $vertex->getVerticesEdgeTo(); if ($vertices->hasDuplicates()) { throw new UnexpectedValueException(); } return $vertices; } } ================================================ FILE: src/Tree/OutTree.php ================================================ B * \ * \--> C * * also known as arborescence * * @link http://en.wikipedia.org/wiki/Arborescence_%28graph_theory%29 * @see DirectedTree for more information on directed, rooted trees */ class OutTree extends DirectedTree { public function getVerticesChildren(Vertex $vertex) { $vertices = $vertex->getVerticesEdgeTo(); if ($vertices->hasDuplicates()) { throw new UnexpectedValueException(); } return $vertices; } protected function getVerticesParent(Vertex $vertex) { $vertices = $vertex->getVerticesEdgeFrom(); if ($vertices->hasDuplicates()) { throw new UnexpectedValueException(); } return $vertices; } } ================================================ FILE: src/Tree/Undirected.php ================================================ graph->getVertices()->isEmpty()) { return false; } // every vertex can represent a root vertex, so just pick one $root = $this->graph->getVertices()->getVertexFirst(); $vertices = array(); try { $this->getVerticesSubtreeRecursive($root, $vertices, null); } catch (UnexpectedValueException $e) { return false; } return (\count($vertices) === \count($this->graph->getVertices())); } /** * checks if the given $vertex is a leaf (outermost vertex with exactly one edge) * * @param Vertex $vertex * @return bool * @uses Degree::getDegreeVertex() */ public function isVertexLeaf(Vertex $vertex) { return ($this->degree->getDegreeVertex($vertex) === 1); } /** * checks if the given $vertex is an internal vertex (inner vertex with at least 2 edges) * * @param Vertex $vertex * @return bool * @uses Degree::getDegreeVertex() */ public function isVertexInternal(Vertex $vertex) { return ($this->degree->getDegreeVertex($vertex) >= 2); } /** * get subtree for given Vertex and ignore path to "parent" ignoreVertex * * @param Vertex $vertex * @param Vertex[] $vertices * @param Vertex|null $ignore * @throws UnexpectedValueException for cycles or directed edges (check isTree()!) * @uses self::getVerticesNeighbor() * @uses self::getVerticesSubtreeRecursive() to recurse into sub-subtrees */ private function getVerticesSubtreeRecursive(Vertex $vertex, array &$vertices, Vertex $ignore = null) { if (isset($vertices[$vertex->getId()])) { // vertex already visited => must be a cycle throw new UnexpectedValueException('Vertex already visited'); } $vertices[$vertex->getId()] = $vertex; foreach ($this->getVerticesNeighbor($vertex) as $vertexNeighboor) { if ($vertexNeighboor === $ignore) { // ignore source vertex only once $ignore = null; continue; } $this->getVerticesSubtreeRecursive($vertexNeighboor, $vertices, $vertex); } } /** * get neighbor vertices for given start vertex * * @param Vertex $vertex * @return Vertices (might include possible duplicates) * @throws UnexpectedValueException for directed edges * @uses Vertex::getEdges() * @uses Edge::getVertexToFrom() * @see Vertex::getVerticesEdge() */ private function getVerticesNeighbor(Vertex $vertex) { $vertices = array(); foreach ($vertex->getEdges() as $edge) { if (!$edge instanceof EdgeUndirected) { throw new UnexpectedValueException('Directed edge encountered'); } $vertices[] = $edge->getVertexToFrom($vertex); } return new Vertices($vertices); } } ================================================ FILE: src/Weight.php ================================================ set->getEdges() as $edge) { if ($edge->getWeight() !== NULL) { return true; } } return false; } /** * get total weight of graph (sum of weight of all edges) * * edges with no weight assigned will evaluate to weight (int) 0. thus an * unweighted graph (see isWeighted()) will return total weight of (int) 0. * * returned weight can also be negative or (int) 0 if edges have been * assigned a negative weight or a weight of (int) 0. * * @return float total weight * @see self::isWeighted() * @uses Edge::getWeight() */ public function getWeight() { $weight = 0; foreach ($this->set->getEdges() as $edge) { $w = $edge->getWeight(); if ($w !== NULL) { $weight += $w; } } return $weight; } /** * get minimum weight assigned to all edges * * minimum weight is often needed because some algorithms do not support * negative weights or edges with zero weight. * * edges with NO (null) weight will NOT be considered for the minimum weight. * * @return float|NULL minimum edge weight or NULL if graph is not weighted or empty * @uses Edge::getWeight() */ public function getWeightMin() { $min = NULL; foreach ($this->set->getEdges() as $edge) { $weight = $edge->getWeight(); if ($weight !== null && ($min === NULL || $weight < $min)) { $min = $weight; } } return $min; } /** * get total weight of current flow (sum of all edges flow(e) * weight(e)) * * @return float * @see Graph::getWeight() to just get the sum of all edges' weights * @uses Edge::getFlow() * @uses Edge::getWeight() */ public function getWeightFlow() { $sum = 0; foreach ($this->set->getEdges() as $edge) { $sum += $edge->getFlow() * $edge->getWeight(); } return $sum; } } ================================================ FILE: tests/BipartitTest.php ================================================ assertTrue($alg->isBipartit()); $this->assertEquals(array(), $alg->getColors()); $this->assertEquals(array(0 => array(), 1 => array()), $alg->getColorVertices()); } public function testGraphPairIsBipartit() { // 1 -> 2 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $graph->createEdgeDirected($v1, $v2); $alg = new AlgorithmBipartit($graph); $this->assertTrue($alg->isBipartit()); $this->assertEquals(array(1 => 0, 2 => 1), $alg->getColors()); $this->assertEquals(array(0 => array(1 => $v1), 1 => array(2 => $v2)), $alg->getColorVertices()); return $alg; } /** * * @param AlgorithmBipartit $alg * @depends testGraphPairIsBipartit */ public function testGraphPairBipartitGroups(AlgorithmBipartit $alg) { // graph does not have any groups assigned, so its groups are not bipartit $this->assertFalse($alg->isBipartitGroups()); // create a cloned graph with groups assigned according to bipartition $graph = $alg->createGraphGroups(); $this->assertInstanceOf('Graphp\Graph\Graph', $graph); $alg2 = new AlgorithmBipartit($graph); $this->assertTrue($alg2->isBipartitGroups()); } public function testGraphTriangleCycleIsNotBipartit() { // 1 -> 2 --> 3 --> 1 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $v3 = $graph->createVertex(3); $graph->createEdgeDirected($v1, $v2); $graph->createEdgeDirected($v2, $v3); $graph->createEdgeDirected($v3, $v1); $alg = new AlgorithmBipartit($graph); $this->assertFalse($alg->isBipartit()); return $alg; } /** * * @param AlgorithmBipartit $alg * @depends testGraphTriangleCycleIsNotBipartit */ public function testGraphTriangleCycleColorsInvalid(AlgorithmBipartit $alg) { $this->setExpectedException('UnexpectedValueException'); $alg->getColors(); } /** * * @param AlgorithmBipartit $alg * @depends testGraphTriangleCycleIsNotBipartit */ public function testGraphTriangleCycleColorVerticesInvalid(AlgorithmBipartit $alg) { $this->setExpectedException('UnexpectedValueException'); $alg->getColorVertices(); } } ================================================ FILE: tests/CompleteTest.php ================================================ assertTrue($alg->isComplete()); } public function testGraphSingleTrivialK1() { $graph = new Graph(); $graph->createVertex(1); $alg = new AlgorithmComplete($graph); $this->assertTrue($alg->isComplete()); } public function testGraphSimplePairK2() { // 1 -- 2 $graph = new Graph(); $graph->createEdgeUndirected($graph->createVertex(1), $graph->createVertex(2)); $alg = new AlgorithmComplete($graph); $this->assertTrue($alg->isComplete()); } public function testGraphSingleDirectedIsNotComplete() { // 1 -> 2 $graph = new Graph(); $graph->createEdgeDirected($graph->createVertex(1), $graph->createVertex(2)); $alg = new AlgorithmComplete($graph); $this->assertFalse($alg->isComplete()); } public function testAdditionalEdgesToNotAffectCompleteness() { // 1 -> 2 // 1 -- 2 // 2 -> 1 // 1 -> 1 $graph = new Graph(); $graph->createEdgeDirected($graph->createVertex(1), $graph->createVertex(2)); $graph->createEdgeUndirected($graph->getVertex(1), $graph->getVertex(2)); $graph->createEdgeDirected($graph->getVertex(2), $graph->getVertex(1)); $graph->createEdgeDirected($graph->getVertex(1), $graph->getVertex(1)); $alg = new AlgorithmComplete($graph); $this->assertTrue($alg->isComplete()); } } ================================================ FILE: tests/ConnectedComponentsTest.php ================================================ assertEquals(0, $alg->getNumberOfComponents()); $this->assertFalse($alg->isSingle()); $this->assertCount(0, $alg->createGraphsComponents()); } public function testGraphSingleTrivial() { $graph = new Graph(); $graph->createVertex(1); $alg = new AlgorithmConnected($graph); $this->assertEquals(1, $alg->getNumberOfComponents()); $this->assertTrue($alg->isSingle()); $graphs = $alg->createGraphsComponents(); $this->assertCount(1, $graphs); $this->assertGraphEquals($graph, \reset($graphs)); } public function testGraphEdgeDirections() { // 1 -- 2 -> 3 <- 4 $graph = new Graph(); $graph->createEdgeUndirected($graph->createVertex(1), $graph->createVertex(2)); $graph->createEdgeDirected($graph->getVertex(2), $graph->createVertex(3)); $graph->createEdgeDirected($graph->createVertex(4), $graph->getVertex(3)); $alg = new AlgorithmConnected($graph); $this->assertEquals(1, $alg->getNumberOfComponents()); $this->assertTrue($alg->isSingle()); $graphs = $alg->createGraphsComponents(); $this->assertCount(1, $graphs); $this->assertGraphEquals($graph, \reset($graphs)); $this->assertGraphEquals($graph, $alg->createGraphComponentVertex($graph->getVertex(1))); } public function testComponents() { // 1 -- 2, 3 -> 4, 5 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $v3 = $graph->createVertex(3); $v4 = $graph->createVertex(4); $v5 = $graph->createVertex(5); $graph->createEdgeUndirected($v1, $v2); $graph->createEdgeDirected($v3, $v4); $alg = new AlgorithmConnected($graph); $this->assertEquals(3, $alg->getNumberOfComponents()); $this->assertFalse($alg->isSingle()); $graphs = $alg->createGraphsComponents(); $this->assertCount(3, $graphs); $ge = new Graph(); $ge->createEdgeUndirected($ge->createVertex(1), $ge->createVertex(2)); $this->assertGraphEquals($ge, $alg->createGraphComponentVertex($v2)); $ge = new Graph(); $ge->createVertex(5); $this->assertEquals($ge, $alg->createGraphComponentVertex($v5)); } public function testInvalidVertexPassedToAlgorithm() { $graph = new Graph(); $graph2 = new Graph(); $v2 = $graph2->createVertex(12); $alg = new AlgorithmConnected($graph); $this->setExpectedException('InvalidArgumentException'); $alg->createGraphComponentVertex($v2); } } ================================================ FILE: tests/DegreeTest.php ================================================ getDegree(); $this->fail(); } catch (UnderflowException $e) { } try { $alg->getDegreeMin(); $this->fail(); } catch (UnderflowException $e) { } try { $alg->getDegreeMax(); $this->fail(); } catch (UnderflowException $e) { } $this->assertTrue($alg->isRegular()); $this->assertTrue($alg->isBalanced()); } public function testGraphIsolated() { $graph = new Graph(); $graph->createVertex(1); $graph->createVertex(2); $alg = new AlgorithmDegree($graph); $this->assertEquals(0, $alg->getDegree()); $this->assertEquals(0, $alg->getDegreeMin()); $this->assertEquals(0, $alg->getDegreeMax()); $this->assertTrue($alg->isRegular()); $this->assertTrue($alg->isBalanced()); } public function testGraphIrregular() { // 1 -> 2 -> 3 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $v3 = $graph->createVertex(3); $graph->createEdgeDirected($v1, $v2); $graph->createEdgeDirected($v2, $v3); $alg = new AlgorithmDegree($graph); try { $this->assertEquals(0, $alg->getDegree()); $this->fail(); } catch (UnexpectedValueException $e) { } $this->assertEquals(1, $alg->getDegreeMin()); $this->assertEquals(2, $alg->getDegreeMax()); $this->assertFalse($alg->isRegular()); $this->assertFalse($alg->isBalanced()); $this->assertEquals(0, $alg->getDegreeInVertex($v1)); $this->assertEquals(1, $alg->getDegreeOutVertex($v1)); $this->assertEquals(1, $alg->getDegreeVertex($v1)); $this->assertFalse($alg->isVertexIsolated($v1)); $this->assertFalse($alg->isVertexSink($v1)); $this->assertTrue($alg->isVertexSource($v1)); $this->assertEquals(1, $alg->getDegreeInVertex($v2)); $this->assertEquals(1, $alg->getDegreeOutVertex($v2)); $this->assertEquals(2, $alg->getDegreeVertex($v2)); $this->assertFalse($alg->isVertexIsolated($v2)); $this->assertFalse($alg->isVertexSink($v2)); $this->assertFalse($alg->isVertexSource($v2)); $this->assertEquals(1, $alg->getDegreeInVertex($v3)); $this->assertEquals(0, $alg->getDegreeOutVertex($v3)); $this->assertEquals(1, $alg->getDegreeVertex($v3)); $this->assertFalse($alg->isVertexIsolated($v3)); $this->assertTrue($alg->isVertexSink($v3)); $this->assertFalse($alg->isVertexSource($v3)); } } ================================================ FILE: tests/DetectNegativeCycleTest.php ================================================ assertFalse($alg->hasCycleNegative()); return $alg; } /** * * @param DetectNegativeCycle $alg * @depends testNullGraph */ public function testNullGraphHasNoCycle(DetectNegativeCycle $alg) { $this->setExpectedException('UnderflowException'); $alg->getCycleNegative(); } /** * * @param DetectNegativeCycle $alg * @depends testNullGraph */ public function testNullGraphHasNoCycleGraph(DetectNegativeCycle $alg) { $this->setExpectedException('UnderflowException'); $alg->createGraph(); } public function testNegativeLoop() { // 1 --[-1]--> 1 $graph = new Graph(); $v1 = $graph->createVertex(1); $e1 = $graph->createEdgeDirected($v1, $v1)->setWeight(-1); $alg = new DetectNegativeCycle($graph); $this->assertTrue($alg->hasCycleNegative()); $cycle = $alg->getCycleNegative(); $this->assertCount(1, $cycle->getEdges()); $this->assertCount(2, $cycle->getVertices()); $this->assertEquals($e1, $cycle->getEdges()->getEdgeFirst()); $this->assertEquals($v1, $cycle->getVertices()->getVertexFirst()); } public function testNegativeCycle() { // 1 --[-1]--> 2 // ^ | // \---[-2]----/ $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $graph->createEdgeDirected($v1, $v2)->setWeight(-1); $graph->createEdgeDirected($v2, $v1)->setWeight(-2); $alg = new DetectNegativeCycle($graph); $this->assertTrue($alg->hasCycleNegative()); $cycle = $alg->getCycleNegative(); $this->assertCount(2, $cycle->getEdges()); $this->assertCount(3, $cycle->getVertices()); } public function testNegativeUndirectedIsNegativeCycle() { // 1 --[-1]-- 2 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $graph->createEdgeUndirected($v1, $v2)->setWeight(-1); $alg = new DetectNegativeCycle($graph); $this->assertTrue($alg->hasCycleNegative()); $cycle = $alg->getCycleNegative(); $this->assertCount(2, $cycle->getEdges()); $this->assertCount(3, $cycle->getVertices()); } public function testNegativeCycleSubgraph() { // 1 --[1]--> 2 --[1]--> 3 --[1]--> 4 // ^ | // \---[-2]---/ $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $v3 = $graph->createVertex(3); $v4 = $graph->createVertex(4); $graph->createEdgeDirected($v1, $v2)->setWeight(1); $graph->createEdgeDirected($v2, $v3)->setWeight(1); $graph->createEdgeDirected($v3, $v4)->setWeight(1); $graph->createEdgeDirected($v4, $v3)->setWeight(-2); $alg = new DetectNegativeCycle($graph); $this->assertTrue($alg->hasCycleNegative()); $cycle = $alg->getCycleNegative(); $this->assertCount(2, $cycle->getEdges()); $this->assertCount(3, $cycle->getVertices()); $this->assertTrue($cycle->getVertices()->hasVertexId(3)); $this->assertTrue($cycle->getVertices()->hasVertexId(4)); } public function testNegativeComponents() { // 1 -- 2 3 --[-1]--> 4 // ^ | // \---[-2]----/ $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $v3 = $graph->createVertex(3); $v4 = $graph->createVertex(4); $graph->createEdgeUndirected($v1, $v2); $graph->createEdgeDirected($v3, $v4)->setWeight(-1); $graph->createEdgeDirected($v4, $v3)->setWeight(-2); $alg = new DetectNegativeCycle($graph); $this->assertTrue($alg->hasCycleNegative()); $cycle = $alg->getCycleNegative(); $this->assertCount(2, $cycle->getEdges()); $this->assertCount(3, $cycle->getVertices()); $this->assertTrue($cycle->getVertices()->hasVertexId(3)); $this->assertTrue($cycle->getVertices()->hasVertexId(4)); } } ================================================ FILE: tests/DirectedTest.php ================================================ assertFalse($alg->hasDirected()); $this->assertFalse($alg->hasUndirected()); $this->assertFalse($alg->isMixed()); } public function testGraphUndirected() { // 1 -- 2 $graph = new Graph(); $graph->createEdgeUndirected($graph->createVertex(1), $graph->createVertex(2)); $alg = new AlgorithmDirected($graph); $this->assertFalse($alg->hasDirected()); $this->assertTrue($alg->hasUndirected()); $this->assertFalse($alg->isMixed()); } public function testGraphDirected() { // 1 -> 2 $graph = new Graph(); $graph->createEdgeDirected($graph->createVertex(1), $graph->createVertex(2)); $alg = new AlgorithmDirected($graph); $this->assertTrue($alg->hasDirected()); $this->assertFalse($alg->hasUndirected()); $this->assertFalse($alg->isMixed()); } public function testGraphMixed() { // 1 -- 2 -> 3 $graph = new Graph(); $graph->createEdgeUndirected($graph->createVertex(1), $graph->createVertex(2)); $graph->createEdgeDirected($graph->getVertex(2), $graph->createVertex(3)); $alg = new AlgorithmDirected($graph); $this->assertTrue($alg->hasDirected()); $this->assertTrue($alg->hasUndirected()); $this->assertTrue($alg->isMixed()); } } ================================================ FILE: tests/EulerianTest.php ================================================ assertFalse($alg->hasCycle()); } public function testGraphPairHasNoCycle() { // 1 -- 2 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $graph->createEdgeUndirected($v1, $v2); $alg = new AlgorithmEulerian($graph); $this->assertFalse($alg->hasCycle()); } public function testGraphTriangleCycleIsNotBipartit() { // 1 -- 2 -- 3 -- 1 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $v3 = $graph->createVertex(3); $graph->createEdgeUndirected($v1, $v2); $graph->createEdgeUndirected($v2, $v3); $graph->createEdgeUndirected($v3, $v1); $alg = new AlgorithmEulerian($graph); $this->assertTrue($alg->hasCycle()); } } ================================================ FILE: tests/FlowTest.php ================================================ assertFalse($alg->hasFlow()); $this->assertEquals(0, $alg->getBalance()); $this->assertTrue($alg->isBalancedFlow()); return $graph; } public function testEdgeWithZeroFlowIsConsideredFlow() { // 1 -> 2 $graph = new Graph(); $graph->createEdgeDirected($graph->createVertex(1), $graph->createVertex(2))->setFlow(0); $alg = new AlgorithmFlow($graph); $this->assertTrue($alg->hasFlow()); $this->assertEquals(0, $alg->getFlowVertex($graph->getVertex(1))); $this->assertEquals(0, $alg->getFlowVertex($graph->getVertex(2))); } /** * * @param Graph $graph * @depends testGraphEmpty */ public function testGraphSimple(Graph $graph) { // 1 -> 2 $graph->createEdgeDirected($graph->createVertex(1), $graph->createVertex(2)); $alg = new AlgorithmFlow($graph); $this->assertFalse($alg->hasFlow()); $this->assertEquals(0, $alg->getFlowVertex($graph->getVertex(1))); $this->assertEquals(0, $alg->getFlowVertex($graph->getVertex(2))); return $graph; } /** * * @param Graph $graph * @depends testGraphSimple */ public function testGraphWithUnweightedEdges(Graph $graph) { // additional flow edge: 2 -> 3 $graph->createEdgeDirected($graph->getVertex(2), $graph->createVertex(3))->setFlow(10); $alg = new AlgorithmFlow($graph); $this->assertTrue($alg->hasFlow()); $this->assertEquals(10, $alg->getFlowVertex($graph->getVertex(2))); $this->assertEquals(-10, $alg->getFlowVertex($graph->getVertex(3))); } public function testGraphBalance() { // source(+100) -> sink(-10) $graph = new Graph(); $graph->createVertex('source')->setBalance(100); $graph->createVertex('sink')->setBalance(-10); $alg = new AlgorithmFlow($graph); $this->assertEquals(90, $alg->getBalance()); $this->assertFalse($alg->isBalancedFlow()); } public function testVertexWithUndirectedEdgeHasInvalidFlow() { // 1 -- 2 $graph = new Graph(); $graph->createEdgeUndirected($graph->createVertex(1), $graph->createVertex(2))->setFlow(10); $alg = new AlgorithmFlow($graph); $this->setExpectedException('UnexpectedValueException'); $alg->getFlowVertex($graph->getVertex(1)); } } ================================================ FILE: tests/GroupsTest.php ================================================ assertEquals(array(), $alg->getGroups()); $this->assertEquals(0, $alg->getNumberOfGroups()); $this->assertTrue($alg->getVerticesGroup(123)->isEmpty()); $this->assertFalse($alg->isBipartit()); } public function testGraphPairIsBipartit() { // 1 -> 2 $graph = new Graph(); $v1 = $graph->createVertex(1)->setGroup(1); $v2 = $graph->createVertex(2)->setGroup(2); $graph->createEdgeDirected($v1, $v2); $alg = new AlgorithmGroups($graph); $this->assertEquals(array(1, 2), $alg->getGroups()); $this->assertEquals(2, $alg->getNumberOfGroups()); $this->assertTrue($alg->getVerticesGroup(123)->isEmpty()); $this->assertEquals(array(1 => $v1), $alg->getVerticesGroup(1)->getMap()); $this->assertTrue($alg->isBipartit()); } public function testGraphTriangleCycleIsNotBipartit() { // 1 -> 2 -> 3 -> 1 $graph = new Graph(); $v1 = $graph->createVertex(1)->setGroup(1); $v2 = $graph->createVertex(2)->setGroup(2); $v3 = $graph->createVertex(3)->setGroup(1); $graph->createEdgeDirected($v1, $v2); $graph->createEdgeDirected($v2, $v3); $graph->createEdgeDirected($v3, $v1); $alg = new AlgorithmGroups($graph); $this->assertEquals(array(1, 2), $alg->getGroups()); $this->assertEquals(2, $alg->getNumberOfGroups()); $this->assertTrue($alg->getVerticesGroup(123)->isEmpty()); $this->assertEquals(array(1 => $v1, 3 => $v3), $alg->getVerticesGroup(1)->getMap()); $this->assertFalse($alg->isBipartit()); } } ================================================ FILE: tests/LoopTest.php ================================================ assertFalse($alg->hasLoop()); } public function testGraphWithMixedCircuitIsNotConsideredLoop() { // 1 -> 2 // 2 -- 1 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $graph->createEdgeDirected($v1, $v2); $graph->createEdgeUndirected($v2, $v1); $alg = new AlgorithmLoop($graph); $this->assertFalse($alg->hasLoop()); $this->assertFalse($alg->hasLoopVertex($v1)); $this->assertFalse($alg->hasLoopVertex($v2)); } public function testGraphUndirectedLoop() { // 1 -- 1 $graph = new Graph(); $graph->createEdgeUndirected($graph->createVertex(1), $v1 = $graph->getVertex(1)); $alg = new AlgorithmLoop($graph); $this->assertTrue($alg->hasLoop()); $this->assertTrue($alg->hasLoopVertex($v1)); } public function testGraphDirectedLoop() { // 1 -> 1 $graph = new Graph(); $graph->createEdgeDirected($graph->createVertex(1), $v1 = $graph->getVertex(1)); $alg = new AlgorithmLoop($graph); $this->assertTrue($alg->hasLoop()); $this->assertTrue($alg->hasLoopVertex($v1)); } } ================================================ FILE: tests/MaxFlow/EdmondsKarpTest.php ================================================ 1 $graph = new Graph(); $v0 = $graph->createVertex(0); $v1 = $graph->createVertex(1); $graph->createEdgeDirected($v0, $v1)->setCapacity(10); // 0 -[10/10]-> 1 $alg = new AlgorithmMaxFlowEdmondsKarp($v0, $v1); $this->assertEquals(10, $alg->getFlowMax()); } public function testEdgesMultiplePaths() { // 0 -[0/5]---------> 1 // | ^ // | | // \-[0/7]-> 2 -[0/9]-/ $graph = new Graph(); $v0 = $graph->createVertex(0); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $graph->createEdgeDirected($v0, $v1)->setCapacity(5); $graph->createEdgeDirected($v0, $v2)->setCapacity(7); $graph->createEdgeDirected($v2, $v1)->setCapacity(9); // 0 -[5/5]---------> 1 // | ^ // | | // \-[7/7]-> 2 -[7/9]-/ $alg = new AlgorithmMaxFlowEdmondsKarp($v0, $v1); $this->assertEquals(12, $alg->getFlowMax()); } public function testEdgesMultiplePathsTwo() { // 0 -[0/5]---------> 1-[0/10]-> 3 // | ^ | // | | | // \-[0/7]-> 2 -[0/9]-/ | // ^ | // \---[0/2]-----------/ $graph = new Graph(); $v0 = $graph->createVertex(0); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $v3 = $graph->createVertex(3); $graph->createEdgeDirected($v0, $v1)->setCapacity(5); $graph->createEdgeDirected($v0, $v2)->setCapacity(7); $graph->createEdgeDirected($v2, $v1)->setCapacity(9); $graph->createEdgeDirected($v1, $v3)->setCapacity(10); $graph->createEdgeDirected($v3, $v2)->setCapacity(2); $alg = new AlgorithmMaxFlowEdmondsKarp($v0, $v3); $this->assertEquals(10, $alg->getFlowMax()); $alg = new AlgorithmMaxFlowEdmondsKarp($v0, $v2); $this->assertEquals(9, $alg->getFlowMax()); } public function testEdgesMultiplePathsTree() { $graph = new Graph(); $v0 = $graph->createVertex(0); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $v3 = $graph->createVertex(3); $graph->createEdgeDirected($v0, $v1)->setCapacity(4); $graph->createEdgeDirected($v0, $v2)->setCapacity(2); $graph->createEdgeDirected($v1, $v2)->setCapacity(3); $graph->createEdgeDirected($v1, $v3)->setCapacity(1); $graph->createEdgeDirected($v2, $v3)->setCapacity(6); $alg = new AlgorithmMaxFlowEdmondsKarp($v0, $v3); $this->assertEquals(6, $alg->getFlowMax()); } // public function testEdgesParallel(){ // $graph = new Graph(); // $v0 = $graph->createVertex(0); // $v1 = $graph->createVertex(1); // $graph->createEdgeDirected($v0, $v1)->setCapacity(3.4); // $graph->createEdgeDirected($v0, $v1)->setCapacity(6.6); // $alg = new AlgorithmMaxFlowEdmondsKarp($v0, $v1); // $this->assertEquals(10, $alg->getFlowMax()); // } public function testEdgesUndirected() { // 0 -[0/7]- 1 $graph = new Graph(); $v0 = $graph->createVertex(0); $v1 = $graph->createVertex(1); $graph->createEdgeUndirected($v1, $v0)->setCapacity(7); // 0 -[7/7]- 1 $alg = new AlgorithmMaxFlowEdmondsKarp($v0, $v1); $this->setExpectedException('UnexpectedValueException'); $this->assertEquals(7, $alg->getFlowMax()); } /** * run algorithm with bigger graph and check result against known result (will take several seconds) */ // public function testKnownResultBig(){ // $graph = $this->readGraph('G_1_2.txt'); // $alg = new AlgorithmMaxFlowEdmondsKarp($graph->getVertex(0), $graph->getVertex(4)); // $this->assertEquals(0.735802, $alg->getFlowMax()); // } public function testInvalidFlowToOtherGraph() { $graph1 = new Graph(); $vg1 = $graph1->createVertex(1); $graph2 = new Graph(); $vg2 = $graph2->createVertex(2); $this->setExpectedException('InvalidArgumentException'); new AlgorithmMaxFlowEdmondsKarp($vg1, $vg2); } public function testInvalidFlowToSelf() { $graph = new Graph(); $v1 = $graph->createVertex(1); $this->setExpectedException('InvalidArgumentException'); new AlgorithmMaxFlowEdmondsKarp($v1, $v1); } } ================================================ FILE: tests/MaximumMatching/FlowTest.php ================================================ setEnableDirectedEdges(false); // $graph = $loader->createGraph(); // $alg = new Flow($graph); // $this->assertEquals(100, $alg->getNumberOfMatches()); // } public function testSingleEdge() { $graph = new Graph(); $edge = $graph->createEdgeUndirected($graph->createVertex(0)->setGroup(0), $graph->createVertex(1)->setGroup(1)); $alg = new Flow($graph); // correct number of edges $this->assertEquals(1, $alg->getNumberOfMatches()); // actual edge instance returned $this->assertEquals(array($edge), $alg->getEdges()->getVector()); // check $flowgraph = $alg->createGraph(); $this->assertInstanceOf('Graphp\Graph\Graph', $flowgraph); } /** * expect exception for directed edges */ public function testInvalidDirected() { $graph = new Graph(); $graph->createEdgeDirected($graph->createVertex(0)->setGroup(0), $graph->createVertex(1)->setGroup(1)); $alg = new Flow($graph); $this->setExpectedException('UnexpectedValueException'); $alg->getNumberOfMatches(); } /** * expect exception for non-bipartit graphs */ public function testInvalidBipartit() { $graph = new Graph(); $graph->createEdgeUndirected($graph->createVertex(0)->setGroup(1), $graph->createVertex(1)->setGroup(1)); $alg = new Flow($graph); $this->setExpectedException('UnexpectedValueException'); $alg->getNumberOfMatches(); } } ================================================ FILE: tests/MinimumCostFlow/BaseMcfTest.php ================================================ createAlgorithm($graph); $this->assertEquals(0, $alg->getWeightFlow()); } public function testSingleIntermediary() { $graph = new Graph(); $graph->createVertex(1); $alg = $this->createAlgorithm($graph); $this->assertEquals(0, $alg->getWeightFlow()); } public function testSimpleEdge() { // 1(+2) -[0/2/2]-> 2(-2) $graph = new Graph(); $v1 = $graph->createVertex(1)->setBalance(2); $v2 = $graph->createVertex(2)->setBalance(-2); $graph->createEdgeDirected($v1, $v2)->setWeight(2)->setCapacity(2); $alg = $this->createAlgorithm($graph); $this->assertEquals(4, $alg->getWeightFlow()); // 2x2 } public function testMultipleSinks() { // 1(+2) -[0/2/2]-> 2(-1) // -[0/4/-5]-> 3(-1) $graph = new Graph(); $v1 = $graph->createVertex(1)->setBalance(2); $v2 = $graph->createVertex(2)->setBalance(-1); $v3 = $graph->createVertex(3)->setBalance(-1); $graph->createEdgeDirected($v1, $v2)->setWeight(2)->setCapacity(2); $graph->createEdgeDirected($v1, $v3)->setWeight(-5)->setCapacity(4); $alg = $this->createAlgorithm($graph); $this->assertEquals(-3, $alg->getWeightFlow()); // 1*2 + 1*-5 } public function testIntermediaryVertices() { // 1(+2) -[0/1/4]-> 2 -[0/6/-2]-> 4(-2) // -[0/4/5]-> 3 -[0/6/8]-> $graph = new Graph(); $v1 = $graph->createVertex(1)->setBalance(2); $v2 = $graph->createVertex(2); $v3 = $graph->createVertex(3); $v4 = $graph->createVertex(4)->setBalance(-2); $graph->createEdgeDirected($v1, $v2)->setWeight(4)->setCapacity(1); $graph->createEdgeDirected($v2, $v4)->setWeight(-2)->setCapacity(6); $graph->createEdgeDirected($v1, $v3)->setWeight(5)->setCapacity(4); $graph->createEdgeDirected($v3, $v4)->setWeight(8)->setCapacity(6); $alg = $this->createAlgorithm($graph); $this->assertEquals(15, $alg->getWeightFlow()); // 1*4 + 1*-2 + 1*5 + 1*8 } public function testEdgeCapacities() { // 1(+2) -[0/3/4]-> 2 -[0/4/5]-> 3 ->[0/6/-2]-> 4(-2) $graph = new Graph(); $v1 = $graph->createVertex(1)->setBalance(2); $v2 = $graph->createVertex(2); $v3 = $graph->createVertex(3); $v4 = $graph->createVertex(4)->setBalance(-2); $graph->createEdgeDirected($v1, $v2)->setWeight(4)->setCapacity(3); $graph->createEdgeDirected($v2, $v3)->setWeight(5)->setCapacity(4); $graph->createEdgeDirected($v3, $v4)->setWeight(-2)->setCapacity(6); $alg = $this->createAlgorithm($graph); $this->assertEquals(14, $alg->getWeightFlow()); // 2*4 + 2*5 + 2*-2 } public function testEdgeFlows() { // 1(+4) ---[3/4/2]---> 2 ---[3/3/3]---> 4(-4) // | | ^ // | [0/2/1] | // | ↓ | // \-------[1/2/2]---> 3 ---[1/5/1]-------/ $graph = new Graph(); $v1 = $graph->createVertex(1)->setBalance(4); $v2 = $graph->createVertex(2); $v3 = $graph->createVertex(3); $v4 = $graph->createVertex(4)->setBalance(-4); $graph->createEdgeDirected($v1, $v2)->setFlow(3)->setCapacity(4)->setWeight(2); $graph->createEdgeDirected($v2, $v4)->setFlow(3)->setCapacity(3)->setWeight(3); $graph->createEdgeDirected($v1, $v3)->setFlow(1)->setCapacity(2)->setWeight(2); $graph->createEdgeDirected($v3, $v4)->setFlow(1)->setCapacity(5)->setWeight(1); $graph->createEdgeDirected($v2, $v3)->setFlow(0)->setCapacity(2)->setWeight(1); $alg = $this->createAlgorithm($graph); $this->assertEquals(14, $alg->getWeightFlow()); // 4*1 + 2*2 + 2*1 + 2*2 } public function testEdgeCapacityInsufficientFails() { // 1(+2) -[0/1]-> 2(-2) $graph = new Graph(); $v1 = $graph->createVertex(1)->setBalance(2); $v2 = $graph->createVertex(2)->setBalance(-2); $graph->createEdgeDirected($v1, $v2)->setCapacity(1); $alg = $this->createAlgorithm($graph); $this->setExpectedException('UnexpectedValueException'); $alg->getWeightFlow(); } public function testEdgeCapacityUnsetFails() { // 1(+2) -> 2(-2) $graph = new Graph(); $v1 = $graph->createVertex(1)->setBalance(2); $v2 = $graph->createVertex(2)->setBalance(-2); $graph->createEdgeDirected($v1, $v2); $alg = $this->createAlgorithm($graph); $this->setExpectedException('UnexpectedValueException'); $alg->getWeightFlow(); } public function testIsolatedVerticesFail() { // 1(+2), 2(-2) $graph = new Graph(); $graph->createVertex(1)->setBalance(2); $graph->createVertex(2)->setBalance(-2); $alg = $this->createAlgorithm($graph); $this->setExpectedException('UnexpectedValueException'); $alg->getWeightFlow(); } public function testUnbalancedFails() { // 1(+2) -> 2(-3) $graph = new Graph(); $v1 = $graph->createVertex(1)->setBalance(2); $v2 = $graph->createVertex(2)->setBalance(-3); $graph->createEdgeDirected($v1, $v2)->setCapacity(3); $alg = $this->createAlgorithm($graph); $this->setExpectedException('UnexpectedValueException'); $alg->getWeightFlow(); } public function testUndirectedFails() { // 1(+2) -- 2(-2) $graph = new Graph(); $v1 = $graph->createVertex(1)->setBalance(2); $v2 = $graph->createVertex(2)->setBalance(-2); $graph->createEdgeUndirected($v1, $v2)->setCapacity(2); $alg = $this->createAlgorithm($graph); $this->setExpectedException('UnexpectedValueException'); $alg->getWeightFlow(); } public function testUndirectedNegativeCycleFails() { // 1(+2) -[0/2/-1]- 2(-2) $graph = new Graph(); $v1 = $graph->createVertex(1)->setBalance(2); $v2 = $graph->createVertex(2)->setBalance(-2); $graph->createEdgeUndirected($v1, $v2)->setCapacity(2)->setWeight(-1); $alg = $this->createAlgorithm($graph); $this->setExpectedException('UnexpectedValueException'); $alg->getWeightFlow(); } } ================================================ FILE: tests/MinimumCostFlow/CycleCancellingTest.php ================================================ createVertex(1); $alg = $this->createAlg($v1); $this->assertCount(0, $alg->getEdges()); $this->assertEquals(0, $alg->getWeight()); $graphMst = $alg->createGraph(); $this->assertGraphEquals($graph, $graphMst); } public function testSingleEdge() { // 1 --[3]-- 2 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $graph->createEdgeUndirected($v1, $v2)->setWeight(3); $alg = $this->createAlg($v1); $this->assertCount(1, $alg->getEdges()); $this->assertEquals(3, $alg->getWeight()); $this->assertGraphEquals($graph, $alg->createGraph()); } public function testSimpleGraph() { // 1 --[6]-- 2 --[9]-- 3 --[7]-- 4 --[8]-- 5 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $v3 = $graph->createVertex(3); $v4 = $graph->createVertex(4); $v5 = $graph->createVertex(5); $graph->createEdgeUndirected($v1, $v2)->setWeight(6); $graph->createEdgeUndirected($v2, $v3)->setWeight(9); $graph->createEdgeUndirected($v3, $v4)->setWeight(7); $graph->createEdgeUndirected($v4, $v5)->setWeight(8); $alg = $this->createAlg($v1); $graphMst = $alg->createGraph(); $this->assertGraphEquals($graph, $graphMst); } public function testFindingCheapestEdge() { // /--[4]--\ // / \ // 1 ---[3]--- 2 // \ / // \--[5]--/ $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $graph->createEdgeUndirected($v1, $v2)->setWeight(4); $graph->createEdgeUndirected($v1, $v2)->setWeight(3); $graph->createEdgeUndirected($v1, $v2)->setWeight(5); $alg = $this->createAlg($v1); $edges = $alg->getEdges(); $this->assertCount(1, $edges); $this->assertEquals(3, $edges->getEdgeFirst()->getWeight()); $this->assertEquals(3, $alg->getWeight()); } public function testFindingCheapestTree() { // 1 --[4]-- 2 --[5]-- 3 // \ / // \-------[6]-----/ $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $v3 = $graph->createVertex(3); $graph->createEdgeUndirected($v1, $v2)->setWeight(4); $graph->createEdgeUndirected($v2, $v3)->setWeight(5); $graph->createEdgeUndirected($v3, $v1)->setWeight(6); // 1 --[4]-- 2 -- [5] -- 3 $graphExpected = new Graph(); $ve1 = $graphExpected->createVertex(1); $ve2 = $graphExpected->createVertex(2); $ve3 = $graphExpected->createVertex(3); $graphExpected->createEdgeUndirected($ve1, $ve2)->setWeight(4); $graphExpected->createEdgeUndirected($ve2, $ve3)->setWeight(5); $alg = $this->createAlg($v1); $this->assertCount(2, $alg->getEdges()); $this->assertEquals(9, $alg->getWeight()); $this->assertGraphEquals($graphExpected, $alg->createGraph()); } public function testMixedGraphDirectionIsIgnored() { // 1 --[6]-> 2 --[7]-- 3 --[8]-- 4 <-[9]-- 5 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $v3 = $graph->createVertex(3); $v4 = $graph->createVertex(4); $v5 = $graph->createVertex(5); $graph->createEdgeDirected($v1, $v2)->setWeight(6); $graph->createEdgeUndirected($v2, $v3)->setWeight(7); $graph->createEdgeUndirected($v4, $v3)->setWeight(8); $graph->createEdgeDirected($v5, $v4)->setWeight(9); $alg = $this->createAlg($v1); $this->assertCount(4, $alg->getEdges()); $this->assertEquals(30, $alg->getWeight()); $this->assertGraphEquals($graph, $alg->createGraph()); } public function testMultipleComponentsFail() { // 1 --[1]-- 2, 3 --[1]-- 4 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $v3 = $graph->createVertex(3); $v4 = $graph->createVertex(4); $graph->createEdgeUndirected($v1, $v2)->setWeight(1); $graph->createEdgeUndirected($v3, $v4)->setWeight(1); $alg = $this->createAlg($v1); $this->setExpectedException('UnexpectedValueException'); $alg->getEdges(); } public function testMultipleIsolatedVerticesFormMultipleComponentsFail() { // 1, 2 $graph = new Graph(); $v1 = $graph->createVertex(1); $graph->createVertex(2); $alg = $this->createAlg($v1); $this->setExpectedException('UnexpectedValueException'); $alg->getEdges(); } } ================================================ FILE: tests/MinimumSpanningTree/KruskalTest.php ================================================ getGraph()); } public function testNullGraphIsNotConsideredToBeConnected() { $graph = new Graph(); $alg = new Kruskal($graph); $this->setExpectedException('UnexpectedValueException'); $alg->getEdges(); } } ================================================ FILE: tests/MinimumSpanningTree/PrimTest.php ================================================ assertFalse($alg->hasEdgeParallel()); } public function testDirectedCycleIsNotConsideredParallel() { // 1 -> 2 // 2 -> 1 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $e1 = $graph->createEdgeDirected($v1, $v2); $e2 = $graph->createEdgeDirected($v2, $v1); $alg = new AlgorithmParallel($graph); $this->assertFalse($alg->hasEdgeParallel()); $this->assertEquals(array(), $alg->getEdgesParallelEdge($e1)->getVector()); $this->assertEquals(array(), $alg->getEdgesParallelEdge($e2)->getVector()); } public function testDirectedParallelEdge() { // 1 -> 2 // 1 -> 2 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $e1 = $graph->createEdgeDirected($v1, $v2); $e2 = $graph->createEdgeDirected($v1, $v2); $alg = new AlgorithmParallel($graph); $this->assertTrue($alg->hasEdgeParallel()); $this->assertEquals(array($e2), $alg->getEdgesParallelEdge($e1)->getVector()); $this->assertEquals(array($e1), $alg->getEdgesParallelEdge($e2)->getVector()); } public function testMixedParallelEdge() { // 1 -> 2 // 1 -- 2 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $e1 = $graph->createEdgeDirected($v1, $v2); $e2 = $graph->createEdgeUndirected($v1, $v2); $alg = new AlgorithmParallel($graph); $this->assertTrue($alg->hasEdgeParallel()); $this->assertEquals(array($e2), $alg->getEdgesParallelEdge($e1)->getVector()); $this->assertEquals(array($e1), $alg->getEdgesParallelEdge($e2)->getVector()); } public function testMixedParallelEdgesMultiple() { // 1 -> 2 // 1 -> 2 // 1 -- 2 // 1 -- 2 // 2 -> 1 // 2 -> 1 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $e1 = $graph->createEdgeDirected($v1, $v2); $e2 = $graph->createEdgeDirected($v1, $v2); $e3 = $graph->createEdgeUndirected($v1, $v2); $e4 = $graph->createEdgeUndirected($v1, $v2); $e5 = $graph->createEdgeDirected($v2, $v1); $e6 = $graph->createEdgeDirected($v2, $v1); $alg = new AlgorithmParallel($graph); $this->assertTrue($alg->hasEdgeParallel()); $this->assertEquals(array($e2, $e3, $e4), $alg->getEdgesParallelEdge($e1)->getVector()); $this->assertEquals(array($e1, $e3, $e4), $alg->getEdgesParallelEdge($e2)->getVector()); $this->assertEquals(array($e1, $e2, $e4, $e5, $e6), $alg->getEdgesParallelEdge($e3)->getVector()); $this->assertEquals(array($e1, $e2, $e3, $e5, $e6), $alg->getEdgesParallelEdge($e4)->getVector()); $this->assertEquals(array($e3, $e4, $e6), $alg->getEdgesParallelEdge($e5)->getVector()); $this->assertEquals(array($e3, $e4, $e5), $alg->getEdgesParallelEdge($e6)->getVector()); } } ================================================ FILE: tests/Property/PropertyGraphTest.php ================================================ assertTrue($alg->isNull()); $this->assertTrue($alg->isEdgeless()); $this->assertFalse($alg->isTrivial()); } public function testSingleVertexIsTrivial() { $graph = new Graph(); $graph->createVertex(1); $alg = new GraphProperty($graph); $this->assertFalse($alg->isNull()); $this->assertTrue($alg->isEdgeless()); $this->assertTrue($alg->isTrivial()); } } ================================================ FILE: tests/Property/WalkPropertyTest.php ================================================ createVertex(1); $walk = Walk::factoryFromEdges(array(), $v1); $this->assertCount(1, $walk->getVertices()); $this->assertCount(0, $walk->getEdges()); $alg = new WalkProperty($walk); $this->assertFalse($alg->isLoop()); $this->assertFalse($alg->hasLoop()); $this->assertFalse($alg->isCycle()); $this->assertFalse($alg->hasCycle()); $this->assertTrue($alg->isPath()); $this->assertTrue($alg->isSimple()); $this->assertTrue($alg->isEulerian()); $this->assertTrue($alg->isHamiltonian()); } public function testLoop() { // 1 -- 1 $graph = new Graph(); $v1 = $graph->createVertex(1); $e1 = $graph->createEdgeUndirected($v1, $v1); $walk = Walk::factoryFromEdges(array($e1), $v1); $alg = new WalkProperty($walk); $this->assertTrue($alg->isLoop()); $this->assertTrue($alg->hasLoop()); $this->assertTrue($alg->isCycle()); $this->assertTrue($alg->hasCycle()); $this->assertTrue($alg->isPath()); $this->assertTrue($alg->isSimple()); $this->assertTrue($alg->isEulerian()); $this->assertTrue($alg->isHamiltonian()); } public function testCycle() { // 1 -- 2 -- 1 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $e1 = $graph->createEdgeUndirected($v1, $v2); $e2 = $graph->createEdgeUndirected($v2, $v1); $walk = Walk::factoryFromEdges(array($e1, $e2), $v1); $this->assertCount(3, $walk->getVertices()); $this->assertCount(2, $walk->getEdges()); $alg = new WalkProperty($walk); $this->assertTrue($alg->isCycle()); $this->assertTrue($alg->hasCycle()); $this->assertTrue($alg->isPath()); $this->assertTrue($alg->isSimple()); $this->assertTrue($alg->isEulerian()); $this->assertTrue($alg->isHamiltonian()); } public function testCircuit() { // 1 -> 2 -> 1, 2 -> 2 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $e1 = $graph->createEdgeDirected($v1, $v2); $e2 = $graph->createEdgeDirected($v2, $v1); $e3 = $graph->createEdgeDirected($v2, $v2); // 1 -> 2 -> 2 -> 1 $walk = Walk::factoryFromEdges(array($e1, $e3, $e2), $v1); $this->assertEquals(array(1, 2, 2, 1), $walk->getVertices()->getIds()); $alg = new WalkProperty($walk); $this->assertTrue($alg->isCycle()); $this->assertTrue($alg->isCircuit()); } public function testNonCircuit() { // 1 -> 2 -> 1, 2 -> 2 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $e1 = $graph->createEdgeDirected($v1, $v2); $e2 = $graph->createEdgeDirected($v2, $v1); $e3 = $graph->createEdgeDirected($v2, $v2); // non-circuit: taking loop twice // 1 -> 2 -> 2 -> 2 -> 1 $walk = Walk::factoryFromEdges(array($e1, $e3, $e3, $e2), $v1); $this->assertEquals(array(1, 2, 2, 2, 1), $walk->getVertices()->getIds()); $alg = new WalkProperty($walk); $this->assertTrue($alg->isCycle()); $this->assertFalse($alg->isCircuit()); } public function testDigon() { // 1 -> 2 -> 1 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $e1 = $graph->createEdgeDirected($v1, $v2); $e2 = $graph->createEdgeDirected($v2, $v1); $walk = Walk::factoryFromEdges(array($e1, $e2), $v1); $alg = new WalkProperty($walk); $this->assertTrue($alg->isDigon()); } public function testTriangle() { // 1 -> 2 -> 3 -> 1 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $v3 = $graph->createVertex(3); $e1 = $graph->createEdgeDirected($v1, $v2); $e2 = $graph->createEdgeDirected($v2, $v3); $e3 = $graph->createEdgeDirected($v3, $v1); $walk = Walk::factoryFromEdges(array($e1, $e2, $e3), $v1); $alg = new WalkProperty($walk); $this->assertTrue($alg->isTriangle()); } public function testSimplePathWithinGraph() { // 1 -- 2 -- 2 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $graph->createEdgeUndirected($v1, $v2); $e2 = $graph->createEdgeUndirected($v2, $v2); // only use "2 -- 2" part $walk = Walk::factoryFromEdges(array($e2), $v2); $this->assertCount(2, $walk->getVertices()); $this->assertCount(1, $walk->getEdges()); $alg = new WalkProperty($walk); $this->assertTrue($alg->isCycle()); $this->assertTrue($alg->hasCycle()); $this->assertTrue($alg->isPath()); $this->assertTrue($alg->isSimple()); $this->assertFalse($alg->isEulerian()); $this->assertFalse($alg->isHamiltonian()); } } ================================================ FILE: tests/ResidualGraphTest.php ================================================ createGraph(); $this->assertGraphEquals($graph, $residual); } /** * test an edge with capacity unused */ public function testEdgeUnused() { $graph = new Graph(); $graph->createEdgeDirected($graph->createVertex(0), $graph->createVertex(1))->setFlow(0) ->setCapacity(2) ->setWeight(3); $alg = new ResidualGraph($graph); $residual = $alg->createGraph(); $this->assertGraphEquals($graph, $residual); } /** * test an edge with capacity completely used */ public function testEdgeUsed() { $graph = new Graph(); $graph->createEdgeDirected($graph->createVertex(0), $graph->createVertex(1))->setFlow(2) ->setCapacity(2) ->setWeight(3); $alg = new ResidualGraph($graph); $residual = $alg->createGraph(); $expected = new Graph(); $expected->createEdgeDirected($expected->createVertex(1), $expected->createVertex(0))->setFlow(0) ->setCapacity(2) ->setWeight(-3); $this->assertGraphEquals($expected, $residual); } /** * test an edge with capacity remaining */ public function testEdgePartial() { $graph = new Graph(); $graph->createEdgeDirected($graph->createVertex(0), $graph->createVertex(1))->setFlow(1) ->setCapacity(2) ->setWeight(3); $alg = new ResidualGraph($graph); $residual = $alg->createGraph(); $expected = new Graph(); $expected->createVertex(0); $expected->createVertex(1); // remaining edge $expected->createEdgeDirected($expected->getVertex(0), $expected->getVertex(1))->setFlow(0) ->setCapacity(1) ->setWeight(3); // back edge $expected->createEdgeDirected($expected->getVertex(1), $expected->getVertex(0))->setFlow(0) ->setCapacity(1) ->setWeight(-3); $this->assertGraphEquals($expected, $residual); } public function testResidualGraphCanOptionallyKeepNullCapacityForEdgeWithZeroFlow() { // 1 -[0/2]-> 2 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $graph->createEdgeDirected($v1, $v2)->setFlow(0)->setCapacity(2); // 1 -[0/2]-> 2 // ^ | // \--[0/0]---/ $expected = new Graph(); $v1 = $expected->createVertex(1); $v2 = $expected->createVertex(2); $expected->createEdgeDirected($v1, $v2)->setFlow(0)->setCapacity(2); $expected->createEdgeDirected($v2, $v1)->setFlow(0)->setCapacity(0); $alg = new ResidualGraph($graph); $alg->setKeepNullCapacity(true); $residual = $alg->createGraph(); $this->assertGraphEquals($expected, $residual); } public function testResidualGraphCanOptionallyKeepNullCapacityForEdgeWithZeroCapacityRemaining() { // 1 -[2/2]-> 2 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $graph->createEdgeDirected($v1, $v2)->setFlow(2)->setCapacity(2); // 1 -[0/0]-> 2 // ^ | // \--[0/2]---/ $expected = new Graph(); $v1 = $expected->createVertex(1); $v2 = $expected->createVertex(2); $expected->createEdgeDirected($v1, $v2)->setFlow(0)->setCapacity(0); $expected->createEdgeDirected($v2, $v1)->setFlow(0)->setCapacity(2); $alg = new ResidualGraph($graph); $alg->setKeepNullCapacity(true); $residual = $alg->createGraph(); $this->assertGraphEquals($expected, $residual); } public function testParallelEdgesCanBeMerged() { // 1 -[1/2]-> 2 // | ^ // \--[2/3]---/ $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $graph->createEdgeDirected($v1, $v2)->setFlow(1)->setCapacity(2); $graph->createEdgeDirected($v1, $v2)->setFlow(2)->setCapacity(3); // 1 -[0/2]-> 2 // ^ | // \--[0/3]---/ $expected = new Graph(); $v1 = $expected->createVertex(1); $v2 = $expected->createVertex(2); $expected->createEdgeDirected($v1, $v2)->setFlow(0)->setCapacity(2); $expected->createEdgeDirected($v2, $v1)->setFlow(0)->setCapacity(3); $alg = new ResidualGraph($graph); $alg->setMergeParallelEdges(true); $residual = $alg->createGraph(); $this->assertGraphEquals($expected, $residual); } /** * expect exception for undirected edges */ public function testInvalidUndirected() { $graph = new Graph(); $graph->createEdgeUndirected($graph->createVertex(), $graph->createVertex())->setFlow(1) ->setCapacity(2); $alg = new ResidualGraph($graph); $this->setExpectedException('UnexpectedValueException'); $alg->createGraph(); } /** * expect exception for edges with no flow */ public function testInvalidNoFlow() { $graph = new Graph(); $graph->createEdgeDirected($graph->createVertex(), $graph->createVertex())->setCapacity(1); $alg = new ResidualGraph($graph); $this->setExpectedException('UnexpectedValueException'); $alg->createGraph(); } /** * expect exception for edges with no capacity */ public function testInvalidNoCapacity() { $graph = new Graph(); $graph->createEdgeDirected($graph->createVertex(), $graph->createVertex())->setFlow(1); $alg = new ResidualGraph($graph); $this->setExpectedException('UnexpectedValueException'); $alg->createGraph(); } } ================================================ FILE: tests/Search/BreadthFirstTest.php ================================================ array( "edges" => array( array(1, 2), array(2, 3), array(3, 4), array(4, 5), ), "subject" => 1, "maxDepth" => null, "expected" => array(1, 2, 3, 4, 5), ), "simple path (limit = 0)" => array( "edges" => array( array(1, 2), array(2, 3), array(3, 4), array(4, 5), ), "subject" => 1, "maxDepth" => 0, "expected" => array(1), ), "simple path (limit = 1)" => array( "edges" => array( array(1, 2), array(2, 3), array(3, 4), array(4, 5), ), "subject" => 1, "maxDepth" => 1, "expected" => array(1, 2), ), ); } /** * @dataProvider providerMaxDepth */ public function testMaxDepth(array $edges, $subject, $maxDepth, array $expected) { $g = new Graph(); foreach ($edges as $e) { $g->createEdgeDirected($g->createVertex($e[0], true), $g->createVertex($e[1], true)); } $a = new BreadthFirst($g->getVertex($subject)); if ($maxDepth !== null) { $v = $a->getVertices($maxDepth); } else { $v = $a->getVertices(); // Simulate default } $this->assertSame($expected, $v->getIds()); } } ================================================ FILE: tests/ShortestPath/BaseShortestPathTest.php ================================================ createVertex(1); $alg = $this->createAlg($v1); $this->assertFalse($alg->hasVertex($v1)); //$this->assertEquals(0, $alg->getDistance($v1)); $this->assertEquals(array(), $alg->getDistanceMap()); $this->assertEquals(array(), $alg->getEdges()->getVector()); //$this->assertEquals(array(), $alg->getEdgesTo($v1)); $this->assertEquals(array(), $alg->getVertices()->getVector()); $this->assertEquals(array(), $alg->getVertices()->getIds()); $clone = $alg->createGraph(); $this->assertGraphEquals($graph,$clone); } public function testGraphSingleLoop() { // 1 -[4]> 1 $graph = new Graph(); $v1 = $graph->createVertex(1); $e1 = $graph->createEdgeDirected($v1, $v1)->setWeight(4); $alg = $this->createAlg($v1); $this->assertEquals(array($e1), $alg->getEdges()->getVector()); $expectedWeight = $this->getExpectedWeight(array($e1)); $this->assertTrue($alg->hasVertex($v1)); $this->assertEquals($expectedWeight, $alg->getDistance($v1)); $this->assertEquals(array(1 => $expectedWeight), $alg->getDistanceMap()); $this->assertEquals(array($e1), $alg->getEdgesTo($v1)->getVector()); $this->assertEquals(array(1 => $v1), $alg->getVertices()->getMap()); $this->assertEquals(array(1), $alg->getVertices()->getIds()); } public function testGraphCycle() { // 1 -[4]-> 2 -[2]-> 1 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $e1 = $graph->createEdgeDirected($v1, $v2)->setWeight(4); $e2 = $graph->createEdgeDirected($v2, $v1)->setWeight(2); $alg = $this->createAlg($v1); //$this->assertEquals(array($e2, $e1), $alg->getEdges()); $expectedWeight = $this->getExpectedWeight(array($e1)); $this->assertTrue($alg->hasVertex($v2)); $this->assertEquals(array($e1), $alg->getEdgesTo($v2)->getVector()); $this->assertEquals($expectedWeight, $alg->getDistance($v2)); $expectedWeight = $this->getExpectedWeight(array($e1, $e2)); $this->assertTrue($alg->hasVertex($v1)); $this->assertEquals(array($e1, $e2), $alg->getEdgesTo($v1)->getVector()); $this->assertEquals($expectedWeight, $alg->getDistance($v1)); $walk = $alg->getWalkTo($v1); $this->assertCount(2, $walk->getEdges()); } public function testIsolatedVertexIsNotReachable() { // 1, 2 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $alg = $this->createAlg($v1); $this->assertFalse($alg->hasVertex($v2)); $this->setExpectedException('OutOfBoundsException'); $alg->getEdgesTo($v2); } public function testSeparateGraphsAreNotReachable() { // 1 $graph1 = new Graph(); $vg1 = $graph1->createVertex(1); $graph2 = new Graph(); $vg2 = $graph2->createVertex(1); $alg = $this->createAlg($vg1); $this->setExpectedException('OutOfBoundsException'); $alg->getEdgesTo($vg2); } public function testGraphUnweighted() { // 1 -> 2 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $e1 = $graph->createEdgeDirected($v1, $v2); $alg = $this->createAlg($v1); $expectedWeight = $this->getExpectedWeight(array($e1)); $this->assertEquals($expectedWeight, $alg->getDistance($v2)); $this->assertEquals(array(2 => $expectedWeight), $alg->getDistanceMap()); $this->assertEquals(array($e1), $alg->getEdges()->getVector()); $this->assertEquals(array($e1), $alg->getEdgesTo($v2)->getVector()); $this->assertEquals(array(2), $alg->getVertices()->getIds()); } public function testGraphTwoComponents() { // 1 -[10]-> 2 // 3 -[20]-> 4 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $v3 = $graph->createVertex(3); $v4 = $graph->createVertex(4); $e1 = $graph->createEdgeDirected($v1, $v2)->setWeight(10); $graph->createEdgeDirected($v3, $v4)->setWeight(20); $alg = $this->createAlg($v1); $expectedWeight = $this->getExpectedWeight(array($e1)); $this->assertEquals($expectedWeight, $alg->getDistance($v2)); $this->assertEquals(array(2 => $expectedWeight), $alg->getDistanceMap()); $this->assertEquals(array($e1), $alg->getEdges()->getVector()); // $this->assertEquals(array(), $alg->getEdgesTo($v1)); $this->assertEquals(array($e1), $alg->getEdgesTo($v2)->getVector()); $this->assertEquals(array(2 => $v2), $alg->getVertices()->getMap()); $this->assertEquals(array(2), $alg->getVertices()->getIds()); } protected function getExpectedWeight($edges) { $sum = 0; foreach ($edges as $edge) { $sum += $edge->getWeight(); } return $sum; } } ================================================ FILE: tests/ShortestPath/BreadthFirstTest.php ================================================ 2 // | ^ // \--[-1]---/ $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $e1 = $graph->createEdgeDirected($v1, $v2)->setWeight(10); $graph->createEdgeDirected($v1, $v2)->setWeight(-1); $alg = $this->createAlg($v1); $this->assertEquals(1, $alg->getDistance($v2)); $this->assertEquals(array(2 => 1), $alg->getDistanceMap()); $this->assertEquals(array($e1), $alg->getEdges()->getVector()); $this->assertEquals(array($e1), $alg->getEdgesTo($v2)->getVector()); $this->assertEquals(array(2 => $v2), $alg->getVertices()->getMap()); $this->assertEquals(array(2), $alg->getVertices()->getIds()); } protected function getExpectedWeight($edges) { return \count($edges); } } ================================================ FILE: tests/ShortestPath/DijkstraTest.php ================================================ 2 // | ^ // \--[-1]---/ $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $graph->createEdgeDirected($v1, $v2)->setWeight(10); $graph->createEdgeDirected($v1, $v2)->setWeight(-1); $alg = $this->createAlg($v1); $this->setExpectedException('UnexpectedValueException'); $alg->getEdges(); } } ================================================ FILE: tests/ShortestPath/MooreBellmanFordTest.php ================================================ 2 // | ^ // \--[-1]---/ $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $graph->createEdgeDirected($v1, $v2)->setWeight(10); $e2 = $graph->createEdgeDirected($v1, $v2)->setWeight(-1); $alg = $this->createAlg($v1); // $this->assertEquals(0, $alg->getDistance($v1)); $this->assertEquals(-1, $alg->getDistance($v2)); $this->assertEquals(array(2 => -1), $alg->getDistanceMap()); $this->assertEquals(array($e2), $alg->getEdges()->getVector()); //$this->assertEquals(array(), $alg->getEdgesTo($v1)); $this->assertEquals(array($e2), $alg->getEdgesTo($v2)->getVector()); $this->assertEquals(array(2 => $v2), $alg->getVertices()->getMap()); $this->assertEquals(array(2), $alg->getVertices()->getIds()); return $alg; } /** * @param MooreBellmanFord $alg * @depends testGraphParallelNegative */ public function testNoNegativeCycle(MooreBellmanFord $alg) { $this->setExpectedException('UnderflowException'); $alg->getCycleNegative(); } public function testUndirectedNegativeWeightIsCycle() { // 1 -[-10]- 2 $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $graph->createEdgeUndirected($v1, $v2)->setWeight(-10); $alg = $this->createAlg($v1); $cycle = $alg->getCycleNegative(); $this->assertInstanceOf('Graphp\Graph\Walk', $cycle); } public function testLoopNegativeWeightIsCycle() { // 1 -[-10]-> 1 $graph = new Graph(); $v1 = $graph->createVertex(1); $graph->createEdgeUndirected($v1, $v1)->setWeight(-10); $alg = $this->createAlg($v1); $cycle = $alg->getCycleNegative(); $this->assertInstanceOf('Graphp\Graph\Walk', $cycle); } public function testNegativeComponentHasCycle() { // 1 -[1]-> 2 3 --[-1]--> 4 // ^ | // \---[-2]----/ $graph = new Graph(); $v1 = $graph->createVertex(1); $v2 = $graph->createVertex(2); $v3 = $graph->createVertex(3); $v4 = $graph->createVertex(4); $graph->createEdgeDirected($v1, $v2)->setWeight(1); $graph->createEdgeDirected($v3, $v4)->setWeight(-1); $graph->createEdgeDirected($v4, $v3)->setWeight(-2); // second component has a cycle $alg = $this->createAlg($v3); $cycle = $alg->getCycleNegative(); assert(isset($cycle)); // first component does not have a cycle $alg = $this->createAlg($v1); $this->setExpectedException('UnderflowException'); $alg->getCycleNegative(); } } ================================================ FILE: tests/SymmetricTest.php ================================================ assertTrue($alg->isSymmetric()); } public function testGraphIsolated() { $graph = new Graph(); $graph->createVertex(1); $graph->createVertex(2); $alg = new AlgorithmSymmetric($graph); $this->assertTrue($alg->isSymmetric()); } public function testGraphSingleArcIsNotSymmetricr() { // 1 -> 2 $graph = new Graph(); $graph->createEdgeDirected($graph->createVertex(1), $graph->createVertex(2)); $alg = new AlgorithmSymmetric($graph); $this->assertFalse($alg->isSymmetric()); } public function testGraphAntiparallelIsSymmetricr() { // 1 -> 2 -> 1 $graph = new Graph(); $graph->createEdgeDirected($graph->createVertex(1), $graph->createVertex(2)); $graph->createEdgeDirected($graph->getVertex(2), $graph->getVertex(1)); $alg = new AlgorithmSymmetric($graph); $this->assertTrue($alg->isSymmetric()); } public function testGraphSingleUndirectedIsSymmetricr() { // 1 -- 2 $graph = new Graph(); $graph->createEdgeUndirected($graph->createVertex(1), $graph->createVertex(2)); $alg = new AlgorithmSymmetric($graph); $this->assertTrue($alg->isSymmetric()); } } ================================================ FILE: tests/TestCase.php ================================================ getVertices()); $ret .= PHP_EOL . 'edges: ' . \count($graph->getEdges()); return $ret; }; // assert graph base parameters are equal $this->assertEquals($f($expected), $f($actual)); // next, assert that all vertices in both graphs are the same // each vertex has a unique ID, therefor it's easy to search a matching partner // do not use assertVertexEquals() in order to not increase assertion counter foreach ($expected->getVertices()->getMap() as $vid => $vertex) { $other = $actual->getVertex($vid); assert(isset($other)); if ($this->getVertexDump($vertex) !== $this->getVertexDump($vertex)) { $this->fail(); } } // next, assert that all edges in both graphs are the same // assertEdgeEquals() does not work, as the order of the edges is unknown // therefor, build an array of edge dump and make sure each entry has a match $edgesExpected = array(); foreach ($expected->getEdges() as $edge) { $edgesExpected[] = $this->getEdgeDump($edge); } foreach ($actual->getEdges() as $edge) { $dump = $this->getEdgeDump($edge); $pos = \array_search($dump, $edgesExpected, true); if ($pos === false) { $this->fail('given edge ' . $dump . ' not found'); } else { unset($edgesExpected[$pos]); } } } protected function assertVertexEquals(Vertex $expected, Vertex $actual) { $this->assertEquals($this->getVertexDump($expected), $this->getVertexDump($actual)); } protected function assertEdgeEquals(Edge $expected, Edge $actual) { $this->assertEquals($this->getEdgeDump($expected), $this->getEdgeDump($actual)); } private function getVertexDump(Vertex $vertex) { $ret = \get_class($vertex); $ret .= PHP_EOL . 'id: ' . $vertex->getId(); $ret .= PHP_EOL . 'attributes: ' . \json_encode($vertex->getAttributeBag()->getAttributes()); $ret .= PHP_EOL . 'balance: ' . $vertex->getBalance(); $ret .= PHP_EOL . 'group: ' . $vertex->getGroup(); return $ret; } private function getEdgeDump(Edge $edge) { $ret = \get_class($edge) . ' '; if ($edge instanceof EdgeDirected) { $ret .= $edge->getVertexStart()->getId() . ' -> ' . $edge->getVertexEnd()->getId(); } else { $vertices = $edge->getVertices()->getIds(); $ret .= $vertices[0] . ' -- ' . $vertices[1]; } $ret .= PHP_EOL . 'flow: ' . $edge->getFlow(); $ret .= PHP_EOL . 'capacity: ' . $edge->getCapacity(); $ret .= PHP_EOL . 'weight: ' . $edge->getWeight(); $ret .= PHP_EOL . 'attributes: ' . \json_encode($edge->getAttributeBag()->getAttributes()); return $ret; } public function setExpectedException($exception, $exceptionMessage = '', $exceptionCode = null) { if (method_exists($this, 'expectException')) { // PHPUnit 6+ $this->expectException($exception); if ($exceptionMessage !== '') { $this->expectExceptionMessage($exceptionMessage); } if ($exceptionCode !== null) { $this->expectExceptionCode($exceptionCode); } } else { // legacy PHPUnit 4 - PHPUnit 5 parent::setExpectedException($exception, $exceptionMessage, $exceptionCode); } } } ================================================ FILE: tests/TopologicalSortTest.php ================================================ assertInstanceOf('Graphp\Graph\Set\Vertices', $alg->getVertices()); $this->assertTrue($alg->getVertices()->isEmpty()); } public function testGraphIsolated() { $graph = new Graph(); $graph->createVertex(1); $graph->createVertex(2); $alg = new TopologicalSort($graph); $this->assertSame(array($graph->getVertex(1), $graph->getVertex(2)), $alg->getVertices()->getVector()); } public function testGraphSimple() { $graph = new Graph(); $graph->createEdgeDirected($graph->createVertex(1), $graph->createVertex(2)); $alg = new TopologicalSort($graph); $this->assertSame(array($graph->getVertex(1), $graph->getVertex(2)), $alg->getVertices()->getVector()); } public function testFailUndirected() { $graph = new Graph(); $graph->createEdgeUndirected($graph->createVertex(1), $graph->createVertex(2)); $alg = new TopologicalSort($graph); $this->setExpectedException('UnexpectedValueException'); $alg->getVertices(); } public function testFailLoop() { $graph = new Graph(); $graph->createEdgeDirected($graph->createVertex(1), $graph->getVertex(1)); $alg = new TopologicalSort($graph); $this->setExpectedException('UnexpectedValueException'); $alg->getVertices(); } public function testFailCycle() { $graph = new Graph(); $graph->createEdgeDirected($graph->createVertex(1), $graph->createVertex(2)); $graph->createEdgeDirected($graph->getVertex(2), $graph->getVertex(1)); $alg = new TopologicalSort($graph); $this->setExpectedException('UnexpectedValueException'); $alg->getVertices(); } } ================================================ FILE: tests/TravelingSalesmanProblem/BruteforceTest.php ================================================ createVertex(); $b = $graph->createVertex(); $c = $graph->createVertex(); $graph->createEdgeDirected($a, $b)->setWeight(1); $graph->createEdgeDirected($b, $c)->setWeight(2); $graph->createEdgeDirected($c, $a)->setWeight(3); $alg = new Bruteforce($graph); $this->assertEquals(6, $alg->getWeight()); } public function testSetUpperLimitMstSetsExactLimitForSimpleCycle() { $graph = new Graph(); $a = $graph->createVertex(); $b = $graph->createVertex(); $c = $graph->createVertex(); $graph->createEdgeDirected($a, $b)->setWeight(1); $graph->createEdgeDirected($b, $c)->setWeight(2); $graph->createEdgeDirected($c, $a)->setWeight(3); $alg = new Bruteforce($graph); $alg->setUpperLimitMst(); $ref = new \ReflectionProperty($alg, 'upperLimit'); $ref->setAccessible(true); $this->assertEquals(6, $ref->getValue($alg)); } } ================================================ FILE: tests/Tree/BaseDirectedTest.php ================================================ createTreeAlg($graph); $this->assertFalse($tree->isTree()); $this->assertTrue($tree->getVerticesLeaf()->isEmpty()); $this->assertTrue($tree->getVerticesInternal()->isEmpty()); return $tree; } /** * @param BaseDirected $tree * @depends testNullGraph */ public function testEmptyGraphDoesNotHaveRootVertex(BaseDirected $tree) { $this->setExpectedException('UnderflowException'); $tree->getVertexRoot(); } /** * @param BaseDirected $tree * @depends testNullGraph */ public function testEmptyGraphDoesNotHaveDegree(BaseDirected $tree) { $this->setExpectedException('UnderflowException'); $tree->getDegree(); } /** * @param BaseDirected $tree * @depends testNullGraph */ public function testEmptyGraphDoesNotHaveHeight(BaseDirected $tree) { $this->setExpectedException('UnderflowException'); $tree->getHeight(); } public function testGraphTree() { $graph = $this->createGraphTree(); $root = $graph->getVertices()->getVertexFirst(); $nonRoot = $graph->getVertices()->getMap(); unset($nonRoot[$root->getId()]); $nonRoot = new Vertices($nonRoot); $c1 = $nonRoot->getVertexFirst(); $tree = $this->createTreeAlg($graph); $this->assertTrue($tree->isTree()); $this->assertSame($root, $tree->getVertexRoot()); $this->assertSame($graph->getVertices()->getVector(), $tree->getVerticesSubtree($root)->getVector()); $this->assertSame($nonRoot->getVector(), $tree->getVerticesChildren($root)->getVector()); $this->assertSame($nonRoot->getVector(), $tree->getVerticesDescendant($root)->getVector()); $this->assertSame($nonRoot->getVector(), $tree->getVerticesLeaf()->getVector()); $this->assertSame(array(), $tree->getVerticesInternal()->getVector()); $this->assertSame($root, $tree->getVertexParent($c1)); $this->assertSame(array(), $tree->getVerticesChildren($c1)->getVector()); $this->assertSame(array(), $tree->getVerticesDescendant($c1)->getVector()); $this->assertSame(array($c1), $tree->getVerticesSubtree($c1)->getVector()); $this->assertEquals(2, $tree->getDegree()); $this->assertEquals(0, $tree->getDepthVertex($root)); $this->assertEquals(1, $tree->getDepthVertex($c1)); $this->assertEquals(1, $tree->getHeight()); $this->assertEquals(1, $tree->getHeightVertex($root)); $this->assertEquals(0, $tree->getHeightvertex($c1)); return $tree; } /** * * @param BaseDirected $tree * @depends testGraphTree */ public function testGraphTreeRootDoesNotHaveParent(BaseDirected $tree) { $root = $tree->getVertexRoot(); $this->setExpectedException('UnderflowException'); $tree->getVertexParent($root); } public function testNonTree() { $graph = $this->createGraphNonTree(); $tree = $this->createTreeAlg($graph); $this->assertFalse($tree->isTree()); } public function testNonTreeVertexHasMoreThanOneParent() { $graph = $this->createGraphNonTree(); $tree = $this->createTreeAlg($graph); $this->setExpectedException('UnexpectedValueException'); $tree->getVertexParent($graph->getVertex('v3')); } public function testGraphWithParallelEdgeIsNotTree() { $graph = $this->createGraphParallelEdge(); $tree = $this->createTreeAlg($graph); $this->assertFalse($tree->isTree()); } public function testGraphWithLoopIsNotTree() { // v1 -> v1 $graph = new Graph(); $graph->createEdgeDirected($graph->createVertex('v1'), $graph->getVertex('v1')); $tree = $this->createTreeAlg($graph); $this->assertFalse($tree->isTree()); } public function testGraphWithLoopCanNotGetSubgraph() { // v1 -> v1 $graph = new Graph(); $graph->createEdgeDirected($graph->createVertex('v1'), $graph->getVertex('v1')); $tree = $this->createTreeAlg($graph); $this->setExpectedException('UnexpectedValueException'); $tree->getVerticesSubtree($graph->getVertex('v1')); } public function testGraphWithUndirectedEdgeIsNotTree() { // v1 -- v2 $graph = new Graph(); $graph->createEdgeUndirected($graph->createVertex('v1'), $graph->createVertex('v2')); $tree = $this->createTreeAlg($graph); $this->assertFalse($tree->isTree()); } public function testGraphWithMixedEdgesIsNotTree() { // v1 -> v2 -- v3 -> v4 $graph = new Graph(); $graph->createEdgeDirected($graph->createVertex('v1'), $graph->createVertex('v2')); $graph->createEdgeUndirected($graph->getVertex('v2'), $graph->createVertex('v3')); $graph->createEdgeDirected($graph->getVertex('v3'), $graph->createVertex('v4')); $tree = $this->createTreeAlg($graph); $this->assertFalse($tree->isTree()); } } ================================================ FILE: tests/Tree/InTreeTest.php ================================================ root <- c2 $graph = new Graph(); $root = $graph->createVertex(); $c1 = $graph->createVertex(); $graph->createEdgeDirected($c1, $root); $c2 = $graph->createVertex(); $graph->createEdgeDirected($c2, $root); return $graph; } protected function createTreeAlg(Graph $graph) { return new InTree($graph); } protected function createGraphNonTree() { // v1 -> v2 <- v3 -> v4 $graph = new Graph(); $graph->createEdgeDirected($graph->createVertex('v1'), $graph->createVertex('v2')); $graph->createEdgeDirected($graph->createVertex('v3'), $graph->getVertex('v2')); $graph->createEdgeDirected($graph->getVertex('v3'), $graph->createVertex('v4')); return $graph; } protected function createGraphParallelEdge() { // v1 <- v2, v1 <- v2 $graph = new Graph(); $graph->createEdgeDirected($graph->createVertex('v2'), $graph->createVertex('v1')); $graph->createEdgeDirected($graph->getVertex('v2'), $graph->getVertex('v1')); return $graph; } } ================================================ FILE: tests/Tree/OutTreeTest.php ================================================ c2 $graph = new Graph(); $root = $graph->createVertex(); $c1 = $graph->createVertex(); $graph->createEdgeDirected($root, $c1); $c2 = $graph->createVertex(); $graph->createEdgeDirected($root, $c2); return $graph; } protected function createTreeAlg(Graph $graph) { return new OutTree($graph); } protected function createGraphNonTree() { // v1 -> v3 <- v2 -> v4 $graph = new Graph(); $graph->createEdgeDirected($graph->createVertex('v1'), $graph->createVertex('v3')); $graph->createEdgeDirected($graph->createVertex('v2'), $graph->getVertex('v3')); $graph->createEdgeDirected($graph->getVertex('v2'), $graph->createVertex('v4')); return $graph; } protected function createGraphParallelEdge() { // v1 -> v2, v1 -> v2 $graph = new Graph(); $graph->createEdgeDirected($graph->createVertex('v1'), $graph->createVertex('v2')); $graph->createEdgeDirected($graph->getVertex('v1'), $graph->getVertex('v2')); return $graph; } } ================================================ FILE: tests/Tree/UndirectedTest.php ================================================ createTree($graph); $this->assertFalse($tree->isTree()); $this->assertTrue($tree->getVerticesInternal()->isEmpty()); $this->assertTrue($tree->getVerticesLeaf()->isEmpty()); } public function testGraphTrivial() { $graph = new Graph(); $graph->createVertex('v1'); $tree = $this->createTree($graph); $this->assertTrue($tree->isTree()); $this->assertSame(array(), $tree->getVerticesInternal()->getVector()); $this->assertSame(array(), $tree->getVerticesLeaf()->getVector()); } public function testGraphSimplePair() { // v1 -- v2 $graph = new Graph(); $graph->createEdgeUndirected($graph->createVertex('v1'), $graph->createVertex('v2')); $tree = $this->createTree($graph); $this->assertTrue($tree->isTree()); $this->assertSame(array(), $tree->getVerticesInternal()->getVector()); $this->assertSame($graph->getVertices()->getVector(), $tree->getVerticesLeaf()->getVector()); } public function testGraphSimpleLine() { // v1 -- v2 -- v3 $graph = new Graph(); $graph->createEdgeUndirected($graph->createVertex('v1'), $graph->createVertex('v2')); $graph->createEdgeUndirected($graph->getVertex('v2'), $graph->createVertex('v3')); $tree = $this->createTree($graph); $this->assertTrue($tree->isTree()); $this->assertSame(array($graph->getVertex('v2')), $tree->getVerticesInternal()->getVector()); $this->assertSame(array($graph->getVertex('v1'), $graph->getVertex('v3')), $tree->getVerticesLeaf()->getVector()); } public function testGraphPairParallelIsNotTree() { // v1 -- v2 -- v1 $graph = new Graph(); $graph->createEdgeUndirected($graph->createVertex('v1'), $graph->createVertex('v2')); $graph->createEdgeUndirected($graph->getVertex('v1'), $graph->getVertex('v2')); $tree = $this->createTree($graph); $this->assertFalse($tree->isTree()); } public function testGraphLoopIsNotTree() { // v1 -- v1 $graph = new Graph(); $graph->createEdgeUndirected($graph->createVertex('v1'), $graph->getVertex('v1')); $tree = $this->createTree($graph); $this->assertFalse($tree->isTree()); } public function testGraphCycleIsNotTree() { // v1 -- v2 -- v3 -- v1 $graph = new Graph(); $graph->createEdgeUndirected($graph->createVertex('v1'), $graph->createVertex('v2')); $graph->createEdgeUndirected($graph->getVertex('v2'), $graph->createVertex('v3')); $graph->createEdgeUndirected($graph->getVertex('v3'), $graph->getVertex('v1')); $tree = $this->createTree($graph); $this->assertFalse($tree->isTree()); } public function testGraphDirectedIsNotTree() { // v1 -> v2 $graph = new Graph(); $graph->createEdgeDirected($graph->createVertex('v1'), $graph->createVertex('v2')); $tree = $this->createTree($graph); $this->assertFalse($tree->isTree()); } public function testGraphMixedIsNotTree() { // v1 -- v2 -> v3 $graph = new Graph(); $graph->createEdgeUndirected($graph->createVertex('v1'), $graph->createVertex('v2')); $graph->createEdgeDirected($graph->getVertex('v2'), $graph->createVertex('v3')); $tree = $this->createTree($graph); $this->assertFalse($tree->isTree()); } } ================================================ FILE: tests/WeightTest.php ================================================ assertEquals(null, $alg->getWeight()); $this->assertEquals(0, $alg->getWeightFlow()); $this->assertEquals(null, $alg->getWeightMin()); $this->assertFalse($alg->isWeighted()); return $graph; } /** * * @param Graph $graph * @depends testGraphEmpty */ public function testGraphSimple(Graph $graph) { // 1 -> 2 $graph->createEdgeDirected($graph->createVertex(1), $graph->createVertex(2))->setWeight(3)->setFlow(4); $alg = new AlgorithmWeight($graph); $this->assertEquals(3, $alg->getWeight()); $this->assertEquals(12, $alg->getWeightFlow()); $this->assertEquals(3, $alg->getWeightMin()); $this->assertTrue($alg->isWeighted()); return $graph; } /** * * @param Graph $graph * @depends testGraphSimple */ public function testGraphWithUnweightedEdges(Graph $graph) { $graph->createEdgeDirected($graph->createVertex(5), $graph->createVertex(6))->setFlow(7); $alg = new AlgorithmWeight($graph); $this->assertEquals(3, $alg->getWeight()); $this->assertEquals(12, $alg->getWeightFlow()); $this->assertEquals(3, $alg->getWeightMin()); $this->assertTrue($alg->isWeighted()); } }