diff --git a/classes/controllers/class.ilQuestionSetPoolPathGraphGUI.php b/classes/controllers/class.ilQuestionSetPoolPathGraphGUI.php index 931afb24dea70e6b6dfce637ff097e3d3c7b7ebd..4b3fc8d52286fc0c3b854a001f4d760f5c2bbd8b 100644 --- a/classes/controllers/class.ilQuestionSetPoolPathGraphGUI.php +++ b/classes/controllers/class.ilQuestionSetPoolPathGraphGUI.php @@ -1,6 +1,6 @@ renderPathTable($set, $template); $this->ctrl->setParameter($this->controller, 'set_id', (int)$_GET['set_id']); - $cmd = $this->ctrl->getLinkTarget($this->controller, __CLASS__ . '.renderPathGraph'); - + $template->setVariable('SET_TITLE', $set->getTitle()); $template->setVariable('GRAPH_TITLE', $this->plugin->txt("graph_viz_title")); - $template->setVariable('PATH_GRAPH_SRC', $cmd); - + $template->setVariable('PATH_GRAPH_SRC', $this->renderPathGraph()); + $this->tpl->setContent($template->get()); } /** - * + * */ public function renderPathGraph() { $set = new ilQuestionSetPoolSet((int)$_GET['set_id']); $set->read(); - $graph_viz = new Image_GraphViz(); + $graph = new Fhaculty\Graph\Graph(); $pathes = $set->getPathList()->getData(); foreach($pathes as $path) @@ -55,10 +54,17 @@ class ilQuestionSetPoolPathGraphGUI extends ilPluginControllerGUI $num_nodes = count($nodes); for($i = 0; $i < $num_nodes; $i++) { - $graph_viz->addNode('Q' . $nodes[$i]['question_index'], array('shape' => 'box')); + $nodeId = 'Q' . $nodes[$i]['question_index']; + try { + $graph->getVertex($nodeId); + } catch (Exception $exception) { + $vertex = $graph->createVertex($nodeId); + $vertex->setAttribute('graphviz.shape', 'box'); + } } } - $graph_viz->addNode($this->controller->plugin->txt('end_of_set'), array('shape' => 'box')); + $vertex = $graph->createVertex($this->controller->plugin->txt('end_of_set')); + $vertex->setAttribute('graphviz.shape', 'box'); $edges = array(); @@ -92,7 +98,7 @@ class ilQuestionSetPoolPathGraphGUI extends ilPluginControllerGUI } } } - + foreach($edges as $node_source => $data_target) { foreach($data_target as $node_target => $data_conditions) @@ -100,25 +106,32 @@ class ilQuestionSetPoolPathGraphGUI extends ilPluginControllerGUI if(count($data_conditions['initial']) > 0) { $initial = join("\n", array_filter($data_conditions['initial'])); - $graph_viz->addEdge(array($node_source => $node_target), array('color' => 'green', 'label' => $initial)); + $edge = new Fhaculty\Graph\Edge\Directed($graph->getVertex($node_source), $graph->getVertex($node_target)); + $edge->setAttribute('graphviz.color', 'darkgreen'); + $edge->setAttribute('graphviz.label', $initial); } if(count($data_conditions['jump']) > 0) { $jump = join("\n", array_filter($data_conditions['jump'])); - $graph_viz->addEdge(array($node_source => $node_target), array('color' => 'blue', 'label' => $jump)); + $edge = new Fhaculty\Graph\Edge\Directed($graph->getVertex($node_source), $graph->getVertex($node_target)); + $edge->setAttribute('graphviz.color', 'blue'); + $edge->setAttribute('graphviz.label', $jump); } if(count($data_conditions['end']) > 0) { $end = join("\n", array_filter($data_conditions['end'])); - $graph_viz->addEdge(array($node_source => $node_target), array('color' => 'red', 'label' => $end)); + $edge = new Fhaculty\Graph\Edge\Directed($graph->getVertex($node_source), $graph->getVertex($node_target)); + $edge->setAttribute('graphviz.color', 'red'); + $edge->setAttribute('graphviz.label', $end); } } } - $graph_viz->image(); - exit(); + $graphviz = new Graphp\GraphViz\GraphViz(); + $imageSrc = $graphviz->createImageSrc($graph); + return $imageSrc; } - + /** * Returns the current tab name * return string @@ -229,4 +242,4 @@ class ilQuestionSetPoolPathGraphGUI extends ilPluginControllerGUI $template->parseCurrentBlock(); } } -} \ No newline at end of file +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..55bbc3e6c8c9c6597862a4243db4c2fb504addbd --- /dev/null +++ b/composer.json @@ -0,0 +1,13 @@ +{ + "name": "ilias-plugins/question-set-pool", + "authors": [ + { + "name": "Niels Theen", + "email": "ntheen@databay.de" + } + ], + "require": { + "php": ">=5.3", + "graphp/graphviz": "~0.2.0" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000000000000000000000000000000000000..a44d1e3fecca3e5b31993a03a850556b6d2ac49e --- /dev/null +++ b/composer.lock @@ -0,0 +1,157 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "content-hash": "ca690f14dfdf057e95d3a24b02721814", + "packages": [ + { + "name": "clue/graph", + "version": "v0.9.0", + "source": { + "type": "git", + "url": "https://github.com/clue/graph.git", + "reference": "0336a4d5229fa61a20ccceaeab25e52ac9542700" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/graph/zipball/0336a4d5229fa61a20ccceaeab25e52ac9542700", + "reference": "0336a4d5229fa61a20ccceaeab25e52ac9542700", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "graphp/algorithms": "Common graph algorithms, such as Dijkstra and Moore-Bellman-Ford (shortest path), minimum spanning tree (MST), Kruskal, Prim and many more..", + "graphp/graphviz": "GraphViz graph drawing / DOT output" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fhaculty\\Graph\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A mathematical graph/network library written in PHP", + "homepage": "https://github.com/clue/graph", + "keywords": [ + "edge", + "graph", + "mathematical", + "network", + "vertex" + ], + "time": "2015-03-07T18:11:31+00:00" + }, + { + "name": "graphp/algorithms", + "version": "v0.8.1", + "source": { + "type": "git", + "url": "https://github.com/graphp/algorithms.git", + "reference": "81db4049c35730767ec8f97fb5c4844234b86cef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/graphp/algorithms/zipball/81db4049c35730767ec8f97fb5c4844234b86cef", + "reference": "81db4049c35730767ec8f97fb5c4844234b86cef", + "shasum": "" + }, + "require": { + "clue/graph": "~0.9.0|~0.8.0", + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Graphp\\Algorithms\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@lueck.tv" + } + ], + "description": "Common mathematical graph algorithms", + "homepage": "https://github.com/graphp/algorithms", + "keywords": [ + "Graph algorithms", + "dijkstra", + "kruskal", + "minimum spanning tree", + "moore-bellman-ford", + "prim", + "shortest path" + ], + "time": "2015-03-08T10:12:01+00:00" + }, + { + "name": "graphp/graphviz", + "version": "v0.2.1", + "source": { + "type": "git", + "url": "https://github.com/graphp/graphviz.git", + "reference": "2676522dfcd907fd3cb52891ea64a052c4ac4c2a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/graphp/graphviz/zipball/2676522dfcd907fd3cb52891ea64a052c4ac4c2a", + "reference": "2676522dfcd907fd3cb52891ea64a052c4ac4c2a", + "shasum": "" + }, + "require": { + "clue/graph": "~0.9.0|~0.8.0", + "graphp/algorithms": "~0.8.0", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Graphp\\GraphViz\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "GraphViz graph drawing for mathematical graph/network", + "homepage": "https://github.com/graphp/graphviz", + "keywords": [ + "dot output", + "graph drawing", + "graph image", + "graphviz" + ], + "time": "2015-03-08T10:30:28+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.3" + }, + "platform-dev": [] +} diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 0000000000000000000000000000000000000000..65f3173afa50214f967a7c775362db9731d18f13 --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,7 @@ +getVertexFirst()` | + | `Walk::getVertexSource()` | `Walk::getVertices()->getVertexFirst()` | + | `Walk::getVertexTarget()` | `Walk::getVertices()->getVertexLast()` | + +## 0.7.1 (2014-03-12) + +* Fix: Throwing an `UnexpectedValueException` if writing GraphViz Dot script + to a temporary file fails and remove its debugging output + ([#77](https://github.com/clue/graph/issues/77) and [#78](https://github.com/clue/graph/issues/78) @Metabor) + +* Fix: Improved GraphViz support for MS Windows + ([#99](https://github.com/clue/graph/issues/99)) + +## 0.7.0 (2013-09-11) + +* Feature: Add new `Set\Vertices` and `Set\Edges` classes that handle common + operations on a Set of multiple `Vertex` and `Edge` instances respectively. + ([#48](https://github.com/clue/graph/issues/48)) + +* BC break: Move operations and their corresponding constants concerning Sets + to their corresponding Sets: + + | Old name | New name | + |---|---| + | `Edge\Base::getFirst()` | `Set\Edges::getEdgeOrder()` | + | `Edge\Base::getAll()` | `Set\Edges::getEdgesOrder()` | + | `Edge\Base::ORDER_*` | `Set\Edges::ORDER_*` | + |---|---| + | `Vertex::getFirst()` | `Set\Vertices::getVertexOrder()` | + | `Vertex::getAll()` | `Set\Vertices::getVerticesOrder()` | + | `Vertex::ORDER_` | `Set\Vertices::ORDER_*` | + +* BC break: Each `getVertices*()` and `getEdges*()` method now returns a `Set` + instead of a primitive array of instances. *Most* of the time this should + work without changing your code, because each `Set` implements an `Iterator` + interface and can easily be iterated using `foreach`. However, using a `Set` + instead of a plain array differs when checking its boolean value or + comparing two Sets. I.e. if you happen to want to check if an `Set` is empty, + you now have to use the more explicit syntax `$set->isEmpty()`. + +* BC break: `Vertex::getVertices()`, `Vertex::getVerticesEdgeTo()` and + `Vertex::getVerticesEdgeFrom()` now return a `Set\Vertices` instance that + may contain duplicate vertices if parallel (multiple) edges exist. Previously + there was no easy way to detect this situation - this is now the default. If + you also want to get unique / distinct `Vertex` instances, use + `Vertex::getVertices()->getVerticesDistinct()` where applicable. + +* BC break: Remove all occurances of `getVerticesId()`, use + `getVertices()->getIds()` instead. + +* BC break: Merge `Cycle` into `Walk` ([#61](https://github.com/clue/graph/issues/61)). + As such, its static factory methods had to be renamed. Update your references if applicable: + + | Old name | New name | + |---|---| + | `Cycle::factoryFromPredecessorMap()` | `Walk::factoryCycleFromPredecessorMap()` | + | `Cycle::factoryFromVertices()` | `Walk::factoryCycleFromVertices()` | + | `Cycle::factoryFromEdges()` | `Walk::factoryCycleFromEdges()` | + +* BC break: Remove `Graph::isEmpty()` because it's not well-defined and might + be confusing. Most literature suggests it should check for existing edges, + whereas the old behavior was to check for existing vertices instead. Use either + of the new and more transparent methods + `Algorithm\Property\GraphProperty::isNull()` (old behavior) or (where applicable) + `Algorithm\Property\GraphProperty::isEdgeless()` ([#63](https://github.com/clue/graph/issues/63)). + +* BC break: Each of the above methods (`Walk::factoryCycleFromPredecessorMap()`, + `Walk::factoryCycleFromVertices()`, `Walk::factoryCycleFromEdges()`) now + actually makes sure the returned `Walk` instance is actually a valid Cycle, + i.e. the start `Vertex` is the same as the end `Vertex` ([#61](https://github.com/clue/graph/issues/61)) + +* BC break: Each `Algorithm\ShortestPath` algorithm now consistenly does not + return a zero weight for the root Vertex and now supports loop edges on the root + Vertex ([#62](https://github.com/clue/graph/issues/62)) + +* BC break: Each `Algorithm\ShortestPath` algorithm now consistently throws an + `OutOfBoundsException` for unreachable vertices + ([#62](https://github.com/clue/graph/issues/62)) + +* BC break: A null Graph (a Graph with no Vertices and thus no Edges) is not a + valid tree (because it is not connected), adjust `Algorithm\Tree\Base::isTree()` + accordingly. + ([#72](https://github.com/clue/graph/issues/72)) + +* BC break: Remove all occurances of `getNumberOfVertices()` and + `getNumberOfEdges()` ([#75](https://github.com/clue/graph/issues/75) and + [#48](https://github.com/clue/graph/issues/48)): + + | Old name | New name | + |---|---| + | `$set->getNumberOfVertices()` | `count($set->getVertices())` | + | `$set->getNumberOfEdges()` | `count($set->getEdges())` | + +* BC break: Replace base `Set` class with `Set\DualAggregate` interface. This + is unlikely to affect you, but might potentially break your custom + inheritance or polymorphism for algorithms. + ([#75](https://github.com/clue/graph/issues/75)) + +* Feature: Add `Algorithm\ShortestPath\Base::hasVertex(Vertex $vertex)` to check whether + a path to the given Vertex exists ([#62](https://github.com/clue/graph/issues/62)). + +* Feature: Support opening GraphViz images on Mac OS X in default image viewer + ([#67](https://github.com/clue/graph/issues/67) @onigoetz) + +* Feature: Add `Algorithm\MinimumSpanningTree\Base::getWeight()` to get total + weight of resulting minimum spanning tree (MST). + ([#73](https://github.com/clue/graph/issues/73)) + +* Feature: Each `Algorithm\MinimumSpanningTree` algorithm now supports + undirected and mixed Graphs, as well as null weights for Edges. + ([#73](https://github.com/clue/graph/issues/73)) + +* BC break: Each `Algorithm\MinimumSpanningTree` algorithm now throws an + `UnexpectedValueException` for unconnected Graphs (and thus also null Graphs). + ([#73](https://github.com/clue/graph/issues/73)) + +* Feature: Add `Walk::factoryFromVertices()` + ([#64](https://github.com/clue/graph/issues/64)). + +* Fix: Checking `Walk::isValid()` + ([#61](https://github.com/clue/graph/issues/61)) + +* Fix: Missing import prevented + `Algorithm\ShortestPath\MooreBellmanFord::getCycleNegative()` from actually + throwing the right `UnderflowException` if no cycle was found + ([#62](https://github.com/clue/graph/issues/62)) + +* Fix: Calling `Exporter\Image::setFormat()` had no effect due to misassignment + ([#70](https://github.com/clue/graph/issues/70) @FGM) + +## 0.6.0 (2013-07-11) + +* BC break: Move algorithm definitions in base classes to separate algorithm classes ([#27](https://github.com/clue/graph/issues/27)). + The following methods containing algorithms were now moved to separate algorithm classes. This + change encourages code-reuse, simplifies spotting algorithms, helps reducing complexity, + improves testablity and avoids tight coupling. Update your references if applicable: + + | Old name | New name | Related ticket | + |---|---|---| + | `Set::getWeight()` | `Algorithm\Weight::getWeight()` | [#33](https://github.com/clue/graph/issues/33) | + | `Set::getWeightFlow()` | `Algorithm\Weight::getWeightFlow()` | [#33](https://github.com/clue/graph/issues/33) | + | `Set::getWeightMin()` | `Algorithm\Weight::getWeightMin()` | [#33](https://github.com/clue/graph/issues/33) | + | `Set::isWeighted()` | `Algorithm\Weight::isWeighted()` | [#33](https://github.com/clue/graph/issues/33) | + |-|-|-| + | `Graph::getDegree()` | `Algorithm\Degree::getDegree()` | [#29](https://github.com/clue/graph/issues/29) | + | `Graph::getDegreeMin()` | `Algorithm\Degree::getDegreeMin()` | [#29](https://github.com/clue/graph/issues/29) | + | `Graph::getDegreeMax()` | `Algorithm\Degree::getDegreeMax()` | [#29](https://github.com/clue/graph/issues/29) | + | `Graph::isRegular()` | `Algorithm\Degree::isRegular()` | [#29](https://github.com/clue/graph/issues/29) | + | `Graph::isBalanced()` | `Algorithm\Degree::isBalanced()` | [#29](https://github.com/clue/graph/issues/29) | + | `Vertex::getDegree()` | `Algorithm\Degree:getDegreeVertex()` | [#49](https://github.com/clue/graph/issues/49) | + | `Vertex::getDegreeIn()` | `Algorithm\Degree:getDegreeInVertex()` | [#49](https://github.com/clue/graph/issues/49) | + | `Vertex::getDegreeOut()` | `Algorithm\Degree:getDegreeOutVertex()` | [#49](https://github.com/clue/graph/issues/49) | + | `Vertex::isSink()` | `Algorithm\Degree:isVertexSink()` | [#49](https://github.com/clue/graph/issues/49) | + | `Vertex::isSource()` | `Algorithm\Degree:isVertexSource()` | [#49](https://github.com/clue/graph/issues/49) | + | `Vertex::isIsolated()` | `Algorithm\Degree::isVertexIsolated()` | [#49](https://github.com/clue/graph/issues/49) | + |-|-|-| + | `Set::isDirected()` | `Algorithm\Directed::isDirected()` | [#34](https://github.com/clue/graph/issues/34) | + |-|-|-| + | `Graph::isSymmetric()` | `Algorithm\Symmetric::isSymmetric()` | [#41](https://github.com/clue/graph/issues/41) | + |-|-|-| + | `Graph::isComplete()` | `Algorithm\Complete::isComplete()` | [#43](https://github.com/clue/graph/issues/43) | + |-|-|-| + | `Set::hasFlow()` | `Algorithm\Flow::hasFlow()` | [#47](https://github.com/clue/graph/issues/47) | + | `Graph::getBalance()` | `Algorithm\Flow::getBalance()` | [#30](https://github.com/clue/graph/issues/30), [#47](https://github.com/clue/graph/issues/47) | + | `Graph::isBalancedFlow()` | `Algorithm\Flow::isBalancedFlow()` | [#30](https://github.com/clue/graph/issues/39), [#47](https://github.com/clue/graph/issues/47) | + | `Vertex::getFlow()` | `Algorithm\Flow::getFlowVertex()` | [#47](https://github.com/clue/graph/issues/47) | + |-|-|-| + | `Vertex::isLeaf()` | `Algorithm\Tree\Undirected::isVertexLeaf()` | [#44](https://github.com/clue/graph/issues/44) | + |-|-|-| + | `Set::hasLoop()` | `Algorithm\Loop::hasLoop()` | [#51](https://github.com/clue/graph/issues/51) | + | `Vertex::hasLoop()` | `Algorithm\Loop::hasLoopVertex()` | [#51](https://github.com/clue/graph/issues/51) | + |-|-|-| + | `Set::hasEdgeParallel()` | `Algorithm\Parallel::hasEdgeParallel()` | [#52](https://github.com/clue/graph/issues/52) | + | `Edge\Base::hasEdgeParallel()` | `Algorithm\Parallel::hasEdgeParallelEdge()` | [#52](https://github.com/clue/graph/issues/52) | + | `Edge\Base::getEdgesParallel()` | `Algorithm\Parallel::getEdgeParallelEdge()` | [#52](https://github.com/clue/graph/issues/52) | + |-|-|-| + | `Graph::isEdgeless()` | `Algorithm\Property\GraphProperty::isEdgeless()` | [#54](https://github.com/clue/graph/issues/54) | + | `Graph::isTrivial()` | `Algorithm\Property\GraphProperty::isTrivial()` | [#54](https://github.com/clue/graph/issues/54) | + | `Walk::isCycle()` | `Algorithm\Property\WalkProperty::isCycle()` | [#54](https://github.com/clue/graph/issues/54) | + | `Walk::isPath()` | `Algorithm\Property\WalkProperty::isPath()` | [#54](https://github.com/clue/graph/issues/54) | + | `Walk::hasCycle()` | `Algorithm\Property\WalkProperty::hasCycle()` | [#54](https://github.com/clue/graph/issues/54) | + | `Walk::isLoop()` | `Algorithm\Property\WalkProperty::isLoop()` | [#54](https://github.com/clue/graph/issues/54) | + | `Walk::isDigon()` | `Algorithm\Property\WalkProperty::isDigon()` | [#54](https://github.com/clue/graph/issues/54) | + | `Walk::isTriangle()` | `Algorithm\Property\WalkProperty::isTriangle()` | [#54](https://github.com/clue/graph/issues/54) | + | `Walk::isSimple()` | `Algorithm\Property\WalkProperty::isSimple()` | [#54](https://github.com/clue/graph/issues/54) | + | `Walk::isHamiltonian()` | `Algorithm\Property\WalkProperty::isHamiltonian()` | [#54](https://github.com/clue/graph/issues/54) | + | `Walk::isEulerian()` | `Algorithm\Property\WalkProperty::isEulerian()` | [#54](https://github.com/clue/graph/issues/54) | + +* BC break: Remove unneeded algorithm alias definitions ([#31](https://github.com/clue/graph/issues/31), [#50](https://github.com/clue/graph/issues/50)). The following *alias definitions* + have been removed, their original/actual name has already existed before and continues to work + unchanged. Update your references if applicable: + + | Old/removed alias definition | Actual name | + |---|---| + | `Graph::isConnected()` | `Algorithm\ConnectedComponents::isSingle()` | + | `Graph::hasEulerianCycle()` | `Algorithm\Eulerian::hasCycle()` | + | `Graph::getNumberOfComponents()` | `Algorithm\ConnectedComponents::getNumberOfComponents()` | + | `Graph::getNumberOfGroups()` | `Algorithm\Groups::getNumberOfGroups()` | + | `Graph::isBipartit()` | `Algorithm\Bipartit::isBipartit()` | + | `Vertex::hasPathTo()` | `Algorithm\ShortestPath\BreadthFirst::hasVertex()` | + | `Vertex::hasPathFrom()` | `Algorithm\ShortestPath\BreadthFirst::hasVertex()` | + | `Vertex::getVerticesPathTo()` | `Algorithm\ShortestPath\BreadthFirst::getVertices()` | + | `Vertex::getVerticesPathFrom()` | `Algorithm\ShortestPath\BreadthFirst::getVertices()` | + +* BC break: `Graph::createVertices()` now returns an array of vertices instead of the + chainable `Graph` ([#19](https://github.com/clue/graph/issues/19)) + +* BC break: Move `Loader\UmlClassDiagram` to separate [fhaculty/graph-uml](https://github.com/fhaculty/graph-uml) + repo ([#38](https://github.com/clue/graph/issues/38)) + +* BC break: Remove needless `Algorithm\MinimumSpanningTree\PrimWithIf` + (use `Algorithm\MinimumSpanningTree\Prim` instead) + ([#45](https://github.com/clue/graph/issues/45)) + +* BC break: `Vertex::createEdgeTo()` now returns an instance of type + `Edge\Undirected` instead of `Edge\UndirectedId` + ([#46](https://github.com/clue/graph/issues/46)) + +* BC break: `Edge\Base::setCapacity()` now consistently throws an `RangeException` + instead of `InvalidArgumentException` if the current flow exceeds the new maximum + capacity ([#53](https://github.com/clue/graph/issues/53)) + +* Feature: New `Algorithm\Tree` namespace with algorithms for undirected and directed, + rooted trees ([#44](https://github.com/clue/graph/issues/44)) + +* Feature: According to be above list of moved algorithm methods, the following algorithm + classes have been added ([#27](https://github.com/clue/graph/issues/27)): + * New `Algorithm\Weight` ([#33](https://github.com/clue/graph/issues/33)) + * New `Algorithm\Degree` ([#29](https://github.com/clue/graph/issues/29), [#49](https://github.com/clue/graph/issues/49)) + * New `Algorithm\Directed` ([#34](https://github.com/clue/graph/issues/34)) + * New `Algorithm\Symmetric` ([#41](https://github.com/clue/graph/issues/41)) + * New `Algorithm\Complete` ([#43](https://github.com/clue/graph/issues/43)) + * New `Algorithm\Flow` ([#30](https://github.com/clue/graph/issues/30), [#47](https://github.com/clue/graph/issues/47)) + * New `Algorithm\Tree` ([#44](https://github.com/clue/graph/issues/44)) + * New `Algorithm\Loop` ([#51](https://github.com/clue/graph/issues/51)) + * New `Algorithm\Parallel` ([#52](https://github.com/clue/graph/issues/52)) + * New `Algorithm\Property` ([#54](https://github.com/clue/graph/issues/54)) + +* Feature: `Graph::createVertices()` now also accepts an array of vertex IDs + ([#19](https://github.com/clue/graph/issues/19)) + +* Feature: Add `Algorithm\Property\WalkProperty::hasLoop()` alias definition for + completeness ([#54](https://github.com/clue/graph/issues/54)) + +* Feature: Add `Algorithm\Property\WalkProperty::isCircuit()` definition to distinguish + circuits from cycles ([#54](https://github.com/clue/graph/issues/54)) + +* Fix: Checking hamiltonian cycles always returned false + ([#54](https://github.com/clue/graph/issues/54)) + +* Fix: A Walk with no edges is no longer considered a valid cycle + ([#54](https://github.com/clue/graph/issues/54)) + +* Fix: Various issues with `Vertex`/`Edge` layout attributes + ([#32](https://github.com/clue/graph/issues/32)) + +* Fix: Getting multiple parallel edges for undirected edges + ([#52](https://github.com/clue/graph/issues/52)) + +## 0.5.0 (2013-05-07) + +* First tagged release (See issue [#20](https://github.com/clue/graph/issues/20) for more info on why it starts as v0.5.0) diff --git a/vendor/clue/graph/LICENSE b/vendor/clue/graph/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..358d929ad811d9dc5a36e74c3b664ed3da1c56eb --- /dev/null +++ b/vendor/clue/graph/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2012+ Christian Lück (Maintainer) +Copyright (c) 2012+ Fhaculty Core Team and our awesome contributors + +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. diff --git a/vendor/clue/graph/README.md b/vendor/clue/graph/README.md new file mode 100644 index 0000000000000000000000000000000000000000..54756070fd2153ada6a458b259df4f42a836065d --- /dev/null +++ b/vendor/clue/graph/README.md @@ -0,0 +1,130 @@ +# clue/graph [![Build Status](https://travis-ci.org/clue/graph.png?branch=master)](https://travis-ci.org/clue/graph) + +A mathematical graph/network library written in PHP + +## Quickstart examples + +Once [installed](#install), let's initialize a sample graph: + +````php +createVertex('Rome'); +$madrid = $graph->createVertex('Madrid'); +$cologne = $graph->createVertex('Cologne'); + +// build some roads +$cologne->createEdgeTo($madrid); +$madrid->createEdgeTo($rome); +// create loop +$rome->createEdgeTo($rome); +```` + +Let's see which city (Vertex) has road (i.e. an edge pointing) to Rome +````php +foreach ($rome->getVerticesEdgeFrom() as $vertex) { + echo $vertex->getId().' leads to rome'.PHP_EOL; + // result: Madrid and Rome itself +} +```` + +## Features + +This library is built around the concept of [mathematical graph theory](http://en.wikipedia.org/wiki/Graph_%28mathematics%29) (i.e. it is **not** a [charting](http://en.wikipedia.org/wiki/Chart) library for drawing a [graph of a function](http://en.wikipedia.org/wiki/Graph_of_a_function)). In essence, a graph is a set of *nodes* with any number of *connections* inbetween. In graph theory, [vertices](http://en.wikipedia.org/wiki/Vertex_%28graph_theory%29) (plural of vertex) are an abstract representation of these *nodes*, while *connections* are represented as *edges*. Edges may be either undirected ("two-way") or directed ("one-way", aka di-edges, arcs). + +Depending on how the edges are constructed, the whole graph can either be undirected, can be a [directed graph](http://en.wikipedia.org/wiki/Directed_graph) (aka digraph) or be a [mixed graph](http://en.wikipedia.org/wiki/Simple_graph#Mixed_graph). Edges are also allowed to form [loops](http://en.wikipedia.org/wiki/Loop_%28graph_theory%29) (i.e. an edge from vertex A pointing to vertex A again). Also, [multiple edges](http://en.wikipedia.org/wiki/Multiple_edges) from vertex A to vertex B are supported as well (aka parallel edges), effectively forming a [multigraph](http://en.wikipedia.org/wiki/Multigraph) (aka pseudograph). And of course, any combination thereof is supported as well. While many authors try to differentiate between these core concepts, this library tries hard to not impose any artificial limitations or assumptions on your graphs. + +## Components + +This library provides the core data structures for working with graphs, its vertices, edges and attributes. + +There are several official components built on top of these structures to provide commonly needed functionality. +This architecture allows these components to be used independently and on demand only. + +Following is a list of some highlighted components. A list of all official components can be found in the [graphp project](https://github.com/graphp). + +### Graph drawing + +This library is built to support visualizing graph images, including them into webpages, opening up images from within CLI applications and exporting them as PNG, JPEG or SVG file formats (among many others). Because [graph drawing](http://en.wikipedia.org/wiki/Graph_drawing) is a complex area on its own, the actual layouting of the graph is left up to the excelent [GraphViz](http://www.graphviz.org/) "Graph Visualization Software" and we merely provide some convenient APIs to interface with GraphViz. + +See [graphp/graphviz](https://github.com/graphp/graphviz) for more details. + +### Common algorithms + +Besides graph drawing, one of the most common things to do with graphs is running algorithms to solve common graph problems. +Therefor this library is being used as the basis for implementations for a number of commonly used graph algorithms: + +* Search + * Deep first (DFS) + * Breadth first search (BFS) +* Shortest path + * Dijkstra + * Moore-Bellman-Ford (MBF) + * Counting number of hops (simple BFS) +* Minimum spanning tree (MST) + * Kruskal + * Prim +* Traveling salesman problem (TSP) + * Bruteforce algorithm + * Minimum spanning tree heuristic (TSP MST heuristic) + * Nearest neighbor heuristic (NN heuristic) +* Maximum flow + * Edmonds-Karp +* Minimum cost flow (MCF) + * Cycle canceling + * Successive shortest path +* Maximum matching + * Flow algorithm + +See [graphp/algorithms](https://github.com/graphp/algorithms) for more details. + +## Install + +The recommended way to install this library is [through composer](http://getcomposer.org). [New to composer?](http://getcomposer.org/doc/00-intro.md) + +```JSON +{ + "require": { + "clue/graph": "~0.9.0" + } +} +``` + +You may also want to install some of the [additional components](#components). +A list of all official components can be found in the [graphp project](https://github.com/graphp). + +## Tests + +This library uses phpunit for its extensive testsuite. +You can either use a global installation or rely on the one composer installs +when you first run `$ composer install`. +This sets up the developer environment, so that you +can now run it from the project root directory: + +```bash +$ php vendor/bin/phpunit +``` + +## Contributing + +This library comes with an extensive testsuite and is regularly tested and used in the *real world*. +Despite this, this library is still considered beta software and its API is subject to change. +The [changelog](CHANGELOG.md) lists all relevant information for updates between releases. + +If you encounter any issues, please don't hesitate to drop us a line, file a bug report or even best provide us with a patch / pull request and/or unit test to reproduce your problem. + +Besides directly working with the code, any additional documentation, additions to our readme or even fixing simple typos are appreciated just as well. + +Any feedback and/or contribution is welcome! + +Check out #graphp on irc.freenode.net. + +## License + +Released under the terms of the permissive [MIT license](http://opensource.org/licenses/MIT). diff --git a/vendor/clue/graph/composer.json b/vendor/clue/graph/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..32480a375c8cd6d653f24879bdd68a6e9174f23e --- /dev/null +++ b/vendor/clue/graph/composer.json @@ -0,0 +1,21 @@ +{ + "name": "clue/graph", + "type": "library", + "description": "A mathematical graph/network library written in PHP", + "keywords": ["graph", "network", "mathematical", "vertex", "edge"], + "homepage": "https://github.com/clue/graph", + "license": "MIT", + "autoload": { + "psr-4": {"Fhaculty\\Graph\\": "src/"} + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "graphp/graphviz": "GraphViz graph drawing / DOT output", + "graphp/algorithms": "Common graph algorithms, such as Dijkstra and Moore-Bellman-Ford (shortest path), minimum spanning tree (MST), Kruskal, Prim and many more.." + } +} diff --git a/vendor/clue/graph/phpunit.xml.dist b/vendor/clue/graph/phpunit.xml.dist new file mode 100644 index 0000000000000000000000000000000000000000..828ef003fda9d1f914f19be9bdd79480e4fbaa65 --- /dev/null +++ b/vendor/clue/graph/phpunit.xml.dist @@ -0,0 +1,18 @@ + + + + + + ./tests/ + + + + + ./src/ + + + \ No newline at end of file diff --git a/vendor/clue/graph/src/Attribute/AttributeAware.php b/vendor/clue/graph/src/Attribute/AttributeAware.php new file mode 100644 index 0000000000000000000000000000000000000000..8c4bd283c674681fa22c1c339b1a5a5a91800aab --- /dev/null +++ b/vendor/clue/graph/src/Attribute/AttributeAware.php @@ -0,0 +1,35 @@ +attributes[$name]) ? $this->attributes[$name] : $default; + } + + /** + * set a single attribute with the given $name to given $value + * + * @param string $name + * @param mixed $value + * @return self For a fluid interface. + */ + public function setAttribute($name, $value) + { + $this->attributes[$name] = $value; + + return $this; + } + + /** + * get an array of all attributes + * + * @return array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * set an array of additional attributes + * + * @param array $attributes + * @return self For a fluid interface. + */ + public function setAttributes(array $attributes) + { + $this->attributes = $attributes + $this->attributes; + + return $this; + } + + /** + * get a container for all attributes + * + * @return AttributeBag + */ + public function getAttributeBag() + { + return $this; + } +} diff --git a/vendor/clue/graph/src/Attribute/AttributeBagNamespaced.php b/vendor/clue/graph/src/Attribute/AttributeBagNamespaced.php new file mode 100644 index 0000000000000000000000000000000000000000..2e2341237e27596ac5790f16a58acaef73abc3c4 --- /dev/null +++ b/vendor/clue/graph/src/Attribute/AttributeBagNamespaced.php @@ -0,0 +1,116 @@ +getAttributeBag(); + } + $this->bag = $bag; + $this->prefix = $prefix; + } + + /** + * get a single attribute with the given $name (or return $default if attribute was not found) + * + * This prefixes the attribute name before requesting from the base bag. + * + * @param string $name + * @param mixed $default to return if attribute was not found + * @return mixed + */ + public function getAttribute($name, $default = null) + { + return $this->bag->getAttribute($this->prefix . $name, $default); + } + + /** + * set a single attribute with the given $name to given $value + * + * This prefixes the attribute name before setting in the base bag. + * + * @param string $name + * @param mixed $value + * @return void + */ + public function setAttribute($name, $value) + { + $this->bag->setAttribute($this->prefix . $name, $value); + } + + /** + * get an array of all attributes + * + * The prefix will not be included in the returned attribute keys. + * + * @return array + */ + public function getAttributes() + { + $attributes = array(); + $len = strlen($this->prefix); + + foreach ($this->bag->getAttributes() as $name => $value) { + if (strpos($name, $this->prefix) === 0) { + $attributes[substr($name, $len)] = $value; + } + } + + return $attributes; + } + + /** + * set an array of additional attributes + * + * Each attribute is prefixed before setting in the base bag. + * + * @param array $attributes + * @return void + */ + public function setAttributes(array $attributes) + { + foreach ($attributes as $name => $value) { + $this->bag->setAttribute($this->prefix . $name, $value); + } + } + + /** + * get a container for all attributes + * + * @return AttributeBag + */ + public function getAttributeBag() + { + return $this; + } +} diff --git a/vendor/clue/graph/src/Attribute/AttributeBagReference.php b/vendor/clue/graph/src/Attribute/AttributeBagReference.php new file mode 100644 index 0000000000000000000000000000000000000000..2aae63769fdc17dfe24aec9ee870f1ab06f7396e --- /dev/null +++ b/vendor/clue/graph/src/Attribute/AttributeBagReference.php @@ -0,0 +1,90 @@ +attributes =& $attributes; + } + + /** + * get a single attribute with the given $name (or return $default if attribute was not found) + * + * @param string $name + * @param mixed $default to return if attribute was not found + * @return mixed + */ + public function getAttribute($name, $default = null) + { + return isset($this->attributes[$name]) ? $this->attributes[$name] : $default; + } + + /** + * set a single attribute with the given $name to given $value + * + * @param string $name + * @param mixed $value + * @return self For a fluid interface. + */ + public function setAttribute($name, $value) + { + $this->attributes[$name] = $value; + + return $this; + } + + /** + * get an array of all attributes + * + * @return array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * set an array of additional attributes + * + * @param array $attributes + * @return self For a fluid interface. + */ + public function setAttributes(array $attributes) + { + $this->attributes = $attributes + $this->attributes; + + return $this; + } + + /** + * get a container for all attributes + * + * @return AttributeBag + */ + public function getAttributeBag() + { + return $this; + } +} diff --git a/vendor/clue/graph/src/Edge/Base.php b/vendor/clue/graph/src/Edge/Base.php new file mode 100644 index 0000000000000000000000000000000000000000..c4ef7c8e000736abda6cb308dd768255639ee6aa --- /dev/null +++ b/vendor/clue/graph/src/Edge/Base.php @@ -0,0 +1,310 @@ +weight; + } + + /** + * set new weight for edge + * + * @param float|int|NULL $weight new numeric weight of edge or NULL=unset weight + * @return Edge $this (chainable) + * @throws DomainException if given weight is not numeric + */ + public function setWeight($weight) + { + if ($weight !== NULL && !is_float($weight) && !is_int($weight)) { + throw new InvalidArgumentException('Invalid weight given - must be numeric or NULL'); + } + $this->weight = $weight; + + return $this; + } + + /** + * get total capacity of this edge + * + * @return float|int|NULL numeric capacity or NULL=not set + */ + public function getCapacity() + { + return $this->capacity; + } + + /** + * get the capacity remaining (total capacity - current flow) + * + * @return float|int|NULL numeric capacity remaining or NULL=no upper capacity set + */ + public function getCapacityRemaining() + { + if ($this->capacity === NULL) { + return NULL; + } + + return $this->capacity - $this->flow; + } + + /** + * set new total capacity of this edge + * + * @param float|int|NULL $capacity + * @return Edge $this (chainable) + * @throws InvalidArgumentException if $capacity is invalid (not numeric or negative) + * @throws RangeException if current flow exceeds new capacity + */ + public function setCapacity($capacity) + { + if ($capacity !== NULL) { + if (!is_float($capacity) && !is_int($capacity)) { + throw new InvalidArgumentException('Invalid capacity given - must be numeric'); + } + if ($capacity < 0) { + throw new InvalidArgumentException('Capacity must not be negative'); + } + if ($this->flow !== NULL && $this->flow > $capacity) { + throw new RangeException('Current flow of ' . $this->flow . ' exceeds new capacity'); + } + } + $this->capacity = $capacity; + + return $this; + } + + /** + * get current flow (capacity currently in use) + * + * @return float|int|NULL numeric flow or NULL=not set + */ + public function getFlow() + { + return $this->flow; + } + + /** + * set new total flow (capacity currently in use) + * + * @param float|int|NULL $flow + * @return Edge $this (chainable) + * @throws InvalidArgumentException if $flow is invalid (not numeric or negative) + * @throws RangeException if flow exceeds current maximum capacity + */ + public function setFlow($flow) + { + if ($flow !== NULL) { + if (!is_float($flow) && !is_int($flow)) { + throw new InvalidArgumentException('Invalid flow given - must be numeric'); + } + if ($flow < 0) { + throw new InvalidArgumentException('Flow must not be negative'); + } + if ($this->capacity !== NULL && $flow > $this->capacity) { + throw new RangeException('New flow exceeds maximum capacity'); + } + } + $this->flow = $flow; + + return $this; + } + + /** + * get set of all Vertices this edge connects + * + * @return Vertices + */ + //abstract public function getVertices(); + + /** + * get graph instance this edge is attached to + * + * @return Graph + * @throws LogicException + */ + public function getGraph() + { + foreach ($this->getVertices() as $vertex) { + return $vertex->getGraph(); + + // the following code can only be reached if this edge does not + // contain any vertices (invalid state), so ignore its coverage + // @codeCoverageIgnoreStart + } + + throw new LogicException('Internal error: should not be reached'); + // @codeCoverageIgnoreEnd + } + + /** + * destroy edge and remove reference from vertices and graph + * + * @uses Graph::removeEdge() + * @uses Vertex::removeEdge() + * @return void + */ + public function destroy() + { + $this->getGraph()->removeEdge($this); + foreach ($this->getVertices() as $vertex) { + $vertex->removeEdge($this); + } + } + + /** + * create new clone of this edge between adjacent vertices + * + * @return Edge new edge + * @uses Graph::createEdgeClone() + */ + public function createEdgeClone() + { + return $this->getGraph()->createEdgeClone($this); + } + + /** + * create new clone of this edge inverted (in opposite direction) between adjacent vertices + * + * @return Edge new edge + * @uses Graph::createEdgeCloneInverted() + */ + public function createEdgeCloneInverted() + { + return $this->getGraph()->createEdgeCloneInverted($this); + } + + /** + * do NOT allow cloning of objects + * + * @throws Exception + */ + private function __clone() + { + // @codeCoverageIgnoreStart + throw new BadMethodCallException(); + // @codeCoverageIgnoreEnd + } + + public function getAttribute($name, $default = null) + { + return isset($this->attributes[$name]) ? $this->attributes[$name] : $default; + } + + public function setAttribute($name, $value) + { + $this->attributes[$name] = $value; + } + + public function getAttributeBag() + { + return new AttributeBagReference($this->attributes); + } +} diff --git a/vendor/clue/graph/src/Edge/Directed.php b/vendor/clue/graph/src/Edge/Directed.php new file mode 100644 index 0000000000000000000000000000000000000000..59eb3293fdb5a170668802e096a1bc413c464ae4 --- /dev/null +++ b/vendor/clue/graph/src/Edge/Directed.php @@ -0,0 +1,121 @@ +getGraph() !== $to->getGraph()) { + throw new InvalidArgumentException('Vertices have to be within the same graph'); + } + + $this->from = $from; + $this->to = $to; + + $from->getGraph()->addEdge($this); + $from->addEdge($this); + $to->addEdge($this); + } + + public function getVerticesTarget() + { + return new Vertices(array($this->to)); + } + + public function getVerticesStart() + { + return new Vertices(array($this->from)); + } + + public function getVertices() + { + return new Vertices(array($this->from, $this->to)); + } + + /** + * get end/target vertex + * + * @return Vertex + */ + public function getVertexEnd() + { + return $this->to; + } + + /** + * get start vertex + * + * @return Vertex + */ + public function getVertexStart() + { + return $this->from; + } + + public function isConnection(Vertex $from, Vertex $to) + { + return ($this->to === $to && $this->from === $from); + } + + public function isLoop() + { + return ($this->to === $this->from); + } + + public function getVertexToFrom(Vertex $startVertex) + { + if ($this->from !== $startVertex) { + throw new InvalidArgumentException('Invalid start vertex'); + } + + return $this->to; + } + + public function getVertexFromTo(Vertex $endVertex) + { + if ($this->to !== $endVertex) { + throw new InvalidArgumentException('Invalid end vertex'); + } + + return $this->from; + } + + public function hasVertexStart(Vertex $startVertex) + { + return ($this->from === $startVertex); + } + + public function hasVertexTarget(Vertex $targetVertex) + { + return ($this->to === $targetVertex); + } +} diff --git a/vendor/clue/graph/src/Edge/Undirected.php b/vendor/clue/graph/src/Edge/Undirected.php new file mode 100644 index 0000000000000000000000000000000000000000..667903f38abb92a1565b17176d263fd326fe4a0e --- /dev/null +++ b/vendor/clue/graph/src/Edge/Undirected.php @@ -0,0 +1,104 @@ +getGraph() !== $b->getGraph()) { + throw new InvalidArgumentException('Vertices have to be within the same graph'); + } + + $this->a = $a; + $this->b = $b; + + $a->getGraph()->addEdge($this); + $a->addEdge($this); + $b->addEdge($this); + } + + public function getVerticesTarget() + { + return new Vertices(array($this->b, $this->a)); + } + + public function getVerticesStart() + { + return new Vertices(array($this->a, $this->b)); + } + + public function getVertices() + { + return new Vertices(array($this->a, $this->b)); + } + + public function isConnection(Vertex $from, Vertex $to) + { + // one way or other way + return (($this->a === $from && $this->b === $to) || ($this->b === $from && $this->a === $to)); + } + + public function isLoop() + { + return ($this->a === $this->b); + } + + public function getVertexToFrom(Vertex $startVertex) + { + if ($this->a === $startVertex) { + return $this->b; + } elseif ($this->b === $startVertex) { + return $this->a; + } else { + throw new InvalidArgumentException('Invalid start vertex'); + } + } + + public function getVertexFromTo(Vertex $endVertex) + { + if ($this->a === $endVertex) { + return $this->b; + } elseif ($this->b === $endVertex) { + return $this->a; + } else { + throw new InvalidArgumentException('Invalid end vertex'); + } + } + + public function hasVertexStart(Vertex $startVertex) + { + return ($this->a === $startVertex || $this->b === $startVertex); + } + + public function hasVertexTarget(Vertex $targetVertex) + { + // same implementation as direction does not matter + return $this->hasVertexStart($targetVertex); + } +} diff --git a/vendor/clue/graph/src/Exception.php b/vendor/clue/graph/src/Exception.php new file mode 100644 index 0000000000000000000000000000000000000000..70cc8cca0ccd3aed0f0d391062868dff9fe87d62 --- /dev/null +++ b/vendor/clue/graph/src/Exception.php @@ -0,0 +1,7 @@ +cycle = $cycle; + } + + /** + * + * @return Walk + */ + public function getCycle() + { + return $this->cycle; + } +} diff --git a/vendor/clue/graph/src/Exception/OutOfBoundsException.php b/vendor/clue/graph/src/Exception/OutOfBoundsException.php new file mode 100644 index 0000000000000000000000000000000000000000..25a76a70a21b85db8c409c3b344df5fd2a9a0287 --- /dev/null +++ b/vendor/clue/graph/src/Exception/OutOfBoundsException.php @@ -0,0 +1,9 @@ +vertices = VerticesMap::factoryArrayReference($this->verticesStorage); + $this->edges = Edges::factoryArrayReference($this->edgesStorage); + } + + /** + * return set of Vertices added to this graph + * + * @return Vertices + */ + public function getVertices() + { + return $this->vertices; + } + + /** + * return set of ALL Edges added to this graph + * + * @return Edges + */ + public function getEdges() + { + return $this->edges; + } + + /** + * create a new Vertex in the Graph + * + * @param int|NULL $id new vertex ID to use (defaults to NULL: use next free numeric ID) + * @param boolean $returnDuplicate normal operation is to throw an exception if given id already exists. pass true to return original vertex instead + * @return Vertex (chainable) + * @throws InvalidArgumentException if given vertex $id is invalid + * @throws OverflowException if given vertex $id already exists and $returnDuplicate is not set + * @uses Vertex::getId() + */ + public function createVertex($id = NULL, $returnDuplicate = false) + { + // no ID given + if ($id === NULL) { + $id = $this->getNextId(); + } + if ($returnDuplicate && $this->vertices->hasVertexId($id)) { + return $this->vertices->getVertexId($id); + } + + return new Vertex($this, $id); + } + + /** + * create a new Vertex in this Graph from the given input Vertex of another graph + * + * @param Vertex $originalVertex + * @return Vertex new vertex in this graph + * @throws RuntimeException if vertex with this ID already exists + */ + public function createVertexClone(Vertex $originalVertex) + { + $id = $originalVertex->getId(); + if ($this->vertices->hasVertexId($id)) { + throw new RuntimeException('Id of cloned vertex already exists'); + } + $newVertex = new Vertex($this, $id); + // TODO: properly set attributes of vertex + $newVertex->getAttributeBag()->setAttributes($originalVertex->getAttributeBag()->getAttributes()); + $newVertex->setBalance($originalVertex->getBalance()); + $newVertex->setGroup($originalVertex->getGroup()); + + return $newVertex; + } + + /** + * create new clone/copy of this graph - copy all attributes and vertices, but do NOT copy edges + * + * using this method is faster than creating a new graph and calling createEdgeClone() yourself + * + * @return Graph + */ + public function createGraphCloneEdgeless() + { + $graph = new Graph(); + $graph->getAttributeBag()->setAttributes($this->getAttributeBag()->getAttributes()); + // TODO: set additional graph attributes + foreach ($this->getVertices() as $originalVertex) { + $vertex = $graph->createVertexClone($originalVertex); + // $graph->vertices[$vid] = $vertex; + } + + return $graph; + } + + /** + * create new clone/copy of this graph - copy all attributes and vertices. but only copy all given edges + * + * @param Edges|Edge[] $edges set or array of edges to be cloned + * @return Graph + * @uses Graph::createGraphCloneEdgeless() + * @uses Graph::createEdgeClone() for each edge to be cloned + */ + public function createGraphCloneEdges($edges) + { + $graph = $this->createGraphCloneEdgeless(); + foreach ($edges as $edge) { + $graph->createEdgeClone($edge); + } + + return $graph; + } + + /** + * create new clone/copy of this graph - copy all attributes, vertices and edges + * + * @return Graph + * @uses Graph::createGraphCloneEdges() to clone graph with current edges + */ + public function createGraphClone() + { + return $this->createGraphCloneEdges($this->edges); + } + + /** + * create a new clone/copy of this graph - copy all attributes and given vertices and its edges + * + * @param Vertices $vertices set of vertices to keep + * @return Graph + * @uses Graph::createGraphClone() to create a complete clone + * @uses Vertex::destroy() to remove unneeded vertices again + */ + public function createGraphCloneVertices($vertices) + { + $verticesKeep = Vertices::factory($vertices); + + $graph = $this->createGraphClone(); + foreach ($graph->getVertices()->getMap() as $vid => $vertex) { + if (!$verticesKeep->hasVertexId($vid)) { + $vertex->destroy(); + } + } + + return $graph; + } + + /** + * create new clone of the given edge between adjacent vertices + * + * @param Edge $originalEdge original edge (not neccessarily from this graph) + * @return Edge new edge in this graph + * @uses Graph::createEdgeCloneInternal() + */ + public function createEdgeClone(Edge $originalEdge) + { + return $this->createEdgeCloneInternal($originalEdge, 0, 1); + } + + /** + * create new clone of the given edge inverted (in opposite direction) between adjacent vertices + * + * @param Edge $originalEdge original edge (not neccessarily from this graph) + * @return Edge new edge in this graph + * @uses Graph::createEdgeCloneInternal() + */ + public function createEdgeCloneInverted(Edge $originalEdge) + { + return $this->createEdgeCloneInternal($originalEdge, 1, 0); + } + + /** + * create new clone of the given edge between adjacent vertices + * + * @param Edge $originalEdge original edge from old graph + * @param int $ia index of start vertex + * @param int $ib index of end vertex + * @return Edge new edge in this graph + * @uses Edge::getVertices() + * @uses Graph::getVertex() + * @uses Vertex::createEdge() to create a new undirected edge if given edge was undrected + * @uses Vertex::createEdgeTo() to create a new directed edge if given edge was directed + * @uses Edge::getWeight() + * @uses Edge::setWeight() + * @uses Edge::getFlow() + * @uses Edge::setFlow() + * @uses Edge::getCapacity() + * @uses Edge::setCapacity() + */ + private function createEdgeCloneInternal(Edge $originalEdge, $ia, $ib) + { + $ends = $originalEdge->getVertices()->getIds(); + + // get start vertex from old start vertex id + $a = $this->getVertex($ends[$ia]); + // get target vertex from old target vertex id + $b = $this->getVertex($ends[$ib]); + + if ($originalEdge instanceof EdgeDirected) { + $newEdge = $a->createEdgeTo($b); + } else { + // create new edge between new a and b + $newEdge = $a->createEdge($b); + } + // TODO: copy edge attributes + $newEdge->getAttributeBag()->setAttributes($originalEdge->getAttributeBag()->getAttributes()); + $newEdge->setWeight($originalEdge->getWeight()); + $newEdge->setFlow($originalEdge->getFlow()); + $newEdge->setCapacity($originalEdge->getCapacity()); + + return $newEdge; + } + + /** + * create the given number of vertices or given array of Vertex IDs + * + * @param int|array $n number of vertices to create or array of Vertex IDs to create + * @return Vertices set of Vertices created + * @uses Graph::getNextId() + */ + public function createVertices($n) + { + $vertices = array(); + if (is_int($n) && $n >= 0) { + for ($id = $this->getNextId(), $n += $id; $id < $n; ++$id) { + $vertices[$id] = new Vertex($this, $id); + } + } elseif (is_array($n)) { + // array given => check to make sure all given IDs are available (atomic operation) + foreach ($n as $id) { + if (!is_int($id) && !is_string($id)) { + throw new InvalidArgumentException('All Vertex IDs have to be of type integer or string'); + } elseif ($this->vertices->hasVertexId($id)) { + throw new OverflowException('Given array of Vertex IDs contains an ID that already exists. Given IDs must be unique'); + } elseif (isset($vertices[$id])) { + throw new InvalidArgumentException('Given array of Vertex IDs contain duplicate IDs. Given IDs must be unique'); + } + + // temporary marker to check for duplicate IDs in the array + $vertices[$id] = false; + } + + // actually create all requested vertices + foreach ($n as $id) { + $vertices[$id] = new Vertex($this, $id); + } + } else { + throw new InvalidArgumentException('Invalid number of vertices given. Must be non-negative integer or an array of Vertex IDs'); + } + + return new Vertices($vertices); + } + + /** + * get next free/unused/available vertex ID + * + * its guaranteed there's NO other vertex with a greater ID + * + * @return int + */ + private function getNextId() + { + if (!$this->verticesStorage) { + return 0; + } + + // auto ID + return max(array_keys($this->verticesStorage))+1; + } + + /** + * returns the Vertex with identifier $id + * + * @param int|string $id identifier of Vertex + * @return Vertex + * @throws OutOfBoundsException if given vertex ID does not exist + */ + public function getVertex($id) + { + return $this->vertices->getVertexId($id); + } + + /** + * checks whether given vertex ID exists in this graph + * + * @param int|string $id identifier of Vertex + * @return boolean + */ + public function hasVertex($id) + { + return $this->vertices->hasVertexId($id); + } + + /** + * adds a new Vertex to the Graph (MUST NOT be called manually!) + * + * @param Vertex $vertex instance of the new Vertex + * @return void + * @private + * @see self::createVertex() instead! + */ + public function addVertex(Vertex $vertex) + { + if (isset($this->verticesStorage[$vertex->getId()])) { + throw new OverflowException('ID must be unique'); + } + $this->verticesStorage[$vertex->getId()] = $vertex; + } + + /** + * adds a new Edge to the Graph (MUST NOT be called manually!) + * + * @param Edge $edge instance of the new Edge + * @return void + * @private + * @see Vertex::createEdge() instead! + */ + public function addEdge(Edge $edge) + { + $this->edgesStorage []= $edge; + } + + /** + * remove the given edge from list of connected edges (MUST NOT be called manually!) + * + * @param Edge $edge + * @return void + * @throws InvalidArgumentException if given edge does not exist (should not ever happen) + * @private + * @see Edge::destroy() instead! + */ + public function removeEdge(Edge $edge) + { + try { + unset($this->edgesStorage[$this->edges->getIndexEdge($edge)]); + } + catch (OutOfBoundsException $e) { + throw new InvalidArgumentException('Invalid Edge does not exist in this Graph'); + } + } + + /** + * remove the given vertex from list of known vertices (MUST NOT be called manually!) + * + * @param Vertex $vertex + * @return void + * @throws InvalidArgumentException if given vertex does not exist (should not ever happen) + * @private + * @see Vertex::destroy() instead! + */ + public function removeVertex(Vertex $vertex) + { + try { + unset($this->verticesStorage[$this->vertices->getIndexVertex($vertex)]); + } + catch (OutOfBoundsException $e) { + throw new InvalidArgumentException('Invalid Vertex does not exist in this Graph'); + } + } + + /** + * Extracts edge from this graph + * + * @param Edge $edge + * @return Edge + * @throws UnderflowException if no edge was found + * @throws OverflowException if multiple edges match + */ + public function getEdgeClone(Edge $edge) + { + // Extract endpoints from edge + $vertices = $edge->getVertices()->getVector(); + + return $this->getEdgeCloneInternal($edge, $vertices[0], $vertices[1]); + } + + /** + * Extracts inverted edge from this graph + * + * @param Edge $edge + * @return Edge + * @throws UnderflowException if no edge was found + * @throws OverflowException if multiple edges match + */ + public function getEdgeCloneInverted(Edge $edge) + { + // Extract endpoints from edge + $vertices = $edge->getVertices()->getVector(); + + return $this->getEdgeCloneInternal($edge, $vertices[1], $vertices[0]); + } + + private function getEdgeCloneInternal(Edge $edge, Vertex $startVertex, Vertex $targetVertex) + { + // Get original vertices from resultgraph + $residualGraphEdgeStartVertex = $this->getVertex($startVertex->getId()); + $residualGraphEdgeTargetVertex = $this->getVertex($targetVertex->getId()); + + // Now get the edge + $residualEdgeArray = $residualGraphEdgeStartVertex->getEdgesTo($residualGraphEdgeTargetVertex); + $residualEdgeArray = Edges::factory($residualEdgeArray)->getVector(); + + // Check for parallel edges + if (!$residualEdgeArray) { + throw new UnderflowException('No original edges for given cloned edge found'); + } elseif (count($residualEdgeArray) !== 1) { + throw new OverflowException('More than one cloned edge? Parallel edges (multigraph) not supported'); + } + + return $residualEdgeArray[0]; + } + + /** + * do NOT allow cloning of objects (MUST NOT be called!) + * + * @throws BadMethodCallException + * @see Graph::createGraphClone() instead + */ + private function __clone() + { + // @codeCoverageIgnoreStart + throw new BadMethodCallException(); + // @codeCoverageIgnoreEnd + } + + public function getAttribute($name, $default = null) + { + return isset($this->attributes[$name]) ? $this->attributes[$name] : $default; + } + + public function setAttribute($name, $value) + { + $this->attributes[$name] = $value; + } + + public function getAttributeBag() + { + return new AttributeBagReference($this->attributes); + } +} diff --git a/vendor/clue/graph/src/Set/DualAggregate.php b/vendor/clue/graph/src/Set/DualAggregate.php new file mode 100644 index 0000000000000000000000000000000000000000..6bd370c395c32a3706d91bed0d54e0caea09f5e7 --- /dev/null +++ b/vendor/clue/graph/src/Set/DualAggregate.php @@ -0,0 +1,30 @@ +getEdges(); + } + return new self($edges); + } + + /** + * create new Edges instance that references the given source array of Edge instances + * + * Any changes in the referenced source array will automatically be + * reflected in this Set of Edges, e.g. if you add an Edge instance to the + * array, it will automatically be included in this Set. + * + * @param array $edgesArray + * @return Edges + */ + public static function factoryArrayReference(array &$edgesArray) + { + $edges = new static(); + $edges->edges =& $edgesArray; + return $edges; + } + + /** + * instantiate new Set of Edges + * + * @param array $edges + */ + public function __construct(array $edges = array()) + { + $this->edges = $edges; + } + + /** + * get array index for given Edge + * + * @param Edge $edge + * @throws OutOfBoundsException + * @return mixed + */ + public function getIndexEdge(Edge $edge) + { + $id = array_search($edge, $this->edges, true); + if ($id === false) { + throw new OutOfBoundsException('Given edge does NOT exist'); + } + return $id; + } + + /** + * return first Edge in this set of Edges + * + * some algorithms do not need a particular edge, but merely a (random) + * starting point. this is a convenience function to just pick the first + * edge from the list of known edges. + * + * @return Edge first Edge in this set of Edges + * @throws UnderflowException if set is empty + * @see self::getEdgeOrder() if you need to apply ordering first + */ + public function getEdgeFirst() + { + if (!$this->edges) { + throw new UnderflowException('Does not contain any edges'); + } + reset($this->edges); + + return current($this->edges); + } + + /** + * return last Edge in this set of Edges + * + * @return Edge last Edge in this set of Edges + * @throws UnderflowException if set is empty + */ + public function getEdgeLast() + { + if (!$this->edges) { + throw new UnderflowException('Does not contain any edges'); + } + end($this->edges); + + return current($this->edges); + } + + /** + * return Edge at given array index + * + * @param mixed $index + * @throws OutOfBoundsException if the given index does not exist + * @return Edge + */ + public function getEdgeIndex($index) + { + if (!isset($this->edges[$index])) { + throw new OutOfBoundsException('Invalid edge index'); + } + return $this->edges[$index]; + } + + /** + * return first Edge that matches the given callback filter function + * + * @param callback $callbackCheck + * @return Edge + * @throws UnderflowException if no Edge matches the given callback filter function + * @uses self::getEdgeMatchOrNull() + * @see self::getEdgesMatch() if you want to return *all* Edges that match + */ + public function getEdgeMatch($callbackCheck) + { + $ret = $this->getEdgeMatchOrNull($callbackCheck); + if ($ret === null) { + throw new UnderflowException('No edge found'); + } + return $ret; + } + + /** + * checks whethere there's an Edge that matches the given callback filter function + * + * @param callback $callbackCheck + * @return boolean + * @see self::getEdgeMatch() to return the Edge instance that matches the given callback filter function + * @uses self::getEdgeMatchOrNull() + */ + public function hasEdgeMatch($callbackCheck) + { + return ($this->getEdgeMatchOrNull($callbackCheck) !== null); + } + + /** + * get a new set of Edges that match the given callback filter function + * + * This only keeps Edge elements if the $callbackCheck returns a boolean + * true and filters out everything else. + * + * Edge index positions will be left unchanged. + * + * @param callable $callbackCheck + * @return Edges a new Edges instance + * @see self::getEdgeMatch() + */ + public function getEdgesMatch($callbackCheck) + { + return new static(array_filter($this->edges, $callbackCheck)); + } + + /** + * get new set of Edges ordered by given criterium $orderBy + * + * Edge index positions will be left unchanged. + * + * @param int $orderBy criterium to sort by. see self::ORDER_WEIGHT, etc. + * @param boolean $desc whether to return biggest first (true) instead of smallest first (default:false) + * @return Edges a new Edges set ordered by the given $orderBy criterium + * @throws InvalidArgumentException if criterium is unknown + */ + public function getEdgesOrder($orderBy, $desc = false) + { + if ($orderBy === self::ORDER_RANDOM) { + // shuffle the edge positions + $keys = array_keys($this->edges); + shuffle($keys); + + // re-order according to shuffled edge positions + $edges = array(); + foreach ($keys as $key) { + $edges[$key] = $this->edges[$key]; + } + + // create iterator for shuffled array (no need to check DESC flag) + return new static($edges); + } + + $callback = $this->getCallback($orderBy); + $array = $this->edges; + + uasort($array, function (Edge $va, Edge $vb) use ($callback, $desc) { + $ra = $callback($desc ? $vb : $va); + $rb = $callback($desc ? $va : $vb); + + if ($ra < $rb) { + return -1; + } elseif ($ra > $rb) { + return 1; + } else { + return 0; + } + }); + + return new static($array); + } + + /** + * get first edge ordered by given criterium $orderBy + * + * @param int $orderBy criterium to sort by. see self::ORDER_WEIGHT, etc. + * @param boolean $desc whether to return biggest (true) instead of smallest (default:false) + * @return Edge + * @throws InvalidArgumentException if criterium is unknown + * @throws UnderflowException if no edges exist + */ + public function getEdgeOrder($orderBy, $desc=false) + { + if (!$this->edges) { + throw new UnderflowException('No edge found'); + } + // random order + if ($orderBy === self::ORDER_RANDOM) { + // just return by random key (no need to check for DESC flag) + return $this->edges[array_rand($this->edges)]; + } + + $callback = $this->getCallback($orderBy); + + $ret = NULL; + $best = NULL; + foreach ($this->edges as $edge) { + $now = $callback($edge); + + if ($ret === NULL || ($desc && $now > $best) || (!$desc && $now < $best)) { + $ret = $edge; + $best = $now; + } + } + + return $ret; + } + + /** + * return self reference to Set of Edges + * + * @return Edges + * @see self::factory() + */ + public function getEdges() + { + return $this; + } + + /** + * get a new set of Edges where each Edge is distinct/unique + * + * @return Edges a new Edges instance + */ + public function getEdgesDistinct() + { + $edges = array(); + foreach ($this->edges as $edge) { + // filter duplicate edges + if (!in_array($edge, $edges, true)) { + $edges []= $edge; + } + } + + return new Edges($edges); + } + + /** + * get intersection of Edges with given other Edges + * + * The intersection contains all Edge instances that are present in BOTH + * this set of Edges and the given set of other Edges. + * + * Edge index/keys will be preserved from original array. + * + * Duplicate Edge instances will be kept if the corresponding number of + * Edge instances is also found in $otherEdges. + * + * @param Edges|Edge[] $otherEdges + * @return Edges a new Edges set + */ + public function getEdgesIntersection($otherEdges) + { + $otherArray = self::factory($otherEdges)->getVector(); + + $edges = array(); + foreach ($this->edges as $eid => $edge) { + $i = array_search($edge, $otherArray, true); + + if ($i !== false) { + // remove from other array in order to check for duplicate matches + unset($otherArray[$i]); + + $edges[$eid] = $edge; + } + } + + return new static($edges); + } + + /** + * return array of Edge instances + * + * @return Edge[] + */ + public function getVector() + { + return array_values($this->edges); + } + + /** + * count number of Edges + * + * @return int + * @see self::isEmpty() + */ + public function count() + { + return count($this->edges); + } + + /** + * check whether this Set of Edges is empty + * + * A Set if empty if no single Edge instance is added. This is faster + * than calling `count() === 0`. + * + * @return boolean + */ + public function isEmpty() + { + return !$this->edges; + } + + /** + * get Iterator + * + * This method implements the IteratorAggregate interface and allows this + * Set of Edges to be used in foreach loops. + * + * @return IteratorIterator + */ + public function getIterator() + { + return new IteratorIterator(new ArrayIterator($this->edges)); + } + + /** + * call given $callback on each Edge and sum their results + * + * @param callable $callback + * @return number + * @throws InvalidArgumentException for invalid callbacks + * @uses self::getCallback() + */ + public function getSumCallback($callback) + { + $callback = $this->getCallback($callback); + + // return array_sum(array_map($callback, $this->edges)); + + $sum = 0; + foreach ($this->edges as $edge) { + $sum += $callback($edge); + } + return $sum; + } + + private function getEdgeMatchOrNull($callbackCheck) + { + $callbackCheck = $this->getCallback($callbackCheck); + + foreach ($this->edges as $edge) { + if ($callbackCheck($edge)) { + return $edge; + } + } + return null; + } + + /** + * get callback/Closure to be called on Edge instances for given callback identifier + * + * @param callable|int $callback + * @throws InvalidArgumentException + * @return Closure + */ + private function getCallback($callback) + { + if (is_callable($callback)) { + if (is_array($callback)) { + $callback = function (Edge $edge) use ($callback) { + return call_user_func($callback, $edge); + }; + } + return $callback; + } + + static $methods = array( + self::ORDER_WEIGHT => 'getWeight', + self::ORDER_CAPACITY => 'getCapacity', + self::ORDER_CAPACITY_REMAINING => 'getCapacityRemaining', + self::ORDER_FLOW => 'getFlow' + ); + + if (!is_int($callback) || !isset($methods[$callback])) { + throw new InvalidArgumentException('Invalid callback given'); + } + + $method = $methods[$callback]; + + return function (Edge $edge) use ($method) { + return $edge->$method(); + }; + } +} diff --git a/vendor/clue/graph/src/Set/EdgesAggregate.php b/vendor/clue/graph/src/Set/EdgesAggregate.php new file mode 100644 index 0000000000000000000000000000000000000000..ab73c4200bc7d2a840fee378a314bbb340bf4548 --- /dev/null +++ b/vendor/clue/graph/src/Set/EdgesAggregate.php @@ -0,0 +1,16 @@ +getVertices(); + } + return new self($vertices); + } + + /** + * create new Vertices instance that references the given source array of Vertex instances + * + * Any changes in the referenced source array will automatically be + * reflected in this Set of Vertices, e.g. if you add a Vertex instance to + * the array, it will automatically be included in this Set. + * + * @param array $verticesArray + * @return Vertices + */ + public static function factoryArrayReference(array &$verticesArray) + { + $vertices = new static(); + $vertices->vertices =& $verticesArray; + return $vertices; + } + + /** + * instantiate new Set of Vertices + * + * @param array $vertices + */ + public function __construct(array $vertices = array()) + { + $this->vertices = $vertices; + } + + /** + * get Vertex with the given vertex $id + * + * @param int|string $id + * @return Vertex + * @throws OutOfBoundsException if no Vertex with the given ID exists + * @uses self::getVertexMatch() + */ + public function getVertexId($id) + { + try { + return $this->getVertexMatch($this->getCallbackId($id)); + } + catch (UnderflowException $e) { + throw new OutOfBoundsException('Vertex ' . $id . ' does not exist', 0, $e); + } + } + + /** + * checks whether given vertex ID exists in this set of vertices + * + * @param int|string $id identifier of Vertex + * @return boolean + * @uses self::hasVertexMatch() + */ + public function hasVertexId($id) + { + return $this->hasVertexMatch($this->getCallbackId($id)); + } + + /** + * get array index for given Vertex + * + * not every set of Vertices represents a map, as such array index and + * Vertex ID do not necessarily have to match. + * + * @param Vertex $vertex + * @throws OutOfBoundsException + * @return mixed + */ + public function getIndexVertex(Vertex $vertex) + { + $id = array_search($vertex, $this->vertices, true); + if ($id === false) { + throw new OutOfBoundsException('Given vertex does NOT exist'); + } + return $id; + } + + /** + * return first Vertex in this set of Vertices + * + * some algorithms do not need a particular vertex, but merely a (random) + * starting point. this is a convenience function to just pick the first + * vertex from the list of known vertices. + * + * @return Vertex first Vertex in this set of Vertices + * @throws UnderflowException if set is empty + * @see self::getVertexOrder() if you need to apply ordering first + */ + public function getVertexFirst() + { + if (!$this->vertices) { + throw new UnderflowException('Does not contain any vertices'); + } + reset($this->vertices); + + return current($this->vertices); + } + + /** + * return last Vertex in this set of Vertices + * + * @return Vertex last Vertex in this set of Vertices + * @throws UnderflowException if set is empty + */ + public function getVertexLast() + { + if (!$this->vertices) { + throw new UnderflowException('Does not contain any vertices'); + } + end($this->vertices); + + return current($this->vertices); + } + + /** + * return first Vertex that matches the given callback filter function + * + * @param callback $callbackCheck + * @return Vertex + * @throws UnderflowException if no Vertex matches the given callback filter function + * @uses self::getVertexMatchOrNull() + * @see self::getVerticesMatch() if you want to return *all* Vertices that match + */ + public function getVertexMatch($callbackCheck) + { + $ret = $this->getVertexMatchOrNull($callbackCheck); + if ($ret === null) { + throw new UnderflowException('No vertex found'); + } + return $ret; + } + + /** + * checks whether there's a Vertex that matches the given callback filter function + * + * @param callback $callbackCheck + * @return boolean + * @see self::getVertexMatch() to return the Vertex instance that matches the given callback filter function + * @uses self::getVertexMatchOrNull() + */ + public function hasVertexMatch($callbackCheck) + { + return ($this->getVertexMatchOrNull($callbackCheck) !== null); + } + + /** + * get a new set of Vertices that match the given callback filter function + * + * This only keeps Vertex elements if the $callbackCheck returns a boolean + * true and filters out everything else. + * + * Vertex index positions will be left unchanged, so if you call this method + * on a VerticesMap, it will also return a VerticesMap. + * + * @param callable $callbackCheck + * @return Vertices a new Vertices instance + * @see self::getVertexMatch() + */ + public function getVerticesMatch($callbackCheck) + { + return new static(array_filter($this->vertices, $callbackCheck)); + } + + /** + * get new Set of Vertices ordered by given criterium $orderBy + * + * Vertex index positions will be left unchanged, so if you call this method + * on a VerticesMap, it will also return a VerticesMap. + * + * @param int $orderBy criterium to sort by. see Vertex::ORDER_ID, etc. + * @param boolean $desc whether to return biggest first (true) instead of smallest first (default:false) + * @return Vertices a new Vertices set ordered by the given $orderBy criterium + * @throws InvalidArgumentException if criterium is unknown + * @see self::getVertexOrder() + */ + public function getVerticesOrder($orderBy, $desc = false) + { + if ($orderBy === self::ORDER_RANDOM) { + // shuffle the vertex positions + $keys = array_keys($this->vertices); + shuffle($keys); + + // re-order according to shuffled vertex positions + $vertices = array(); + foreach ($keys as $key) { + $vertices[$key] = $this->vertices[$key]; + } + + // create iterator for shuffled array (no need to check DESC flag) + return new static($vertices); + } + + $callback = $this->getCallback($orderBy); + $array = $this->vertices; + + uasort($array, function (Vertex $va, Vertex $vb) use ($callback, $desc) { + $ra = $callback($desc ? $vb : $va); + $rb = $callback($desc ? $va : $vb); + + if ($ra < $rb) { + return -1; + } elseif ($ra > $rb) { + return 1; + } else { + return 0; + } + }); + + return new static($array); + } + + /** + * get intersection of Vertices with given other Vertices + * + * The intersection contains all Vertex instances that are present in BOTH + * this set of Vertices and the given set of other Vertices. + * + * Vertex index/keys will be preserved from original array. + * + * Duplicate Vertex instances will be kept if the corresponding number of + * Vertex instances is also found in $otherVertices. + * + * @param Vertices|Vertex[] $otherVertices + * @return Vertices a new Vertices set + */ + public function getVerticesIntersection($otherVertices) + { + $otherArray = self::factory($otherVertices)->getVector(); + + $vertices = array(); + foreach ($this->vertices as $vid => $vertex) { + $i = array_search($vertex, $otherArray, true); + + if ($i !== false) { + // remove from other array in order to check for duplicate matches + unset($otherArray[$i]); + + $vertices[$vid] = $vertex; + } + } + + return new static($vertices); + } + + /** + * get first vertex (optionally ordered by given criterium $by) from given array of vertices + * + * @param int $orderBy criterium to sort by. see Vertex::ORDER_ID, etc. + * @param boolean $desc whether to return biggest (true) instead of smallest (default:false) + * @return Vertex + * @throws InvalidArgumentException if criterium is unknown + * @throws UnderflowException if no vertices exist + * @see self::getVerticesOrder() + */ + public function getVertexOrder($orderBy, $desc=false) + { + if (!$this->vertices) { + throw new UnderflowException('No vertex found'); + } + // random order + if ($orderBy === self::ORDER_RANDOM) { + // just return by random key (no need to check for DESC flag) + return $this->vertices[array_rand($this->vertices)]; + } + + $callback = $this->getCallback($orderBy); + + $ret = NULL; + $best = NULL; + foreach ($this->vertices as $vertex) { + $now = $callback($vertex); + + if ($ret === NULL || ($desc && $now > $best) || (!$desc && $now < $best)) { + $ret = $vertex; + $best = $now; + } + } + + return $ret; + } + + /** + * return self reference to Set of Vertices + * + * @return Vertices + * @see self::factory() + */ + public function getVertices() + { + return $this; + } + + /** + * get a new set of Vertices where each Vertex is distinct/unique + * + * @return VerticesMap a new VerticesMap instance + * @uses self::getMap() + */ + public function getVerticesDistinct() + { + return new VerticesMap($this->getMap()); + } + + /** + * get a mapping array of Vertex ID => Vertex instance and thus remove duplicate vertices + * + * @return Vertex[] Vertex ID => Vertex instance + * @uses Vertex::getId() + */ + public function getMap() + { + $vertices = array(); + foreach ($this->vertices as $vertex) { + $vertices[$vertex->getId()] = $vertex; + } + return $vertices; + } + + /** + * return array of Vertex IDs + * + * @return array + */ + public function getIds() + { + $ids = array(); + foreach ($this->vertices as $vertex) { + $ids []= $vertex->getId(); + } + return $ids; + } + + /** + * return array of Vertex instances + * + * @return Vertex[] + */ + public function getVector() + { + return array_values($this->vertices); + } + + /** + * count number of vertices + * + * @return int + * @see self::isEmpty() + */ + public function count() + { + return count($this->vertices); + } + + /** + * check whether this Set of Vertices is empty + * + * A Set if empty if no single Vertex instance is added. This is faster + * than calling `count() === 0`. + * + * @return boolean + */ + public function isEmpty() + { + return !$this->vertices; + } + + /** + * check whether this set contains any duplicate vertex instances + * + * @return boolean + * @uses self::getMap() + */ + public function hasDuplicates() + { + return (count($this->vertices) !== count($this->getMap())); + } + + /** + * get Iterator + * + * This method implements the IteratorAggregate interface and allows this + * Set of Vertices to be used in foreach loops. + * + * @return IteratorIterator + */ + public function getIterator() + { + return new IteratorIterator(new ArrayIterator($this->vertices)); + } + + /** + * call given $callback on each Vertex and sum their results + * + * @param callable $callback + * @return number + * @throws InvalidArgumentException for invalid callbacks + * @uses self::getCallback() + */ + public function getSumCallback($callback) + { + $callback = $this->getCallback($callback); + + // return array_sum(array_map($callback, $this->vertices)); + + $sum = 0; + foreach ($this->vertices as $vertex) { + $sum += $callback($vertex); + } + return $sum; + } + + private function getCallbackId($id) + { + return function (Vertex $vertex) use ($id) { + return ($vertex->getId() == $id); + }; + } + + private function getVertexMatchOrNull($callbackCheck) + { + $callbackCheck = $this->getCallback($callbackCheck); + + foreach ($this->vertices as $vertex) { + if ($callbackCheck($vertex)) { + return $vertex; + } + } + return null; + } + + /** + * get callback/Closure to be called on Vertex instances for given callback identifier + * + * @param callable|int $callback + * @throws InvalidArgumentException + * @return Closure + */ + private function getCallback($callback) + { + if (is_callable($callback)) { + if (is_array($callback)) { + $callback = function (Vertex $vertex) use ($callback) { + return call_user_func($callback, $vertex); + }; + } + return $callback; + } + + static $methods = array( + self::ORDER_ID => 'getId', + self::ORDER_GROUP => 'getGroup' + ); + + if (!is_int($callback) || !isset($methods[$callback])) { + throw new InvalidArgumentException('Invalid callback given'); + } + + $method = $methods[$callback]; + + return function (Vertex $vertex) use ($method) { + return $vertex->$method(); + }; + } +} diff --git a/vendor/clue/graph/src/Set/VerticesAggregate.php b/vendor/clue/graph/src/Set/VerticesAggregate.php new file mode 100644 index 0000000000000000000000000000000000000000..b67afd87c0e6636b05c80ae232f89dd1c406a24d --- /dev/null +++ b/vendor/clue/graph/src/Set/VerticesAggregate.php @@ -0,0 +1,16 @@ + Vertex instance mapping array + * + * Among others, using a mapped array significantly speeds up accessing vertices + * by ID. However, there's no way to store multiple vertices with the same ID + * (i.e. each Vertex ID has to be unique). + */ +class VerticesMap extends Vertices +{ + public function getMap() + { + return $this->vertices; + } + + public function getVertexId($id) + { + if (!isset($this->vertices[$id])) { + throw new OutOfBoundsException('Invalid vertex ID'); + } + return $this->vertices[$id]; + } + + public function hasVertexId($id) + { + return isset($this->vertices[$id]); + } + + public function getVerticesDistinct() + { + return $this; + } + + public function getIds() + { + return array_keys($this->vertices); + } + + public function getIndexVertex(Vertex $vertex) + { + $id = $vertex->getId(); + if (!isset($this->vertices[$id]) || $this->vertices[$id] !== $vertex) { + throw new OutOfBoundsException(); + } + return $id; + } + + /** + * + * @return VerticesMap + */ + public function getVertices() + { + return $this; + } + + public function hasDuplicates() + { + return false; + } +} diff --git a/vendor/clue/graph/src/Vertex.php b/vendor/clue/graph/src/Vertex.php new file mode 100644 index 0000000000000000000000000000000000000000..a020de404aa8d26ba155fb03f809076854009740 --- /dev/null +++ b/vendor/clue/graph/src/Vertex.php @@ -0,0 +1,389 @@ +id = $id; + $this->graph = $graph; + + $graph->addVertex($this); + } + + /** + * get graph this vertex is attached to + * + * @return Graph + */ + public function getGraph() + { + return $this->graph; + } + + public function getBalance() + { + return $this->balance; + } + + public function setBalance($balance) + { + if ($balance !== NULL && !is_float($balance) && !is_int($balance)) { + throw new InvalidArgumentException('Invalid balance given - must be numeric'); + } + $this->balance = $balance; + + return $this; + } + + /** + * set group number of this vertex + * + * @param int $group + * @return Vertex $this (chainable) + * @throws InvalidArgumentException if group is not numeric + */ + public function setGroup($group) + { + if (!is_int($group)) { + throw new InvalidArgumentException('Invalid group number'); + } + $this->group = $group; + + return $this; + } + + /** + * get group number + * + * @return int + */ + public function getGroup() + { + return $this->group; + } + + /** + * returns id of this Vertex + * + * @return int|string + */ + public function getId() + { + return $this->id; + } + + /** + * create new directed edge from this start vertex to given target vertex + * + * @param Vertex $vertex target vertex + * @return EdgeDirected + * @throws InvalidArgumentException + * @uses Graph::addEdge() + */ + public function createEdgeTo(Vertex $vertex) + { + return new EdgeDirected($this, $vertex); + } + + /** + * add new undirected (bidirectional) edge between this vertex and given vertex + * + * @param Vertex $vertex + * @return EdgeUndirected + * @throws InvalidArgumentException + * @uses Graph::addEdge() + */ + public function createEdge(Vertex $vertex) + { + return new EdgeUndirected($this, $vertex); + } + + /** + * add the given edge to list of connected edges (MUST NOT be called manually) + * + * @param Edge $edge + * @return void + * @private + * @see self::createEdge() instead! + */ + public function addEdge(Edge $edge) + { + $this->edges[] = $edge; + } + + /** + * remove the given edge from list of connected edges (MUST NOT be called manually) + * + * @param Edge $edge + * @return void + * @throws InvalidArgumentException if given edge does not exist + * @private + * @see Edge::destroy() instead! + */ + public function removeEdge(Edge $edge) + { + $id = array_search($edge, $this->edges, true); + if ($id === false) { + throw new InvalidArgumentException('Given edge does NOT exist'); + } + unset($this->edges[$id]); + } + + /** + * check whether this vertex has a direct edge to given $vertex + * + * @param Vertex $vertex + * @return boolean + * @uses Edge::hasVertexTarget() + */ + public function hasEdgeTo(Vertex $vertex) + { + $that = $this; + + return $this->getEdges()->hasEdgeMatch(function (Edge $edge) use ($that, $vertex) { + return $edge->isConnection($that, $vertex); + }); + } + + /** + * check whether the given vertex has a direct edge to THIS vertex + * + * @param Vertex $vertex + * @return boolean + * @uses Vertex::hasEdgeTo() + */ + public function hasEdgeFrom(Vertex $vertex) + { + return $vertex->hasEdgeTo($this); + } + + /** + * get set of ALL Edges attached to this vertex + * + * @return Edges + */ + public function getEdges() + { + return new Edges($this->edges); + } + + /** + * get set of all outgoing Edges attached to this vertex + * + * @return Edges + */ + public function getEdgesOut() + { + $that = $this; + + return $this->getEdges()->getEdgesMatch(function (Edge $edge) use ($that) { + return $edge->hasVertexStart($that); + }); + } + + /** + * get set of all ingoing Edges attached to this vertex + * + * @return Edges + */ + public function getEdgesIn() + { + $that = $this; + + return $this->getEdges()->getEdgesMatch(function (Edge $edge) use ($that) { + return $edge->hasVertexTarget($that); + }); + } + + /** + * get set of Edges FROM this vertex TO the given vertex + * + * @param Vertex $vertex + * @return Edges + * @uses Edge::hasVertexTarget() + */ + public function getEdgesTo(Vertex $vertex) + { + $that = $this; + + return $this->getEdges()->getEdgesMatch(function (Edge $edge) use ($that, $vertex) { + return $edge->isConnection($that, $vertex); + }); + } + + /** + * get set of Edges FROM the given vertex TO this vertex + * + * @param Vertex $vertex + * @return Edges + * @uses Vertex::getEdgesTo() + */ + public function getEdgesFrom(Vertex $vertex) + { + return $vertex->getEdgesTo($this); + } + + /** + * get set of adjacent Vertices of this vertex (edge FROM or TO this vertex) + * + * If there are multiple parallel edges between the same Vertex, it will be + * returned several times in the resulting Set of Vertices. If you only + * want unique Vertex instances, use `getVerticesDistinct()`. + * + * @return Vertices + * @uses Edge::hasVertexStart() + * @uses Edge::getVerticesToFrom() + * @uses Edge::getVerticesFromTo() + */ + public function getVerticesEdge() + { + $ret = array(); + foreach ($this->edges as $edge) { + if ($edge->hasVertexStart($this)) { + $ret []= $edge->getVertexToFrom($this); + } else { + $ret []= $edge->getVertexFromTo($this); + } + } + + return new Vertices($ret); + } + + /** + * get set of all Vertices this vertex has an edge to + * + * If there are multiple parallel edges to the same Vertex, it will be + * returned several times in the resulting Set of Vertices. If you only + * want unique Vertex instances, use `getVerticesDistinct()`. + * + * @return Vertices + * @uses Vertex::getEdgesOut() + * @uses Edge::getVerticesToFrom() + */ + public function getVerticesEdgeTo() + { + $ret = array(); + foreach ($this->getEdgesOut() as $edge) { + $ret []= $edge->getVertexToFrom($this); + } + + return new Vertices($ret); + } + + /** + * get set of all Vertices that have an edge TO this vertex + * + * If there are multiple parallel edges from the same Vertex, it will be + * returned several times in the resulting Set of Vertices. If you only + * want unique Vertex instances, use `getVerticesDistinct()`. + * + * @return Vertices + * @uses Vertex::getEdgesIn() + * @uses Edge::getVerticesFromTo() + */ + public function getVerticesEdgeFrom() + { + $ret = array(); + foreach ($this->getEdgesIn() as $edge) { + $ret []= $edge->getVertexFromTo($this); + } + + return new Vertices($ret); + } + + /** + * destroy vertex and all edges connected to it and remove reference from graph + * + * @uses Edge::destroy() + * @uses Graph::removeVertex() + */ + public function destroy() + { + foreach ($this->edges as $edge) { + $edge->destroy(); + } + $this->graph->removeVertex($this); + } + + /** + * do NOT allow cloning of objects + * + * @throws BadMethodCallException + */ + private function __clone() + { + // @codeCoverageIgnoreStart + throw new BadMethodCallException(); + // @codeCoverageIgnoreEnd + } + + public function getAttribute($name, $default = null) + { + return isset($this->attributes[$name]) ? $this->attributes[$name] : $default; + } + + public function setAttribute($name, $value) + { + $this->attributes[$name] = $value; + } + + public function getAttributeBag() + { + return new AttributeBagReference($this->attributes); + } +} diff --git a/vendor/clue/graph/src/Walk.php b/vendor/clue/graph/src/Walk.php new file mode 100644 index 0000000000000000000000000000000000000000..c825e5f9772b11d9259641d39f2c1e5599b18522 --- /dev/null +++ b/vendor/clue/graph/src/Walk.php @@ -0,0 +1,313 @@ +getVertexToFrom($vertexCurrent); + $vertices []= $vertexCurrent; + } + + return new self($vertices, $edges); + } + + /** + * create new walk instance between given set of Vertices / array of Vertex instances + * + * @param Vertices|Vertex[] $vertices + * @param int|null $by + * @param boolean $desc + * @return Walk + * @throws UnderflowException if no vertices were given + * @see Edges::getEdgeOrder() for parameters $by and $desc + */ + public static function factoryFromVertices($vertices, $by = null, $desc = false) + { + $edges = array(); + $first = NULL; + $last = NULL; + foreach ($vertices as $vertex) { + // skip first vertex as last is unknown + if ($first === NULL) { + $first = $vertex; + } else { + // pick edge between last vertex and this vertex + if ($by === null) { + $edges []= $last->getEdgesTo($vertex)->getEdgeFirst(); + } else { + $edges []= $last->getEdgesTo($vertex)->getEdgeOrder($by, $desc); + } + } + $last = $vertex; + } + if ($last === NULL) { + throw new UnderflowException('No vertices given'); + } + + return new self($vertices, $edges); + } + + /** + * create new cycle instance from given predecessor map + * + * @param Vertex[] $predecessors map of vid => predecessor vertex instance + * @param Vertex $vertex start vertex to search predecessors from + * @param int|null $by + * @param boolean $desc + * @return Walk + * @throws UnderflowException + * @see Edges::getEdgeOrder() for parameters $by and $desc + * @uses self::factoryFromVertices() + */ + public static function factoryCycleFromPredecessorMap(array $predecessors, Vertex $vertex, $by = null, $desc = false) + { + // find a vertex in the cycle + $vid = $vertex->getId(); + $startVertices = array(); + do { + if (!isset($predecessors[$vid])) { + throw new InvalidArgumentException('Predecessor map is incomplete and does not form a cycle'); + } + + $startVertices[$vid] = $vertex; + + $vertex = $predecessors[$vid]; + $vid = $vertex->getId(); + } while (!isset($startVertices[$vid])); + + // find negative cycle + $vid = $vertex->getId(); + // build array of vertices in cycle + $vertices = array(); + do { + // add new vertex to cycle + $vertices[$vid] = $vertex; + + // get predecessor of vertex + $vertex = $predecessors[$vid]; + $vid = $vertex->getId(); + // continue until we find a vertex that's already in the circle (i.e. circle is closed) + } while (!isset($vertices[$vid])); + + // reverse cycle, because cycle is actually built in opposite direction due to checking predecessors + $vertices = array_reverse($vertices, true); + + // additional edge from last vertex to first vertex + $vertices[] = reset($vertices); + + return self::factoryCycleFromVertices($vertices, $by, $desc); + } + + /** + * create new cycle instance with edges between given vertices + * + * @param Vertex[]|Vertices $vertices + * @param int|null $by + * @param boolean $desc + * @return Walk + * @throws UnderflowException if no vertices were given + * @see Edges::getEdgeOrder() for parameters $by and $desc + * @uses self::factoryFromVertices() + */ + public static function factoryCycleFromVertices($vertices, $by = null, $desc = false) + { + $cycle = self::factoryFromVertices($vertices, $by, $desc); + + if ($cycle->getEdges()->isEmpty()) { + throw new InvalidArgumentException('Cycle with no edges can not exist'); + } + + if ($cycle->getVertices()->getVertexFirst() !== $cycle->getVertices()->getVertexLast()) { + throw new InvalidArgumentException('Cycle has to start and end at the same vertex'); + } + + return $cycle; + } + + /** + * create new cycle instance with vertices connected by given edges + * + * @param Edges|Edge[] $edges + * @param Vertex $startVertex + * @return Walk + * @throws InvalidArgumentException if the given array of edges does not represent a valid cycle + * @uses self::factoryFromEdges() + */ + public static function factoryCycleFromEdges($edges, Vertex $startVertex) + { + $cycle = self::factoryFromEdges($edges, $startVertex); + + // ensure this walk is actually a cycle by checking start = end + if ($cycle->getVertices()->getVertexLast() !== $startVertex) { + throw new InvalidArgumentException('The given array of edges does not represent a cycle'); + } + + return $cycle; + } + + /** + * + * @var Vertices + */ + protected $vertices; + + /** + * + * @var Edges + */ + protected $edges; + + protected function __construct($vertices, $edges) + { + $this->vertices = Vertices::factory($vertices); + $this->edges = Edges::factory($edges); + } + + /** + * return original graph + * + * @return Graph + * @uses self::getVertices() + * @uses Vertices::getVertexFirst() + * @uses Vertex::getGraph() + */ + public function getGraph() + { + return $this->getVertices()->getVertexFirst()->getGraph(); + } + + /** + * create new graph clone with only vertices and edges actually in the walk + * + * do not add duplicate vertices and edges for loops and intersections, etc. + * + * @return Graph + * @uses Walk::getEdges() + * @uses Graph::createGraphCloneEdges() + */ + public function createGraph() + { + // create new graph clone with only edges of walk + $graph = $this->getGraph()->createGraphCloneEdges($this->getEdges()); + $vertices = $this->getVertices()->getMap(); + // get all vertices + foreach ($graph->getVertices()->getMap() as $vid => $vertex) { + if (!isset($vertices[$vid])) { + // remove those not present in the walk (isolated vertices, etc.) + $vertex->destroy(); + } + } + + return $graph; + } + + /** + * return set of all Edges of walk (in sequence visited in walk, may contain duplicates) + * + * If you need to return set a of all unique Edges of walk, use + * `Walk::getEdges()->getEdgesDistinct()` instead. + * + * @return Edges + */ + public function getEdges() + { + return $this->edges; + } + + /** + * return set of all Vertices of walk (in sequence visited in walk, may contain duplicates) + * + * If you need to return set a of all unique Vertices of walk, use + * `Walk::getVertices()->getVerticesDistinct()` instead. + * + * If you need to return the source vertex (first vertex of walk), use + * `Walk::getVertices()->getVertexFirst()` instead. + * + * If you need to return the target/destination vertex (last vertex of walk), use + * `Walk::getVertices()->getVertexLast()` instead. + * + * @return Vertices + */ + public function getVertices() + { + return $this->vertices; + } + + /** + * get alternating sequence of vertex, edge, vertex, edge, ..., vertex + * + * @return array + */ + public function getAlternatingSequence() + { + $edges = $this->edges->getVector(); + $vertices = $this->vertices->getVector(); + + $ret = array(); + for ($i = 0, $l = count($this->edges); $i < $l; ++$i) { + $ret []= $vertices[$i]; + $ret []= $edges[$i]; + } + $ret[] = $vertices[$i]; + + return $ret; + } + + /** + * check to make sure this walk is still valid (i.e. source graph still contains all vertices and edges) + * + * @return boolean + * @uses Walk::getGraph() + * @uses Graph::getVertices() + * @uses Graph::getEdges() + */ + public function isValid() + { + $vertices = $this->getGraph()->getVertices()->getMap(); + // check source graph contains all vertices + foreach ($this->getVertices()->getMap() as $vid => $vertex) { + // make sure vertex ID exists and has not been replaced + if (!isset($vertices[$vid]) || $vertices[$vid] !== $vertex) { + return false; + } + } + $edges = $this->getGraph()->getEdges()->getVector(); + // check source graph contains all edges + foreach ($this->edges as $edge) { + if (!in_array($edge, $edges, true)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/clue/graph/tests/Attribute/AbstractAttributeAwareTest.php b/vendor/clue/graph/tests/Attribute/AbstractAttributeAwareTest.php new file mode 100644 index 0000000000000000000000000000000000000000..869ca0fe9aec06995b5966d88b658a742a22886f --- /dev/null +++ b/vendor/clue/graph/tests/Attribute/AbstractAttributeAwareTest.php @@ -0,0 +1,39 @@ +createAttributeAware(); + $this->assertInstanceOf('Fhaculty\Graph\Attribute\AttributeAware', $entity); + + return $entity; + } + + /** + * @depends testAttributeAwareInterface + * @param AttributeAware $entity + */ + public function testAttributeSetGetDefault(AttributeAware $entity) + { + $entity->setAttribute('test', 'value'); + $this->assertEquals('value', $entity->getAttribute('test')); + + $this->assertEquals(null, $entity->getAttribute('unknown')); + + $this->assertEquals('default', $entity->getAttribute('unknown', 'default')); + } + + /** + * @depends testAttributeAwareInterface + * @param AttributeAware $entity + */ + public function testAttributeBag(AttributeAware $entity) + { + $bag = $entity->getAttributeBag(); + $this->assertInstanceOf('Fhaculty\Graph\Attribute\AttributeBag', $bag); + } +} diff --git a/vendor/clue/graph/tests/Attribute/AttributeBagContainerTest.php b/vendor/clue/graph/tests/Attribute/AttributeBagContainerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..66807726466e3fbfb738fb92a80227e6d07684d5 --- /dev/null +++ b/vendor/clue/graph/tests/Attribute/AttributeBagContainerTest.php @@ -0,0 +1,36 @@ +assertNull($bag->getAttribute('unknown')); + $this->assertEquals('default', $bag->getAttribute('unknown', 'default')); + + $this->assertEquals(array(), $bag->getAttributes()); + + $this->assertSame($bag, $bag->getAttributeBag()); + } + + public function testSome() + { + $bag = new AttributeBagContainer(); + + $bag->setAttribute('true', true); + $bag->setAttribute('two', 2); + + $this->assertSame(true, $bag->getAttribute('true')); + $this->assertSame(2, $bag->getAttribute('two')); + $this->assertEquals(array('true' => true, 'two' => 2), $bag->getAttributes()); + + $bag->setAttribute('float', '1.2'); + $bag->setAttributes(array('two' => 'two', 'three' => 3)); + + $expected = array('true' => true, 'two' => 'two', 'float' => 1.2, 'three' => 3); + $this->assertEquals($expected, $bag->getAttributes()); + } +} diff --git a/vendor/clue/graph/tests/Attribute/AttributeBagNamespacedTest.php b/vendor/clue/graph/tests/Attribute/AttributeBagNamespacedTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0ac11c64a07d68e06474154732df8538685b9077 --- /dev/null +++ b/vendor/clue/graph/tests/Attribute/AttributeBagNamespacedTest.php @@ -0,0 +1,69 @@ +assertSame($bag, $bag->getAttributeBag()); + + $container->setAttribute('a.b', 'c'); + $container->setAttribute('test.d', 'e'); + + $this->assertEquals('e', $bag->getAttribute('d')); + + $this->assertNull($bag->getAttribute('unknown')); + $this->assertEquals('default', $bag->getAttribute('unknown', 'default')); + + $bag->setAttribute('d', 'test'); + + $this->assertEquals('test', $bag->getAttribute('d')); + $this->assertEquals('test', $container->getAttribute('test.d')); + + $bag->setAttributes(array('d' => 'd', 'e' => 'e')); + + $this->assertEquals(array('a.b' => 'c', 'test.d' => 'd', 'test.e' => 'e'), $container->getAttributes()); + } + + /** + * + * @param AttributeAware $entity + * @dataProvider provideNamespacable + */ + public function testReadableEntities(AttributeAware $entity) + { + $bag = new AttributeBagNamespaced($entity, 'test.'); + $this->assertSame($bag, $bag->getAttributeBag()); + + $entity->setAttribute('a.b', 'c'); + $entity->setAttribute('test.d', 'e'); + + $this->assertEquals('e', $bag->getAttribute('d')); + + $this->assertNull($bag->getAttribute('a.b')); + $this->assertNull($bag->getAttribute('test.d')); + + $this->assertEquals(array('d' => 'e'), $bag->getAttributes()); + } + + public function provideNamespacable() + { + $graph = new Graph(); + $vertex = $graph->createVertex(); + $bag = $vertex->getAttributeBag(); + $subNamespace = new AttributeBagNamespaced($bag, 'prefix'); + + return array( + array($graph), + array($vertex), + array($bag), + array($subNamespace), + ); + } +} diff --git a/vendor/clue/graph/tests/Attribute/AttributeBagReferenceTest.php b/vendor/clue/graph/tests/Attribute/AttributeBagReferenceTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d03248a1e8eb01050791f0dc744e0ea68137922f --- /dev/null +++ b/vendor/clue/graph/tests/Attribute/AttributeBagReferenceTest.php @@ -0,0 +1,42 @@ +assertNull($bag->getAttribute('unknown')); + $this->assertEquals('default', $bag->getAttribute('unknown', 'default')); + + $this->assertEquals(array(), $bag->getAttributes()); + + $this->assertSame($bag, $bag->getAttributeBag()); + } + + public function testSome() + { + $attributes = array( + 'true' => true, + 'two' => 2, + ); + + $bag = new AttributeBagReference($attributes); + + $this->assertSame(true, $bag->getAttribute('true')); + $this->assertSame(2, $bag->getAttribute('two')); + $this->assertEquals(array('true' => true, 'two' => 2), $bag->getAttributes()); + + $bag->setAttribute('float', '1.2'); + $bag->setAttributes(array('two' => 'two', 'three' => 3)); + + $expected = array('true' => true, 'two' => 'two', 'float' => 1.2, 'three' => 3); + $this->assertEquals($expected, $bag->getAttributes()); + + $this->assertEquals($expected, $attributes); + } +} diff --git a/vendor/clue/graph/tests/Edge/EdgeAttributesTest.php b/vendor/clue/graph/tests/Edge/EdgeAttributesTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ac943cba092eaaf082467e2c5e51958c40d5c1c4 --- /dev/null +++ b/vendor/clue/graph/tests/Edge/EdgeAttributesTest.php @@ -0,0 +1,98 @@ +createVertex(1); + $graph->createVertex(2); + + // 1 -> 2 + $this->edge = $graph->getVertex(1)->createEdge($graph->getVertex(2)); + } + + public function testCanSetFlowAndCapacity() + { + $this->edge->setCapacity(100); + $this->edge->setFlow(10); + + $this->assertEquals(90, $this->edge->getCapacityRemaining()); + } + + public function testCanSetFlowBeforeCapacity() + { + $this->edge->setFlow(20); + + $this->assertEquals(null, $this->edge->getCapacityRemaining()); + } + + /** + * @expectedException RangeException + */ + public function testFlowMustNotExceedCapacity() + { + $this->edge->setCapacity(20); + $this->edge->setFlow(100); + } + + /** + * @expectedException RangeException + */ + public function testCapacityMustBeGreaterThanFlow() + { + $this->edge->setFlow(100); + $this->edge->setCapacity(20); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testWeightMustBeNumeric() + { + $this->edge->setWeight("10"); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testCapacityMustBeNumeric() + { + $this->edge->setCapacity("10"); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testCapacityMustBePositive() + { + $this->edge->setCapacity(-10); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testFlowMustBeNumeric() + { + $this->edge->setFlow("10"); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testFlowMustBePositive() + { + $this->edge->setFlow(-10); + } +} diff --git a/vendor/clue/graph/tests/Edge/EdgeBaseTest.php b/vendor/clue/graph/tests/Edge/EdgeBaseTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9bdf14c1b73f40a5a3046da11b501538e36a019c --- /dev/null +++ b/vendor/clue/graph/tests/Edge/EdgeBaseTest.php @@ -0,0 +1,104 @@ +graph = new Graph(); + $this->v1 = $this->graph->createVertex(1); + $this->v2 = $this->graph->createVertex(2); + + $this->edge = $this->createEdge(); + } + + public function testEdgeVertices() + { + $this->assertEquals(array($this->v1, $this->v2), $this->edge->getVertices()->getVector()); + $this->assertEquals(array(1, 2), $this->edge->getVertices()->getIds()); + + $this->assertSame($this->graph, $this->edge->getGraph()); + } + + public function testEdgeStartVertex() + { + $this->assertTrue($this->edge->hasVertexStart($this->v1)); + $this->assertTrue($this->edge->hasVertexTarget($this->v2)); + + $v3 = $this->graph->createVertex(3); + + $this->assertFalse($this->edge->hasVertexStart($v3)); + $this->assertFalse($this->edge->hasVertexTarget($v3)); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testEdgeFromInvalid() + { + $v3 = $this->graph->createVertex(3); + $this->edge->getVertexFromTo($v3); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testEdgeToInvalid() + { + $v3 = $this->graph->createVertex(3); + $this->edge->getVertexToFrom($v3); + } + + public function testClone() + { + $edge = $this->edge->createEdgeClone(); + + $this->assertEdgeEquals($this->edge, $edge); + } + + public function testCloneDoubleInvertedIsOriginal() + { + $edgeInverted = $this->edge->createEdgeCloneInverted(); + + $this->assertInstanceOf(get_class($this->edge), $edgeInverted); + + $edge = $edgeInverted->createEdgeCloneInverted(); + + $this->assertEdgeEquals($this->edge, $edge); + } + + public function testLoop() + { + $edge = $this->createEdgeLoop(); + + $this->assertTrue($edge->isLoop()); + $this->assertEquals(array($this->v1, $this->v1), $edge->getVertices()->getVector()); + $this->assertSame($this->v1, $edge->getVertexFromTo($this->v1)); + $this->assertSame($this->v1, $edge->getVertexToFrom($this->v1)); + } + + protected function createAttributeAware() + { + return $this->createEdge(); + } +} diff --git a/vendor/clue/graph/tests/Edge/EdgeDirectedTest.php b/vendor/clue/graph/tests/Edge/EdgeDirectedTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ed2f549ac5c72ee8b45014585a49c0f3c0e4153c --- /dev/null +++ b/vendor/clue/graph/tests/Edge/EdgeDirectedTest.php @@ -0,0 +1,24 @@ + 2 + return $this->v1->createEdgeTo($this->v2); + } + + protected function createEdgeLoop() + { + // 1 --\ + // ^ | + // \---/ + return $this->v1->createEdgeTo($this->v1); + } + + public function testVerticesEnds() + { + $this->assertEquals(array($this->v1), $this->edge->getVerticesStart()->getVector()); + $this->assertEquals(array($this->v2), $this->edge->getVerticesTarget()->getVector()); + } +} diff --git a/vendor/clue/graph/tests/Edge/EdgeUndirectedTest.php b/vendor/clue/graph/tests/Edge/EdgeUndirectedTest.php new file mode 100644 index 0000000000000000000000000000000000000000..158d18a645deaa7ef8da384e7199bc12ccefff0a --- /dev/null +++ b/vendor/clue/graph/tests/Edge/EdgeUndirectedTest.php @@ -0,0 +1,24 @@ +v1->createEdge($this->v2); + } + + protected function createEdgeLoop() + { + // 1 --\ + // | | + // \---/ + return $this->v1->createEdge($this->v1); + } + + public function testVerticesEnds() + { + $this->assertEquals(array($this->v1, $this->v2), $this->edge->getVerticesStart()->getVector()); + $this->assertEquals(array($this->v2, $this->v1), $this->edge->getVerticesTarget()->getVector()); + } +} diff --git a/vendor/clue/graph/tests/Exception/NegativeCycleExceptionTest.php b/vendor/clue/graph/tests/Exception/NegativeCycleExceptionTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0ef4530511eb8a2f6b4a857cedfec820a24b8a05 --- /dev/null +++ b/vendor/clue/graph/tests/Exception/NegativeCycleExceptionTest.php @@ -0,0 +1,19 @@ +getMockBuilder('Fhaculty\Graph\Walk') + ->disableOriginalConstructor() + ->getMock(); + + $exception = new NegativeCycleException('test', 0, null, $cycle); + + $this->assertEquals('test', $exception->getMessage()); + $this->assertEquals($cycle, $exception->getCycle()); + } +} diff --git a/vendor/clue/graph/tests/GraphTest.php b/vendor/clue/graph/tests/GraphTest.php new file mode 100644 index 0000000000000000000000000000000000000000..24a4c86ff1e1f9ebfcd860b7ad8233bc7164acdc --- /dev/null +++ b/vendor/clue/graph/tests/GraphTest.php @@ -0,0 +1,397 @@ +graph = new Graph(); + } + + public function testVertexClone() + { + $graph = new Graph(); + $vertex = $graph->createVertex(123)->setBalance(10)->setGroup(4); + + $newgraph = new Graph(); + $newvertex = $newgraph->createVertexClone($vertex); + + $this->assertVertexEquals($vertex, $newvertex); + } + + /** + * test to make sure vertex can not be cloned into same graph (due to duplicate id) + * + * @expectedException RuntimeException + */ + public function testInvalidVertexClone() + { + $graph = new Graph(); + $vertex = $graph->createVertex(123); + $graph->createVertexClone($vertex); + } + + public function testGraphCloneEmpty() + { + $graph = new Graph(); + $newgraph = $graph->createGraphClone(); + $this->assertGraphEquals($graph, $newgraph); + } + + /** + * @expectedException OutOfBoundsException + */ + public function testGetVertexNonexistant() + { + $graph = new Graph(); + $graph->getVertex('non-existant'); + } + + public function testGraphClone() + { + $graph = new Graph(); + $graph->createVertex(123)->setBalance(10)->setGroup(4); + + $newgraph = $graph->createGraphClone(); + + $this->assertGraphEquals($graph, $newgraph); + + $graphClonedTwice = $newgraph->createGraphClone(); + + $this->assertGraphEquals($graph, $graphClonedTwice); + } + + public function testGraphCloneEdgeless() + { + $graph = new Graph(); + $graph->createVertex(1)->createEdgeTo($graph->createVertex(2)); + $graph->createVertex(3)->createEdge($graph->getVertex(2)); + + $graphEdgeless = $graph->createGraphCloneEdgeless(); + + $graphExpected = new Graph(); + $graphExpected->createVertex(1); + $graphExpected->createVertex(2); + $graphExpected->createVertex(3); + + $this->assertGraphEquals($graphExpected, $graphEdgeless); + } + + /** + * check to make sure we can actually create vertices with automatic IDs + */ + public function testCanCreateVertex() + { + $graph = new Graph(); + $vertex = $graph->createVertex(); + $this->assertInstanceOf('\Fhaculty\Graph\Vertex', $vertex); + } + + /** + * check to make sure we can actually create vertices with automatic IDs + */ + public function testCanCreateVertexId() + { + $graph = new Graph(); + $vertex = $graph->createVertex(11); + $this->assertInstanceOf('\Fhaculty\Graph\Vertex', $vertex); + $this->assertEquals(11, $vertex->getId()); + } + + /** + * fail to create two vertices with same ID + * @expectedException OverflowException + */ + public function testFailDuplicateVertex() + { + $graph = new Graph(); + $graph->createVertex(33); + $graph->createVertex(33); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testCreateInvalidId() + { + $graph = new Graph(); + $graph->createVertex(array('invalid')); + } + + public function testCreateDuplicateReturn() + { + $graph = new Graph(); + $v1 = $graph->createVertex(1); + + $v1again = $graph->createVertex(1, true); + + $this->assertSame($v1, $v1again); + } + + public function testHasVertex() + { + $graph = new Graph(); + $graph->createVertex(1); + $graph->createVertex('string'); + + // check integer IDs + $this->assertFalse($graph->hasVertex(2)); + $this->assertTrue($graph->hasVertex(1)); + + // check string IDs + $this->assertFalse($graph->hasVertex('non-existant')); + $this->assertTrue($graph->hasVertex('string')); + + // integer IDs can also be checked as string IDs + $this->assertTrue($graph->hasVertex('1')); + } + + public function testCreateMultigraph() + { + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $v2 = $graph->createVertex(2); + + $e1 = $v1->createEdge($v2); + $e2 = $v1->createEdge($v2); + + $this->assertEquals(2, count($graph->getEdges())); + $this->assertEquals(2, count($v1->getEdges())); + + $this->assertEquals(array(2, 2), $v1->getVerticesEdge()->getIds()); + } + + public function testCreateMixedGraph() + { + // v1 -- v2 -> v3 + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $v2 = $graph->createVertex(2); + $v3 = $graph->createVertex(3); + + $v1->createEdge($v2); + $v2->createEdgeTo($v3); + + $this->assertEquals(2, count($graph->getEdges())); + + $this->assertEquals(2, count($v2->getEdges())); + $this->assertEquals(2, count($v2->getEdgesOut())); + $this->assertEquals(1, count($v2->getEdgesIn())); + + $this->assertEquals(array(1, 3), $v2->getVerticesEdgeTo()->getIds()); + $this->assertEquals(array(1), $v2->getVerticesEdgeFrom()->getIds()); + } + + public function testCreateVerticesNone() + { + $graph = new Graph(); + + $this->assertEquals(array(), $graph->createVertices(0)->getVector()); + $this->assertEquals(array(), $graph->createVertices(array())->getVector()); + + $this->assertEquals(0, count($graph->getVertices())); + } + + /** + * expect to fail for invalid number of vertices + * @expectedException InvalidArgumentException + * @dataProvider testCreateVerticesFailProvider + */ + public function testCreateVerticesFail($number) + { + $graph = new Graph(); + $graph->createVertices($number); + } + + public static function testCreateVerticesFailProvider() + { + return array( + array(-1), + array("10"), + array(0.5), + array(null), + array(array(1, 1)) + ); + } + + public function testCreateVerticesOkay() + { + $graph = new Graph(); + + $vertices = $graph->createVertices(2); + $this->assertCount(2, $vertices); + $this->assertEquals(array(0, 1), $graph->getVertices()->getIds()); + + $vertices = $graph->createVertices(array(7, 9)); + $this->assertCount(2, $vertices); + $this->assertEquals(array(0, 1, 7, 9), $graph->getVertices()->getIds()); + + $vertices = $graph->createVertices(3); + $this->assertCount(3, $vertices); + $this->assertEquals(array(0, 1, 7, 9, 10, 11, 12), $graph->getVertices()->getIds()); + } + + public function testCreateVerticesAtomic() + { + $graph = new Graph(); + + // create vertices 10-19 (inclusive) + $vertices = $graph->createVertices(range(10, 19)); + $this->assertCount(10, $vertices); + + try { + $graph->createVertices(array(9, 19, 20)); + $this->fail('Should be unable to create vertices because of duplicate IDs'); + } + catch (OverflowException $ignoreExpected) { + $this->assertEquals(10, count($graph->getVertices())); + } + + try { + $graph->createVertices(array(20, 21, 21)); + $this->fail('Should be unable to create vertices because of duplicate IDs'); + } + catch (InvalidArgumentException $ignoreExpected) { + $this->assertEquals(10, count($graph->getVertices())); + } + } + + /** + * @expectedException InvalidArgumentException + */ + public function testCreateVerticesContainsInvalid() + { + $graph = new Graph(); + $graph->createVertices(array(1, 2, array(), 3)); + } + + public function testRemoveEdge() + { + // 1 -- 2 + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $v2 = $graph->createVertex(2); + $edge = $v1->createEdge($v2); + + $this->assertEquals(array($edge), $graph->getEdges()->getVector()); + + $edge->destroy(); + //$graph->removeEdge($edge); + + $this->assertEquals(array(), $graph->getEdges()->getVector()); + + return $graph; + } + + /** + * @param Graph $graph + * @expectedException InvalidArgumentException + * @depends testRemoveEdge + */ + public function testRemoveEdgeInvalid(Graph $graph) + { + $edge = $graph->getVertex(1)->createEdge($graph->getVertex(2)); + + $edge->destroy(); + $edge->destroy(); + } + + public function testRemoveVertex() + { + $graph = new Graph(); + $vertex = $graph->createVertex(1); + + $this->assertEquals(array(1 => $vertex), $graph->getVertices()->getMap()); + + $vertex->destroy(); + + $this->assertEquals(array(), $graph->getVertices()->getVector()); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testRemoveVertexInvalid() + { + $graph = new Graph(); + $vertex = $graph->createVertex(1); + + $vertex->destroy(); + $vertex->destroy(); + } + + public function testGetEdgesClones() + { + // 1 -> 2 -> 1 + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $v2 = $graph->createVertex(2); + $e1 = $v1->createEdgeTo($v2); + $e2 = $v2->createEdgeTo($v1); + + $graphClone = $graph->createGraphClone(); + + $this->assertEdgeEquals($e1, $graphClone->getEdgeClone($e1)); + $this->assertEdgeEquals($e2, $graphClone->getEdgeCloneInverted($e1)); + } + + /** + * @expectedException OverflowException + */ + public function testEdgesFailParallel() + { + // 1 -> 2, 1 -> 2 + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $v2 = $graph->createVertex(2); + $e1 = $v1->createEdgeTo($v2); + $e2 = $v1->createEdgeTo($v2); + + // which one to return? e1? e2? + $graph->getEdgeClone($e1); + } + + /** + * @expectedException UnderflowException + */ + public function testEdgesFailEdgeless() + { + // 1 -> 2 + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $v2 = $graph->createVertex(2); + $e1 = $v1->createEdgeTo($v2); + $e2 = $v1->createEdgeTo($v2); + + $graphCloneEdgeless = $graph->createGraphCloneEdgeless(); + + // nothing to return + $graphCloneEdgeless->getEdgeClone($e1); + } + + public function testCreateGraphCloneVertices() + { + // 1 -- 2 -- 3 + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $v2 = $graph->createVertex(2); + $v3 = $graph->createVertex(3); + $e1 = $v1->createEdgeTo($v2); + $e2 = $v2->createEdgeTo($v3); + + $graphClone = $graph->createGraphCloneVertices(array(1 => $v1, 2 => $v2)); + + $this->assertEquals(2, count($graphClone->getVertices())); + $this->assertEquals(1, count($graphClone->getEdges())); + } + + protected function createAttributeAware() + { + return new Graph(); + } +} diff --git a/vendor/clue/graph/tests/Set/BaseVerticesTest.php b/vendor/clue/graph/tests/Set/BaseVerticesTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1e2fa16875f476cbe23ec274de1797ef9e45dc91 --- /dev/null +++ b/vendor/clue/graph/tests/Set/BaseVerticesTest.php @@ -0,0 +1,272 @@ +createVertex(); + + $verticesFromArray = $this->createVertices(array($vertex)); + $this->assertInstanceOf('Fhaculty\Graph\Set\Vertices', $verticesFromArray); + $this->assertSame($vertex, $verticesFromArray->getVertexFirst()); + + $verticesFromVertices = Vertices::factory($verticesFromArray); + $this->assertSame($verticesFromArray, $verticesFromVertices); + } + + public function testEmpty() + { + $vertices = $this->createVertices(array()); + + $this->assertEquals(0, $vertices->count()); + $this->assertEquals(0, count($vertices)); + $this->assertEquals(array(), $vertices->getIds()); + $this->assertEquals(array(), $vertices->getMap()); + $this->assertEquals(array(), $vertices->getVector()); + $this->assertTrue($vertices->isEmpty()); + $this->assertTrue($vertices->getVertices()->isEmpty()); + $this->assertTrue($vertices->getVerticesOrder(Vertices::ORDER_ID)->isEmpty()); + $this->assertTrue($vertices->getVerticesDistinct()->isEmpty()); + $this->assertTrue($vertices->getVerticesMatch(function() { })->isEmpty()); + $this->assertFalse($vertices->hasDuplicates()); + + return $vertices; + } + + /** + * + * @param Vertices $vertices + * @depends testEmpty + * @expectedException UnderflowException + */ + public function testEmptyDoesNotHaveFirst(Vertices $vertices) + { + $vertices->getVertexFirst(); + } + + /** + * + * @param Vertices $vertices + * @depends testEmpty + * @expectedException UnderflowException + */ + public function testEmptyDoesNotHaveLast(Vertices $vertices) + { + $vertices->getVertexLast(); + } + + /** + * + * @param Vertices $vertices + * @depends testEmpty + * @expectedException UnderflowException + */ + public function testEmptyDoesNotHaveOrdered(Vertices $vertices) + { + $vertices->getVertexOrder(Vertices::ORDER_ID); + } + + public function testTwo() + { + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $v2 = $graph->createVertex(2); + + $vertices = $this->createVertices(array(1 => $v1, 2 => $v2)); + + $this->assertTrue($vertices->hasVertexId(1)); + $this->assertTrue($vertices->hasVertexId(2)); + $this->assertFalse($vertices->hasVertexId(3)); + $this->assertEquals(2, count($vertices)); + + $this->assertSame($v1, $vertices->getVertexFirst()); + $this->assertSame($v1, $vertices->getVertexId(1)); + + $this->assertSame($v2, $vertices->getVertexLast()); + $this->assertSame($v2, $vertices->getVertexId(2)); + + $this->assertEquals(1, $vertices->getIndexVertex($v1)); + $this->assertEquals(array(1, 2), $vertices->getIds()); + + return $vertices; + } + + /** + * + * @param Vertices $vertices + * @depends testTwo + * @expectedException OutOfBoundsException + */ + public function testTwoDoesNotContainId3(Vertices $vertices) + { + $vertices->getVertexId(3); + } + + /** + * + * @param Vertices $vertices + * @depends testTwo + * @expectedException OutOfBoundsException + */ + public function testTwoDoesNotContainVertex3(Vertices $vertices) + { + $graph = new Graph(); + $v3 = $graph->createVertex(3); + + $vertices->getIndexVertex($v3); + } + + /** + * + * @param Vertices $vertices + * @depends testTwo + */ + public function testTwoAsMap(Vertices $vertices) + { + $distinct = $vertices->getVerticesDistinct(); + + $this->assertInstanceOf('Fhaculty\Graph\Set\VerticesMap', $distinct); + $this->assertEquals(2, count($distinct)); + $this->assertEquals(array(1, 2), $distinct->getIds()); + } + + /** + * + * @param Vertices $vertices + * @depends testTwo + */ + public function testTwoRandom(Vertices $vertices) + { + $vertexRandom = $vertices->getVertexOrder(Vertices::ORDER_RANDOM); + + $this->assertInstanceOf('Fhaculty\Graph\Vertex', $vertexRandom); + $this->assertTrue($vertices->hasVertexId($vertexRandom->getId())); + + $verticesRandom = $vertices->getVerticesOrder(Vertices::ORDER_RANDOM); + + $this->assertInstanceOf('Fhaculty\Graph\Set\Vertices', $verticesRandom); + $this->assertEquals(2, count($verticesRandom)); + } + + /** + * + * @param Vertices $vertices + * @depends testTwo + */ + public function testTwoIterator(Vertices $vertices) + { + $this->assertInstanceOf('Iterator', $vertices->getIterator()); + + $values = array_values(iterator_to_array($vertices)); + $this->assertEquals($vertices->getVector(), $values); + } + + /** + * + * @param Vertices $vertices + * @depends testTwo + */ + public function testTwoMatch(Vertices $vertices) + { + $verticesMatch = $vertices->getVerticesMatch(array($this, 'returnTrue')); + $this->assertEquals($vertices->getVector(), $verticesMatch->getVector()); + + $vertexMatch = $vertices->getVertexMatch(array($this, 'returnTrue')); + $this->assertEquals($vertices->getVertexFirst(), $vertexMatch); + } + + public function returnTrue(Vertex $vertex) + { + return true; + } + + public function testOrderByGroup() + { + $graph = new Graph(); + $graph->createVertex()->setGroup(1); + $graph->createVertex()->setGroup(100); + $graph->createVertex()->setGroup(5); + $graph->createVertex()->setGroup(100); + $graph->createVertex()->setGroup(100); + $graph->createVertex()->setGroup(2); + $biggest = $graph->createVertex()->setGroup(200); + + $vertices = $graph->getVertices(); + $verticesOrdered = $vertices->getVerticesOrder(Vertices::ORDER_GROUP); + + $this->assertInstanceOf('Fhaculty\Graph\Set\Vertices', $verticesOrdered); + $this->assertEquals(1, $verticesOrdered->getVertexFirst()->getGroup()); + $this->assertEquals(200, $verticesOrdered->getVertexLast()->getGroup()); + + $this->assertSame($biggest, $verticesOrdered->getVertexLast()); + $this->assertSame($biggest, $vertices->getVertexOrder(Vertices::ORDER_GROUP, true)); + + $sumgroups = function(Vertex $vertex) { + return $vertex->getGroup(); + }; + $this->assertSame(508, $vertices->getSumCallback($sumgroups)); + $this->assertSame(508, $verticesOrdered->getSumCallback($sumgroups)); + } + + /** + * + * @param Vertices $vertices + * @depends testEmpty + */ + public function testEmptyIntersectionSelf(Vertices $vertices) + { + $verticesIntersection = $vertices->getVerticesIntersection($vertices); + $this->assertCount(0, $verticesIntersection); + } + + /** + * + * @param Vertices $verticesEmpty + * @param Vertices $verticesTwo + * @depends testEmpty + * @depends testTwo + */ + public function testEmptyIntersectionTwo(Vertices $verticesEmpty, Vertices $verticesTwo) + { + $verticesIntersection = $verticesEmpty->getVerticesIntersection($verticesTwo); + $this->assertCount(0, $verticesIntersection); + } + + /** + * + * @param Vertices $vertices + * @depends testTwo + */ + public function testTwoIntersectionSelf(Vertices $vertices) + { + $verticesIntersection = $vertices->getVerticesIntersection($vertices); + $this->assertCount(2, $verticesIntersection); + $this->assertEquals($vertices->getMap(), $verticesIntersection->getMap()); + } + + /** + * + * @param Vertices $verticesTwo + * @param Vertices $verticesEmpty + * @depends testTwo + * @depends testEmpty + */ + public function testTwoIntersectionEmpty(Vertices $verticesTwo, Vertices $verticesEmpty) + { + $verticesIntersection = $verticesTwo->getVerticesIntersection($verticesEmpty); + $this->assertCount(0, $verticesIntersection); + } +} diff --git a/vendor/clue/graph/tests/Set/EdgesTest.php b/vendor/clue/graph/tests/Set/EdgesTest.php new file mode 100644 index 0000000000000000000000000000000000000000..6778ad413a4b3f7d4272b11d3196bddc36c622a3 --- /dev/null +++ b/vendor/clue/graph/tests/Set/EdgesTest.php @@ -0,0 +1,320 @@ + 1 + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $e1 = $v1->createEdgeTo($v1); + + $edgesFromArray = $this->createEdges(array($e1)); + $this->assertInstanceOf('Fhaculty\Graph\Set\Edges', $edgesFromArray); + $this->assertSame($e1, $edgesFromArray->getEdgeFirst()); + + $edgesFromEdges = Edges::factory($edgesFromArray); + $this->assertSame($edgesFromArray, $edgesFromEdges); + } + + public function testEmpty() + { + $edges = $this->createEdges(array()); + + $this->assertEquals(0, $edges->count()); + $this->assertEquals(0, count($edges)); + $this->assertEquals(array(), $edges->getVector()); + $this->assertTrue($edges->isEmpty()); + $this->assertTrue($edges->getEdges()->isEmpty()); + $this->assertTrue($edges->getEdgesOrder(Edges::ORDER_WEIGHT)->isEmpty()); + $this->assertTrue($edges->getEdgesDistinct()->isEmpty()); + $this->assertTrue($edges->getEdgesMatch(function() { })->isEmpty()); + + return $edges; + } + + /** + * + * @param Edges $edges + * @depends testEmpty + * @expectedException UnderflowException + */ + public function testEmptyDoesNotHaveFirst(Edges $edges) + { + $edges->getEdgeFirst(); + } + + /** + * + * @param Edges $edges + * @depends testEmpty + * @expectedException UnderflowException + */ + public function testEmptyDoesNotHaveLast(Edges $edges) + { + $edges->getEdgeLast(); + } + + /** + * + * @param Edges $edges + * @depends testEmpty + * @expectedException UnderflowException + */ + public function testEmptyDoesNotHaveOrdered(Edges $edges) + { + $edges->getEdgeOrder(Edges::ORDER_WEIGHT); + } + + public function testTwo() + { + // 1 -- 2 -- 3 + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $v2 = $graph->createVertex(2); + $v3 = $graph->createVertex(3); + $e1 = $v1->createEdge($v2); + $e2 = $v2->createEdge($v3); + + $edges = $this->createEdges(array($e1, $e2)); + + $this->assertEquals(2, count($edges)); + + $this->assertSame($e1, $edges->getEdgeFirst()); + $this->assertSame($e1, $edges->getEdgeIndex(0)); + + $this->assertSame($e2, $edges->getEdgeLast()); + $this->assertSame($e2, $edges->getEdgeIndex(1)); + + $this->assertEquals(0, $edges->getIndexEdge($e1)); + + return $edges; + } + + /** + * + * @param Edges $edges + * @depends testTwo + * @expectedException OutOfBoundsException + */ + public function testTwoDoesNotContainIndex3(Edges $edges) + { + $edges->getEdgeIndex(3); + } + + /** + * + * @param Edges $edges + * @depends testTwo + * @expectedException OutOfBoundsException + */ + public function testTwoDoesNotContainEdge3(Edges $edges) + { + $graph = new Graph(); + $v3 = $graph->createVertex(3); + $e3 = $v3->createEdge($v3); + + $edges->getIndexEdge($e3); + } + + /** + * + * @param Edges $edges + * @depends testTwo + */ + public function testTwoAsMap(Edges $edges) + { + $distinct = $edges->getEdgesDistinct(); + + $this->assertInstanceOf('Fhaculty\Graph\Set\Edges', $distinct); + $this->assertEquals(2, count($distinct)); + } + + /** + * + * @param Edges $edges + * @depends testTwo + */ + public function testTwoRandom(Edges $edges) + { + $edgeRandom = $edges->getEdgeOrder(Edges::ORDER_RANDOM); + + $this->assertInstanceOf('Fhaculty\Graph\Edge\Base', $edgeRandom); + $edges->getEdgeIndex($edges->getIndexEdge($edgeRandom)); + + $edgesRandom = $edges->getEdgesOrder(Edges::ORDER_RANDOM); + + $this->assertInstanceOf('Fhaculty\Graph\Set\Edges', $edgesRandom); + $this->assertEquals(2, count($edgesRandom)); + } + + /** + * + * @param Edges $edges + * @depends testTwo + */ + public function testTwoIterator(Edges $edges) + { + $this->assertInstanceOf('Iterator', $edges->getIterator()); + + $values = array_values(iterator_to_array($edges)); + $this->assertEquals($edges->getVector(), $values); + } + + /** + * + * @param Edges $edges + * @depends testTwo + */ + public function testTwoMatch(Edges $edges) + { + $edgesMatch = $edges->getEdgesMatch(array($this, 'returnTrue')); + $this->assertEquals($edges->getVector(), $edgesMatch->getVector()); + + $edgeMatch = $edges->getEdgeMatch(array($this, 'returnTrue')); + $this->assertEquals($edges->getEdgeFirst(), $edgeMatch); + } + + /** + * + * @param Edges $edges + * @depends testTwo + */ + public function testTwoMatchEmpty(Edges $edges) + { + $edgesMatch = $edges->getEdgesMatch(array($this, 'returnFalse')); + $this->assertCount(0, $edgesMatch); + } + + /** + * + * @param Edges $edges + * @depends testTwo + * @expectedException UnderflowException + */ + public function testTwoMatchFail(Edges $edges) + { + $edges->getEdgeMatch(array($this, 'returnFalse')); + } + + public function returnTrue(Edge $edge) + { + return true; + } + + public function returnFalse(Edge $edge) + { + return false; + } + + /** + * @expectedException InvalidArgumentException + */ + public function testGetEdgeOrderInvalidSortBy() + { + // 1 -> 1 + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $v1->createEdgeTo($v1); + + $edges = $graph->getEdges(); + + $edges->getEdgeOrder('not a valid callback'); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testGetEdgesOrderInvalidSortBy() + { + $edges = $this->createEdges(array()); + + $edges->getEdgesOrder('not a valid callback'); + } + + public function testOrderByGroup() + { + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $v2 = $graph->createVertex(2); + $v1->createEdge($v2)->setWeight(1); + $v1->createEdge($v2)->setWeight(100); + $v1->createEdge($v2)->setWeight(5); + $v1->createEdge($v2)->setWeight(100); + $v1->createEdge($v2)->setWeight(100); + $v1->createEdge($v2)->setWeight(2); + $biggest = $v1->createEdge($v2)->setWeight(200); + + $edges = $graph->getEdges(); + $edgesOrdered = $edges->getEdgesOrder(Edges::ORDER_WEIGHT); + + $this->assertInstanceOf('Fhaculty\Graph\Set\Edges', $edgesOrdered); + $this->assertEquals(1, $edgesOrdered->getEdgeFirst()->getWeight()); + $this->assertEquals(200, $edgesOrdered->getEdgeLast()->getWeight()); + + $this->assertSame($biggest, $edgesOrdered->getEdgeLast()); + $this->assertSame($biggest, $edges->getEdgeOrder(Edges::ORDER_WEIGHT, true)); + + $sumweights = function(Edge $edge) { + return $edge->getWeight(); + }; + $this->assertSame(508, $edges->getSumCallback($sumweights)); + $this->assertSame(508, $edgesOrdered->getSumCallback($sumweights)); + } + + public function testIntersection() + { + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $v2 = $graph->createVertex(2); + $e1 = $v1->createEdge($v2); + $e2 = $v1->createEdge($v2); + $e3 = $v1->createEdge($v2); + + $edges1 = $this->createEdges(array($e1, $e2)); + $edges2 = $this->createEdges(array($e2, $e3)); + + $edges3 = $edges1->getEdgesIntersection($edges2); + $this->assertCount(1, $edges3); + $this->assertEquals($e2, $edges3->getEdgeFirst()); + } + + public function testIntersectionDuplicates() + { + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $v2 = $graph->createVertex(2); + $e1 = $v1->createEdge($v2); + + $edges1 = $this->createEdges(array($e1, $e1, $e1)); + $edges2 = $this->createEdges(array($e1, $e1)); + + $edges3 = $edges1->getEdgesIntersection($edges2); + $this->assertCount(2, $edges3); + } + + public function testIntersectionEmpty() + { + $edges1 = new Edges(); + $edges2 = new Edges(); + + $edges3 = $edges1->getEdgesIntersection($edges2); + $this->assertCount(0, $edges3); + } +} diff --git a/vendor/clue/graph/tests/Set/VerticesMapTest.php b/vendor/clue/graph/tests/Set/VerticesMapTest.php new file mode 100644 index 0000000000000000000000000000000000000000..23be0cab9a43f0cbecff5bfcd9685107ad48f9d2 --- /dev/null +++ b/vendor/clue/graph/tests/Set/VerticesMapTest.php @@ -0,0 +1,11 @@ +assertInstanceOf('Fhaculty\Graph\Set\Vertices', $vertices); + $this->assertTrue($vertices->isEmpty()); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testGetVertexOrderInvalidSortBy() + { + $graph = new Graph(); + $graph->createVertex(1); + + $vertices = $graph->getVertices(); + + $vertices->getVertexOrder('not a valid callback'); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testGetVicesOrderInvalidSortBy() + { + $vertices = $this->createVertices(array()); + + $vertices->getVerticesOrder('not a valid callback'); + } + + public function testDuplicates() + { + $graph = new Graph(); + $v1 = $graph->createVertex(1); + + $vertices = $this->createVertices(array($v1, $v1, $v1)); + + $this->assertInstanceOf('Fhaculty\Graph\Set\Vertices', $vertices); + $this->assertCount(3, $vertices); + $this->assertTrue($vertices->hasDuplicates()); + + $verticesDistinct = $vertices->getVerticesDistinct(); + + $this->assertInstanceOf('Fhaculty\Graph\Set\Vertices', $verticesDistinct); + $this->assertCount(1, $verticesDistinct); + $this->assertFalse($verticesDistinct->hasDuplicates()); + + $this->assertSame($verticesDistinct, $verticesDistinct->getVerticesDistinct()); + } +} diff --git a/vendor/clue/graph/tests/VertexTest.php b/vendor/clue/graph/tests/VertexTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0c3603cc63925c495330a6f022417a824059fd40 --- /dev/null +++ b/vendor/clue/graph/tests/VertexTest.php @@ -0,0 +1,140 @@ +graph = new Graph(); + $this->vertex = $this->graph->createVertex(1); + } + + public function testPrecondition() + { + $this->assertCount(1, $this->graph->getVertices()); + $this->assertTrue($this->graph->hasVertex(1)); + $this->assertFalse($this->graph->hasVertex(2)); + $this->assertSame($this->vertex, $this->graph->getVertex(1)); + } + + public function testConstructor() + { + $v2 = new Vertex($this->graph, 2); + + $this->assertCount(2, $this->graph->getVertices()); + $this->assertTrue($this->graph->hasVertex(2)); + + $this->assertSame($v2, $this->graph->getVertex(2)); + } + + /** + * @expectedException OverflowException + */ + public function testCanNotConstructDuplicateVertex() + { + $v2 = new Vertex($this->graph, 1); + } + + public function testEdges() + { + // v1 -> v2, v1 -- v3, v1 <- v4 + $v2 = $this->graph->createVertex(2); + $v3 = $this->graph->createVertex(3); + $v4 = $this->graph->createVertex(4); + $e1 = $this->vertex->createEdgeTo($v2); + $e2 = $this->vertex->createEdge($v3); + $e3 = $v4->createEdgeTo($this->vertex); + + $this->assertEquals(array($e1, $e2, $e3), $this->vertex->getEdges()->getVector()); + $this->assertEquals(array($e2, $e3), $this->vertex->getEdgesIn()->getVector()); + $this->assertEquals(array($e1, $e2), $this->vertex->getEdgesOut()->getVector()); + + $this->assertTrue($this->vertex->hasEdgeTo($v2)); + $this->assertTrue($this->vertex->hasEdgeTo($v3)); + $this->assertFalse($this->vertex->hasEdgeTo($v4)); + + $this->assertFalse($this->vertex->hasEdgeFrom($v2)); + $this->assertTrue($this->vertex->hasEdgeFrom($v3)); + $this->assertTrue($this->vertex->hasEdgeFrom($v4)); + + $this->assertEquals(array($e1), $this->vertex->getEdgesTo($v2)->getVector()); + $this->assertEquals(array($e2), $this->vertex->getEdgesTo($v3)->getVector()); + $this->assertEquals(array(), $this->vertex->getEdgesTo($v4)->getVector()); + + $this->assertEquals(array(), $this->vertex->getEdgesFrom($v2)->getVector()); + $this->assertEquals(array($e2), $this->vertex->getEdgesTo($v3)->getVector()); + $this->assertEquals(array($e3), $this->vertex->getEdgesFrom($v4)->getVector()); + + $this->assertEquals(array($v2, $v3, $v4), $this->vertex->getVerticesEdge()->getVector()); + $this->assertEquals(array($v2, $v3), $this->vertex->getVerticesEdgeTo()->getVector()); + $this->assertEquals(array($v3, $v4), $this->vertex->getVerticesEdgeFrom()->getVector()); + } + + public function testBalance() + { + $this->vertex->setBalance(10); + $this->assertEquals(10, $this->vertex->getBalance()); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testBalanceInvalid() + { + $this->vertex->setBalance("10"); + } + + public function testGroup() + { + $this->vertex->setGroup(2); + $this->assertEquals(2, $this->vertex->getGroup()); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testGroupInvalid() + { + $this->vertex->setGroup("3"); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testCreateEdgeOtherGraphFails() + { + $graphOther = new Graph(); + + $this->vertex->createEdge($graphOther->createVertex(2)); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testCreateEdgeToOtherGraphFails() + { + $graphOther = new Graph(); + + $this->vertex->createEdgeTo($graphOther->createVertex(2)); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testRemoveInvalidEdge() + { + // 2 -- 3 + $v2 = $this->graph->createVertex(2); + $v3 = $this->graph->createVertex(3); + $edge = $v2->createEdge($v3); + + $this->vertex->removeEdge($edge); + } + + protected function createAttributeAware() + { + return new Vertex(new Graph(), 1); + } +} diff --git a/vendor/clue/graph/tests/WalkTest.php b/vendor/clue/graph/tests/WalkTest.php new file mode 100644 index 0000000000000000000000000000000000000000..250bce9178dcff359487afd153b8fd4c59d33ed7 --- /dev/null +++ b/vendor/clue/graph/tests/WalkTest.php @@ -0,0 +1,242 @@ +createVertex(1); + $v2 = $graph->createVertex(2); + $v3 = $graph->createVertex(3); + $e1 = $v1->createEdgeTo($v2); + $e2 = $v2->createEdgeTo($v3); + + $walk = Walk::factoryFromEdges(array($e1, $e2), $v1); + + $this->assertEquals(3, count($walk->getVertices())); + $this->assertEquals(2, count($walk->getEdges())); + $this->assertSame($v1, $walk->getVertices()->getVertexFirst()); + $this->assertSame($v3, $walk->getVertices()->getVertexLast()); + $this->assertSame(array($v1, $e1, $v2, $e2, $v3), $walk->getAlternatingSequence()); + $this->assertTrue($walk->isValid()); + + $graphClone = $walk->createGraph(); + $this->assertGraphEquals($graph, $graphClone); + + return $walk; + } + + /** + * @param Walk $walk + * @depends testWalkPath + */ + public function testWalkPathInvalidateByDestroyingVertex(Walk $walk) + { + // delete v3 + $walk->getVertices()->getVertexLast()->destroy(); + + $this->assertFalse($walk->isValid()); + } + + public function testWalkWithinGraph() + { + // 1 -- 2 -- 3 + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $v2 = $graph->createVertex(2); + $v3 = $graph->createVertex(3); + $e1 = $v1->createEdgeTo($v2); + $e2 = $v2->createEdgeTo($v3); + + // construct partial walk "1 -- 2" + $walk = Walk::factoryFromEdges(array($e1), $v1); + + $this->assertEquals(2, count($walk->getVertices())); + $this->assertEquals(1, count($walk->getEdges())); + $this->assertSame($v1, $walk->getVertices()->getVertexFirst()); + $this->assertSame($v2, $walk->getVertices()->getVertexLast()); + $this->assertSame(array($v1, $e1, $v2), $walk->getAlternatingSequence()); + $this->assertTrue($walk->isValid()); + + $graphExpected = new Graph(); + $graphExpected->createVertex(1)->createEdgeTo($graphExpected->createVertex(2)); + + $this->assertGraphEquals($graphExpected, $walk->createGraph()); + + // construct same partial walk "1 -- 2" + $walkVertices = Walk::factoryFromVertices(array($v1, $v2)); + + $this->assertEquals(2, count($walkVertices->getVertices())); + $this->assertEquals(1, count($walkVertices->getEdges())); + + $this->assertGraphEquals($graphExpected, $walkVertices->createGraph()); + + return $walk; + } + + public function testWalkLoop() + { + // 1 -- 1 + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $e1 = $v1->createEdge($v1); + + $walk = Walk::factoryFromEdges(array($e1), $v1); + + $this->assertEquals(2, count($walk->getVertices())); + $this->assertEquals(1, count($walk->getEdges())); + $this->assertSame($v1, $walk->getVertices()->getVertexFirst()); + $this->assertSame($v1, $walk->getVertices()->getVertexLast()); + $this->assertTrue($walk->isValid()); + + return $walk; + } + + /** + * @param Walk $walk + * @depends testWalkLoop + */ + public function testWalkInvalidByDestroyingEdge(Walk $walk) + { + // destroy first edge found + foreach ($walk->getEdges() as $edge) { + $edge->destroy(); + break; + } + + $this->assertFalse($walk->isValid()); + } + + public function testWalkLoopCycle() + { + // 1 -- 1 + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $e1 = $v1->createEdge($v1); + + $walk = Walk::factoryCycleFromEdges(array($e1), $v1); + + $this->assertEquals(2, count($walk->getVertices())); + $this->assertEquals(1, count($walk->getEdges())); + $this->assertSame($v1, $walk->getVertices()->getVertexFirst()); + $this->assertSame($v1, $walk->getVertices()->getVertexLast()); + $this->assertTrue($walk->isValid()); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testWalkCycleFromVerticesIncomplete() + { + // 1 -- 2 -- 1 + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $v2 = $graph->createVertex(2); + $e1 = $v1->createEdge($v2); + $e2 = $v2->createEdge($v1); + + // should actually be [v1, v2, v1] + Walk::factoryCycleFromVertices(array($v1, $v2)); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testWalkCycleInvalid() + { + // 1 -- 2 + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $v2 = $graph->createVertex(2); + $e1 = $v1->createEdge($v2); + + Walk::factoryCycleFromEdges(array($e1), $v1); + } + + public function testLoopCycle() + { + // 1 --\ + // ^ | + // \---/ + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $e1 = $v1->createEdgeTo($v1); + + $cycle = Walk::factoryCycleFromEdges(array($e1), $v1); + $this->assertGraphEquals($graph, $cycle->createGraph()); + + $cycle = Walk::factoryCycleFromPredecessorMap(array(1 => $v1), $v1); + $this->assertGraphEquals($graph, $cycle->createGraph()); + + $cycle = Walk::factoryCycleFromVertices(array($v1, $v1)); + $this->assertGraphEquals($graph, $cycle->createGraph()); + + $this->assertCount(2, $cycle->getVertices()); + $this->assertCount(1, $cycle->getEdges()); + $this->assertSame($v1, $cycle->getVertices()->getVertexFirst()); + $this->assertSame($v1, $cycle->getVertices()->getVertexLast()); + $this->assertTrue($cycle->isValid()); + + return $v1; + } + + /** + * + * @param Vertex $v1 + * @depends testLoopCycle + * @expectedException InvalidArgumentException + */ + public function testFactoryCycleFromVerticesIncomplete(Vertex $v1) + { + // should actually be [v1, v1] + Walk::factoryCycleFromVertices(array($v1)); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testInvalidPredecessors() + { + $graph = new Graph(); + $v1 = $graph->createVertex(1); + + Walk::factoryCycleFromPredecessorMap(array(), $v1); + } + + public function testFactoryFromVertices() + { + // 1 -- 2 + // \----/ + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $v2 = $graph->createVertex(2); + $e1 = $v1->createEdge($v2)->setWeight(10); + $e2 = $v1->createEdge($v2)->setWeight(20); + + // any edge in walk + $walk = Walk::factoryFromVertices(array($v1, $v2)); + + // edge with weight 10 + $walk = Walk::factoryFromVertices(array($v1, $v2), Edges::ORDER_WEIGHT); + $this->assertSame($e1, $walk->getEdges()->getEdgeFirst()); + + // edge with weight 20 + $walk = Walk::factoryFromVertices(array($v1, $v2), Edges::ORDER_WEIGHT, true); + $this->assertSame($e2, $walk->getEdges()->getEdgeFirst()); + } +} diff --git a/vendor/clue/graph/tests/bootstrap.php b/vendor/clue/graph/tests/bootstrap.php new file mode 100644 index 0000000000000000000000000000000000000000..0e3be73dd91815efa68e7f10d35ff21744b0b565 --- /dev/null +++ b/vendor/clue/graph/tests/bootstrap.php @@ -0,0 +1,100 @@ +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) { + try { + $other = $actual->getVertex($vid); + } catch (Exception $e) { + $this->fail(); + } + 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 Directed) { + $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; + } +} diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php new file mode 100644 index 0000000000000000000000000000000000000000..dc02dfb114fb6af2eacf89407a529c37ab8e7eb8 --- /dev/null +++ b/vendor/composer/ClassLoader.php @@ -0,0 +1,445 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath.'\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..f27399a042d95c4708af3a8c74d35d338763cf8f --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +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. + diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000000000000000000000000000000000000..7a91153b0d8ea10bc693176a81d8a9eb96883a76 --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,9 @@ + array($vendorDir . '/graphp/graphviz/src'), + 'Graphp\\Algorithms\\' => array($vendorDir . '/graphp/algorithms/src'), + 'Fhaculty\\Graph\\' => array($vendorDir . '/clue/graph/src'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000000000000000000000000000000000000..ff7cd4860a4c498b00990d27ff635b26db5d9672 --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,52 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit5eeebf534aceb45b65756d4e6db1f8cf::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + return $loader; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 0000000000000000000000000000000000000000..24c4bedb60c27a6bae4a4d2d58e6bd5d294f0c2b --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,44 @@ + + array ( + 'Graphp\\GraphViz\\' => 16, + 'Graphp\\Algorithms\\' => 18, + ), + 'F' => + array ( + 'Fhaculty\\Graph\\' => 15, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Graphp\\GraphViz\\' => + array ( + 0 => __DIR__ . '/..' . '/graphp/graphviz/src', + ), + 'Graphp\\Algorithms\\' => + array ( + 0 => __DIR__ . '/..' . '/graphp/algorithms/src', + ), + 'Fhaculty\\Graph\\' => + array ( + 0 => __DIR__ . '/..' . '/clue/graph/src', + ), + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit5eeebf534aceb45b65756d4e6db1f8cf::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit5eeebf534aceb45b65756d4e6db1f8cf::$prefixDirsPsr4; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000000000000000000000000000000000000..e949ed630d0d81d784176e3bb634c4527dc6ddb1 --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,145 @@ +[ + { + "name": "clue/graph", + "version": "v0.9.0", + "version_normalized": "0.9.0.0", + "source": { + "type": "git", + "url": "https://github.com/clue/graph.git", + "reference": "0336a4d5229fa61a20ccceaeab25e52ac9542700" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/graph/zipball/0336a4d5229fa61a20ccceaeab25e52ac9542700", + "reference": "0336a4d5229fa61a20ccceaeab25e52ac9542700", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "graphp/algorithms": "Common graph algorithms, such as Dijkstra and Moore-Bellman-Ford (shortest path), minimum spanning tree (MST), Kruskal, Prim and many more..", + "graphp/graphviz": "GraphViz graph drawing / DOT output" + }, + "time": "2015-03-07T18:11:31+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Fhaculty\\Graph\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A mathematical graph/network library written in PHP", + "homepage": "https://github.com/clue/graph", + "keywords": [ + "edge", + "graph", + "mathematical", + "network", + "vertex" + ] + }, + { + "name": "graphp/algorithms", + "version": "v0.8.1", + "version_normalized": "0.8.1.0", + "source": { + "type": "git", + "url": "https://github.com/graphp/algorithms.git", + "reference": "81db4049c35730767ec8f97fb5c4844234b86cef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/graphp/algorithms/zipball/81db4049c35730767ec8f97fb5c4844234b86cef", + "reference": "81db4049c35730767ec8f97fb5c4844234b86cef", + "shasum": "" + }, + "require": { + "clue/graph": "~0.9.0|~0.8.0", + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "time": "2015-03-08T10:12:01+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Graphp\\Algorithms\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@lueck.tv" + } + ], + "description": "Common mathematical graph algorithms", + "homepage": "https://github.com/graphp/algorithms", + "keywords": [ + "Graph algorithms", + "dijkstra", + "kruskal", + "minimum spanning tree", + "moore-bellman-ford", + "prim", + "shortest path" + ] + }, + { + "name": "graphp/graphviz", + "version": "v0.2.1", + "version_normalized": "0.2.1.0", + "source": { + "type": "git", + "url": "https://github.com/graphp/graphviz.git", + "reference": "2676522dfcd907fd3cb52891ea64a052c4ac4c2a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/graphp/graphviz/zipball/2676522dfcd907fd3cb52891ea64a052c4ac4c2a", + "reference": "2676522dfcd907fd3cb52891ea64a052c4ac4c2a", + "shasum": "" + }, + "require": { + "clue/graph": "~0.9.0|~0.8.0", + "graphp/algorithms": "~0.8.0", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "time": "2015-03-08T10:30:28+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Graphp\\GraphViz\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "GraphViz graph drawing for mathematical graph/network", + "homepage": "https://github.com/graphp/graphviz", + "keywords": [ + "dot output", + "graph drawing", + "graph image", + "graphviz" + ] + } +] diff --git a/vendor/graphp/algorithms/.gitignore b/vendor/graphp/algorithms/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..de4a392c3313c89428ffe2a939f8744aae16b81b --- /dev/null +++ b/vendor/graphp/algorithms/.gitignore @@ -0,0 +1,2 @@ +/vendor +/composer.lock diff --git a/vendor/graphp/algorithms/.travis.yml b/vendor/graphp/algorithms/.travis.yml new file mode 100644 index 0000000000000000000000000000000000000000..6cc3dcd8b33ae5b9fc4ee7d1e85c05c8bdf3055f --- /dev/null +++ b/vendor/graphp/algorithms/.travis.yml @@ -0,0 +1,11 @@ +language: php +php: + - 5.6 + - 5.5 + - 5.4 + - 5.3 + - hhvm +install: + - composer install --prefer-source --no-interaction +script: + - php vendor/bin/phpunit --coverage-text diff --git a/vendor/graphp/algorithms/CHANGELOG.md b/vendor/graphp/algorithms/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..b92e6d9e6d8696895165512eae05a0316fb1b7e7 --- /dev/null +++ b/vendor/graphp/algorithms/CHANGELOG.md @@ -0,0 +1,14 @@ +# Changelog + +## 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)) diff --git a/vendor/graphp/algorithms/LICENSE b/vendor/graphp/algorithms/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..dc09d1e6caf350c70107dc7680f65c41e852bfee --- /dev/null +++ b/vendor/graphp/algorithms/LICENSE @@ -0,0 +1,21 @@ +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. diff --git a/vendor/graphp/algorithms/README.md b/vendor/graphp/algorithms/README.md new file mode 100644 index 0000000000000000000000000000000000000000..efcb7b65d6731fccfcde431651a477619a1854db --- /dev/null +++ b/vendor/graphp/algorithms/README.md @@ -0,0 +1,21 @@ +# graphp/algorithms [![Build Status](https://travis-ci.org/graphp/algorithms.svg?branch=master)](https://travis-ci.org/graphp/algorithms) + +Common mathematical graph algorithms implemented in PHP + +> Note: This project is in beta stage! Feel free to report any issues you encounter. + +## Install + +The recommended way to install this library is [through composer](http://getcomposer.org). [New to composer?](http://getcomposer.org/doc/00-intro.md) + +```JSON +{ + "require": { + "graphp/algorithms": "~0.8.0" + } +} +``` + +## License + +Released under the terms of the permissive [MIT license](http://opensource.org/licenses/MIT). diff --git a/vendor/graphp/algorithms/composer.json b/vendor/graphp/algorithms/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..83eda52453513e1dec6c3227b6df89c8f4f89f7c --- /dev/null +++ b/vendor/graphp/algorithms/composer.json @@ -0,0 +1,23 @@ +{ + "name": "graphp/algorithms", + "description": "Common mathematical graph algorithms", + "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@lueck.tv" + } + ], + "require": { + "php": ">=5.3", + "clue/graph": "~0.9.0|~0.8.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "autoload": { + "psr-4": {"Graphp\\Algorithms\\": "src/"} + } +} diff --git a/vendor/graphp/algorithms/phpunit.xml.dist b/vendor/graphp/algorithms/phpunit.xml.dist new file mode 100644 index 0000000000000000000000000000000000000000..874886315e0eaf85c7390e0a7a587e04ce7bb5f2 --- /dev/null +++ b/vendor/graphp/algorithms/phpunit.xml.dist @@ -0,0 +1,19 @@ + + + + + + ./tests/ + + + + + ./src/ + + + \ No newline at end of file diff --git a/vendor/graphp/algorithms/src/Base.php b/vendor/graphp/algorithms/src/Base.php new file mode 100644 index 0000000000000000000000000000000000000000..11e95e536eb8b44ba0b91d01f48a1dca37c02152 --- /dev/null +++ b/vendor/graphp/algorithms/src/Base.php @@ -0,0 +1,8 @@ +set = $graphOrWalk; + } +} diff --git a/vendor/graphp/algorithms/src/BaseGraph.php b/vendor/graphp/algorithms/src/BaseGraph.php new file mode 100644 index 0000000000000000000000000000000000000000..efad831090e1305c1ad7f1440195d8c1a082afe8 --- /dev/null +++ b/vendor/graphp/algorithms/src/BaseGraph.php @@ -0,0 +1,31 @@ +graph = $graph; + } +} diff --git a/vendor/graphp/algorithms/src/BaseVertex.php b/vendor/graphp/algorithms/src/BaseVertex.php new file mode 100644 index 0000000000000000000000000000000000000000..112fe06f2bb93a0ce9d6dda07cabcedcb184b54c --- /dev/null +++ b/vendor/graphp/algorithms/src/BaseVertex.php @@ -0,0 +1,31 @@ +vertex = $vertex; + } +} diff --git a/vendor/graphp/algorithms/src/Bipartit.php b/vendor/graphp/algorithms/src/Bipartit.php new file mode 100644 index 0000000000000000000000000000000000000000..0e40d97b8aeb33297a156a4202064fdf12d5cf51 --- /dev/null +++ b/vendor/graphp/algorithms/src/Bipartit.php @@ -0,0 +1,122 @@ +getColors(); + + return true; + } catch (UnexpectedValueException $ignore) { } + + return false; + } + + /** + * checks whether the input graph's vertex groups are a valid bipartition + * + * @return boolean + * @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; + } +} diff --git a/vendor/graphp/algorithms/src/Complete.php b/vendor/graphp/algorithms/src/Complete.php new file mode 100644 index 0000000000000000000000000000000000000000..088812c851d72956d236e94479f8608b8697d287 --- /dev/null +++ b/vendor/graphp/algorithms/src/Complete.php @@ -0,0 +1,43 @@ +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; + } +} diff --git a/vendor/graphp/algorithms/src/ConnectedComponents.php b/vendor/graphp/algorithms/src/ConnectedComponents.php new file mode 100644 index 0000000000000000000000000000000000000000..8a1f86552f17e4fed744889b41d820f7c75c53f0 --- /dev/null +++ b/vendor/graphp/algorithms/src/ConnectedComponents.php @@ -0,0 +1,151 @@ +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 boolean + * @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; + } +} diff --git a/vendor/graphp/algorithms/src/Degree.php b/vendor/graphp/algorithms/src/Degree.php new file mode 100644 index 0000000000000000000000000000000000000000..7acd74e8adc73cb1020e8f6fe6cb751b6ddff58f --- /dev/null +++ b/vendor/graphp/algorithms/src/Degree.php @@ -0,0 +1,220 @@ +getDegreeVertex($this->graph->getVertices()->getVertexFirst()); + + foreach ($this->graph->getVertices() as $vertex) { + /** @var $vertex 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 Exception if graph is empty or directed + * @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 Exception if graph is empty or directed + * @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 boolean + * @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 boolean + * @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 boolean + * @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 boolean + * @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 boolean + */ + 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; + } +} diff --git a/vendor/graphp/algorithms/src/DetectNegativeCycle.php b/vendor/graphp/algorithms/src/DetectNegativeCycle.php new file mode 100644 index 0000000000000000000000000000000000000000..f3c17e84eb6ed6ea5e853b0b0167b85a6d87510a --- /dev/null +++ b/vendor/graphp/algorithms/src/DetectNegativeCycle.php @@ -0,0 +1,82 @@ +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 Exception if there's no negative cycle + * @uses AlgorithmDetectNegativeCycle::getCycleNegative() + * @uses Walk::createGraph() + */ + public function createGraph() + { + return $this->getCycleNegative()->createGraph(); + } +} diff --git a/vendor/graphp/algorithms/src/Directed.php b/vendor/graphp/algorithms/src/Directed.php new file mode 100644 index 0000000000000000000000000000000000000000..44cb24a705f9a85c55a9f7a0932c818fc20f94f5 --- /dev/null +++ b/vendor/graphp/algorithms/src/Directed.php @@ -0,0 +1,66 @@ +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 boolean + */ + 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 boolean + * @uses self::hasDirected() + * @uses self::hasUndirected() + */ + public function isMixed() + { + return ($this->hasDirected() && $this->hasUndirected()); + } +} diff --git a/vendor/graphp/algorithms/src/Eulerian.php b/vendor/graphp/algorithms/src/Eulerian.php new file mode 100644 index 0000000000000000000000000000000000000000..b40b55baeb6b3526d01800c5baa13d06c85ac1a5 --- /dev/null +++ b/vendor/graphp/algorithms/src/Eulerian.php @@ -0,0 +1,38 @@ +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; + } +} diff --git a/vendor/graphp/algorithms/src/Flow.php b/vendor/graphp/algorithms/src/Flow.php new file mode 100644 index 0000000000000000000000000000000000000000..b67fbc6a77052cb9b63e48848428536657405ddd --- /dev/null +++ b/vendor/graphp/algorithms/src/Flow.php @@ -0,0 +1,118 @@ +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 boolean + * @see Algorithm\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; + } +} diff --git a/vendor/graphp/algorithms/src/Groups.php b/vendor/graphp/algorithms/src/Groups.php new file mode 100644 index 0000000000000000000000000000000000000000..66fd1333a64976d46616a696947ace33796f3e28 --- /dev/null +++ b/vendor/graphp/algorithms/src/Groups.php @@ -0,0 +1,87 @@ +getGroups()); + } + + /** + * checks whether the input graph's vertex groups are a valid bipartition + * + * @return boolean + * @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) { + $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); + } +} diff --git a/vendor/graphp/algorithms/src/Loop.php b/vendor/graphp/algorithms/src/Loop.php new file mode 100644 index 0000000000000000000000000000000000000000..ff89b5db25a6f407d96720f26c22e37677ffbf38 --- /dev/null +++ b/vendor/graphp/algorithms/src/Loop.php @@ -0,0 +1,52 @@ +set->getEdges() as $edge) { + if ($edge->isLoop()) { + return true; + } + } + + return false; + } + + /** + * checks whether this vertex has a loop (edge to itself) + * + * @return boolean + * @uses Edge::isLoop() + */ + public function hasLoopVertex(Vertex $vertex) + { + foreach ($vertex->getEdges() as $edge) { + if ($edge->isLoop()) { + return true; + } + } + + return false; + } +} diff --git a/vendor/graphp/algorithms/src/MaxFlow/EdmondsKarp.php b/vendor/graphp/algorithms/src/MaxFlow/EdmondsKarp.php new file mode 100644 index 0000000000000000000000000000000000000000..0d989a17d503b315a52e9fa29cfbdfa5f79d951d --- /dev/null +++ b/vendor/graphp/algorithms/src/MaxFlow/EdmondsKarp.php @@ -0,0 +1,129 @@ +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 + */ + 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 double + */ + 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; + } +} diff --git a/vendor/graphp/algorithms/src/MaximumMatching/Base.php b/vendor/graphp/algorithms/src/MaximumMatching/Base.php new file mode 100644 index 0000000000000000000000000000000000000000..14ba1bae805f3b1ec1ab2e141bf9844d13fec048 --- /dev/null +++ b/vendor/graphp/algorithms/src/MaximumMatching/Base.php @@ -0,0 +1,42 @@ +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(); +} diff --git a/vendor/graphp/algorithms/src/MaximumMatching/Flow.php b/vendor/graphp/algorithms/src/MaximumMatching/Flow.php new file mode 100644 index 0000000000000000000000000000000000000000..2dd11fd9ccfb6d92a5f9f403a010216a058367a6 --- /dev/null +++ b/vendor/graphp/algorithms/src/MaximumMatching/Flow.php @@ -0,0 +1,87 @@ +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]; + $groupB = $groups[1]; + + // connect supersource s* to set A and supersink t* to set B + foreach ($graphFlow->getVertices() as $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(); + + // source + if ($group === $groupA) { + $superSource->createEdgeTo($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) { + $vertex->createEdgeTo($graphFlow->getVertex($vertexTarget->getId()))->setCapacity(1)->setFlow(0); + } + // sink + } elseif ($group === $groupB) { + $vertex->createEdgeTo($superSink)->setCapacity(1)->setFlow(0); + } else { + // @codeCoverageIgnoreStart + throw new LogicException('Should not happen. Unknown set: ' + $belongingSet); + // @codeCoverageIgnoreEnd + } + } + + // 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); + } +} diff --git a/vendor/graphp/algorithms/src/MinimumCostFlow/Base.php b/vendor/graphp/algorithms/src/MinimumCostFlow/Base.php new file mode 100644 index 0000000000000000000000000000000000000000..4d90d136724b555bce171f49cae78646774466aa --- /dev/null +++ b/vendor/graphp/algorithms/src/MinimumCostFlow/Base.php @@ -0,0 +1,83 @@ +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); + // if the edge doesn't exist => use the residual edge + } catch (UnderflowException $ignore) { + $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 + * + * @throws Exception if the graph has not enough capacity for the minimum-cost flow + * @return Graph + */ + abstract public function createGraph(); +} diff --git a/vendor/graphp/algorithms/src/MinimumCostFlow/CycleCanceling.php b/vendor/graphp/algorithms/src/MinimumCostFlow/CycleCanceling.php new file mode 100644 index 0000000000000000000000000000000000000000..1005a8b980e9652b2c99b1abf0f64c4a73bbe1b2 --- /dev/null +++ b/vendor/graphp/algorithms/src/MinimumCostFlow/CycleCanceling.php @@ -0,0 +1,79 @@ +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 + $superSource->createEdgeTo($vertex)->setCapacity($balance); + + $sumBalance += $balance; + } elseif ($balance < 0) { + // negative balance => sink capacity (positive) + $vertex->createEdgeTo($superSink)->setCapacity(-$balance); + } + } + + // calculate (s*, t*)-flow + $algMaxFlow = new MaxFlowEdmondsKarp($superSource, $superSink); + $flowMax = $algMaxFlow->getFlowMax(); + + if ($flowMax !== $sumBalance) { + throw new UnexpectedValueException('Network does not support required flow of ' . $sumBalance . ' (maximum possible flow limited to ' . $flowMax . ')'); + } + + $resultGraph = $algMaxFlow->createGraph(); + + while (true) { + // create residual graph + $algRG = new ResidualGraph($resultGraph); + $residualGraph = $algRG->createGraph(); + + // get negative cycle + $alg = new DetectNegativeCycle($residualGraph); + try { + $clonedEdges = $alg->getCycleNegative()->getEdges(); + } catch (UnderflowException $ignore) { + // no negative cycle found => end algorithm + break; + } + + // calculate maximal possible flow = minimum capacity remaining for all edges + $newFlow = $clonedEdges->getEdgeOrder(Edges::ORDER_CAPACITY_REMAINING)->getCapacityRemaining(); + + // set flow on original graph + $this->addFlow($resultGraph, $clonedEdges, $newFlow); + } + + // destroy temporary supersource and supersink again + $resultGraph->getVertex($superSink->getId())->destroy(); + $resultGraph->getVertex($superSource->getId())->destroy(); + + return $resultGraph; + } +} diff --git a/vendor/graphp/algorithms/src/MinimumCostFlow/SuccessiveShortestPath.php b/vendor/graphp/algorithms/src/MinimumCostFlow/SuccessiveShortestPath.php new file mode 100644 index 0000000000000000000000000000000000000000..b5df409816f3ddfd85d9bfa016cbeb634ef2ab34 --- /dev/null +++ b/vendor/graphp/algorithms/src/MinimumCostFlow/SuccessiveShortestPath.php @@ -0,0 +1,161 @@ +checkBalance(); + $resultGraph = $this->graph->createGraphClone(); + + // initial balance to 0 + $vertices = $resultGraph->getVertices(); + foreach ($vertices as $vertex) { + $vertex->setBalance(0); + } + + // initial flow of edges + $edges = $resultGraph->getEdges(); + foreach ($edges as $edge) { + if (!($edge instanceof EdgeDirected)) { + throw new UnexpectedValueException('Undirected edges are not supported for SuccessiveShortestPath'); + } + + // 0 if weight of edge is positive + $flow = 0; + + // maximal flow if weight of edge is negative + if ($edge->getWeight() < 0) { + $flow = $edge->getCapacity(); + + $startVertex = $edge->getVertexStart(); + $endVertex = $edge->getVertexEnd(); + + // add balance to start- and end-vertex + $this->addBalance($startVertex, $flow); + $this->addBalance($endVertex, - $flow); + } + + $edge->setFlow($flow); + } + + // return or Exception inside this while + while (true) { + // create residual graph + $algRG = new ResidualGraph($resultGraph); + $residualGraph = $algRG->createGraph(); + + // search for a source + try { + $sourceVertex = $this->getVertexSource($residualGraph); + } catch (UnderflowException $ignore) { + // no source is found => minimum-cost flow is found + break; + } + + // search for reachable target sink from this source + try { + $targetVertex = $this->getVertexSink($sourceVertex); + } catch (UnderflowException $e) { + // no target found => network does not have enough capacity + throw new UnexpectedValueException('The graph has not enough capacity for the minimum-cost flow', 0, $e); + } + + // calculate shortest path between source- and target-vertex + $algSP = new SpMooreBellmanFord($sourceVertex); + $edgesOnFlow = $algSP->getEdgesTo($targetVertex); + + // calculate the maximal possible flow + // new flow is the maximal possible flow for this path + $newflow = $this->graph->getVertex($sourceVertex->getId())->getBalance() - $sourceVertex->getBalance(); + $targetFlow = - ($this->graph->getVertex($targetVertex->getId())->getBalance() - $targetVertex->getBalance()); + + // get minimum of source and target + if ($targetFlow < $newflow) { + $newflow = $targetFlow; + } + + // get minimum of capacity remaining on path + $minCapacity = $edgesOnFlow->getEdgeOrder(Edges::ORDER_CAPACITY_REMAINING)->getCapacityRemaining(); + if ($minCapacity < $newflow) { + $newflow = $minCapacity; + } + + // add the new flow to the path + $this->addFlow($resultGraph, $edgesOnFlow, $newflow); + + // add balance to source and remove for the target sink + $oriSourceVertex = $resultGraph->getVertex($sourceVertex->getId()); + $oriTargetVertex = $resultGraph->getVertex($targetVertex->getId()); + + $this->addBalance($oriSourceVertex, $newflow); + $this->addBalance($oriTargetVertex, - $newflow); + } + + return $resultGraph; + } + + /** + * + * + * @param Graph $graph + * @throws Exception if there is no left source vertex + * + * @return Vertex a source vertex in the given graph + */ + 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 + * @throws Exception if there is no reachable sink vertex + * + * @return Vertex a sink-vertex that is reachable from the source + * @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); + } +} diff --git a/vendor/graphp/algorithms/src/MinimumSpanningTree/Base.php b/vendor/graphp/algorithms/src/MinimumSpanningTree/Base.php new file mode 100644 index 0000000000000000000000000000000000000000..4455f28e0df5dd0af270e66848f61a6ef8458a4e --- /dev/null +++ b/vendor/graphp/algorithms/src/MinimumSpanningTree/Base.php @@ -0,0 +1,91 @@ +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) { + /* @var $edge Edge */ + // ignore loops (a->a) + if (!$edge->isLoop()) { + // Add edges with negative weight because of order in stl + $sortedEdges->insert($edge, -$edge->getWeight()); + } + } + } +} diff --git a/vendor/graphp/algorithms/src/MinimumSpanningTree/Kruskal.php b/vendor/graphp/algorithms/src/MinimumSpanningTree/Kruskal.php new file mode 100644 index 0000000000000000000000000000000000000000..acfdc056ab1176488e577d003710086d9bfd219f --- /dev/null +++ b/vendor/graphp/algorithms/src/MinimumSpanningTree/Kruskal.php @@ -0,0 +1,134 @@ +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) { + /* @var $edge EdgeDirected */ + // 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); + } +} diff --git a/vendor/graphp/algorithms/src/MinimumSpanningTree/Prim.php b/vendor/graphp/algorithms/src/MinimumSpanningTree/Prim.php new file mode 100644 index 0000000000000000000000000000000000000000..b42f9c12a768e3b3c417630fed2c8b366e5a6f42 --- /dev/null +++ b/vendor/graphp/algorithms/src/MinimumSpanningTree/Prim.php @@ -0,0 +1,81 @@ +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 { + try { + // Get next cheapest edge + $cheapestEdge = $edgeQueue->extract(); + /* @var $cheapestEdge EdgeDirected */ + } catch (Exception $e) { + throw new UnexpectedValueException('Graph has more than one component', 0, $e); + } + + // Check if edge is between unmarked and marked edge + + $vertices = $cheapestEdge->getVertices(); + $vertexA = $vertices->getVertexFirst(); + $vertexB = $vertices->getVertexLast(); + + // Edge is between marked and unmared vertex + } 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(); + } +} diff --git a/vendor/graphp/algorithms/src/Parallel.php b/vendor/graphp/algorithms/src/Parallel.php new file mode 100644 index 0000000000000000000000000000000000000000..d38578e45bacc052570a16b84237057e720876b8 --- /dev/null +++ b/vendor/graphp/algorithms/src/Parallel.php @@ -0,0 +1,84 @@ +graph->getEdges() as $edge) { + if ($this->hasEdgeParallelEdge($edge)) { + return true; + } + } + + return false; + } + + + /** + * checks whether this edge has any parallel edges + * + * @return boolean + * @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 + * @throws LogicException + */ + public function getEdgesParallelEdge(Edge $edge) + { + if ($edge instanceof DirectedEdge) { + // 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); + + if ($pos === false) { + // @codeCoverageIgnoreStart + throw new LogicException('Internal error: Current edge not found'); + // @codeCoverageIgnoreEnd + } + + // exclude current edge from parallel edges + unset($edges[$pos]); + + return new Edges(array_values($edges)); + } +} diff --git a/vendor/graphp/algorithms/src/Property/GraphProperty.php b/vendor/graphp/algorithms/src/Property/GraphProperty.php new file mode 100644 index 0000000000000000000000000000000000000000..29fd5a43f83fa64854f2fb20f90f09a8e161e098 --- /dev/null +++ b/vendor/graphp/algorithms/src/Property/GraphProperty.php @@ -0,0 +1,51 @@ +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 boolean + */ + public function isNull() + { + return $this->graph->getVertices()->isEmpty(); + } + + /** + * checks whether this graph is trivial (one vertex and no edges) + * + * @return boolean + */ + public function isTrivial() + { + return ($this->graph->getEdges()->isEmpty() && count($this->graph->getVertices()) === 1); + } +} diff --git a/vendor/graphp/algorithms/src/Property/WalkProperty.php b/vendor/graphp/algorithms/src/Property/WalkProperty.php new file mode 100644 index 0000000000000000000000000000000000000000..4ed8eb1ca6f57f36abc54b0c9d8f5ee0bab2fef0 --- /dev/null +++ b/vendor/graphp/algorithms/src/Property/WalkProperty.php @@ -0,0 +1,381 @@ +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 boolean + * @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 boolean + * @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 boolean + * @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 boolean + * @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 boolean + * @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 boolean + * @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 boolean + * @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 boolean + * @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 boolean + */ + 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; + } +} diff --git a/vendor/graphp/algorithms/src/ResidualGraph.php b/vendor/graphp/algorithms/src/ResidualGraph.php new file mode 100644 index 0000000000000000000000000000000000000000..496e3141a966ac2956bdfd5c49dd8132e2ce3502 --- /dev/null +++ b/vendor/graphp/algorithms/src/ResidualGraph.php @@ -0,0 +1,109 @@ +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) + { + $parallelEdges = $newEdge->getEdgesParallel(); + if ($parallelEdges) { + + $mergedCapacity = 0; + + foreach ($parallelEdges as $parallelEdge) { + $mergedCapacity += $parallelEdge->getCapacity(); + } + + $newEdge->setCapacity($newEdge->getCapacity() + $mergedCapacity); + + foreach ($parallelEdges as $parallelEdge) { + $parallelEdge->destroy(); + } + } + } +} diff --git a/vendor/graphp/algorithms/src/Search/Base.php b/vendor/graphp/algorithms/src/Search/Base.php new file mode 100644 index 0000000000000000000000000000000000000000..e852cdc6e32178d7385bb08f3cbd8e29887e25c6 --- /dev/null +++ b/vendor/graphp/algorithms/src/Search/Base.php @@ -0,0 +1,56 @@ +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(); + } elseif ($this->direction === self::DIRECTION_BOTH) { + return $vertex->getVerticesEdge(); + } else { + throw new DomainException('Should not happen. Invalid direction setting'); + } + } + + /** + * get set of all Vertices that can be reached from start vertex + * + * @return Vertices + */ + abstract public function getVertices(); +} diff --git a/vendor/graphp/algorithms/src/Search/BreadthFirst.php b/vendor/graphp/algorithms/src/Search/BreadthFirst.php new file mode 100644 index 0000000000000000000000000000000000000000..df245c092b86bdfa59f1e32822c01a55a987ff51 --- /dev/null +++ b/vendor/graphp/algorithms/src/Search/BreadthFirst.php @@ -0,0 +1,45 @@ +vertex); + // to not add vertices twice in array visited + $mark = array($this->vertex->getId() => true); + // visited vertices + $visited = array(); + + do { + // get first from queue + $t = array_shift($queue); + // save as visited + $visited[$t->getId()]= $t; + + // get next vertices + foreach ($this->getVerticesAdjacent($t)->getMap() as $id => $vertex) { + // if not "touched" before + if (!isset($mark[$id])) { + // add to queue + $queue[] = $vertex; + // and mark + $mark[$id] = true; + } + } + + // untill queue is empty + } while ($queue); + + return new Vertices($visited); + } +} diff --git a/vendor/graphp/algorithms/src/Search/DepthFirst.php b/vendor/graphp/algorithms/src/Search/DepthFirst.php new file mode 100644 index 0000000000000000000000000000000000000000..0722d7129dae071841af7c08df7b8ff866e8b4f7 --- /dev/null +++ b/vendor/graphp/algorithms/src/Search/DepthFirst.php @@ -0,0 +1,66 @@ +visitedVertices + * + * @param Vertex $vertex + */ + private function recursiveDepthFirstSearch(Vertex $vertex, array & $visitedVertices) + { + // If I didn't visited this vertex before + if (!isset($visitedVertices[$vertex->getId()])) { + // Add Vertex to already visited vertices + $visitedVertices[$vertex->getId()] = $vertex; + + // Get next vertices + $nextVertices = $vertex->getVerticesEdgeTo(); + + foreach ($nextVertices as $nextVertix) { + // recursive call for next vertices + $this->recursiveDepthFirstSearch($nextVertix, $visitedVertices); + } + } + } + + private function iterativeDepthFirstSearch(Vertex $vertex) + { + $visited = array(); + $todo = array($vertex); + while ($vertex = array_shift($todo)) { + if (!isset($visited[$vertex->getId()])) { + $visited[$vertex->getId()] = $vertex; + + foreach (array_reverse($this->getVerticesAdjacent($vertex)->getMap(), true) as $vid => $nextVertex) { + $todo[] = $nextVertex; + } + } + } + + return new Vertices($visited); + } + + /** + * calculates a recursive depth-first search + * + * @return Vertices + */ + public function getVertices() + { + return $this->iterativeDepthFirstSearch($this->vertex); + + $visitedVertices = array(); + $this->recursiveDepthFirstSearch($this->vertex, $visitedVertices); + + return $visitedVertices; + } +} diff --git a/vendor/graphp/algorithms/src/ShortestPath/Base.php b/vendor/graphp/algorithms/src/ShortestPath/Base.php new file mode 100644 index 0000000000000000000000000000000000000000..623492f6ec6df90cf64d27988e780d93f7fca883 --- /dev/null +++ b/vendor/graphp/algorithms/src/ShortestPath/Base.php @@ -0,0 +1,287 @@ +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 $endVertex + * @return boolean + * @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(); +} diff --git a/vendor/graphp/algorithms/src/ShortestPath/BreadthFirst.php b/vendor/graphp/algorithms/src/ShortestPath/BreadthFirst.php new file mode 100644 index 0000000000000000000000000000000000000000..ec66a9bdf651d5c616bdc33828e109b4989c048c --- /dev/null +++ b/vendor/graphp/algorithms/src/ShortestPath/BreadthFirst.php @@ -0,0 +1,132 @@ +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 int[] + * @uses Vertex::hasLoop() + */ + public function getDistanceMap() + { + $ret = array(); + foreach ($this->getEdgesMap() as $vid => $edges) { + $ret[$vid] = 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 ($this->getEdgesMap() as $vid => $unusedEdges) { + $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); + } +} diff --git a/vendor/graphp/algorithms/src/ShortestPath/Dijkstra.php b/vendor/graphp/algorithms/src/ShortestPath/Dijkstra.php new file mode 100644 index 0000000000000000000000000000000000000000..bb15ba84bb0718c408afdba2fbe6056d23d900b2 --- /dev/null +++ b/vendor/graphp/algorithms/src/ShortestPath/Dijkstra.php @@ -0,0 +1,121 @@ +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); + } +} diff --git a/vendor/graphp/algorithms/src/ShortestPath/MooreBellmanFord.php b/vendor/graphp/algorithms/src/ShortestPath/MooreBellmanFord.php new file mode 100644 index 0000000000000000000000000000000000000000..4bba56152c8b81e41e4d4d5e4ad0432d54fe4ac9 --- /dev/null +++ b/vendor/graphp/algorithms/src/ShortestPath/MooreBellmanFord.php @@ -0,0 +1,123 @@ +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'); + } +} diff --git a/vendor/graphp/algorithms/src/Symmetric.php b/vendor/graphp/algorithms/src/Symmetric.php new file mode 100644 index 0000000000000000000000000000000000000000..ce69df6ed0d345345edc053a71e30a08ccce9c20 --- /dev/null +++ b/vendor/graphp/algorithms/src/Symmetric.php @@ -0,0 +1,44 @@ +b there's also an edge b->a) + * + * @return boolean + * @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; + } +} diff --git a/vendor/graphp/algorithms/src/TopologicalSort.php b/vendor/graphp/algorithms/src/TopologicalSort.php new file mode 100644 index 0000000000000000000000000000000000000000..839c7a2c6768332b1ccc3aa067bb0a57fde2dba5 --- /dev/null +++ b/vendor/graphp/algorithms/src/TopologicalSort.php @@ -0,0 +1,65 @@ +graph->getVertices()->getVector()) as $vertex) { + $this->visit($vertex, $visited, $tsl); + } + + return new Vertices(array_reverse($tsl, true)); + } + + protected function visit(Vertex $vertex, array &$visited, array &$tsl) + { + $vid = $vertex->getId(); + if (isset($visited[$vid])) { + if ($visited[$vid] === false) { + // temporary mark => not a DAG + throw new UnexpectedValueException('Not a DAG'); + } + // otherwise already marked/visisted => no need to check again + } else { + // temporary mark + $visited[$vid] = false; + + foreach (array_reverse($vertex->getVerticesEdgeTo()->getVector()) as $v) { + $this->visit($v, $visited, $tsl); + } + + // mark as visited and include in result + $visited[$vid] = true; + $tsl[$vid] = $vertex; + } + } +} diff --git a/vendor/graphp/algorithms/src/TransposeGraph.php b/vendor/graphp/algorithms/src/TransposeGraph.php new file mode 100644 index 0000000000000000000000000000000000000000..8c8404d7655f9b94f56654fd8b91a00e8a35a231 --- /dev/null +++ b/vendor/graphp/algorithms/src/TransposeGraph.php @@ -0,0 +1,36 @@ +graph->createGraphCloneEdgeless(); + + foreach ($this->graph->getEdges() as $edge) { + if (!($edge instanceof EdgeDirected)) { + throw new UnexpectedValueException('Edge is undirected'); + } + $newgraph->createEdgeCloneInverted($edge); + } + + return $newgraph; + } +} diff --git a/vendor/graphp/algorithms/src/TravelingSalesmanProblem/Base.php b/vendor/graphp/algorithms/src/TravelingSalesmanProblem/Base.php new file mode 100644 index 0000000000000000000000000000000000000000..0fcbd98896d59e4de4ea70e3a3f8208e82af64b2 --- /dev/null +++ b/vendor/graphp/algorithms/src/TravelingSalesmanProblem/Base.php @@ -0,0 +1,70 @@ +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 AlgorithmTsp::getEdges() + * @uses AlgorithmTsp::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(); +} diff --git a/vendor/graphp/algorithms/src/TravelingSalesmanProblem/Bruteforce.php b/vendor/graphp/algorithms/src/TravelingSalesmanProblem/Bruteforce.php new file mode 100644 index 0000000000000000000000000000000000000000..8760b17e2b69e54b6e4518a057b391e054ff3de1 --- /dev/null +++ b/vendor/graphp/algorithms/src/TravelingSalesmanProblem/Bruteforce.php @@ -0,0 +1,215 @@ +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 double $limit + * @return AlgorithmTspBruteforce $this (chainable) + */ + public function setUpperLimit($limit) + { + $this->upperLimit = $limit; + + return $this; + } + + public function setUpperLimitMst() + { + $alg = new AlgorithmTspMst($this->graph); + $limit = $alg->createGraph()->getWeight(); + + return $this->setUpperLimit($limit); + } + + 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 boolean[] $visitedVertices + * @param Edge[] $visitedEdges + * @return Edge[] + */ + 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; + } +} diff --git a/vendor/graphp/algorithms/src/TravelingSalesmanProblem/MinimumSpanningTree.php b/vendor/graphp/algorithms/src/TravelingSalesmanProblem/MinimumSpanningTree.php new file mode 100644 index 0000000000000000000000000000000000000000..fcb3fefbf741e80ec8859faaab84eee2bb969032 --- /dev/null +++ b/vendor/graphp/algorithms/src/TravelingSalesmanProblem/MinimumSpanningTree.php @@ -0,0 +1,74 @@ +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) + $returnEdges []= $oldVertex->getEdgesTo($vertex)->getEdgeFirst(); + } + + $oldVertex = $vertex; + } + + // connect last vertex with start vertex + // multiple edges are possible (returns an array if undirected edge) + $returnEdges []= $oldVertex->getEdgesTo($startVertex)->getEdgeFirst(); + + return new Edges($returnEdges); + } +} diff --git a/vendor/graphp/algorithms/src/TravelingSalesmanProblem/NearestNeighbor.php b/vendor/graphp/algorithms/src/TravelingSalesmanProblem/NearestNeighbor.php new file mode 100644 index 0000000000000000000000000000000000000000..20bf262eb3af0e1709964f0aa1463700f4d0cd10 --- /dev/null +++ b/vendor/graphp/algorithms/src/TravelingSalesmanProblem/NearestNeighbor.php @@ -0,0 +1,93 @@ +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 = $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 + $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); + } +} diff --git a/vendor/graphp/algorithms/src/Tree/Base.php b/vendor/graphp/algorithms/src/Tree/Base.php new file mode 100644 index 0000000000000000000000000000000000000000..3094138644b7f2b6c752b3744b91c9e5e7f64ec5 --- /dev/null +++ b/vendor/graphp/algorithms/src/Tree/Base.php @@ -0,0 +1,107 @@ +degree = new Degree($graph); + } + + /** + * checks whether the given graph is actually a tree + * + * @return boolean + */ + 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 boolean + */ + 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 boolean + */ + 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')); + } +} diff --git a/vendor/graphp/algorithms/src/Tree/BaseDirected.php b/vendor/graphp/algorithms/src/Tree/BaseDirected.php new file mode 100644 index 0000000000000000000000000000000000000000..c0b31187b7e6f6b97027afd38d28c189542df0f4 --- /dev/null +++ b/vendor/graphp/algorithms/src/Tree/BaseDirected.php @@ -0,0 +1,325 @@ + 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 boolean + * @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 boolean + * @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 boolean + * @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 boolean + * @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); + } +} diff --git a/vendor/graphp/algorithms/src/Tree/InTree.php b/vendor/graphp/algorithms/src/Tree/InTree.php new file mode 100644 index 0000000000000000000000000000000000000000..e6a13dbc8334c00849bd15edc58d9e19a962add2 --- /dev/null +++ b/vendor/graphp/algorithms/src/Tree/InTree.php @@ -0,0 +1,44 @@ +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; + } +} diff --git a/vendor/graphp/algorithms/src/Tree/OutTree.php b/vendor/graphp/algorithms/src/Tree/OutTree.php new file mode 100644 index 0000000000000000000000000000000000000000..c757bd9323d16e541fb8bdb3ccc1878ba1a75380 --- /dev/null +++ b/vendor/graphp/algorithms/src/Tree/OutTree.php @@ -0,0 +1,44 @@ + 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; + } +} diff --git a/vendor/graphp/algorithms/src/Tree/Undirected.php b/vendor/graphp/algorithms/src/Tree/Undirected.php new file mode 100644 index 0000000000000000000000000000000000000000..41b184ef8d81d7a7efef17a8680f104fc1eb9956 --- /dev/null +++ b/vendor/graphp/algorithms/src/Tree/Undirected.php @@ -0,0 +1,150 @@ +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 boolean + * @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 boolean + * @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 + * @throws UnexpectedValueException for directed edges + * @return Vertices (might include possible duplicates) + * @uses Vertex::getEdges() + * @uses Edge::getVertexToFrom() + * @see Vertex::getVerticesEdge() + */ + private function getVerticesNeighbor(Vertex $vertex) + { + $vertices = array(); + foreach ($vertex->getEdges() as $edge) { + /* @var Edge $edge */ + if (!($edge instanceof UndirectedEdge)) { + throw new UnexpectedValueException('Directed edge encountered'); + } + $vertices[] = $edge->getVertexToFrom($vertex); + } + return new Vertices($vertices); + } +} diff --git a/vendor/graphp/algorithms/src/Weight.php b/vendor/graphp/algorithms/src/Weight.php new file mode 100644 index 0000000000000000000000000000000000000000..2fe908a3d3f328237cc5f04e969bce90e3372563 --- /dev/null +++ b/vendor/graphp/algorithms/src/Weight.php @@ -0,0 +1,107 @@ +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; + } +} diff --git a/vendor/graphp/algorithms/tests/BipartitTest.php b/vendor/graphp/algorithms/tests/BipartitTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e89b4cb58bb35280d801834aba33336a7ec0829a --- /dev/null +++ b/vendor/graphp/algorithms/tests/BipartitTest.php @@ -0,0 +1,94 @@ +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); + $v1->createEdgeTo($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('Fhaculty\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); + $v1->createEdgeTo($v2); + $v2->createEdgeTo($v3); + $v3->createEdgeTo($v1); + + $alg = new AlgorithmBipartit($graph); + + $this->assertFalse($alg->isBipartit()); + + return $alg; + } + + /** + * + * @param AlgorithmBipartit $alg + * @expectedException UnexpectedValueException + * @depends testGraphTriangleCycleIsNotBipartit + */ + public function testGraphTriangleCycleColorsInvalid(AlgorithmBipartit $alg) + { + $alg->getColors(); + } + + /** + * + * @param AlgorithmBipartit $alg + * @expectedException UnexpectedValueException + * @depends testGraphTriangleCycleIsNotBipartit + */ + public function testGraphTriangleCycleColorVerticesInvalid(AlgorithmBipartit $alg) + { + $alg->getColorVertices(); + } +} diff --git a/vendor/graphp/algorithms/tests/CompleteTest.php b/vendor/graphp/algorithms/tests/CompleteTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d163c6c36f4ce029c450f534a200c3a45cd34b3b --- /dev/null +++ b/vendor/graphp/algorithms/tests/CompleteTest.php @@ -0,0 +1,65 @@ +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->createVertex(1)->createEdge($graph->createVertex(2)); + + $alg = new AlgorithmComplete($graph); + + $this->assertTrue($alg->isComplete()); + } + + public function testGraphSingleDirectedIsNotComplete() + { + // 1 -> 2 + $graph = new Graph(); + $graph->createVertex(1)->createEdgeTo($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->createVertex(1)->createEdgeTo($graph->createVertex(2)); + $graph->getVertex(1)->createEdge($graph->getVertex(2)); + $graph->getVertex(2)->createEdgeTo($graph->getVertex(1)); + $graph->getVertex(1)->createEdgeTo($graph->getVertex(1)); + + $alg = new AlgorithmComplete($graph); + + $this->assertTrue($alg->isComplete()); + } +} diff --git a/vendor/graphp/algorithms/tests/ConnectedComponentsTest.php b/vendor/graphp/algorithms/tests/ConnectedComponentsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..37c0b96d05f755c4c09dbd4238f315ddd48acb4e --- /dev/null +++ b/vendor/graphp/algorithms/tests/ConnectedComponentsTest.php @@ -0,0 +1,97 @@ +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->createVertex(1)->createEdge($graph->createVertex(2)); + $graph->getVertex(2)->createEdgeTo($graph->createVertex(3)); + $graph->createVertex(4)->createEdgeTo($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); + $e1 = $v1->createEdge($v2); + $e2 = $v3->createEdgeTo($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->createVertex(1)->createEdge($ge->createVertex(2)); + $this->assertGraphEquals($ge, $alg->createGraphComponentVertex($v2)); + + $ge = new Graph(); + $ge->createVertex(5); + $this->assertEquals($ge, $alg->createGraphComponentVertex($v5)); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testInvalidVertexPassedToAlgorithm() + { + $graph = new Graph(); + + $graph2 = new Graph(); + $v2 = $graph2->createVertex(12); + + $alg = new AlgorithmConnected($graph); + $alg->createGraphComponentVertex($v2); + } +} diff --git a/vendor/graphp/algorithms/tests/DegreeTest.php b/vendor/graphp/algorithms/tests/DegreeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..2ed4efe573d05e2f3b1ca3c7e1ee38b0c7f02be5 --- /dev/null +++ b/vendor/graphp/algorithms/tests/DegreeTest.php @@ -0,0 +1,100 @@ +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); + $v1->createEdgeTo($v2); + $v2->createEdgeTo($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)); + } +} diff --git a/vendor/graphp/algorithms/tests/DetectNegativeCycleTest.php b/vendor/graphp/algorithms/tests/DetectNegativeCycleTest.php new file mode 100644 index 0000000000000000000000000000000000000000..65c505a04067d9930189765bbb5aff9bf11c856e --- /dev/null +++ b/vendor/graphp/algorithms/tests/DetectNegativeCycleTest.php @@ -0,0 +1,151 @@ +assertFalse($alg->hasCycleNegative()); + + return $alg; + } + + /** + * + * @param DetectNegativeCycle $alg + * @depends testNullGraph + * @expectedException UnderflowException + */ + public function testNullGraphHasNoCycle(DetectNegativeCycle $alg) + { + $alg->getCycleNegative(); + } + + /** + * + * @param DetectNegativeCycle $alg + * @depends testNullGraph + * @expectedException UnderflowException + */ + public function testNullGraphHasNoCycleGraph(DetectNegativeCycle $alg) + { + $alg->createGraph(); + } + + public function testNegativeLoop() + { + // 1 --[-1]--> 1 + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $e1 = $v1->createEdgeTo($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); + $e1 = $v1->createEdgeTo($v2)->setWeight(-1); + $e2 = $v2->createEdgeTo($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); + $e1 = $v1->createEdge($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); + $e1 = $v1->createEdgeTo($v2)->setWeight(1); + $e2 = $v2->createEdgeTo($v3)->setWeight(1); + $e3 = $v3->createEdgeTo($v4)->setWeight(1); + $e4 = $v4->createEdgeTo($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); + $e1 = $v1->createEdge($v2); + $e2 = $v3->createEdgeTo($v4)->setWeight(-1); + $e3 = $v4->createEdgeTo($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)); + } +} diff --git a/vendor/graphp/algorithms/tests/DirectedTest.php b/vendor/graphp/algorithms/tests/DirectedTest.php new file mode 100644 index 0000000000000000000000000000000000000000..8bd088484bfb9b5bcd42d3c4fc7a0f920865bb06 --- /dev/null +++ b/vendor/graphp/algorithms/tests/DirectedTest.php @@ -0,0 +1,58 @@ +assertFalse($alg->hasDirected()); + $this->assertFalse($alg->hasUndirected()); + $this->assertFalse($alg->isMixed()); + } + + public function testGraphUndirected() + { + // 1 -- 2 + $graph = new Graph(); + $graph->createVertex(1)->createEdge($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->createVertex(1)->createEdgeTo($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->createVertex(1)->createEdge($graph->createVertex(2)); + $graph->getVertex(2)->createEdgeTo($graph->createVertex(3)); + + $alg = new AlgorithmDirected($graph); + + $this->assertTrue($alg->hasDirected()); + $this->assertTrue($alg->hasUndirected()); + $this->assertTrue($alg->isMixed()); + } +} diff --git a/vendor/graphp/algorithms/tests/EulerianTest.php b/vendor/graphp/algorithms/tests/EulerianTest.php new file mode 100644 index 0000000000000000000000000000000000000000..74b17856344bcc5edc959b30d1a42e52ba11c753 --- /dev/null +++ b/vendor/graphp/algorithms/tests/EulerianTest.php @@ -0,0 +1,45 @@ +assertFalse($alg->hasCycle()); + } + + public function testGraphPairHasNoCycle() + { + // 1 -- 2 + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $v2 = $graph->createVertex(2); + $v1->createEdge($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); + $v1->createEdge($v2); + $v2->createEdge($v3); + $v3->createEdge($v1); + + $alg = new AlgorithmEulerian($graph); + + $this->assertTrue($alg->hasCycle()); + } +} diff --git a/vendor/graphp/algorithms/tests/FlowTest.php b/vendor/graphp/algorithms/tests/FlowTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1e6218c2b8285e1a99f260d87d78d90c842180ad --- /dev/null +++ b/vendor/graphp/algorithms/tests/FlowTest.php @@ -0,0 +1,98 @@ +assertFalse($alg->hasFlow()); + $this->assertEquals(0, $alg->getBalance()); + $this->assertTrue($alg->isBalancedFlow()); + + return $graph; + } + + public function testEdgeWithZeroFlowIsConsideredFlow() + { + // 1 -> 2 + $graph = new Graph(); + $graph->createVertex(1)->createEdgeTo($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->createVertex(1)->createEdgeTo($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->getVertex(2)->createEdgeTo($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()); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testVertexWithUndirectedEdgeHasInvalidFlow() + { + // 1 -- 2 + $graph = new Graph(); + $graph->createVertex(1)->createEdge($graph->createVertex(2))->setFlow(10); + + + $alg = new AlgorithmFlow($graph); + + $alg->getFlowVertex($graph->getVertex(1)); + } +} diff --git a/vendor/graphp/algorithms/tests/GroupsTest.php b/vendor/graphp/algorithms/tests/GroupsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..bd7dc79b4e6db0ae334fe6041522ad079747daa5 --- /dev/null +++ b/vendor/graphp/algorithms/tests/GroupsTest.php @@ -0,0 +1,62 @@ +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); + $v1->createEdgeTo($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); + $v1->createEdgeTo($v2); + $v2->createEdgeTo($v3); + $v3->createEdgeTo($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()); + } +} diff --git a/vendor/graphp/algorithms/tests/LoopTest.php b/vendor/graphp/algorithms/tests/LoopTest.php new file mode 100644 index 0000000000000000000000000000000000000000..62881e80600854ad18d1fc3a804d49d6c5c51ae1 --- /dev/null +++ b/vendor/graphp/algorithms/tests/LoopTest.php @@ -0,0 +1,57 @@ +assertFalse($alg->hasLoop()); + } + + public function testGraphWithMixedCircuitIsNotConsideredLoop() + { + // 1 -> 2 + // 2 -- 1 + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $v2 = $graph->createVertex(2); + $v1->createEdgeTo($v2); + $v2->createEdge($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->createVertex(1)->createEdge($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->createVertex(1)->createEdgeTo($v1 = $graph->getVertex(1)); + + $alg = new AlgorithmLoop($graph); + + $this->assertTrue($alg->hasLoop()); + $this->assertTrue($alg->hasLoopVertex($v1)); + } +} diff --git a/vendor/graphp/algorithms/tests/MaxFlow/EdmondsKarpTest.php b/vendor/graphp/algorithms/tests/MaxFlow/EdmondsKarpTest.php new file mode 100644 index 0000000000000000000000000000000000000000..034087e2895bcb70eee1eb220f353c6bb199a60a --- /dev/null +++ b/vendor/graphp/algorithms/tests/MaxFlow/EdmondsKarpTest.php @@ -0,0 +1,169 @@ + 1 + $graph = new Graph(); + $v0 = $graph->createVertex(0); + $v1 = $graph->createVertex(1); + + $v0->createEdgeTo($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); + + $v0->createEdgeTo($v1)->setCapacity(5); + $v0->createEdgeTo($v2)->setCapacity(7); + $v2->createEdgeTo($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); + $v4 = $graph->createVertex(4); + $v5 = $graph->createVertex(5); + + $v0->createEdgeTo($v1)->setCapacity(5); + $v0->createEdgeTo($v2)->setCapacity(7); + $v2->createEdgeTo($v1)->setCapacity(9); + $v1->createEdgeTo($v3)->setCapacity(10); + $v3->createEdgeTo($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); + + $v0->createEdgeTo($v1)->setCapacity(4); + $v0->createEdgeTo($v2)->setCapacity(2); + $v1->createEdgeTo($v2)->setCapacity(3); + $v1->createEdgeTo($v3)->setCapacity(1); + $v2->createEdgeTo($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); + +// $v0->createEdgeTo($v1)->setCapacity(3.4); +// $v0->createEdgeTo($v1)->setCapacity(6.6); + +// $alg = new AlgorithmMaxFlowEdmondsKarp($v0, $v1); + +// $this->assertEquals(10, $alg->getFlowMax()); +// } + + /** + * @expectedException UnexpectedValueException + */ + public function testEdgesUndirected() + { + // 0 -[0/7]- 1 + $graph = new Graph(); + $v0 = $graph->createVertex(0); + $v1 = $graph->createVertex(1); + + $v1->createEdge($v0)->setCapacity(7); + + // 0 -[7/7]- 1 + $alg = new AlgorithmMaxFlowEdmondsKarp($v0, $v1); + + $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()); +// } + + + /** + * @expectedException InvalidArgumentException + */ + public function testInvalidFlowToOtherGraph() + { + $graph1 = new Graph(); + $vg1 = $graph1->createVertex(1); + + $graph2 = new Graph(); + $vg2 = $graph2->createVertex(2); + + new AlgorithmMaxFlowEdmondsKarp($vg1, $vg2); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testInvalidFlowToSelf() + { + $graph = new Graph(); + $v1 = $graph->createVertex(1); + + new AlgorithmMaxFlowEdmondsKarp($v1, $v1); + } + +} diff --git a/vendor/graphp/algorithms/tests/MaximumMatching/FlowTest.php b/vendor/graphp/algorithms/tests/MaximumMatching/FlowTest.php new file mode 100644 index 0000000000000000000000000000000000000000..8ea7f24659d9c5b43ee7e502f9b4d09424259c3e --- /dev/null +++ b/vendor/graphp/algorithms/tests/MaximumMatching/FlowTest.php @@ -0,0 +1,65 @@ +setEnableDirectedEdges(false); +// $graph = $loader->createGraph(); + +// $alg = new Flow($graph); +// $this->assertEquals(100, $alg->getNumberOfMatches()); +// } + + public function testSingleEdge() + { + $graph = new Graph(); + $edge = $graph->createVertex(0)->setGroup(0)->createEdge($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('Fhaculty\Graph\Graph', $flowgraph); + } + + /** + * expect exception for directed edges + * @expectedException UnexpectedValueException + */ + public function testInvalidDirected() + { + $graph = new Graph(); + $graph->createVertex(0)->setGroup(0)->createEdgeTo($graph->createVertex(1)->setGroup(1)); + + $alg = new Flow($graph); + $alg->getNumberOfMatches(); + } + + /** + * expect exception for non-bipartit graphs + * @expectedException UnexpectedValueException + */ + public function testInvalidBipartit() + { + $graph = new Graph(); + $graph->createVertex(0)->setGroup(1)->createEdge($graph->createVertex(1)->setGroup(1)); + + $alg = new Flow($graph); + $alg->getNumberOfMatches(); + } +} diff --git a/vendor/graphp/algorithms/tests/MinimumCostFlow/BaseMcfTest.php b/vendor/graphp/algorithms/tests/MinimumCostFlow/BaseMcfTest.php new file mode 100644 index 0000000000000000000000000000000000000000..59ffb181967697ecfd5752a8601698bf79686945 --- /dev/null +++ b/vendor/graphp/algorithms/tests/MinimumCostFlow/BaseMcfTest.php @@ -0,0 +1,203 @@ +createAlgorithm($graph); + $this->assertEquals(0, $alg->getWeightFlow()); + } + + public function testSingleIntermediary() + { + $graph = new Graph(); + $v1 = $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); + $v1->createEdgeTo($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); + $v1->createEdgeTo($v2)->setWeight(2)->setCapacity(2); + $v1->createEdgeTo($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); + $v1->createEdgeTo($v2)->setWeight(4)->setCapacity(1); + $v2->createEdgeTo($v4)->setWeight(-2)->setCapacity(6); + $v1->createEdgeTo($v3)->setWeight(5)->setCapacity(4); + $v3->createEdgeTo($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); + $v1->createEdgeTo($v2)->setWeight(4)->setCapacity(3); + $v2->createEdgeTo($v3)->setWeight(5)->setCapacity(4); + $v3->createEdgeTo($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); + $v1->createEdgeTo($v2)->setFlow(3)->setCapacity(4)->setWeight(2); + $v2->createEdgeTo($v4)->setFlow(3)->setCapacity(3)->setWeight(3); + $v1->createEdgeTo($v3)->setFlow(1)->setCapacity(2)->setWeight(2); + $v3->createEdgeTo($v4)->setFlow(1)->setCapacity(5)->setWeight(1); + $v2->createEdgeTo($v3)->setFlow(0)->setCapacity(2)->setWeight(1); + + $alg = $this->createAlgorithm($graph); + $this->assertEquals(14, $alg->getWeightFlow()); // 4*1 + 2*2 + 2*1 + 2*2 + } + + /** + * @expectedException UnexpectedValueException + */ + public function testEdgeCapacityInsufficientFails() + { + // 1(+2) -[0/1]-> 2(-2) + $graph = new Graph(); + $v1 = $graph->createVertex(1)->setBalance(2); + $v2 = $graph->createVertex(2)->setBalance(-2); + $v1->createEdgeTo($v2)->setCapacity(1); + + $alg = $this->createAlgorithm($graph); + $alg->getWeightFlow(); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testEdgeCapacityUnsetFails() + { + // 1(+2) -> 2(-2) + $graph = new Graph(); + $v1 = $graph->createVertex(1)->setBalance(2); + $v2 = $graph->createVertex(2)->setBalance(-2); + $v1->createEdgeTo($v2); + + $alg = $this->createAlgorithm($graph); + $alg->getWeightFlow(); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testIsolatedVerticesFail() + { + // 1(+2), 2(-2) + $graph = new Graph(); + $v1 = $graph->createVertex(1)->setBalance(2); + $v2 = $graph->createVertex(2)->setBalance(-2); + + $alg = $this->createAlgorithm($graph); + $alg->getWeightFlow(); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testUnbalancedFails() + { + // 1(+2) -> 2(-3) + $graph = new Graph(); + $v1 = $graph->createVertex(1)->setBalance(2); + $v2 = $graph->createVertex(2)->setBalance(-3); + $v1->createEdgeTo($v2)->setCapacity(3); + + $alg = $this->createAlgorithm($graph); + $alg->getWeightFlow(); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testUndirectedFails() + { + // 1(+2) -- 2(-2) + $graph = new Graph(); + $v1 = $graph->createVertex(1)->setBalance(2); + $v2 = $graph->createVertex(2)->setBalance(-2); + $v1->createEdge($v2)->setCapacity(2); + + $alg = $this->createAlgorithm($graph); + $alg->getWeightFlow(); + } + + /** + * @expectedException UnexpectedValueException + */ + 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); + $v1->createEdge($v2)->setCapacity(2)->setWeight(-1); + + $alg = $this->createAlgorithm($graph); + $alg->getWeightFlow(); + } +} diff --git a/vendor/graphp/algorithms/tests/MinimumCostFlow/CycleCancellingTest.php b/vendor/graphp/algorithms/tests/MinimumCostFlow/CycleCancellingTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d6aca106363f2a1e5acc8d22a2b0534026ae2b37 --- /dev/null +++ b/vendor/graphp/algorithms/tests/MinimumCostFlow/CycleCancellingTest.php @@ -0,0 +1,12 @@ +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); + $v1->createEdge($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); + $v1->createEdge($v2)->setWeight(6); + $v2->createEdge($v3)->setWeight(9); + $v3->createEdge($v4)->setWeight(7); + $v4->createEdge($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); + $v1->createEdge($v2)->setWeight(4); + $v1->createEdge($v2)->setWeight(3); + $v1->createEdge($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); + $v1->createEdge($v2)->setWeight(4); + $v2->createEdge($v3)->setWeight(5); + $v3->createEdge($v1)->setWeight(6); + + // 1 --[4]-- 2 -- [5] -- 3 + $graphExpected = new Graph(); + $ve1 = $graphExpected->createVertex(1); + $ve2 = $graphExpected->createVertex(2); + $ve3 = $graphExpected->createVertex(3); + $ve1->createEdge($ve2)->setWeight(4); + $ve2->createEdge($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); + $v1->createEdgeTo($v2)->setWeight(6); + $v2->createEdge($v3)->setWeight(7); + $v4->createEdge($v3)->setWeight(8); + $v5->createEdgeTo($v4)->setWeight(9); + + $alg = $this->createAlg($v1); + + $this->assertCount(4, $alg->getEdges()); + $this->assertEquals(30, $alg->getWeight()); + $this->assertGraphEquals($graph, $alg->createGraph()); + } + + /** + * @expectedException UnexpectedValueException + */ + 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); + $v1->createEdge($v2)->setWeight(1); + $v3->createEdge($v4)->setWeight(1); + + $alg = $this->createAlg($v1); + $alg->getEdges(); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testMultipleIsolatedVerticesFormMultipleComponentsFail() + { + // 1, 2 + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $v2 = $graph->createVertex(2); + + $alg = $this->createAlg($v1); + $alg->getEdges(); + } + + +} \ No newline at end of file diff --git a/vendor/graphp/algorithms/tests/MinimumSpanningTree/KruskalTest.php b/vendor/graphp/algorithms/tests/MinimumSpanningTree/KruskalTest.php new file mode 100644 index 0000000000000000000000000000000000000000..3e1f21d32d6bca59602d5b9d6076fa0f71e8907b --- /dev/null +++ b/vendor/graphp/algorithms/tests/MinimumSpanningTree/KruskalTest.php @@ -0,0 +1,24 @@ +getGraph()); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testNullGraphIsNotConsideredToBeConnected() + { + $graph = new Graph(); + + $alg = new Kruskal($graph); + $alg->getEdges(); + } +} diff --git a/vendor/graphp/algorithms/tests/MinimumSpanningTree/PrimTest.php b/vendor/graphp/algorithms/tests/MinimumSpanningTree/PrimTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ab862ef9d66eab6429f62d4805044069b8388ba6 --- /dev/null +++ b/vendor/graphp/algorithms/tests/MinimumSpanningTree/PrimTest.php @@ -0,0 +1,12 @@ +assertFalse($alg->hasEdgeParallel()); + } + + public function testDirectedCycleIsNotConsideredParallel() + { + // 1 -> 2 + // 2 -> 1 + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $v2 = $graph->createVertex(2); + $e1 = $v1->createEdgeTo($v2); + $e2 = $v2->createEdgeTo($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 = $v1->createEdgeTo($v2); + $e2 = $v1->createEdgeTo($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 = $v1->createEdgeTo($v2); + $e2 = $v1->createEdge($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 = $v1->createEdgeTo($v2); + $e2 = $v1->createEdgeTo($v2); + $e3 = $v1->createEdge($v2); + $e4 = $v1->createEdge($v2); + $e5 = $v2->createEdgeTo($v1); + $e6 = $v2->createEdgeTo($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()); + } + +} diff --git a/vendor/graphp/algorithms/tests/Property/PropertyGraphTest.php b/vendor/graphp/algorithms/tests/Property/PropertyGraphTest.php new file mode 100644 index 0000000000000000000000000000000000000000..32e03740a9300dd53f836ff1e0288b78887d0502 --- /dev/null +++ b/vendor/graphp/algorithms/tests/Property/PropertyGraphTest.php @@ -0,0 +1,30 @@ +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()); + } +} diff --git a/vendor/graphp/algorithms/tests/Property/WalkPropertyTest.php b/vendor/graphp/algorithms/tests/Property/WalkPropertyTest.php new file mode 100644 index 0000000000000000000000000000000000000000..06612b2cc26c22192e26c8a6c66522d25ef6e45b --- /dev/null +++ b/vendor/graphp/algorithms/tests/Property/WalkPropertyTest.php @@ -0,0 +1,186 @@ +createVertex(1); + + $walk = Walk::factoryFromEdges(array(), $v1); + + $this->assertEquals(1, count($walk->getVertices())); + $this->assertEquals(0, count($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 = $v1->createEdge($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 = $v1->createEdge($v2); + $e2 = $v2->createEdge($v1); + + $walk = Walk::factoryFromEdges(array($e1, $e2), $v1); + + $this->assertEquals(3, count($walk->getVertices())); + $this->assertEquals(2, count($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 = $v1->createEdgeTo($v2); + $e2 = $v2->createEdgeTo($v1); + $e3 = $v2->createEdgeTo($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 = $v1->createEdgeTo($v2); + $e2 = $v2->createEdgeTo($v1); + $e3 = $v2->createEdgeTo($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 = $v1->createEdgeTo($v2); + $e2 = $v2->createEdgeTo($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 = $v1->createEdgeTo($v2); + $e2 = $v2->createEdgeTo($v3); + $e3 = $v3->createEdgeTo($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); + $e1 = $v1->createEdge($v2); + $e2 = $v2->createEdge($v2); + + // only use "2 -- 2" part + $walk = Walk::factoryFromEdges(array($e2), $v2); + + $this->assertEquals(2, count($walk->getVertices())); + $this->assertEquals(1, count($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()); + } +} diff --git a/vendor/graphp/algorithms/tests/ResidualGraphTest.php b/vendor/graphp/algorithms/tests/ResidualGraphTest.php new file mode 100644 index 0000000000000000000000000000000000000000..88112ede0c0c6e3941b482092251cb17fccad05d --- /dev/null +++ b/vendor/graphp/algorithms/tests/ResidualGraphTest.php @@ -0,0 +1,133 @@ +createGraph(); + + $this->assertGraphEquals($graph, $residual); + } + + /** + * test an edge with capacity unused + */ + public function testEdgeUnused() + { + $graph = new Graph(); + + $graph->createVertex(0)->createEdgeTo($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->createVertex(0)->createEdgeTo($graph->createVertex(1))->setFlow(2) + ->setCapacity(2) + ->setWeight(3); + + $alg = new ResidualGraph($graph); + $residual = $alg->createGraph(); + + $expected = new Graph(); + $expected->createVertex(1)->createEdgeTo($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->createVertex(0)->createEdgeTo($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->getVertex(0)->createEdgeTo($expected->getVertex(1))->setFlow(0) + ->setCapacity(1) + ->setWeight(3); + + // back edge + $expected->getVertex(1)->createEdgeTo($expected->getVertex(0))->setFlow(0) + ->setCapacity(1) + ->setWeight(-3); + + $this->assertGraphEquals($expected, $residual); + } + + /** + * expect exception for undirected edges + * @expectedException UnexpectedValueException + */ + public function testInvalidUndirected() + { + $graph = new Graph(); + + $graph->createVertex()->createEdge($graph->createVertex())->setFlow(1) + ->setCapacity(2); + + $alg = new ResidualGraph($graph); + $alg->createGraph(); + } + + /** + * expect exception for edges with no flow + * @expectedException UnexpectedValueException + */ + public function testInvalidNoFlow() + { + $graph = new Graph(); + + $graph->createVertex()->createEdgeTo($graph->createVertex())->setCapacity(1); + + $alg = new ResidualGraph($graph); + $alg->createGraph(); + } + + /** + * expect exception for edges with no capacity + * @expectedException UnexpectedValueException + */ + public function testInvalidNoCapacity() + { + $graph = new Graph(); + + $graph->createVertex()->createEdgeTo($graph->createVertex())->setFlow(1); + + $alg = new ResidualGraph($graph); + $alg->createGraph(); + } + +} diff --git a/vendor/graphp/algorithms/tests/ShortestPath/BaseShortestPathTest.php b/vendor/graphp/algorithms/tests/ShortestPath/BaseShortestPathTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0ecd5f01f8cc1d3fe93d80099a78b2216f77a1a3 --- /dev/null +++ b/vendor/graphp/algorithms/tests/ShortestPath/BaseShortestPathTest.php @@ -0,0 +1,169 @@ +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 = $v1->createEdgeTo($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 = $v1->createEdgeTo($v2)->setWeight(4); + $e2 = $v2->createEdgeTo($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->assertEquals(2, count($walk->getEdges())); + } + + /** + * @expectedException OutOfBoundsException + */ + 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)); + + $alg->getEdgesTo($v2); + } + + /** + * @expectedException OutOfBoundsException + */ + public function testSeparateGraphsAreNotReachable() + { + // 1 + $graph1 = new Graph(); + $vg1 = $graph1->createVertex(1); + + $graph2 = new Graph(); + $vg2 = $graph2->createVertex(1); + + $alg = $this->createAlg($vg1); + + $alg->getEdgesTo($vg2); + } + + public function testGraphUnweighted() + { + // 1 -> 2 + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $v2 = $graph->createVertex(2); + $e1 = $v1->createEdgeTo($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 = $v1->createEdgeTo($v2)->setWeight(10); + $e2 = $v3->createEdgeTo($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; + } +} diff --git a/vendor/graphp/algorithms/tests/ShortestPath/BreadthFirstTest.php b/vendor/graphp/algorithms/tests/ShortestPath/BreadthFirstTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d69d16cf9b67cb7860c36c7870982887c40b985c --- /dev/null +++ b/vendor/graphp/algorithms/tests/ShortestPath/BreadthFirstTest.php @@ -0,0 +1,38 @@ + 2 + // 1 -[-1]-> 2 + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $v2 = $graph->createVertex(2); + $e1 = $v1->createEdgeTo($v2)->setWeight(10); + $e2 = $v1->createEdgeTo($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); + } +} diff --git a/vendor/graphp/algorithms/tests/ShortestPath/DijkstraTest.php b/vendor/graphp/algorithms/tests/ShortestPath/DijkstraTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0014ae42548fb9e53632af8a1b930d78dfd895c1 --- /dev/null +++ b/vendor/graphp/algorithms/tests/ShortestPath/DijkstraTest.php @@ -0,0 +1,31 @@ + 2 + // 1 -[-1]-> 2 + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $v2 = $graph->createVertex(2); + $e1 = $v1->createEdgeTo($v2)->setWeight(10); + $e2 = $v1->createEdgeTo($v2)->setWeight(-1); + + $alg = $this->createAlg($v1); + + $alg->getEdges(); + } +} diff --git a/vendor/graphp/algorithms/tests/ShortestPath/MooreBellmanFordTest.php b/vendor/graphp/algorithms/tests/ShortestPath/MooreBellmanFordTest.php new file mode 100644 index 0000000000000000000000000000000000000000..af2c988dda60b8d5b019fb04e9bdbf3e7d82ef4a --- /dev/null +++ b/vendor/graphp/algorithms/tests/ShortestPath/MooreBellmanFordTest.php @@ -0,0 +1,96 @@ + 2 + // 1 -[-1]-> 2 + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $v2 = $graph->createVertex(2); + $e1 = $v1->createEdgeTo($v2)->setWeight(10); + $e2 = $v1->createEdgeTo($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 + * @expectedException UnderflowException + */ + public function testNoNegativeCycle(MooreBellmanFord $alg) + { + $alg->getCycleNegative(); + } + + public function testUndirectedNegativeWeightIsCycle() + { + // 1 -[-10]- 2 + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $v2 = $graph->createVertex(2); + $e1 = $v1->createEdge($v2)->setWeight(-10); + + $alg = $this->createAlg($v1); + + $cycle = $alg->getCycleNegative(); + } + + public function testLoopNegativeWeightIsCycle() + { + // 1 -[-10]-> 1 + $graph = new Graph(); + $v1 = $graph->createVertex(1); + $e1 = $v1->createEdge($v1)->setWeight(-10); + + $alg = $this->createAlg($v1); + + $cycle = $alg->getCycleNegative(); + } + + 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); + $e1 = $v1->createEdgeTo($v2)->setWeight(1); + $e2 = $v3->createEdgeTo($v4)->setWeight(-1); + $e3 = $v4->createEdgeTo($v3)->setWeight(-2); + + // second component has a cycle + $alg = $this->createAlg($v3); + $cycle = $alg->getCycleNegative(); + + // first component does not have a cycle + $alg = $this->createAlg($v1); + $this->setExpectedException('UnderflowException'); + $alg->getCycleNegative(); + } +} diff --git a/vendor/graphp/algorithms/tests/SymmetricTest.php b/vendor/graphp/algorithms/tests/SymmetricTest.php new file mode 100644 index 0000000000000000000000000000000000000000..18cd723487edd48bc731d04d3516ba518eec36d9 --- /dev/null +++ b/vendor/graphp/algorithms/tests/SymmetricTest.php @@ -0,0 +1,61 @@ +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->createVertex(1)->createEdgeTo($graph->createVertex(2)); + + $alg = new AlgorithmSymmetric($graph); + + $this->assertFalse($alg->isSymmetric()); + } + + public function testGraphAntiparallelIsSymmetricr() + { + // 1 -> 2 -> 1 + $graph = new Graph(); + $graph->createVertex(1)->createEdgeTo($graph->createVertex(2)); + $graph->getVertex(2)->createEdgeTo($graph->getVertex(1)); + + $alg = new AlgorithmSymmetric($graph); + + $this->assertTrue($alg->isSymmetric()); + } + + public function testGraphSingleUndirectedIsSymmetricr() + { + // 1 -- 2 + $graph = new Graph(); + $graph->createVertex(1)->createEdge($graph->createVertex(2)); + + $alg = new AlgorithmSymmetric($graph); + + $this->assertTrue($alg->isSymmetric()); + } +} diff --git a/vendor/graphp/algorithms/tests/TopologicalSortTest.php b/vendor/graphp/algorithms/tests/TopologicalSortTest.php new file mode 100644 index 0000000000000000000000000000000000000000..6f9e9a522bd83191cf7c88fc7d11bdad51798f55 --- /dev/null +++ b/vendor/graphp/algorithms/tests/TopologicalSortTest.php @@ -0,0 +1,77 @@ +assertInstanceOf('Fhaculty\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->createVertex(1)->createEdgeTo($graph->createVertex(2)); + + $alg = new TopologicalSort($graph); + + $this->assertSame(array($graph->getVertex(1), $graph->getVertex(2)), $alg->getVertices()->getVector()); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testFailUndirected() + { + $graph = new Graph(); + $graph->createVertex(1)->createEdge($graph->createVertex(2)); + + $alg = new TopologicalSort($graph); + $alg->getVertices(); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testFailLoop() + { + $graph = new Graph(); + $graph->createVertex(1)->createEdgeTo($graph->getVertex(1)); + + $alg = new TopologicalSort($graph); + $alg->getVertices(); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testFailCycle() + { + $graph = new Graph(); + $graph->createVertex(1)->createEdgeTo($graph->createVertex(2)); + $graph->getVertex(2)->createEdgeTo($graph->getVertex(1)); + + $alg = new TopologicalSort($graph); + $alg->getVertices(); + } +} diff --git a/vendor/graphp/algorithms/tests/Tree/BaseDirectedTest.php b/vendor/graphp/algorithms/tests/Tree/BaseDirectedTest.php new file mode 100644 index 0000000000000000000000000000000000000000..5ac874971d20105d1dff896751c2a59e252d4430 --- /dev/null +++ b/vendor/graphp/algorithms/tests/Tree/BaseDirectedTest.php @@ -0,0 +1,199 @@ +createTreeAlg($graph); + $this->assertFalse($tree->isTree()); + $this->assertTrue($tree->getVerticesLeaf()->isEmpty()); + $this->assertTrue($tree->getVerticesInternal()->isEmpty()); + + return $tree; + } + + /** + * @param BaseDirected $tree + * @depends testNullGraph + * @expectedException UnderflowException + */ + public function testEmptyGraphDoesNotHaveRootVertex(BaseDirected $tree) + { + $tree->getVertexRoot(); + } + + /** + * @param BaseDirected $tree + * @depends testNullGraph + * @expectedException UnderflowException + */ + public function testEmptyGraphDoesNotHaveDegree(BaseDirected $tree) + { + $tree->getDegree(); + } + + /** + * @param BaseDirected $tree + * @depends testNullGraph + * @expectedException UnderflowException + */ + public function testEmptyGraphDoesNotHaveHeight(BaseDirected $tree) + { + $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 + * @expectedException UnderflowException + */ + public function testGraphTreeRootDoesNotHaveParent(BaseDirected $tree) + { + $root = $tree->getVertexRoot(); + $tree->getVertexParent($root); + } + + public function testNonTree() + { + $graph = $this->createGraphNonTree(); + + $tree = $this->createTreeAlg($graph); + + $this->assertFalse($tree->isTree()); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testNonTreeVertexHasMoreThanOneParent() + { + $graph = $this->createGraphNonTree(); + + $tree = $this->createTreeAlg($graph); + + $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->createVertex('v1')->createEdgeTo($graph->getVertex('v1')); + + $tree = $this->createTreeAlg($graph); + + $this->assertFalse($tree->isTree()); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testGraphWithLoopCanNotGetSubgraph() + { + // v1 -> v1 + $graph = new Graph(); + $graph->createVertex('v1')->createEdgeTo($graph->getVertex('v1')); + + $tree = $this->createTreeAlg($graph); + + $tree->getVerticesSubtree($graph->getVertex('v1')); + } + + public function testGraphWithUndirectedEdgeIsNotTree() + { + // v1 -- v2 + $graph = new Graph(); + $graph->createVertex('v1')->createEdge($graph->createVertex('v2')); + + $tree = $this->createTreeAlg($graph); + + $this->assertFalse($tree->isTree()); + } + + public function testGraphWithMixedEdgesIsNotTree() + { + // v1 -> v2 -- v3 -> v4 + $graph = new Graph(); + $graph->createVertex('v1')->createEdgeTo($graph->createVertex('v2')); + $graph->getVertex('v2')->createEdge($graph->createVertex('v3')); + $graph->getVertex('v3')->createEdgeTo($graph->createVertex('v4')); + + $tree = $this->createTreeAlg($graph); + + $this->assertFalse($tree->isTree()); + } +} diff --git a/vendor/graphp/algorithms/tests/Tree/InTreeTest.php b/vendor/graphp/algorithms/tests/Tree/InTreeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..91b7ec571eb5f22edf4cc1b7a4b34b6179b33d4c --- /dev/null +++ b/vendor/graphp/algorithms/tests/Tree/InTreeTest.php @@ -0,0 +1,48 @@ + root <- c2 + $graph = new Graph(); + $root = $graph->createVertex(); + + $c1 = $graph->createVertex(); + $c1->createEdgeTo($root); + + $c2 = $graph->createVertex(); + $c2->createEdgeTo($root); + + return $graph; + } + + protected function createTreeAlg(Graph $graph) + { + return new InTree($graph); + } + + protected function createGraphNonTree() + { + // v1 -> v2 <- v3 -> v4 + $graph = new Graph(); + $graph->createVertex('v1')->createEdgeTo($graph->createVertex('v2')); + $graph->createVertex('v3')->createEdgeTo($graph->getVertex('v2')); + $graph->getVertex('v3')->createEdgeTo($graph->createVertex('v4')); + + return $graph; + } + + protected function createGraphParallelEdge() + { + // v1 <- v2, v1 <- v2 + $graph = new Graph(); + $graph->createVertex('v2')->createEdgeTo($graph->createVertex('v1')); + $graph->getVertex('v2')->createEdgeTo($graph->getVertex('v1')); + + return $graph; + } +} diff --git a/vendor/graphp/algorithms/tests/Tree/OutTreeTest.php b/vendor/graphp/algorithms/tests/Tree/OutTreeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..c8289867a2de53265032041b1c54b403b482aba5 --- /dev/null +++ b/vendor/graphp/algorithms/tests/Tree/OutTreeTest.php @@ -0,0 +1,48 @@ + c2 + $graph = new Graph(); + $root = $graph->createVertex(); + + $c1 = $graph->createVertex(); + $root->createEdgeTo($c1); + + $c2 = $graph->createVertex(); + $root->createEdgeTo($c2); + + return $graph; + } + + protected function createTreeAlg(Graph $graph) + { + return new OutTree($graph); + } + + protected function createGraphNonTree() + { + // v1 -> v3 <- v2 -> v4 + $graph = new Graph(); + $graph->createVertex('v1')->createEdgeTo($graph->createVertex('v3')); + $graph->createVertex('v2')->createEdgeTo($graph->getVertex('v3')); + $graph->getVertex('v2')->createEdgeTo($graph->createVertex('v4')); + + return $graph; + } + + protected function createGraphParallelEdge() + { + // v1 -> v2, v1 -> v2 + $graph = new Graph(); + $graph->createVertex('v1')->createEdgeTo($graph->createVertex('v2')); + $graph->getVertex('v1')->createEdgeTo($graph->getVertex('v2')); + + return $graph; + } +} diff --git a/vendor/graphp/algorithms/tests/Tree/UndirectedTest.php b/vendor/graphp/algorithms/tests/Tree/UndirectedTest.php new file mode 100644 index 0000000000000000000000000000000000000000..5fd49841e18dad04c89291a338a04930f799fb04 --- /dev/null +++ b/vendor/graphp/algorithms/tests/Tree/UndirectedTest.php @@ -0,0 +1,114 @@ +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->createVertex('v1')->createEdge($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->createVertex('v1')->createEdge($graph->createVertex('v2')); + $graph->getVertex('v2')->createEdge($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->createVertex('v1')->createEdge($graph->createVertex('v2')); + $graph->getVertex('v1')->createEdge($graph->getVertex('v2')); + + $tree = $this->createTree($graph); + $this->assertFalse($tree->isTree()); + } + + public function testGraphLoopIsNotTree() + { + // v1 -- v1 + $graph = new Graph(); + $graph->createVertex('v1')->createEdge($graph->getVertex('v1')); + + $tree = $this->createTree($graph); + $this->assertFalse($tree->isTree()); + } + + public function testGraphCycleIsNotTree() + { + // v1 -- v2 -- v3 -- v1 + $graph = new Graph(); + $graph->createVertex('v1')->createEdge($graph->createVertex('v2')); + $graph->getVertex('v2')->createEdge($graph->createVertex('v3')); + $graph->getVertex('v3')->createEdge($graph->getVertex('v1')); + + $tree = $this->createTree($graph); + $this->assertFalse($tree->isTree()); + } + + public function testGraphDirectedIsNotTree() + { + // v1 -> v2 + $graph = new Graph(); + $graph->createVertex('v1')->createEdgeTo($graph->createVertex('v2')); + + $tree = $this->createTree($graph); + $this->assertFalse($tree->isTree()); + } + + public function testGraphMixedIsNotTree() + { + // v1 -- v2 -> v3 + $graph = new Graph(); + $graph->createVertex('v1')->createEdge($graph->createVertex('v2')); + $graph->getVertex('v2')->createEdgeTo($graph->createVertex('v3')); + + $tree = $this->createTree($graph); + $this->assertFalse($tree->isTree()); + } +} diff --git a/vendor/graphp/algorithms/tests/WeightTest.php b/vendor/graphp/algorithms/tests/WeightTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9b3591f29fd7789ca390da3531aa1a669f2a3efc --- /dev/null +++ b/vendor/graphp/algorithms/tests/WeightTest.php @@ -0,0 +1,58 @@ +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->createVertex(1)->createEdgeTo($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->createVertex(5)->createEdgeTo($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()); + } +} diff --git a/vendor/graphp/algorithms/tests/bootstrap.php b/vendor/graphp/algorithms/tests/bootstrap.php new file mode 100644 index 0000000000000000000000000000000000000000..0e3be73dd91815efa68e7f10d35ff21744b0b565 --- /dev/null +++ b/vendor/graphp/algorithms/tests/bootstrap.php @@ -0,0 +1,100 @@ +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) { + try { + $other = $actual->getVertex($vid); + } catch (Exception $e) { + $this->fail(); + } + 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 Directed) { + $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; + } +} diff --git a/vendor/graphp/graphviz/.gitignore b/vendor/graphp/graphviz/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..de4a392c3313c89428ffe2a939f8744aae16b81b --- /dev/null +++ b/vendor/graphp/graphviz/.gitignore @@ -0,0 +1,2 @@ +/vendor +/composer.lock diff --git a/vendor/graphp/graphviz/.travis.yml b/vendor/graphp/graphviz/.travis.yml new file mode 100644 index 0000000000000000000000000000000000000000..6cc3dcd8b33ae5b9fc4ee7d1e85c05c8bdf3055f --- /dev/null +++ b/vendor/graphp/graphviz/.travis.yml @@ -0,0 +1,11 @@ +language: php +php: + - 5.6 + - 5.5 + - 5.4 + - 5.3 + - hhvm +install: + - composer install --prefer-source --no-interaction +script: + - php vendor/bin/phpunit --coverage-text diff --git a/vendor/graphp/graphviz/CHANGELOG.md b/vendor/graphp/graphviz/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..016c89079a8aad2ed749ae801d33572ea77fe04d --- /dev/null +++ b/vendor/graphp/graphviz/CHANGELOG.md @@ -0,0 +1,23 @@ +# CHANGELOG + +This file is a manually maintained list of changes for each release. Feel free +to add your changes here when sending pull requests. Also send corrections if +you spot any mistakes. + +## 0.2.1 (2015-03-08) + +* Support graph v0.9 (while keeping BC) + ([#9](https://github.com/graphp/graphviz/pull/9)) + +## 0.2.0 (2015-01-19) + +* BC break: Refactor to inject Graph into GraphViz on demand, inject GraphViz into exporters + ([#6](https://github.com/graphp/graphviz/pull/6)) + +* BC break: Remove legacy layout helper + ([#5](https://github.com/graphp/graphviz/pull/5)) + +## 0.1.0 (2014-12-31) + +* First tagged release, split off from [clue/graph](https://github.com/clue/graph) v0.8.0 + ([#1](https://github.com/graphp/graphviz/issues/1)) diff --git a/vendor/graphp/graphviz/LICENSE b/vendor/graphp/graphviz/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..358d929ad811d9dc5a36e74c3b664ed3da1c56eb --- /dev/null +++ b/vendor/graphp/graphviz/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2012+ Christian Lück (Maintainer) +Copyright (c) 2012+ Fhaculty Core Team and our awesome contributors + +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. diff --git a/vendor/graphp/graphviz/README.md b/vendor/graphp/graphviz/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a6d90e417447e6cf257f9d87086eb049a2bf45c1 --- /dev/null +++ b/vendor/graphp/graphviz/README.md @@ -0,0 +1,54 @@ +# graphp/graphviz [![Build Status](https://travis-ci.org/graphp/graphviz.svg?branch=master)](https://travis-ci.org/graphp/graphviz) + +GraphViz graph drawing for mathematical graph/network + +The library supports visualizing graph images, including them into webpages, +opening up images from within CLI applications and exporting them +as PNG, JPEG or SVG file formats (among many others). +Because [graph drawing](http://en.wikipedia.org/wiki/Graph_drawing) is a complex area on its own, +the actual layouting of the graph is left up to the excelent [GraphViz](http://www.graphviz.org/) +"Graph Visualization Software" and we merely provide some convenient APIs to interface with GraphViz. + +> Note: This project is in beta stage! Feel free to report any issues you encounter. + +## Quickstart examples + +Once [installed](#install), let's build and display a sample graph: + +````php +$graph = new Fhaculty\Graph\Graph(); + +$blue = $graph->createVertex('blue'); +$blue->setAttribute('graphviz.color', 'blue'); + +$red = $graph->createVertex('red'); +$red->setAttribtue('graphviz.color', 'red'); + +$edge = $blue->createEdgeTo($red); +$edge->setAttribute('graphviz.color', 'grey'); + +$graphviz = new Graphp\GraphViz\GraphViz(); +$graphviz->display($graph); +```` + +## Install + +The recommended way to install this library is [through composer](http://getcomposer.org). [New to composer?](http://getcomposer.org/doc/00-intro.md) + +```JSON +{ + "require": { + "graphp/graphviz": "~0.2.0" + } +} +``` + +In order to be able to use the [graph drawing feature](#graph-drawing) you'll have to +install GraphViz (`dot` executable). Users of Debian/Ubuntu-based distributions may simply +invoke `sudo apt-get install graphviz`, Windows users have to +[download GraphViZ for Windows](http://www.graphviz.org/Download_windows.php) and remaining +users should install from [GraphViz homepage](http://www.graphviz.org/Download.php). + +## License + +Released under the terms of the permissive [MIT license](http://opensource.org/licenses/MIT). diff --git a/vendor/graphp/graphviz/composer.json b/vendor/graphp/graphviz/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..1225766a6aaf9054e06114798c9e9d347707cc20 --- /dev/null +++ b/vendor/graphp/graphviz/composer.json @@ -0,0 +1,19 @@ +{ + "name": "graphp/graphviz", + "type": "library", + "description": "GraphViz graph drawing for mathematical graph/network", + "keywords": ["GraphViz", "graph drawing", "graph image", "dot output"], + "homepage": "https://github.com/graphp/graphviz", + "license": "MIT", + "autoload": { + "psr-4": {"Graphp\\GraphViz\\": "src/"} + }, + "require": { + "php": ">=5.3.0", + "clue/graph": "~0.9.0|~0.8.0", + "graphp/algorithms": "~0.8.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + } +} diff --git a/vendor/graphp/graphviz/phpunit.xml.dist b/vendor/graphp/graphviz/phpunit.xml.dist new file mode 100644 index 0000000000000000000000000000000000000000..52cb837a93d4d66b37af97e25687e0b88c8ac32d --- /dev/null +++ b/vendor/graphp/graphviz/phpunit.xml.dist @@ -0,0 +1,19 @@ + + + + + + ./tests/ + + + + + ./src + + + \ No newline at end of file diff --git a/vendor/graphp/graphviz/src/Dot.php b/vendor/graphp/graphviz/src/Dot.php new file mode 100644 index 0000000000000000000000000000000000000000..dce2b8b58c32df9e7655149248481b76e26f3329 --- /dev/null +++ b/vendor/graphp/graphviz/src/Dot.php @@ -0,0 +1,26 @@ +graphviz = $graphviz; + } + + public function getOutput(Graph $graph) + { + return $this->graphviz->createScript($graph); + } +} diff --git a/vendor/graphp/graphviz/src/GraphViz.php b/vendor/graphp/graphviz/src/GraphViz.php new file mode 100644 index 0000000000000000000000000000000000000000..152d4735e184f8f12893e9e77744f5bd71f1f1d9 --- /dev/null +++ b/vendor/graphp/graphviz/src/GraphViz.php @@ -0,0 +1,433 @@ +executable = 'dot.exe'; + } + } + + /** + * Change the executable to use. + * + * Usually, your graphviz executables should be located in your $PATH + * environment variable and invoking a mere `dot` is sufficient. If you + * have no access to your $PATH variable, use this method to set the path + * to your graphviz dot executable. + * + * This should contain '.exe' on windows. + * - /full/path/to/bin/dot + * - neato + * - dot.exe + * - c:\path\to\bin\dot.exe + * + * @param string $executable + * @return GraphViz $this (chainable) + */ + public function setExecutable($executable) { + $this->executable = $executable; + + return $this; + } + + /** + * return executable to use + * + * @return string + * @see GraphViz::setExecutable() + */ + public function getExecutable() { + return $this->executable; + } + + /** + * set graph image output format + * + * @param string $format png, svg, ps2, etc. (see 'man dot' for details on parameter '-T') + * @return GraphViz $this (chainable) + */ + public function setFormat($format) + { + $this->format = $format; + + return $this; + } + + /** + * create and display image for this graph + * + * @param Graph $graph graph to display + * @return void + * @uses GraphViz::createImageFile() + */ + public function display(Graph $graph) + { + // echo "Generate picture ..."; + $tmp = $this->createImageFile($graph); + + static $next = 0; + if ($next > microtime(true)) { + // wait some time between calling xdg-open because earlier calls will be ignored otherwise + //echo '[delay flooding xdg-open]' . PHP_EOL; + sleep(self::DELAY_OPEN); + } + + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + // open image in untitled, temporary background shell + exec('start "" ' . escapeshellarg($tmp) . ' >NUL'); + } elseif (strtoupper(PHP_OS) === 'DARWIN') { + // open image in background (redirect stdout to /dev/null, sterr to stdout and run in background) + exec('open ' . escapeshellarg($tmp) . ' > /dev/null 2>&1 &'); + } else { + // open image in background (redirect stdout to /dev/null, sterr to stdout and run in background) + exec('xdg-open ' . escapeshellarg($tmp) . ' > /dev/null 2>&1 &'); + } + + $next = microtime(true) + self::DELAY_OPEN; + // echo "... done\n"; + } + + /** + * create image file data contents for this graph + * + * @param Graph $graph graph to display + * @return string + * @uses GraphViz::createImageFile() + */ + public function createImageData(Graph $graph) + { + $file = $this->createImageFile($graph); + $data = file_get_contents($file); + unlink($file); + + return $data; + } + + /** + * create base64-encoded image src target data to be used for html images + * + * @param Graph $graph graph to display + * @return string + * @uses GraphViz::createImageData() + */ + public function createImageSrc(Graph $graph) + { + $format = ($this->format === 'svg' || $this->format === 'svgz') ? 'svg+xml' : $this->format; + + return 'data:image/' . $format . ';base64,' . base64_encode($this->createImageData($graph)); + } + + /** + * create image html code for this graph + * + * @param Graph $graph graph to display + * @return string + * @uses GraphViz::createImageSrc() + */ + public function createImageHtml(Graph $graph) + { + if ($this->format === 'svg' || $this->format === 'svgz') { + return ''; + } + + return ''; + } + + /** + * create image file for this graph + * + * @param Graph $graph graph to display + * @return string filename + * @throws UnexpectedValueException on error + * @uses GraphViz::createScript() + */ + public function createImageFile(Graph $graph) + { + $script = $this->createScript($graph); + // var_dump($script); + + $tmp = tempnam(sys_get_temp_dir(), 'graphviz'); + if ($tmp === false) { + throw new UnexpectedValueException('Unable to get temporary file name for graphviz script'); + } + + $ret = file_put_contents($tmp, $script, LOCK_EX); + if ($ret === false) { + throw new UnexpectedValueException('Unable to write graphviz script to temporary file'); + } + + $ret = 0; + + $executable = $this->getExecutable(); + system(escapeshellarg($executable) . ' -T ' . escapeshellarg($this->format) . ' ' . escapeshellarg($tmp) . ' -o ' . escapeshellarg($tmp . '.' . $this->format), $ret); + if ($ret !== 0) { + throw new UnexpectedValueException('Unable to invoke "' . $executable .'" to create image file (code ' . $ret . ')'); + } + + unlink($tmp); + + return $tmp . '.' . $this->format; + } + + /** + * create graphviz script representing this graph + * + * @param Graph $graph graph to display + * @return string + * @uses Directed::hasDirected() + * @uses Graph::getVertices() + * @uses Graph::getEdges() + */ + public function createScript(Graph $graph) + { + $alg = new Directed($graph); + $directed = $alg->hasDirected(); + + $script = ($directed ? 'di':'') . 'graph G {' . self::EOL; + + // add global attributes + $globals = array( + 'graph' => 'graphviz.graph.', + 'node' => 'graphviz.node.', + 'edge' => 'graphviz.edge.', + ); + + foreach ($globals as $key => $prefix) { + $bag = new AttributeBagNamespaced($graph, $prefix); + + if ($layout = $bag->getAttributes()) { + $script .= $this->formatIndent . $key . ' ' . $this->escapeAttributes($layout) . self::EOL; + } + } + + $alg = new Groups($graph); + // only append group number to vertex label if there are at least 2 different groups + $showGroups = ($alg->getNumberOfGroups() > 1); + + if ($showGroups) { + $gid = 0; + $indent = str_repeat($this->formatIndent, 2); + // put each group of vertices in a separate subgraph cluster + foreach ($alg->getGroups() as $group) { + $script .= $this->formatIndent . 'subgraph cluster_' . $gid++ . ' {' . self::EOL . + $indent . 'label = ' . $this->escape($group) . self::EOL; + foreach($alg->getVerticesGroup($group)->getMap() as $vid => $vertex) { + $layout = $this->getLayoutVertex($vertex); + + $script .= $indent . $this->escapeId($vid); + if($layout){ + $script .= ' ' . $this->escapeAttributes($layout); + } + $script .= self::EOL; + } + $script .= ' }' . self::EOL; + } + } else { + $alg = new Degree($graph); + + // explicitly add all isolated vertices (vertices with no edges) and vertices with special layout set + // other vertices wil be added automatically due to below edge definitions + foreach ($graph->getVertices()->getMap() as $vid => $vertex){ + $layout = $this->getLayoutVertex($vertex); + + if($layout || $alg->isVertexIsolated($vertex)){ + $script .= $this->formatIndent . $this->escapeId($vid); + if($layout){ + $script .= ' ' . $this->escapeAttributes($layout); + } + $script .= self::EOL; + } + } + } + + $edgeop = $directed ? ' -> ' : ' -- '; + + // add all edges as directed edges + foreach ($graph->getEdges() as $currentEdge) { + $both = $currentEdge->getVertices()->getVector(); + $currentStartVertex = $both[0]; + $currentTargetVertex = $both[1]; + + $script .= $this->formatIndent . $this->escapeId($currentStartVertex->getId()) . $edgeop . $this->escapeId($currentTargetVertex->getId()); + + $layout = $this->getLayoutEdge($currentEdge); + + // this edge also points to the opposite direction => this is actually an undirected edge + if ($directed && $currentEdge->isConnection($currentTargetVertex, $currentStartVertex)) { + $layout['dir'] = 'none'; + } + if ($layout) { + $script .= ' ' . $this->escapeAttributes($layout); + } + + $script .= self::EOL; + } + $script .= '}' . self::EOL; + + return $script; + } + + /** + * escape given id string and wrap in quotes if needed + * + * @param string $id + * @return string + * @link http://graphviz.org/content/dot-language + */ + private function escapeId($id) + { + return self::escape($id); + } + + public static function escape($id) + { + // see raw() + if ($id instanceof stdClass && isset($id->string)) { + return $id->string; + } + // see @link: There is no semantic difference between abc_2 and "abc_2" + // numeric or simple string, no need to quote (only for simplicity) + if (preg_match('/^(?:\-?(?:\.\d+|\d+(?:\.\d+)?))$/i', $id)) { + return $id; + } + + return '"' . str_replace(array('&', '<', '>', '"', "'", '\\', "\n"), array('&', '<', '>', '"', ''', '\\\\', '\\l'), $id) . '"'; + } + + /** + * get escaped attribute string for given array of (unescaped) attributes + * + * @param array $attrs + * @return string + * @uses GraphViz::escapeId() + */ + private function escapeAttributes($attrs) + { + $script = '['; + $first = true; + foreach ($attrs as $name => $value) { + if ($first) { + $first = false; + } else { + $script .= ' '; + } + $script .= $name . '=' . self::escape($value); + } + $script .= ']'; + + return $script; + } + + /** + * create a raw string representation, i.e. do NOT escape the given string when used in graphviz output + * + * @param string $string + * @return StdClass + * @see GraphViz::escape() + */ + public static function raw($string) + { + return (object) array('string' => $string); + } + + protected function getLayoutVertex(Vertex $vertex) + { + $bag = new AttributeBagNamespaced($vertex, 'graphviz.'); + $layout = $bag->getAttributes(); + + $balance = $vertex->getBalance(); + if($balance !== NULL){ + if($balance > 0){ + $balance = '+' . $balance; + } + if(!isset($layout['label'])){ + $layout['label'] = $vertex->getId(); + } + $layout['label'] .= ' (' . $balance . ')'; + } + + return $layout; + } + + protected function getLayoutEdge(Edge $edge) + { + $bag = new AttributeBagNamespaced($edge, 'graphviz.'); + $layout = $bag->getAttributes(); + + // use flow/capacity/weight as edge label + $label = NULL; + + $flow = $edge->getFlow(); + $capacity = $edge->getCapacity(); + // flow is set + if ($flow !== NULL) { + // NULL capacity = infinite capacity + $label = $flow . '/' . ($capacity === NULL ? '∞' : $capacity); + // capacity set, but not flow (assume zero flow) + } elseif ($capacity !== NULL) { + $label = '0/' . $capacity; + } + + $weight = $edge->getWeight(); + // weight is set + if ($weight !== NULL) { + if ($label === NULL) { + $label = $weight; + } else { + $label .= '/' . $weight; + } + } + + if ($label !== NULL) { + if (isset($layout['label'])) { + $layout['label'] .= ' ' . $label; + } else { + $layout['label'] = $label; + } + } + return $layout; + } +} diff --git a/vendor/graphp/graphviz/src/Image.php b/vendor/graphp/graphviz/src/Image.php new file mode 100644 index 0000000000000000000000000000000000000000..9b1aaca7d533bdea102377183cacee90b45dca45 --- /dev/null +++ b/vendor/graphp/graphviz/src/Image.php @@ -0,0 +1,40 @@ +setFormat('png'); + } + + $this->graphviz = $graphviz; + } + + public function getOutput(Graph $graph) + { + return $this->graphviz->createImageData($graph); + } + + /** + * set the image output format to use + * + * @param string $type png, svg + * @return self $this (chainable) + * @uses GraphViz::setFormat() + */ + public function setFormat($type) + { + $this->graphviz->setFormat($type); + return $this; + } +} diff --git a/vendor/graphp/graphviz/tests/GraphVizTest.php b/vendor/graphp/graphviz/tests/GraphVizTest.php new file mode 100644 index 0000000000000000000000000000000000000000..3836cad754d99a1967e08fb9a23dd5a37f850808 --- /dev/null +++ b/vendor/graphp/graphviz/tests/GraphVizTest.php @@ -0,0 +1,200 @@ +graphViz = new GraphViz(); + } + + public function testGraphEmpty() + { + $graph = new Graph(); + + $expected = <<assertEquals($expected, $this->graphViz->createScript($graph)); + } + + public function testGraphIsolatedVertices() + { + $graph = new Graph(); + $graph->createVertex('a'); + $graph->createVertex('b'); + + $expected = <<assertEquals($expected, $this->graphViz->createScript($graph)); + } + + public function testEscaping() + { + $graph = new Graph(); + $graph->createVertex('a'); + $graph->createVertex('b¹²³ is; ok\\ay, "right"?'); + $graph->createVertex(3); + $graph->createVertex(4)->setAttribute('graphviz.label', 'normal'); + $graph->createVertex(5)->setAttribute('graphviz.label', GraphViz::raw('')); + + + $expected = <<] +} + +VIZ; + + $this->assertEquals($expected, $this->graphViz->createScript($graph)); + } + + public function testGraphDirected() + { + $graph = new Graph(); + $graph->createVertex('a')->createEdgeTo($graph->createVertex('b')); + + $expected = << "b" +} + +VIZ; + + $this->assertEquals($expected, $this->graphViz->createScript($graph)); + } + + public function testGraphMixed() + { + // a -> b -- c + $graph = new Graph(); + $graph->createVertex('a')->createEdgeTo($graph->createVertex('b')); + $graph->createVertex('c')->createEdge($graph->getVertex('b')); + + $expected = << "b" + "c" -> "b" [dir="none"] +} + +VIZ; + + $this->assertEquals($expected, $this->graphViz->createScript($graph)); + } + + + public function testGraphUndirectedWithIsolatedVerticesFirst() + { + // a -- b -- c d + $graph = new Graph(); + $graph->createVertices(array('a', 'b', 'c', 'd')); + $graph->getVertex('a')->createEdge($graph->getVertex('b')); + $graph->getVertex('b')->createEdge($graph->getVertex('c')); + + $expected = <<assertEquals($expected, $this->graphViz->createScript($graph)); + } + + public function testVertexLabels() + { + $graph = new Graph(); + $graph->createVertex('a')->setBalance(1); + $graph->createVertex('b')->setBalance(0); + $graph->createVertex('c')->setBalance(-1); + $graph->createVertex('d')->setAttribute('graphviz.label', 'test'); + $graph->createVertex('e')->setBalance(2)->setAttribute('graphviz.label', 'unnamed'); + + $expected = <<assertEquals($expected, $this->graphViz->createScript($graph)); + } + + public function testEdgeLayoutAtributes() + { + $graph = new Graph(); + $graph->createVertex('1a')->createEdge($graph->createVertex('1b')); + $graph->createVertex('2a')->createEdge($graph->createVertex('2b'))->setAttribute('graphviz.numeric', 20); + $graph->createVertex('3a')->createEdge($graph->createVertex('3b'))->setAttribute('graphviz.textual', "forty"); + $graph->createVertex('4a')->createEdge($graph->createVertex('4b'))->getAttributeBag()->setAttributes(array('graphviz.1' => 1, 'graphviz.2' => 2)); + $graph->createVertex('5a')->createEdge($graph->createVertex('5b'))->getAttributeBag()->setAttributes(array('graphviz.a' => 'b', 'graphviz.c' => 'd')); + + $expected = <<assertEquals($expected, $this->graphViz->createScript($graph)); + } + + public function testEdgeLabels() + { + $graph = new Graph(); + $graph->createVertex('1a')->createEdge($graph->createVertex('1b')); + $graph->createVertex('2a')->createEdge($graph->createVertex('2b'))->setWeight(20); + $graph->createVertex('3a')->createEdge($graph->createVertex('3b'))->setCapacity(30); + $graph->createVertex('4a')->createEdge($graph->createVertex('4b'))->setFlow(40); + $graph->createVertex('5a')->createEdge($graph->createVertex('5b'))->setFlow(50)->setCapacity(60); + $graph->createVertex('6a')->createEdge($graph->createVertex('6b'))->setFlow(60)->setCapacity(70)->setWeight(80); + $graph->createVertex('7a')->createEdge($graph->createVertex('7b'))->setFlow(70)->setAttribute('graphviz.label', 'prefixed'); + + $expected = <<assertEquals($expected, $this->graphViz->createScript($graph)); + } +} diff --git a/vendor/graphp/graphviz/tests/bootstrap.php b/vendor/graphp/graphviz/tests/bootstrap.php new file mode 100644 index 0000000000000000000000000000000000000000..0b2ea18b620c3b01d8676cf626d52420bf1789a0 --- /dev/null +++ b/vendor/graphp/graphviz/tests/bootstrap.php @@ -0,0 +1,7 @@ +