Actualización

This commit is contained in:
Xes
2025-04-10 12:24:57 +02:00
parent 8969cc929d
commit 45420b6f0d
39760 changed files with 4303286 additions and 0 deletions

2
vendor/graphp/algorithms/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/vendor
/composer.lock

29
vendor/graphp/algorithms/.travis.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
language: php
php:
# - 5.3 # requires old distro, see below
- 5.4
- 5.5
- 5.6
- 7.0
- 7.1
- 7.2
- hhvm # ignore errors, see below
# lock distro so future defaults will not break the build
dist: trusty
matrix:
include:
- php: 5.3
dist: precise
allow_failures:
- php: hhvm
sudo: false
install:
- composer install --no-interaction
script:
- vendor/bin/phpunit --coverage-text

37
vendor/graphp/algorithms/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,37 @@
# Changelog
## 0.8.2 (2020-02-20)
* Feature: Add max depth parameter to breadth first search.
(#27 by @phyrwork)
* Feature: Replace recursive topological sort with iterative algorithm.
(#25 by @phyrwork)
* Fix: Fix option to merge parallel edges when creating residual graph.
(#39 by @clue)
* Fix: Fix setting upper limit for TSP bruteforce via MST algorithm.
(#36 by @clue)
* Minor code style improvements to make PHPStan happy,
clean up dead code for depth first search and
automated native_function_invocation fixes.
(#35 and #40 by @clue and #37 by @draco2003)
* Improve test suite to support PHPUnit 6 and PHPUnit 5 and
support running on legacy PHP 5.3 through PHP 7.2 and HHVM.
(#32 by @clue)
## 0.8.1 (2015-03-08)
* Support graph v0.9 (while keeping BC)
([#16](https://github.com/graphp/algorithms/pull/16))
* Deprecate internal algorithm base classes
([#15](https://github.com/graphp/algorithms/pull/15))
## 0.8.0 (2015-02-25)
* First tagged release, split off from [clue/graph](https://github.com/clue/graph) v0.8.0
([#1](https://github.com/graphp/algorithms/issues/1))

21
vendor/graphp/algorithms/LICENSE vendored Normal file
View File

@@ -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.

42
vendor/graphp/algorithms/README.md vendored Normal file
View File

@@ -0,0 +1,42 @@
# 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](https://getcomposer.org).
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
This will install the latest supported version:
```bash
$ composer require graphp/algorithms:^0.8.2
```
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
This project aims to run on any platform and thus does not require any PHP
extensions and supports running on legacy PHP 5.3 through current PHP 7+ and
HHVM.
It's *highly recommended to use PHP 7+* for this project.
## Tests
To run the test suite, you first need to clone this repo and then install all
dependencies [through Composer](https://getcomposer.org):
```bash
$ composer install
```
To run the test suite, go to the project root and run:
```bash
$ php vendor/bin/phpunit
```
## License
Released under the terms of the permissive [MIT license](http://opensource.org/licenses/MIT).

23
vendor/graphp/algorithms/composer.json vendored Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "graphp/algorithms",
"description": "Common mathematical graph algorithms implemented in PHP",
"keywords": ["Graph algorithms", "shortest path", "dijkstra", "moore-bellman-ford", "minimum spanning tree", "kruskal", "prim"],
"homepage": "https://github.com/graphp/algorithms",
"license": "MIT",
"authors": [
{
"name": "Christian Lück",
"email": "christian@clue.engineering"
}
],
"require": {
"php": ">=5.3",
"clue/graph": "~0.9.0|~0.8.0"
},
"require-dev": {
"phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
},
"autoload": {
"psr-4": {"Graphp\\Algorithms\\": "src/"}
}
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="tests/bootstrap.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
>
<testsuites>
<testsuite name="Algorithm Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./src/</directory>
</whitelist>
</filter>
</phpunit>

8
vendor/graphp/algorithms/src/Base.php vendored Normal file
View File

@@ -0,0 +1,8 @@
<?php
namespace Graphp\Algorithms;
/**
* @deprecated
*/
abstract class Base{ }

View File

@@ -0,0 +1,33 @@
<?php
namespace Graphp\Algorithms;
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Set\DualAggregate;
use Fhaculty\Graph\Walk;
/**
* Abstract base class for algorithms that operate on a given Set instance
*
* @see Set
* @deprecated
*/
abstract class BaseDual extends Base
{
/**
* Set to operate on
*
* @var DualAggregate
*/
protected $set;
/**
* instantiate new algorithm
*
* @param Graph|Walk|DualAggregate $graphOrWalk either the Graph or Walk to operate on (or the common base class Set)
*/
public function __construct(DualAggregate $graphOrWalk)
{
$this->set = $graphOrWalk;
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Graphp\Algorithms;
use Fhaculty\Graph\Graph;
/**
* Abstract base class for algorithms that operate on a given Graph instance
*
* @deprecated
*/
abstract class BaseGraph extends Base
{
/**
* Graph to operate on
*
* @var Graph
*/
protected $graph;
/**
* instantiate new algorithm
*
* @param Graph $graph Graph to operate on
*/
public function __construct(Graph $graph)
{
$this->graph = $graph;
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Graphp\Algorithms;
use Fhaculty\Graph\Vertex;
/**
* Abstract base class for algorithms that operate on a given Vertex instance
*
* @deprecated
*/
abstract class BaseVertex extends Base
{
/**
* Vertex to operate on
*
* @var Vertex
*/
protected $vertex;
/**
* instantiate new algorithm
*
* @param Vertex $vertex Vertex to operate on
*/
public function __construct(Vertex $vertex)
{
$this->vertex = $vertex;
}
}

View File

@@ -0,0 +1,120 @@
<?php
namespace Graphp\Algorithms;
use Fhaculty\Graph\Exception\UnexpectedValueException;
use Fhaculty\Graph\Graph;
class Bipartit extends BaseGraph
{
/**
* check whether this graph is bipartit
*
* @return bool
* @uses AlgorithmBipartit::getColors()
*/
public function isBipartit()
{
try {
$this->getColors();
return true;
} catch (UnexpectedValueException $ignore) { }
return false;
}
/**
* checks whether the input graph's vertex groups are a valid bipartition
*
* @return bool
* @uses AlgorithmGroups::isBipartit()
*/
public function isBipartitGroups()
{
$alg = new Groups($this->graph);
return $alg->isBipartit();
}
/**
* get map of vertex ID to vertex color
*
* @return int[]
* @throws UnexpectedValueException if graph is not bipartit
* @uses AlgorithmBipartit::checkVertex() for every vertex not already colored
*/
public function getColors()
{
$colors = array();
// get color for each vertex
foreach ($this->graph->getVertices()->getMap() as $vid => $startVertex) {
if (!isset($colors[$vid])) {
$queue = array($startVertex);
// initialize each components color
$colors[$vid] = 0;
// breadth search all vertices in same component
do {
// next vertex in color
$vertex = \array_shift($queue);
$color = $colors[$vertex->getId()];
$nextColor = 1-$color;
// scan all vertices connected to this vertex
foreach ($vertex->getVerticesEdge()->getMap() as $vid => $nextVertex) {
// color unknown, so expect next color for this vertex
if (!isset($colors[$vid])) {
$colors[$vid] = $nextColor;
$queue[] = $nextVertex;
// color is known but differs => can not be bipartit
} elseif ($colors[$vid] !== $nextColor) {
throw new UnexpectedValueException('Graph is not bipartit');
}
}
} while ($queue);
}
}
return $colors;
}
/**
* get groups of vertices per color
*
* @return array[] array of arrays of vertices
*/
public function getColorVertices()
{
$colors = $this->getColors();
$ret = array(0 => array(), 1 => array());
foreach ($this->graph->getVertices()->getMap() as $vid => $vertex) {
$ret[$colors[$vid]][$vid] = $vertex;
}
return $ret;
}
/**
* create new graph with valid groups set according to bipartition colors
*
* @return Graph
* @throws UnexpectedValueException if graph is not bipartit
* @uses AlgorithmBipartit::getColors()
* @uses Graph::createGraphClone()
* @uses Vertex::setGroup()
*/
public function createGraphGroups()
{
$colors = $this->getColors();
$graph = $this->graph->createGraphClone();
foreach ($graph->getVertices()->getMap() as $vid => $vertex) {
$vertex->setGroup($colors[$vid]);
}
return $graph;
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace Graphp\Algorithms;
/**
* Basic algorithms for working with complete graphs
*
* A complete graph is a graph in which every pair of vertices is connected
* by an edge.
*
* @link http://en.wikipedia.org/wiki/Complete_graph
* @link http://mathworld.wolfram.com/CompleteGraph.html
*/
class Complete extends BaseGraph
{
/**
* checks whether this graph is complete (every vertex has an edge to any other vertex)
*
* @return bool
* @uses Graph::getVertices()
* @uses Vertex::hasEdgeTo()
*/
public function isComplete()
{
// copy of array (separate iterator but same vertices)
$c = $vertices = $this->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;
}
}

View File

@@ -0,0 +1,149 @@
<?php
namespace Graphp\Algorithms;
use Fhaculty\Graph\Exception\InvalidArgumentException;
use Fhaculty\Graph\Exception\UnderflowException;
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Vertex;
use Graphp\Algorithms\Search\BreadthFirst as SearchBreadthFirst;
/**
* Algorithm for working with connected components
*
* @link http://en.wikipedia.org/wiki/Connected_component_%28graph_theory%29
* @link http://mathworld.wolfram.com/ConnectedGraph.html
* @link http://math.stackexchange.com/questions/50551/is-the-empty-graph-connected
*/
class ConnectedComponents extends BaseGraph
{
/**
* create subgraph with all vertices connected to given vertex (i.e. the connected component of ths given vertex)
*
* @param Vertex $vertex
* @return Graph
* @throws InvalidArgumentException if given vertex is not from same graph
* @uses AlgorithmSearchBreadthFirst::getVertices()
* @uses Graph::createGraphCloneVertices()
*/
public function createGraphComponentVertex(Vertex $vertex)
{
if ($vertex->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
* <pre>($this->getNumberOfComponents() === 1)</pre>. However, using this
* method is faster than calling getNumberOfComponents(), as it only has to
* count all vertices in one component to see if the graph consists of only
* a single component.
*
* As such, a null Graph (a Graph with no vertices) is not considered
* connected here.
*
* @return bool
* @see self::getNumberOfComponents()
*/
public function isSingle()
{
try {
$vertex = $this->graph->getVertices()->getVertexFirst();
} catch (UnderflowException $e) {
// no first vertex => empty graph => has zero components
return false;
}
$alg = $this->createSearch($vertex);
return (\count($this->graph->getVertices()) === \count($alg->getVertices()));
}
/**
* count number of connected components
*
* A null Graph (a Graph with no vertices) will return 0 components.
*
* @return int number of components
* @uses Graph::getVertices()
* @uses AlgorithmSearchBreadthFirst::getVertices()
*/
public function getNumberOfComponents()
{
$visitedVertices = array();
$components = 0;
// for each vertices
foreach ($this->graph->getVertices()->getMap() as $vid => $vertex) {
// did I visit this vertex before?
if (!isset($visitedVertices[$vid])) {
// get all vertices of this component
$newVertices = $this->createSearch($vertex)->getVertices()->getIds();
++$components;
// mark the vertices of this component as visited
foreach ($newVertices as $vid) {
$visitedVertices[$vid] = true;
}
}
}
// return number of components
return $components;
}
/**
* separate input graph into separate independant and unconnected graphs
*
* @return Graph[]
* @uses Graph::getVertices()
* @uses AlgorithmSearchBreadthFirst::getVertices()
*/
public function createGraphsComponents()
{
$visitedVertices = array();
$graphs = array();
// for each vertices
foreach ($this->graph->getVertices()->getMap() as $vid => $vertex) {
// did I visit this vertex before?
if (!isset($visitedVertices[$vid])) {
$alg = $this->createSearch($vertex);
// get all vertices of this component
$newVertices = $alg->getVertices();
// mark the vertices of this component as visited
foreach ($newVertices->getIds() as $vid) {
$visitedVertices[$vid] = true;
}
$graphs[] = $this->graph->createGraphCloneVertices($newVertices);
}
}
return $graphs;
}
}

218
vendor/graphp/algorithms/src/Degree.php vendored Normal file
View File

@@ -0,0 +1,218 @@
<?php
namespace Graphp\Algorithms;
use Fhaculty\Graph\Exception\UnderflowException;
use Fhaculty\Graph\Exception\UnexpectedValueException;
use Fhaculty\Graph\Vertex;
/**
* Basic algorithms for working with the degrees of Graphs.
*
* The degree (or valency) of a Vertex of a Graph is the number of Edges
* incident to the Vertex, with Loops counted twice.
*
* @link http://en.wikipedia.org/wiki/Degree_%28graph_theory%29
* @link http://en.wikipedia.org/wiki/Regular_graph
*/
class Degree extends BaseGraph
{
/**
* get degree for k-regular-graph (only if each vertex has the same degree)
*
* @return int
* @throws UnderflowException if graph is empty
* @throws UnexpectedValueException if graph is not regular (i.e. vertex degrees are not equal)
* @uses self::getDegreeVertex()
* @see self::isRegular()
*/
public function getDegree()
{
// get initial degree of any start vertex to compare others to
$degree = $this->getDegreeVertex($this->graph->getVertices()->getVertexFirst());
foreach ($this->graph->getVertices() as $vertex) {
assert($vertex instanceof Vertex);
$i = $this->getDegreeVertex($vertex);
if ($i !== $degree) {
throw new UnexpectedValueException('Graph is not k-regular (vertex degrees differ)');
}
}
return $degree;
}
/**
* get minimum degree of vertices
*
* @return int
* @throws UnderflowException if graph is empty
* @uses Vertices::getVertexOrder()
* @uses self::getDegreeVertex()
*/
public function getDegreeMin()
{
return $this->getDegreeVertex($this->graph->getVertices()->getVertexOrder(array($this, 'getDegreeVertex')));
}
/**
* get maximum degree of vertices
*
* @return int
* @throws UnderflowException if graph is empty
* @uses Vertices::getVertexOrder()
* @uses self::getDegreeVertex()
*/
public function getDegreeMax()
{
return $this->getDegreeVertex($this->graph->getVertices()->getVertexOrder(array($this, 'getDegreeVertex'), true));
}
/**
* checks whether this graph is regular, i.e. each vertex has the same indegree/outdegree
*
* @return bool
* @uses self::getDegree()
*/
public function isRegular()
{
// an empty graph is considered regular
if ($this->graph->getVertices()->isEmpty()) {
return true;
}
try {
$this->getDegree();
return true;
} catch (UnexpectedValueException $ignore) { }
return false;
}
/**
* checks whether the indegree of every vertex equals its outdegree
*
* @return bool
* @uses self::getDegreeInVertex()
* @uses self::getDegreeOutVertex()
*/
public function isBalanced()
{
foreach ($this->graph->getVertices() as $vertex) {
if ($this->getDegreeInVertex($vertex) !== $this->getDegreeOutVertex($vertex)) {
return false;
}
}
return true;
}
/**
* checks whether this vertex is a source, i.e. its indegree is zero
*
* @param Vertex $vertex
* @return bool
* @uses Edge::hasVertexTarget()
* @see self::getDegreeInVertex()
*/
public function isVertexSource(Vertex $vertex)
{
foreach ($vertex->getEdges() as $edge) {
if ($edge->hasVertexTarget($vertex)) {
return false;
}
}
// reach this point: no edge to this vertex
return true;
}
/**
* checks whether this vertex is a sink, i.e. its outdegree is zero
*
* @param Vertex $vertex
* @return bool
* @uses Edge::hasVertexStart()
* @see self::getDegreeOutVertex()
*/
public function isVertexSink(Vertex $vertex)
{
foreach ($vertex->getEdges() as $edge) {
if ($edge->hasVertexStart($vertex)) {
return false;
}
}
// reach this point: no edge away from this vertex
return true;
}
/**
* get degree of this vertex (total number of edges)
*
* vertex degree counts the total number of edges attached to this vertex
* regardless of whether they're directed or not. loop edges are counted
* twice as both start and end form a 'line' to the same vertex.
*
* @param Vertex $vertex
* @return int
* @see self::getDegreeInVertex()
* @see self::getDegreeOutVertex()
*/
public function getDegreeVertex(Vertex $vertex)
{
return \count($vertex->getEdges());
}
/**
* check whether this vertex is isolated (i.e. has no edges attached)
*
* @param Vertex $vertex
* @return bool
*/
public function isVertexIsolated(Vertex $vertex)
{
return $vertex->getEdges()->isEmpty();
}
/**
* get indegree of this vertex (number of edges TO this vertex)
*
* @param Vertex $vertex
* @return int
* @uses Edge::hasVertexTarget()
* @see self::getDegreeVertex()
*/
public function getDegreeInVertex($vertex)
{
$n = 0;
foreach ($vertex->getEdges() as $edge) {
if ($edge->hasVertexTarget($vertex)) {
++$n;
}
}
return $n;
}
/**
* get outdegree of this vertex (number of edges FROM this vertex TO other vertices)
*
* @param Vertex $vertex
* @return int
* @uses Edge::hasVertexStart()
* @see self::getDegreeVertex()
*/
public function getDegreeOutVertex(Vertex $vertex)
{
$n = 0;
foreach ($vertex->getEdges() as $edge) {
if ($edge->hasVertexStart($vertex)) {
++$n;
}
}
return $n;
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace Graphp\Algorithms;
use Fhaculty\Graph\Exception\NegativeCycleException;
use Fhaculty\Graph\Exception\UnderflowException;
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Walk;
use Graphp\Algorithms\ShortestPath\MooreBellmanFord as SpMooreBellmanFord;
class DetectNegativeCycle extends BaseGraph
{
/**
* check if the input graph has any negative cycles
*
* @return bool
* @uses AlgorithmDetectNegativeCycle::getCycleNegative()
*/
public function hasCycleNegative()
{
try {
$this->getCycleNegative();
// cycle was found => okay
return true;
// no cycle found
} catch (UnderflowException $ignore) {}
return false;
}
/**
* Searches all vertices for the first negative cycle
*
* @return Walk
* @throws UnderflowException if there's no negative cycle
* @uses AlgorithmSpMooreBellmanFord::getVertices()
*/
public function getCycleNegative()
{
// remember vertices already visited, as they can not lead to a new cycle
$verticesVisited = array();
// check for all vertices
foreach ($this->graph->getVertices()->getMap() as $vid => $vertex) {
// skip vertices already visited
if (!isset($verticesVisited[$vid])) {
// start MBF algorithm on current vertex
$alg = new SpMooreBellmanFord($vertex);
try {
// try to get all connected vertices (or throw new cycle)
foreach ($alg->getVertices()->getIds() as $vid) {
// getting connected vertices succeeded, so skip over all of them
$verticesVisited[$vid] = true;
// no cycle found, check next vertex...
}
// yey, negative cycle encountered => return
} catch (NegativeCycleException $e) {
return $e->getCycle();
}
}
// no more vertices to check => abort
}
throw new UnderflowException('No negative cycle found');
}
/**
* create new graph clone with only vertices and edges in negative cycle
*
* @return Graph
* @throws UnderflowException if there's no negative cycle
* @uses AlgorithmDetectNegativeCycle::getCycleNegative()
* @uses Walk::createGraph()
*/
public function createGraph()
{
return $this->getCycleNegative()->createGraph();
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Graphp\Algorithms;
use Fhaculty\Graph\Edge\Directed as EdgeDirected;
use Fhaculty\Graph\Edge\Undirected as EdgeUndirected;
/**
* Basic algorithms for working with the undirected or directed Graphs (digraphs) / Walks.
*
* @link http://en.wikipedia.org/wiki/Glossary_of_graph_theory#Direction
* @link http://en.wikipedia.org/wiki/Digraph_%28mathematics%29
*/
class Directed extends BaseDual
{
/**
* checks whether the graph has any directed edges
*
* This method is intentionally not named "isDirected()" (aka digraph),
* because that might be misleading in regards to empty and/or mixed graphs.
*
* @return bool
*/
public function hasDirected()
{
foreach ($this->set->getEdges() as $edge) {
if ($edge instanceof EdgeDirected) {
return true;
}
}
return false;
}
/**
* checks whether the graph has any undirected edges
*
* This method is intentionally not named "isUndirected()",
* because that might be misleading in regards to empty and/or mixed graphs.
*
* @return bool
*/
public function hasUndirected()
{
foreach ($this->set->getEdges() as $edge) {
if ($edge instanceof EdgeUndirected) {
return true;
}
}
return false;
}
/**
* checks whether this is a mixed graph (contains both directed and undirected edges)
*
* @return bool
* @uses self::hasDirected()
* @uses self::hasUndirected()
*/
public function isMixed()
{
return ($this->hasDirected() && $this->hasUndirected());
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Graphp\Algorithms;
class Eulerian extends BaseGraph
{
/**
* check whether this graph has an eulerian cycle
*
* @return bool
* @uses ConnectedComponents::isSingle()
* @uses Degree::getDegreeVertex()
* @todo isolated vertices should be ignored
* @todo definition is only valid for undirected graphs
*/
public function hasCycle()
{
$components = new ConnectedComponents($this->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;
}
}

115
vendor/graphp/algorithms/src/Flow.php vendored Normal file
View File

@@ -0,0 +1,115 @@
<?php
namespace Graphp\Algorithms;
use Fhaculty\Graph\Edge\Directed as EdgeDirected;
use Fhaculty\Graph\Exception\UnexpectedValueException;
use Fhaculty\Graph\Vertex;
/**
* Basic algorithms for working with flow graphs
*
* A flow network (also known as a transportation network) is a directed graph
* where each edge has a capacity and each edge receives a flow.
*
* @link http://en.wikipedia.org/wiki/Flow_network
* @see Algorithm\Balance
*/
class Flow extends BaseDual
{
/**
* check if this graph has any flow set (any edge has a non-NULL flow)
*
* @return bool
* @uses Edge::getFlow()
*/
public function hasFlow()
{
foreach ($this->set->getEdges() as $edge) {
if ($edge->getFlow() !== NULL) {
return true;
}
}
return false;
}
/**
* Calculates the flow for this Vertex: sum(outflow) - sum(inflow)
*
* Usually, vertices should have a resulting flow of 0: The sum of flows
* entering a vertex must equal the sum of flows leaving a vertex. If the
* resulting flow is < 0, this vertex is considered a sink (i.e. there's
* more flow into this vertex). If the resulting flow is > 0, this vertex
* is considered a "source" (i.e. there's more flow leaving this vertex).
*
* @param Vertex $vertex
* @return float
* @throws UnexpectedValueException if they are undirected edges
* @see Vertex::getBalance()
* @uses Vertex::getEdges()
* @uses Edge::getFlow()
*/
public function getFlowVertex(Vertex $vertex)
{
$sumOfFlow = 0;
foreach ($vertex->getEdges() as $edge) {
if (!($edge instanceof EdgeDirected)) {
throw new UnexpectedValueException("TODO: undirected edges not suported yet");
}
// edge is an outgoing edge of this vertex
if ($edge->hasVertexStart($vertex)) {
// flowing out (flow is "pointing away")
$sumOfFlow += $edge->getFlow();
// this is an ingoing edge
} else {
// flowing in
$sumOfFlow -= $edge->getFlow();
}
}
return $sumOfFlow;
}
public function getBalance()
{
$balance = 0;
// Sum for all vertices of value
foreach ($this->set->getVertices() as $vertex) {
$balance += $vertex->getBalance();
}
return $balance;
}
/**
* check if the current flow is balanced (aka "balanced flow" or "b-flow")
*
* a flow is considered balanced if each edge's current flow does not exceed its
* maximum capacity (which is always guaranteed due to the implementation
* of Edge::setFlow()) and each vertices' flow (i.e. outflow-inflow) equals
* its balance.
*
* checking whether the FLOW is balanced is not to be confused with checking
* whether the GRAPH is balanced (see Graph::isBalanced() instead)
*
* @return bool
* @see Degree::isBalanced() if you merely want to check indegree=outdegree
* @uses self::getFlowVertex()
* @uses Vertex::getBalance()
*/
public function isBalancedFlow()
{
// no need to check for each edge: flow <= capacity (setters already check that)
// check for each vertex: outflow-inflow = balance
foreach ($this->set->getVertices() as $vertex) {
if ($this->getFlowVertex($vertex) !== $vertex->getBalance()) {
return false;
}
}
return true;
}
}

87
vendor/graphp/algorithms/src/Groups.php vendored Normal file
View File

@@ -0,0 +1,87 @@
<?php
namespace Graphp\Algorithms;
use Fhaculty\Graph\Set\Vertices;
use Fhaculty\Graph\Vertex;
class Groups extends BaseGraph
{
/**
* count total number of different groups assigned to vertices
*
* @return int
* @uses AlgorithmGroups::getGroups()
*/
public function getNumberOfGroups()
{
return \count($this->getGroups());
}
/**
* checks whether the input graph's vertex groups are a valid bipartition
*
* @return bool
* @see AlgorithmBipartit() if you do NOT want to take vertex groups into consideration
* @uses AlgorithmGroups::getNumberOfGroups()
* @uses Vertex::getGroup()
*/
public function isBipartit()
{
// graph has to contain exactly 2 groups
if ($this->getNumberOfGroups() !== 2) {
return false;
}
// for each vertex
foreach ($this->graph->getVertices() as $vertex) {
// get current group
$group = $vertex->getGroup();
// for every neighbor vertex
foreach ($vertex->getVerticesEdge() as $vertexNeighbor) {
// vertex group must be other group
if ($vertexNeighbor->getGroup() === $group) {
return false;
}
}
}
return true;
}
/**
* get vector of all group numbers
*
* @return int[]
* @uses Vertex::getGroup()
*/
public function getGroups()
{
$groups = array();
foreach ($this->graph->getVertices() as $vertex) {
assert($vertex instanceof Vertex);
$groups[$vertex->getGroup()] = true;
}
return \array_keys($groups);
}
/**
* get set of all Vertices in the given group
*
* @param int $group
* @return Vertices
* @uses Vertex::getGroup()
*/
public function getVerticesGroup($group)
{
$vertices = array();
foreach ($this->graph->getVertices()->getMap() as $vid => $vertex) {
if ($vertex->getGroup() === $group) {
$vertices[$vid] = $vertex;
}
}
return new Vertices($vertices);
}
}

50
vendor/graphp/algorithms/src/Loop.php vendored Normal file
View File

@@ -0,0 +1,50 @@
<?php
namespace Graphp\Algorithms;
use Fhaculty\Graph\Vertex;
/**
* Basic algorithms for working with loop edges
*
* A loop (also called a self-loop or a "buckle") is an edge that connects a
* Vertex to itself. A simple graph contains no loops.
*
* @link http://en.wikipedia.org/wiki/Loop_%28graph_theory%29
*/
class Loop extends BaseDual
{
/**
* checks whether this graph has any loops (edges from vertex to itself)
*
* @return bool
* @uses Edge::isLoop()
*/
public function hasLoop()
{
foreach ($this->set->getEdges() as $edge) {
if ($edge->isLoop()) {
return true;
}
}
return false;
}
/**
* checks whether this vertex has a loop (edge to itself)
*
* @return bool
* @uses Edge::isLoop()
*/
public function hasLoopVertex(Vertex $vertex)
{
foreach ($vertex->getEdges() as $edge) {
if ($edge->isLoop()) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,121 @@
<?php
namespace Graphp\Algorithms\MaxFlow;
use Fhaculty\Graph\Edge\Directed as EdgeDirected;
use Fhaculty\Graph\Exception\InvalidArgumentException;
use Fhaculty\Graph\Exception\OutOfBoundsException;
use Fhaculty\Graph\Exception\UnderflowException;
use Fhaculty\Graph\Exception\UnexpectedValueException;
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Set\Edges;
use Fhaculty\Graph\Vertex;
use Graphp\Algorithms\Base;
use Graphp\Algorithms\ResidualGraph;
use Graphp\Algorithms\ShortestPath\BreadthFirst;
class EdmondsKarp extends Base
{
/**
* @var Vertex
*/
private $startVertex;
/**
* @var Vertex
*/
private $destinationVertex;
/**
* @param Vertex $startVertex the vertex where the flow search starts
* @param Vertex $destinationVertex the vertex where the flow search ends (destination)
*/
public function __construct(Vertex $startVertex, Vertex $destinationVertex)
{
if ($startVertex === $destinationVertex) {
throw new InvalidArgumentException('Start and destination must not be the same vertex');
}
if ($startVertex->getGraph() !== $destinationVertex->getGraph()) {
throw new InvalidArgumentException('Start and target vertex have to be in the same graph instance');
}
$this->startVertex = $startVertex;
$this->destinationVertex = $destinationVertex;
}
/**
* Returns max flow graph
*
* @return Graph
* @throws UnexpectedValueException for undirected edges
*/
public function createGraph()
{
$graphResult = $this->startVertex->getGraph()->createGraphClone();
// initialize null flow and check edges
foreach ($graphResult->getEdges() as $edge) {
if (!($edge instanceof EdgeDirected)) {
throw new UnexpectedValueException('Undirected edges not supported for edmonds karp');
}
$edge->setFlow(0);
}
$idA = $this->startVertex->getId();
$idB = $this->destinationVertex->getId();
do {
// Generate new residual graph and repeat
$residualAlgorithm = new ResidualGraph($graphResult);
$graphResidual = $residualAlgorithm->createGraph();
// 1. Search _shortest_ (number of hops and cheapest) path from s -> t
$alg = new BreadthFirst($graphResidual->getVertex($idA));
try {
$pathFlow = $alg->getWalkTo($graphResidual->getVertex($idB));
} catch (OutOfBoundsException $e) {
$pathFlow = NULL;
}
// If path exists add the new flow to graph
if ($pathFlow) {
// 2. get max flow from path
$maxFlowValue = $pathFlow->getEdges()->getEdgeOrder(Edges::ORDER_CAPACITY)->getCapacity();
// 3. add flow to path
foreach ($pathFlow->getEdges() as $edge) {
// try to look for forward edge to increase flow
try {
$originalEdge = $graphResult->getEdgeClone($edge);
$originalEdge->setFlow($originalEdge->getFlow() + $maxFlowValue);
// forward edge not found, look for back edge to decrease flow
} catch (UnderflowException $e) {
$originalEdge = $graphResult->getEdgeCloneInverted($edge);
$originalEdge->setFlow($originalEdge->getFlow() - $maxFlowValue);
}
}
}
// repeat while we still finds paths with residual capacity to add flow to
} while ($pathFlow);
return $graphResult;
}
/**
* Returns max flow value
*
* @return float
*/
public function getFlowMax()
{
$resultGraph = $this->createGraph();
$start = $resultGraph->getVertex($this->startVertex->getId());
$maxFlow = 0;
foreach ($start->getEdgesOut() as $edge) {
$maxFlow = $maxFlow + $edge->getFlow();
}
return $maxFlow;
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Graphp\Algorithms\MaximumMatching;
use Fhaculty\Graph\Exception\UnexpectedValueException;
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Set\Edges;
use Graphp\Algorithms\BaseGraph;
abstract class Base extends BaseGraph
{
/**
* Get the count of edges that are in the match
*
* @return int
* @throws UnexpectedValueException if graph is directed or is not bipartit
* @uses Base::getEdges()
*/
public function getNumberOfMatches()
{
return \count($this->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();
}

View File

@@ -0,0 +1,79 @@
<?php
namespace Graphp\Algorithms\MaximumMatching;
use Fhaculty\Graph\Exception\UnexpectedValueException;
use Fhaculty\Graph\Set\Edges;
use Fhaculty\Graph\Vertex;
use Graphp\Algorithms\Directed;
use Graphp\Algorithms\Groups;
use Graphp\Algorithms\MaxFlow\EdmondsKarp as MaxFlowEdmondsKarp;
class Flow extends Base
{
public function getEdges()
{
$alg = new Directed($this->graph);
if ($alg->hasDirected()) {
throw new UnexpectedValueException('Input graph contains directed edges');
}
$alg = new Groups($this->graph);
if (!$alg->isBipartit()) {
throw new UnexpectedValueException('Input graph does not have bipartit groups assigned to each vertex. Consider Using "AlgorithmBipartit::createGraph()" first');
}
// create temporary flow graph with supersource and supersink
$graphFlow = $this->graph->createGraphCloneEdgeless();
$superSource = $graphFlow->createVertex();
$superSink = $graphFlow->createVertex();
$groups = $alg->getGroups();
$groupA = $groups[0];
// connect supersource s* to set A and supersink t* to set B
foreach ($graphFlow->getVertices() as $vertex) {
assert($vertex instanceof Vertex);
// we want to skip over supersource & supersink as they do not have a partition assigned
if ($vertex === $superSource || $vertex === $superSink) continue;
$group = $vertex->getGroup();
if ($group === $groupA) {
// group A: source
$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);
}
} else {
// group B: sink
$vertex->createEdgeTo($superSink)->setCapacity(1)->setFlow(0);
}
}
// visualize($resultGraph);
// calculate (s*, t*)-flow
$algMaxFlow = new MaxFlowEdmondsKarp($superSource, $superSink);
$resultGraph = $algMaxFlow->createGraph();
// destroy temporary supersource and supersink again
$resultGraph->getVertex($superSink->getId())->destroy();
$resultGraph->getVertex($superSource->getId())->destroy();
$returnEdges = array();
foreach ($resultGraph->getEdges() as $edge) {
// only keep matched edges
if ($edge->getFlow() > 0) {
$originalEdge = $this->graph->getEdgeClone($edge);
$returnEdges[] = $originalEdge;
}
}
return new Edges($returnEdges);
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace Graphp\Algorithms\MinimumCostFlow;
use Graphp\Algorithms\BaseGraph;
use Graphp\Algorithms\Weight as AlgorithmWeight;
use Graphp\Algorithms\Flow as AlgorithmFlow;
use Fhaculty\Graph\Exception\UnderflowException;
use Fhaculty\Graph\Exception\UnexpectedValueException;
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Set\Edges;
abstract class Base extends BaseGraph
{
/**
* check if balance is okay and throw exception otherwise
*
* @return $this (chainable)
* @throws UnexpectedValueException
*/
protected function checkBalance()
{
$alg = new AlgorithmFlow($this->graph);
$balance = $alg->getBalance();
$tolerance = 0.000001;
if ($balance >= $tolerance || $balance <= -$tolerance) {
throw new UnexpectedValueException('The given graph is not balanced value is: ' . $balance);
}
return $this;
}
/**
* helper used to add $newFlow to original edges of $clonedEdges in graph $resultGraph
*
* @param Graph $resultGraph graph to look for original edges
* @param Edges $clonedEdges set of cloned edges to be modified
* @param number $newFlow flow to add
* @uses Graph::getEdgeClone()
* @uses Graph::getEdgeCloneInverted()
* @uses Edge::getFlow()
* @uses Edge::setFlow()
*/
protected function addFlow(Graph $resultGraph, Edges $clonedEdges, $newFlow)
{
foreach ($clonedEdges as $clonedEdge) {
try {
// get edge from clone
$edge = $resultGraph->getEdgeClone($clonedEdge);
// add flow
$edge->setFlow($edge->getFlow() + $newFlow);
} catch (UnderflowException $ignore) {
// if the edge doesn't exist => use the residual edge
$edge = $resultGraph->getEdgeCloneInverted($clonedEdge);
// remove flow
$edge->setFlow($edge->getFlow() - $newFlow);
}
}
}
/**
* calculate total weight along minimum-cost flow
*
* @return float
* @uses self::createGraph()
* @uses AlgorithmWeight::getWeightFlow()
*/
public function getWeightFlow()
{
$alg = new AlgorithmWeight($this->createGraph());
return $alg->getWeightFlow();
}
/**
* create new resulting graph with minimum-cost flow on edges
*
* @return Graph
* @throws UnexpectedValueException for undirected edges
* @throws UnexpectedValueException if the graph has not enough capacity for the minimum-cost flow
*/
abstract public function createGraph();
}

View File

@@ -0,0 +1,79 @@
<?php
namespace Graphp\Algorithms\MinimumCostFlow;
use Fhaculty\Graph\Exception\UnderflowException;
use Fhaculty\Graph\Exception\UnexpectedValueException;
use Fhaculty\Graph\Set\Edges;
use Graphp\Algorithms\DetectNegativeCycle;
use Graphp\Algorithms\MaxFlow\EdmondsKarp as MaxFlowEdmondsKarp;
use Graphp\Algorithms\ResidualGraph;
class CycleCanceling extends Base
{
public function createGraph()
{
$this->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
assert($newFlow !== null);
$this->addFlow($resultGraph, $clonedEdges, $newFlow);
}
// destroy temporary supersource and supersink again
$resultGraph->getVertex($superSink->getId())->destroy();
$resultGraph->getVertex($superSource->getId())->destroy();
return $resultGraph;
}
}

View File

@@ -0,0 +1,154 @@
<?php
namespace Graphp\Algorithms\MinimumCostFlow;
use Fhaculty\Graph\Edge\Directed as EdgeDirected;
use Fhaculty\Graph\Exception\UnderflowException;
use Fhaculty\Graph\Exception\UnexpectedValueException;
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Set\Edges;
use Fhaculty\Graph\Vertex;
use Graphp\Algorithms\ResidualGraph;
use Graphp\Algorithms\ShortestPath\MooreBellmanFord as SpMooreBellmanFord;
use Graphp\Algorithms\Search\BreadthFirst as SearchBreadthFirst;
class SuccessiveShortestPath extends Base
{
/**
* @uses Graph::createGraphClone()
* @uses ResidualGraph::createGraph()
* @uses SpMooreBellmanFord::getEdgesTo(Vertex $targetVertex)
* @see Base::createGraph()
*/
public function createGraph()
{
$this->checkBalance();
$resultGraph = $this->graph->createGraphClone();
// initial balance to 0
$vertices = $resultGraph->getVertices();
foreach ($vertices as $vertex) {
$vertex->setBalance(0);
}
// initial flow of edges
$edges = $resultGraph->getEdges();
foreach ($edges as $edge) {
if (!($edge instanceof EdgeDirected)) {
throw new UnexpectedValueException('Undirected edges are not supported for SuccessiveShortestPath');
}
// 0 if weight of edge is positive
$flow = 0;
// maximal flow if weight of edge is negative
if ($edge->getWeight() < 0) {
$flow = $edge->getCapacity();
$startVertex = $edge->getVertexStart();
$endVertex = $edge->getVertexEnd();
// add balance to start- and end-vertex
$this->addBalance($startVertex, $flow);
$this->addBalance($endVertex, - $flow);
}
$edge->setFlow($flow);
}
// return or Exception inside this while
while (true) {
// create residual graph
$algRG = new ResidualGraph($resultGraph);
$residualGraph = $algRG->createGraph();
// search for a source
try {
$sourceVertex = $this->getVertexSource($residualGraph);
} catch (UnderflowException $ignore) {
// no source is found => minimum-cost flow is found
break;
}
// search for reachable target sink from this source
try {
$targetVertex = $this->getVertexSink($sourceVertex);
} catch (UnderflowException $e) {
// no target found => network does not have enough capacity
throw new UnexpectedValueException('The graph has not enough capacity for the minimum-cost flow', 0, $e);
}
// calculate shortest path between source- and target-vertex
$algSP = new SpMooreBellmanFord($sourceVertex);
$edgesOnFlow = $algSP->getEdgesTo($targetVertex);
// calculate the maximal possible flow
// new flow is the maximal possible flow for this path
$newflow = $this->graph->getVertex($sourceVertex->getId())->getBalance() - $sourceVertex->getBalance();
$targetFlow = - ($this->graph->getVertex($targetVertex->getId())->getBalance() - $targetVertex->getBalance());
// get minimum of source and target
if ($targetFlow < $newflow) {
$newflow = $targetFlow;
}
// get minimum of capacity remaining on path
$minCapacity = $edgesOnFlow->getEdgeOrder(Edges::ORDER_CAPACITY_REMAINING)->getCapacityRemaining();
if ($minCapacity < $newflow) {
$newflow = $minCapacity;
}
// add the new flow to the path
assert($newflow !== null);
$this->addFlow($resultGraph, $edgesOnFlow, $newflow);
// add balance to source and remove for the target sink
$oriSourceVertex = $resultGraph->getVertex($sourceVertex->getId());
$oriTargetVertex = $resultGraph->getVertex($targetVertex->getId());
$this->addBalance($oriSourceVertex, $newflow);
$this->addBalance($oriTargetVertex, - $newflow);
}
return $resultGraph;
}
/**
* @param Graph $graph
* @return Vertex a source vertex in the given graph
* @throws UnderflowException if there is no left source vertex
*/
private function getVertexSource(Graph $graph)
{
foreach ($graph->getVertices()->getMap() as $vid => $vertex) {
if ($this->graph->getVertex($vid)->getBalance() - $vertex->getBalance() > 0) {
return $vertex;
}
}
throw new UnderflowException('No source vertex found in graph');
}
/**
* @param Vertex $source
* @return Vertex a sink-vertex that is reachable from the source
* @throws UnderflowException if there is no reachable sink vertex
* @uses BreadthFirst::getVertices()
*/
private function getVertexSink(Vertex $source)
{
// search for reachable Vertices
$algBFS = new SearchBreadthFirst($source);
foreach ($algBFS->getVertices()->getMap() as $vid => $vertex) {
if ($this->graph->getVertex($vid)->getBalance() - $vertex->getBalance() < 0) {
return $vertex;
}
}
throw new UnderflowException('No sink vertex connected to given source vertex found');
}
private function addBalance(Vertex $vertex, $balance)
{
$vertex->setBalance($vertex->getBalance() + $balance);
}
}

View File

@@ -0,0 +1,91 @@
<?php
namespace Graphp\Algorithms\MinimumSpanningTree;
use Fhaculty\Graph\Edge\Base as Edge;
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Set\Edges;
use Graphp\Algorithms\Base as AlgorithmBase;
use SplPriorityQueue;
/**
* Abstract base class for minimum spanning tree (MST) algorithms
*
* A minimum spanning tree of a graph is a subgraph that is a tree and connects
* all the vertices together while minimizing the total sum of all edges'
* weights.
*
* A spanning tree thus requires a connected graph (single connected component),
* otherwise we can span multiple trees (spanning forest) within each component.
* Because a null graph (a Graph with no vertices) is not considered connected,
* it also can not contain a spanning tree.
*
* Most authors demand that the input graph has to be undirected, whereas this
* library supports also directed and mixed graphs. The actual direction of the
* edge will be ignored, only its incident vertices will be checked. This is
* done in order to be consistent to how ConnectedComponents are checked.
*
* @link http://en.wikipedia.org/wiki/Minimum_Spanning_Tree
* @link http://en.wikipedia.org/wiki/Spanning_Tree
* @link http://mathoverflow.net/questions/120536/is-the-empty-graph-a-tree
*/
abstract class Base extends AlgorithmBase
{
/**
* create new resulting graph with only edges on minimum spanning tree
*
* @return Graph
* @uses self::getGraph()
* @uses self::getEdges()
* @uses Graph::createGraphCloneEdges()
*/
public function createGraph()
{
return $this->getGraph()->createGraphCloneEdges($this->getEdges());
}
/**
* get all edges on minimum spanning tree
*
* @return Edges
*/
abstract public function getEdges();
/**
* return reference to current Graph
*
* @return Graph
*/
abstract protected function getGraph();
/**
* get total weight of minimum spanning tree
*
* @return float
*/
public function getWeight()
{
return $this->getEdges()->getSumCallback(function (Edge $edge) {
return $edge->getWeight();
});
}
/**
* helper method to add a set of Edges to the given set of sorted edges
*
* @param Edges $edges
* @param SplPriorityQueue $sortedEdges
*/
protected function addEdgesSorted(Edges $edges, SplPriorityQueue $sortedEdges)
{
// For all edges
foreach ($edges as $edge) {
assert($edge instanceof Edge);
// ignore loops (a->a)
if (!$edge->isLoop()) {
// Add edges with negative weight because of order in stl
$sortedEdges->insert($edge, -$edge->getWeight());
}
}
}
}

View File

@@ -0,0 +1,129 @@
<?php
namespace Graphp\Algorithms\MinimumSpanningTree;
use Fhaculty\Graph\Edge\Base as Edge;
use Fhaculty\Graph\Exception\UnexpectedValueException;
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Set\Edges;
use SplPriorityQueue;
class Kruskal extends Base
{
/**
* @var Graph
*/
private $graph;
public function __construct(Graph $inputGraph)
{
$this->graph = $inputGraph;
}
protected function getGraph()
{
return $this->graph;
}
/**
* @return Edges
*/
public function getEdges()
{
// Sortiere Kanten im Graphen
$sortedEdges = new SplPriorityQueue();
// For all edges
$this->addEdgesSorted($this->graph->getEdges(), $sortedEdges);
$returnEdges = array();
// next color to assign
$colorNext = 0;
// array(color1 => array(vid1, vid2, ...), color2=>...)
$colorVertices = array();
// array(vid1 => color1, vid2 => color1, ...)
$colorOfVertices = array();
// Füge billigste Kanten zu neuen Graphen hinzu und verschmelze teilgragen wenn es nötig ist (keine Kreise)
// solange ich mehr als einen Graphen habe mit weniger als n-1 kanten (bei n knoten im original)
foreach ($sortedEdges as $edge) {
assert($edge instanceof Edge);
// Gucke Kante an:
$vertices = $edge->getVertices()->getIds();
$aId = $vertices[0];
$bId = $vertices[1];
$aColor = isset($colorOfVertices[$aId]) ? $colorOfVertices[$aId] : NULL;
$bColor = isset($colorOfVertices[$bId]) ? $colorOfVertices[$bId] : NULL;
// 1. weder start noch end gehört zu einem graphen
// => neuer Graph mit kanten
if ($aColor === NULL && $bColor === NULL) {
$colorOfVertices[$aId] = $colorNext;
$colorOfVertices[$bId] = $colorNext;
$colorVertices[$colorNext] = array($aId, $bId);
++$colorNext;
// connect both vertices
$returnEdges[] = $edge;
}
// 4. start xor end gehören zu einem graphen
// => erweitere diesesn Graphen
// Only b has color
else if ($aColor === NULL && $bColor !== NULL) {
// paint a in b's color
$colorOfVertices[$aId] = $bColor;
$colorVertices[$bColor][]=$aId;
$returnEdges[] = $edge;
// Only a has color
} elseif ($aColor !== NULL && $bColor === NULL) {
// paint b in a's color
$colorOfVertices[$bId] = $aColor;
$colorVertices[$aColor][]=$bId;
$returnEdges[] = $edge;
}
// 3. start und end gehören zu unterschiedlichen graphen
// => vereinigung
// Different color
else if ($aColor !== $bColor) {
$betterColor = $aColor;
$worseColor = $bColor;
// more vertices with color a => paint all in b in a's color
if (\count($colorVertices[$bColor]) > \count($colorVertices[$aColor])) {
$betterColor = $bColor;
$worseColor = $aColor;
}
// search all vertices with color b
foreach ($colorVertices[$worseColor] as $vid) {
$colorOfVertices[$vid] = $betterColor;
// repaint in a's color
$colorVertices[$betterColor][]=$vid;
}
// delete old color
unset($colorVertices[$worseColor]);
$returnEdges[] = $edge;
}
// 2. start und end gehören zum gleichen graphen => zirkel
// => nichts machen
}
// definition of spanning tree: number of edges = number of vertices - 1
// above algorithm does not check isolated edges or may otherwise return multiple connected components => force check
if (\count($returnEdges) !== (\count($this->graph->getVertices()) - 1)) {
throw new UnexpectedValueException('Graph is not connected');
}
return new Edges($returnEdges);
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace Graphp\Algorithms\MinimumSpanningTree;
use Fhaculty\Graph\Edge\Base as Edge;
use Fhaculty\Graph\Exception\UnexpectedValueException;
use Fhaculty\Graph\Set\Edges;
use Fhaculty\Graph\Vertex;
use SplPriorityQueue;
class Prim extends Base
{
/**
* @var Vertex
*/
private $startVertex;
public function __construct(Vertex $startVertex)
{
$this->startVertex = $startVertex;
}
/**
* @return Edges
*/
public function getEdges()
{
// Initialize algorithm
$edgeQueue = new SplPriorityQueue();
$vertexCurrent = $this->startVertex;
$markInserted = array();
$returnEdges = array();
// iterate n-1 times (per definition, resulting MST MUST have n-1 edges)
for ($i = 0, $n = \count($this->startVertex->getGraph()->getVertices()) - 1; $i < $n; ++$i) {
$markInserted[$vertexCurrent->getId()] = true;
// get unvisited vertex of the edge and add edges from new vertex
// Add all edges from $currentVertex to priority queue
$this->addEdgesSorted($vertexCurrent->getEdges(), $edgeQueue);
do {
if ($edgeQueue->isEmpty()) {
throw new UnexpectedValueException('Graph has more than one component');
}
// Get next cheapest edge
$cheapestEdge = $edgeQueue->extract();
assert($cheapestEdge instanceof Edge);
// Check if edge is between unmarked and marked edge
$vertices = $cheapestEdge->getVertices();
$vertexA = $vertices->getVertexFirst();
$vertexB = $vertices->getVertexLast();
} while (!(isset($markInserted[$vertexA->getId()]) XOR isset($markInserted[$vertexB->getId()])));
// Cheapest Edge found, add edge to returnGraph
$returnEdges[] = $cheapestEdge;
// set current vertex for next iteration in order to add its edges to queue
if (isset($markInserted[$vertexA->getId()])) {
$vertexCurrent = $vertexB;
} else {
$vertexCurrent = $vertexA;
}
}
return new Edges($returnEdges);
}
protected function getGraph()
{
return $this->startVertex->getGraph();
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace Graphp\Algorithms;
use Fhaculty\Graph\Edge\Base as Edge;
use Fhaculty\Graph\Edge\Directed as DirectedEdge;
use Fhaculty\Graph\Set\Edges;
use LogicException;
/**
* Basic algorithms for working with parallel edges
*
* Parallel edges (also called multiple edges or a multi-edge), are two or more
* edges that are incident to the same two vertices. A simple graph has no
* multiple edges.
*
* @link http://en.wikipedia.org/wiki/Multiple_edges
*/
class Parallel extends BaseGraph
{
/**
* checks whether this graph has any parallel edges (aka multigraph)
*
* @return bool
* @uses Edge::hasEdgeParallel() for every edge
*/
public function hasEdgeParallel()
{
foreach ($this->graph->getEdges() as $edge) {
if ($this->hasEdgeParallelEdge($edge)) {
return true;
}
}
return false;
}
/**
* checks whether this edge has any parallel edges
*
* @return bool
* @uses Edge::getEdgesParallel()
*/
public function hasEdgeParallelEdge(Edge $edge)
{
return !$this->getEdgesParallelEdge($edge)->isEmpty();
}
/**
* get set of all Edges parallel to this edge (excluding self)
*
* @param Edge $edge
* @return Edges
* @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);
assert($pos !== false);
// exclude current edge from parallel edges
unset($edges[$pos]);
return new Edges(\array_values($edges));
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Graphp\Algorithms\Property;
use Graphp\Algorithms\BaseGraph;
/**
* Simple algorithms for working with Graph properties
*
* @link https://en.wikipedia.org/wiki/Graph_property
*/
class GraphProperty extends BaseGraph
{
/**
* checks whether this graph has no edges
*
* Also known as empty Graph. An empty Graph contains no edges, but can
* possibly contain any number of isolated vertices.
*
* @return bool
*/
public function isEdgeless()
{
return $this->graph->getEdges()->isEmpty();
}
/**
* checks whether this graph is a null graph (no vertex - and thus no edges)
*
* Each Edge is incident to two Vertices, or in case of an loop Edge,
* incident to the same Vertex twice. As such an Edge can not exist when
* no Vertices exist. So if we check we have no Vertices, we can also be
* sure that no Edges exist either.
*
* @return bool
*/
public function isNull()
{
return $this->graph->getVertices()->isEmpty();
}
/**
* checks whether this graph is trivial (one vertex and no edges)
*
* @return bool
*/
public function isTrivial()
{
return ($this->graph->getEdges()->isEmpty() && \count($this->graph->getVertices()) === 1);
}
}

View File

@@ -0,0 +1,380 @@
<?php
namespace Graphp\Algorithms\Property;
use Fhaculty\Graph\Walk;
use Graphp\Algorithms\Base as BaseAlgorithm;
use Graphp\Algorithms\Loop as AlgorithmLoop;
/**
* Simple algorithms for working with Walk properties
*
* @see GraphProperty
*/
class WalkProperty extends BaseAlgorithm
{
/**
* the Walk to operate on
*
* @var Walk
*/
protected $walk;
/**
* instantiate new WalkProperty algorithm
*
* @param Walk $walk
*/
public function __construct(Walk $walk)
{
$this->walk = $walk;
}
/**
* checks whether walk is a cycle (i.e. source vertex = target vertex)
*
* A cycle is also known as a closed path, a walk that is NOT a cycle is
* also known as an open path.
*
* A walk with no edges is not considered a cycle. The shortest possible
* cycle is a single loop edge:
*
* 1--\
* ^ |
* \--/
*
* The following Walk is also considered a valid cycle:
*
* /->3--\
* | |
* 1 -> 2 -\ |
* ^ ^ | |
* | \--/ |
* | |
* \----------/
*
* @return bool
* @link http://en.wikipedia.org/wiki/Cycle_%28graph_theory%29
* @see self::isCircuit()
* @see self::isLoop()
*/
public function isCycle()
{
$vertices = $this->walk->getVertices();
return ($vertices->getVertexFirst() === $vertices->getVertexLast() && !$this->walk->getEdges()->isEmpty());
}
/**
* checks whether this walk is a circuit (i.e. a cycle with no duplicate edges)
*
* A circuit is also known as a closed (=cycle) trail (=path), that has at
* least one edge.
*
* The following Walk is considered both a valid cycle and a valid circuit:
*
* 1 -> 2 -> 3 -\
* ^ |
* | |
* \------------/
*
* The following Walk is also considered both a valid cycle and a valid circuit:
*
* /->3--\
* | |
* 1 -> 2 -\ |
* ^ ^ | |
* | \--/ |
* | |
* \----------/
*
* The later circuit walk can be expressed by its Vertex IDs as
* "1, 2, 2, 3, 1". If however, the inner loop would be "walked along"
* several times, the resulting walk would be expressed as
* "1, 2, 2, 2, 3, 1", which would still be a valid cycle, but NOT a valid
* circuit anymore.
*
* @return bool
* @link http://www.proofwiki.org/wiki/Definition:Circuit
* @uses self::isCycle()
* @uses self::isPath()
*/
public function isCircuit()
{
return ($this->isCycle() && $this->isPath());
}
/**
* checks whether walk is a path (i.e. does not contain any duplicate edges)
*
* A path Walk is also known as a trail.
*
* @return bool
* @uses self::hasArrayDuplicates()
* @link http://www.proofwiki.org/wiki/Definition:Trail
*/
public function isPath()
{
return !$this->hasArrayDuplicates($this->walk->getEdges()->getVector());
}
/**
* checks whether walk contains a cycle (i.e. contains a duplicate vertex)
*
* A walk that CONTAINS a cycle does not neccessarily have to BE a cycle.
* Conversely, a Walk that *is* a cycle, automatically always *contains* a
* cycle.
*
* The following Walk is NOT a cycle, but it *contains* a valid cycle:
*
* /->4
* |
* 1 -> 2 -> 3 -\
* ^ |
* \-------/
*
* @return bool
* @uses self::hasArrayDuplicates()
* @see self::isCycle()
*/
public function hasCycle()
{
return $this->hasArrayDuplicates($this->walk->getVertices()->getVector());
}
/**
* checks whether this walk IS a loop (single edge connecting vertex A with vertex A again)
*
* A loop is the simplest possible cycle. As such, each loop is also a
* cycle. Accordingly, every Walk that *is* a loop, automatically also *is*
* a cycle and automatically *contains* a loop and automatically *contains*
* a cycle.
*
* The following Walk represents a simple (directed) loop:
*
* 1--\
* ^ |
* \--/
*
* @return bool
* @uses self::isCycle()
* @see self::hasLoop()
*/
public function isLoop()
{
return (\count($this->walk->getEdges()) === 1 && $this->isCycle());
}
/**
* checks whether this walk HAS a loop (single edge connecting vertex A with vertex A again)
*
* The following Walk is NOT a valid loop, but it contains a valid loop:
*
* /->3
* |
* 1 -> 2 -\
* ^ |
* \--/
*
* @return bool
* @uses AlgorithmLoop::hasLoop()
* @see self::isLoop()
*/
public function hasLoop()
{
$alg = new AlgorithmLoop($this->walk);
return $alg->hasLoop();
}
/**
* checks whether this walk is a digon (a pair of parallel edges in a multigraph or a pair of antiparallel edges in a digraph)
*
* A digon is a cycle connecting exactly two distinct vertices with exactly
* two distinct edges.
*
* The following Graph represents a digon in an undirected Graph:
*
* /--\
* 1 2
* \--/
*
* The following Graph represents a digon as a set of antiparallel directed
* Edges in a directed Graph:
*
* 1 -> 2
* ^ |
* | |
* \----/
*
* @return bool
* @uses self::hasArrayDuplicates()
* @uses self::isCycle()
*/
public function isDigon()
{
// exactly 2 edges
return (\count($this->walk->getEdges()) === 2 &&
// no duplicate edges
!$this->hasArrayDuplicates($this->walk->getEdges()->getVector()) &&
// exactly two distinct vertices
\count($this->walk->getVertices()->getVerticesDistinct()) === 2 &&
// this is actually a cycle
$this->isCycle());
}
/**
* checks whether this walk is a triangle (a simple cycle with exactly three distinct vertices)
*
* The following Graph is a valid directed triangle:
*
* 1->2->3
* ^ |
* \-----/
*
* @return bool
* @uses self::isCycle()
*/
public function isTriangle()
{
// exactly 3 (implicitly distinct) edges
return (\count($this->walk->getEdges()) === 3 &&
// exactly three distinct vertices
\count($this->walk->getVertices()->getVerticesDistinct()) === 3 &&
// this is actually a cycle
$this->isCycle());
}
/**
* check whether this walk is simple
*
* contains no duplicate/repeated vertices (and thus no duplicate edges either)
* other than the starting and ending vertices of cycles.
*
* A simple Walk is also known as a chain.
*
* The term "simple walk" is somewhat related to a walk with no cycles. If
* a Walk has a cycle, it is not simple - with one single exception: a Walk
* that IS a cycle automatically also contains a cycle, but if it contains
* no "further" additional cycles, it is considered a simple cycle.
*
* The following Graph represents a (very) simple Walk:
*
* 1 -- 2
*
* The following Graph IS a cycle and is simple:
*
* 1 -> 2
* ^ |
* \----/
*
* The following Graph contains a cycle and is NOT simple:
*
* /->4
* |
* 1 -> 2 -> 3 -\
* ^ |
* \-------/
*
* The following Graph IS a cycle and thus automatically contains a cycle.
* Due to the additional "inner" cycle (loop at vertex 2), it is NOT simple:
*
* /->3--\
* | |
* 1 -> 2 -\ |
* ^ ^ | |
* | \--/ |
* | |
* \----------/
*
* @return bool
* @uses self::isCycle()
* @uses self::hasArrayDuplicates()
* @see self::hasCycle()
*/
public function isSimple()
{
$vertices = $this->walk->getVertices()->getVector();
// ignore starting vertex for cycles as it's always the same as ending vertex
if ($this->isCycle()) {
unset($vertices[0]);
}
return !$this->hasArrayDuplicates($vertices);
}
/**
* checks whether walk is hamiltonian (i.e. walk over ALL VERTICES of the graph)
*
* A hamiltonian Walk is also known as a spanning walk.
*
* @return bool
* @see self::isEulerian() if you want to check for all EDGES instead of VERTICES
* @uses self::isArrayContentsEqual()
* @link http://en.wikipedia.org/wiki/Hamiltonian_path
*/
public function isHamiltonian()
{
$vertices = $this->walk->getVertices()->getVector();
// ignore starting vertex for cycles as it's always the same as ending vertex
if ($this->isCycle()) {
unset($vertices[0]);
}
return $this->isArrayContentsEqual($vertices, $this->walk->getGraph()->getVertices()->getVector());
}
/**
* checks whether walk is eulerian (i.e. a walk over ALL EDGES of the graph)
*
* @return bool
* @see self::isHamiltonian() if you want to check for all VERTICES instead of EDGES
* @uses self::isArrayContentsEqual()
* @link http://en.wikipedia.org/wiki/Eulerian_path
*/
public function isEulerian()
{
return $this->isArrayContentsEqual($this->walk->getEdges()->getVector(), $this->walk->getGraph()->getEdges()->getVector());
}
/**
* checks whether ths given array contains duplicate identical entries
*
* @param array $array
* @return bool
*/
private function hasArrayDuplicates($array)
{
$compare = array();
foreach ($array as $element) {
// duplicate element found
if (\in_array($element, $compare, true)) {
return true;
} else {
// add element to temporary array to check for duplicates
$compare [] = $element;
}
}
return false;
}
/**
* checks whether the contents of array a equals those of array b (ignore keys and order but otherwise strict check)
*
* @param array $a
* @param array $b
* @return bool
*/
private function isArrayContentsEqual($a, $b)
{
foreach ($b as $one) {
$pos = \array_search($one, $a, true);
if ($pos === false) {
return false;
} else {
unset($a[$pos]);
}
}
return $a ? false : true;
}
}

View File

@@ -0,0 +1,109 @@
<?php
namespace Graphp\Algorithms;
use Fhaculty\Graph\Edge\Base as Edge;
use Fhaculty\Graph\Edge\Directed as EdgeDirected;
use Fhaculty\Graph\Exception\UnexpectedValueException;
use Fhaculty\Graph\Graph;
class ResidualGraph extends BaseGraph
{
private $keepNullCapacity = false;
private $mergeParallelEdges = false;
public function setKeepNullCapacity($toggle)
{
$this->keepNullCapacity = !!$toggle;
return $this;
}
public function setMergeParallelEdges($toggle)
{
$this->mergeParallelEdges = !!$toggle;
return $this;
}
/**
* create residual graph
*
* @throws UnexpectedValueException if input graph has undirected edges or flow/capacity is not set
* @return Graph
* @uses Graph::createGraphCloneEdgeless()
* @uses Graph::createEdgeClone()
* @uses Graph::createEdgeCloneInverted()
*/
public function createGraph()
{
$newgraph = $this->graph->createGraphCloneEdgeless();
foreach ($this->graph->getEdges() as $edge) {
if (!($edge instanceof EdgeDirected)) {
throw new UnexpectedValueException('Edge is undirected');
}
$flow = $edge->getFlow();
if ($flow === NULL) {
throw new UnexpectedValueException('Flow not set');
}
$capacity = $edge->getCapacity();
if ($capacity === NULL) {
throw new UnexpectedValueException('Capacity not set');
}
// capacity is still available, clone remaining capacity into new edge
if ($this->keepNullCapacity || $flow < $capacity) {
$newEdge = $newgraph->createEdgeClone($edge)->setFlow(0)->setCapacity($capacity - $flow);
if ($this->mergeParallelEdges) {
$this->mergeParallelEdges($newEdge);
}
}
// flow is set, clone current flow as capacity for back-flow into new inverted edge (opposite direction)
if ($this->keepNullCapacity || $flow > 0) {
$newEdge = $newgraph->createEdgeCloneInverted($edge)->setFlow(0)->setCapacity($flow);
// if weight is set, use negative weight for back-edges
if ($newEdge->getWeight() !== NULL) {
$newEdge->setWeight(-$newEdge->getWeight());
}
if ($this->mergeParallelEdges) {
$this->mergeParallelEdges($newEdge);
}
}
}
return $newgraph;
}
/**
* Will merge all edges that are parallel to to given edge
*
* @param Edge $newEdge
*/
private function mergeParallelEdges(Edge $newEdge)
{
$alg = new Parallel($this->graph);
$parallelEdges = $alg->getEdgesParallelEdge($newEdge)->getVector();
if (!$parallelEdges) {
return;
}
$mergedCapacity = 0;
foreach ($parallelEdges as $parallelEdge) {
$mergedCapacity += $parallelEdge->getCapacity();
}
$newEdge->setCapacity($newEdge->getCapacity() + $mergedCapacity);
foreach ($parallelEdges as $parallelEdge) {
$parallelEdge->destroy();
}
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace Graphp\Algorithms\Search;
use Fhaculty\Graph\Exception\InvalidArgumentException;
use Fhaculty\Graph\Set\Vertices;
use Fhaculty\Graph\Vertex;
use Graphp\Algorithms\BaseVertex;
abstract class Base extends BaseVertex
{
const DIRECTION_FORWARD = 0;
const DIRECTION_REVERSE = 1;
const DIRECTION_BOTH = 2;
private $direction = self::DIRECTION_FORWARD;
/**
* set direction in which to follow adjacent vertices
*
* @param int $direction
* @return $this (chainable)
* @throws InvalidArgumentException
* @see self::getVerticesAdjacent()
*/
public function setDirection($direction)
{
if ($direction !== self::DIRECTION_FORWARD && $direction !== self::DIRECTION_REVERSE && $direction !== self::DIRECTION_BOTH) {
throw new InvalidArgumentException('Invalid direction given');
}
$this->direction = $direction;
return $this;
}
protected function getVerticesAdjacent(Vertex $vertex)
{
if ($this->direction === self::DIRECTION_FORWARD) {
return $vertex->getVerticesEdgeTo();
} elseif ($this->direction === self::DIRECTION_REVERSE) {
return $vertex->getVerticesEdgeFrom();
} else {
return $vertex->getVerticesEdge();
}
}
/**
* get set of all Vertices that can be reached from start vertex
*
* @return Vertices
*/
abstract public function getVertices();
}

View File

@@ -0,0 +1,61 @@
<?php
namespace Graphp\Algorithms\Search;
use Fhaculty\Graph\Set\Vertices;
class BreadthFirst extends Base
{
/**
* @param int $maxDepth
* @return Vertices
*/
public function getVertices($maxDepth = PHP_INT_MAX)
{
$queue = array($this->vertex);
// to not add vertices twice in array visited
$mark = array($this->vertex->getId() => true);
// visited vertices
$visited = array();
// keep track of depth
$currentDepth = 0;
$nodesThisLevel = 1;
$nodesNextLevel = 0;
do {
// get first from queue
$t = \array_shift($queue);
// save as visited
$visited[$t->getId()] = $t;
// get next vertices
$children = $this->getVerticesAdjacent($t);
// track depth
$nodesNextLevel = $children->count();
if (--$nodesThisLevel === 0) {
if (++$currentDepth > $maxDepth) {
return new Vertices($visited);
}
$nodesThisLevel = $nodesNextLevel;
$nodesNextLevel = 0;
}
// process next vertices
foreach ($children->getMap() as $id => $vertex) {
// if not "touched" before
if (!isset($mark[$id])) {
// add to queue
$queue[] = $vertex;
// and mark
$mark[$id] = true;
}
}
// until queue is empty
} while ($queue);
return new Vertices($visited);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Graphp\Algorithms\Search;
use Fhaculty\Graph\Set\Vertices;
class DepthFirst extends Base
{
/**
* calculates an iterative depth-first search
*
* @return Vertices
*/
public function getVertices()
{
$visited = array();
$todo = array($this->vertex);
while ($vertex = \array_shift($todo)) {
if (!isset($visited[$vertex->getId()])) {
$visited[$vertex->getId()] = $vertex;
foreach (\array_reverse($this->getVerticesAdjacent($vertex)->getMap(), true) as $nextVertex) {
$todo[] = $nextVertex;
}
}
}
return new Vertices($visited);
}
}

View File

@@ -0,0 +1,287 @@
<?php
namespace Graphp\Algorithms\ShortestPath;
use Fhaculty\Graph\Edge\Base as Edge;
use Fhaculty\Graph\Exception\InvalidArgumentException;
use Fhaculty\Graph\Exception\OutOfBoundsException;
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Set\Edges;
use Fhaculty\Graph\Set\Vertices;
use Fhaculty\Graph\Vertex;
use Fhaculty\Graph\Walk;
use Graphp\Algorithms\BaseVertex;
/**
* Abstract base class for shortest path algorithms
*
* This abstract base class provides the base interface for working with
* single-source shortest paths (SSSP).
*
* The shortest path problem is the problem of finding a path between two
* vertices such that the sum of the weights of its constituent edges is
* minimized. The weight of the shortest path is referred to as distance.
*
* A--[10]-------------B---E<--F
* \ /
* \--[4]--C--[2]--D
*
* In the above pictured graph, the distance (weight of the shortest path)
* between A and C is 4, and the shortest path between A and B is "A->C->D->B"
* with a distance (total weight) of 6.
*
* In graph theory, it is usually assumed that a path to an unreachable vertex
* has infinite distance. In the above pictured graph, there's no way path
* from A to F, i.e. vertex F is unreachable from vertex A because of the
* directed edge "E <- F" pointing in the opposite direction. This library
* considers this an Exception instead. So if you're asking for the distance
* between A and F, you'll receive an OutOfBoundsException instead.
*
* In graph theory, it is usually assumed that each vertex has a (pseudo-)path
* to itself with a distance of 0. In order to produce reliable, consistent
* results, this library considers this (pseudo-)path to be non-existant, i.e.
* there's NO "magic" path between A and A. So if you're asking for the distance
* between A and A, you'll receive an OutOfBoundsException instead. This allows
* us to check hether there's a real path between A and A (cycle via other
* vertices) as well as working with loop edges.
*
* @link http://en.wikipedia.org/wiki/Shortest_path_problem
* @link http://en.wikipedia.org/wiki/Tree_%28data_structure%29
* @see ShortestPath\Dijkstra
* @see ShortestPath\MooreBellmanFord which also supports negative Edge weights
* @see ShortestPath\BreadthFirst with does not consider Edge weights, but only the number of hops
*/
abstract class Base extends BaseVertex
{
/**
* get walk (path) from start vertex to given end vertex
*
* @param Vertex $endVertex
* @return Walk
* @throws OutOfBoundsException if there's no path to the given end vertex
* @uses self::getEdgesTo()
* @uses Walk::factoryFromEdges()
*/
public function getWalkTo(Vertex $endVertex)
{
return Walk::factoryFromEdges($this->getEdgesTo($endVertex), $this->vertex);
}
/**
* get array of edges (path) from start vertex to given end vertex
*
* @param Vertex $endVertex
* @throws OutOfBoundsException if there's no path to the given end vertex
* @return Edges
* @uses self::getEdges()
* @uses self::getEdgesToInternal()
*/
public function getEdgesTo(Vertex $endVertex)
{
return $this->getEdgesToInternal($endVertex, $this->getEdges());
}
/**
* get array of edges (path) from start vertex to given end vertex
*
* @param Vertex $endVertex
* @param Edges|Edge[] $edges set or array of all input edges to operate on
* @throws OutOfBoundsException if there's no path to the given vertex
* @return Edges
* @uses self::getEdges() if no edges were given
*/
protected function getEdgesToInternal(Vertex $endVertex, $edges)
{
$currentVertex = $endVertex;
$path = array();
do {
$pre = NULL;
// check all edges to search for edge that points TO current vertex
foreach ($edges as $edge) {
try {
// get start point of this edge (fails if current vertex is not its end point)
$pre = $edge->getVertexFromTo($currentVertex);
$path[] = $edge;
$currentVertex = $pre;
break;
} catch (InvalidArgumentException $ignore) {
} // ignore: this edge does not point TO current vertex
}
if ($pre === NULL) {
throw new OutOfBoundsException('No edge leading to vertex');
}
} while ($currentVertex !== $this->vertex);
return new Edges(\array_reverse($path));
}
/**
* get sum of weight of given edges
*
* @param Edges $edges
* @return float
* @uses Edge::getWeight()
*/
private function sumEdges(Edges $edges)
{
$sum = 0;
foreach ($edges as $edge) {
$sum += $edge->getWeight();
}
return $sum;
}
/**
* get set of all Vertices the given start vertex has a path to
*
* @return Vertices
* @uses self::getDistanceMap()
*/
public function getVertices()
{
$vertices = array();
$map = $this->getDistanceMap();
foreach ($this->vertex->getGraph()->getVertices()->getMap() as $vid => $vertex) {
if (isset($map[$vid])) {
$vertices[$vid] = $vertex;
}
}
return new Vertices($vertices);
}
/**
* checks whether there's a path from this start vertex to given end vertex
*
* @param Vertex $vertex
* @return bool
* @uses self::getEdgesTo()
*/
public function hasVertex(Vertex $vertex)
{
try {
$this->getEdgesTo($vertex);
} catch (OutOfBoundsException $e) {
return false;
}
return true;
}
/**
* get map of vertex IDs to distance
*
* @return float[]
* @uses self::getEdges()
* @uses self::getEdgesToInternal()
* @uses self::sumEdges()
*/
public function getDistanceMap()
{
$edges = $this->getEdges();
$ret = array();
foreach ($this->vertex->getGraph()->getVertices()->getMap() as $vid => $vertex) {
try {
$ret[$vid] = $this->sumEdges($this->getEdgesToInternal($vertex, $edges));
} catch (OutOfBoundsException $ignore) {
} // ignore vertices that can not be reached
}
return $ret;
}
/**
* get distance (sum of weights) between start vertex and given end vertex
*
* @param Vertex $endVertex
* @return float
* @throws OutOfBoundsException if there's no path to the given end vertex
* @uses self::getEdgesTo()
* @uses self::sumEdges()
*/
public function getDistance(Vertex $endVertex)
{
return $this->sumEdges($this->getEdgesTo($endVertex));
}
/**
* create new resulting graph with only edges on shortest path
*
* The resulting Graph will always represent a tree with the start vertex
* being the root vertex.
*
* For example considering the following input Graph with equal weights on
* each edge:
*
* A----->F
* / \ ^
* / \ /
* / \ /
* | E
* | \
* | \
* B--->C<---D
*
* The resulting shortest path tree Graph will look like this:
*
* A----->F
* / \
* / \
* / \
* | E
* | \
* | \
* B--->C D
*
* Or by just arranging the Vertices slightly different:
*
* A
* /|\
* / | \
* B E \->F
* / |
* C<-/ D
*
* @return Graph
* @uses self::getEdges()
* @uses Graph::createGraphCloneEdges()
*/
public function createGraph()
{
return $this->vertex->getGraph()->createGraphCloneEdges($this->getEdges());
}
/**
* get cheapest edges (lowest weight) for given map of vertex predecessors
*
* @param Vertex[] $predecessor
* @return Edges
* @uses Graph::getVertices()
* @uses Vertex::getEdgesTo()
* @uses Edges::getEdgeOrder()
*/
protected function getEdgesCheapestPredecesor(array $predecessor)
{
$vertices = $this->vertex->getGraph()->getVertices()->getMap();
$edges = array();
foreach ($vertices as $vid => $vertex) {
if (isset($predecessor[$vid])) {
// get predecor
$predecesVertex = $predecessor[$vid];
// get cheapest edge
$edges[] = $predecesVertex->getEdgesTo($vertex)->getEdgeOrder(Edges::ORDER_WEIGHT);
}
}
return new Edges($edges);
}
/**
* get all edges on shortest path for this vertex
*
* @return Edges
*/
abstract public function getEdges();
}

View File

@@ -0,0 +1,131 @@
<?php
namespace Graphp\Algorithms\ShortestPath;
use Fhaculty\Graph\Vertex;
use Fhaculty\Graph\Exception\OutOfBoundsException;
use Fhaculty\Graph\Set\Edges;
use Fhaculty\Graph\Set\Vertices;
/**
* Simple breadth-first shortest path algorithm
*
* This algorithm ignores edge weights and operates as a level-order algorithm
* on the number of hops. As such, it considers the path with the least number
* of hops to be shortest.
*
* This is particularly useful your Graph doesn't have Edge weights assigned to
* begin with or if you're merely interested in knowing which Vertices can be
* reached at all (path finding). This avoids running expensive operations to
* determine the actual weight (distance) of a path.
*/
class BreadthFirst extends Base
{
/**
* get distance between start vertex and given end vertex
*
* @param Vertex $endVertex
* @throws OutOfBoundsException if there's no path to given end vertex
* @return float
* @uses self::getEdgesTo()
*/
public function getDistance(Vertex $endVertex)
{
return (float)\count($this->getEdgesTo($endVertex));
}
/**
* get array of edges on the walk for each vertex (vertex ID => array of walk edges)
*
* @return array[]
*/
public function getEdgesMap()
{
$vertexQueue = array();
$edges = array();
// $edges[$this->vertex->getId()] = array();
$vertexCurrent = $this->vertex;
$edgesCurrent = array();
do {
foreach ($vertexCurrent->getEdgesOut() as $edge) {
$vertexTarget = $edge->getVertexToFrom($vertexCurrent);
$vid = $vertexTarget->getId();
if (!isset($edges[$vid])) {
$vertexQueue[] = $vertexTarget;
$edges[$vid] = \array_merge($edgesCurrent, array($edge));
}
}
// get next from queue
$vertexCurrent = \array_shift($vertexQueue);
if ($vertexCurrent) {
$edgesCurrent = $edges[$vertexCurrent->getId()];
}
// untill queue is empty
} while ($vertexCurrent);
return $edges;
}
public function getEdgesTo(Vertex $endVertex)
{
if ($endVertex->getGraph() === $this->vertex->getGraph()) {
$map = $this->getEdgesMap();
if (isset($map[$endVertex->getId()])) {
return new Edges($map[$endVertex->getId()]);
}
}
throw new OutOfBoundsException('Given target vertex can not be reached from start vertex');
}
/**
* get map of vertex IDs to distance
*
* @return float[]
* @uses Vertex::hasLoop()
*/
public function getDistanceMap()
{
$ret = array();
foreach ($this->getEdgesMap() as $vid => $edges) {
$ret[$vid] = (float)\count($edges);
}
return $ret;
}
/**
* get array of all target vertices this vertex has a path to
*
* @return Vertices
* @uses self::getEdgesMap()
*/
public function getVertices()
{
$ret = array();
$graph = $this->vertex->getGraph();
foreach (\array_keys($this->getEdgesMap()) as $vid) {
$ret[$vid] = $graph->getVertex($vid);
}
return new Vertices($ret);
}
public function getEdges()
{
$ret = array();
foreach ($this->getEdgesMap() as $edges) {
foreach ($edges as $edge) {
if (!\in_array($edge, $ret, true)) {
$ret[] = $edge;
}
}
}
return new Edges($ret);
}
}

View File

@@ -0,0 +1,121 @@
<?php
namespace Graphp\Algorithms\ShortestPath;
use Fhaculty\Graph\Exception\UnexpectedValueException;
use Fhaculty\Graph\Set\Edges;
use SplPriorityQueue;
/**
* Commonly used Dijkstra's shortest path algorithm
*
* This is asymptotically the fastest known single-source shortest-path
* algorithm for arbitrary graphs with non-negative weights. If your Graph
* contains an Edge with negative weight, if will throw an
* UnexpectedValueException. Consider using (the slower) MooreBellmanFord
* algorithm instead.
*
* @link http://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
* @see MooreBellmanFord
*/
class Dijkstra extends Base
{
/**
* get all edges on shortest path for this vertex
*
* @return Edges
* @throws UnexpectedValueException when encountering an Edge with negative weight
*/
public function getEdges()
{
$totalCostOfCheapestPathTo = Array();
// start node distance
$totalCostOfCheapestPathTo[$this->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);
}
}

View File

@@ -0,0 +1,120 @@
<?php
namespace Graphp\Algorithms\ShortestPath;
use Fhaculty\Graph\Exception\NegativeCycleException;
use Fhaculty\Graph\Exception\UnderflowException;
use Fhaculty\Graph\Set\Edges;
use Fhaculty\Graph\Vertex;
use Fhaculty\Graph\Walk;
/**
* Moore-Bellman-Ford's shortest path algorithm
*
* It is slower than Dijkstra's algorithm for the same problem, but more
* versatile, as it is capable of handling Graphs with negative Edge weights.
*
* Also known as just "BellmanFord algorithm".
*
* @link http://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm
*/
class MooreBellmanFord extends Base
{
/**
* @param Edges $edges
* @param int[] $totalCostOfCheapestPathTo
* @param Vertex[] $predecessorVertexOfCheapestPathTo
* @return Vertex|NULL
*/
private function bigStep(Edges $edges, array &$totalCostOfCheapestPathTo, array &$predecessorVertexOfCheapestPathTo)
{
$changed = NULL;
// check for all edges
foreach ($edges as $edge) {
// check for all "ends" of this edge (or for all targetes)
foreach ($edge->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');
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Graphp\Algorithms;
use Fhaculty\Graph\Edge\Directed as EdgeDirected;
/**
* Basic algorithms for working with symmetric digraphs
*
* A directed graph is called symmetric if, for every arc that belongs to it,
* the corresponding reversed arc (antiparallel directed edge) also belongs to it.
*
* @link http://en.wikipedia.org/wiki/Directed_graph#Classes_of_digraphs
*/
class Symmetric extends BaseGraph
{
/**
* checks whether this graph is symmetric (for every edge a->b there's also an edge b->a)
*
* @return bool
* @uses Graph::getEdges()
* @uses EdgeDirected::getVertexStart()
* @uses EdgeDirected::getVertedEnd()
* @uses Vertex::hasEdgeTo()
*/
public function isSymmetric()
{
// check all edges
foreach ($this->graph->getEdges() as $edge) {
// only check directed edges (undirected ones are symmetric by definition)
if ($edge instanceof EdgeDirected) {
// check if end also has an edge to start
if (!$edge->getVertexEnd()->hasEdgeTo($edge->getVertexStart())) {
return false;
}
}
}
return true;
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace Graphp\Algorithms;
use Fhaculty\Graph\Exception\UnexpectedValueException;
use Fhaculty\Graph\Set\Vertices;
use Fhaculty\Graph\Vertex;
/**
* topological sorting / order, also known as toposort / topsort, commonly used in resolving dependencies
*
* @author clue
* @link http://en.wikipedia.org/wiki/Topological_sorting
*/
class TopologicalSort extends BaseGraph
{
/**
* run algorithm and return an ordered/sorted set of Vertices
*
* the topological sorting may be non-unique depending on your edges
*
* @return Vertices
*/
public function getVertices()
{
$stack = array(); // visited nodes with unvisited children
$visited = array();
$output = array();
// TODO: avoid having to reverse all vertices multiple times
// pick a node to examine next - assume there are isolated nodes
foreach (\array_reverse($this->graph->getVertices()->getVector()) as $top) {
assert($top instanceof Vertex);
$tid = $top->getId();
if (!isset($visited[$tid])) { // don't examine if already found
\array_push($stack, $top);
}
while ($stack) {
$node = \end($stack);
assert($node instanceof Vertex);
$nid = $node->getId();
$visited[$nid] = false; // temporary mark
$found = false; // new children found during visit to this node
// find the next node to visit
foreach (\array_reverse($node->getVerticesEdgeTo()->getVector()) as $child) {
assert($child instanceof Vertex);
$cid = $child->getId();
if (!isset($visited[$cid])) {
// found a new node - push it onto the stack
\array_push($stack, $child);
$found = true; // move onto the new node
break;
} else if ($visited[$cid] === false) {
// temporary mark => not a DAG
throw new UnexpectedValueException('Not a DAG');
}
}
if (!$found) {
\array_pop($stack); // no new children found - we're done with this node
$visited[$nid] = true; // mark as visited
\array_push($output, $node); // add to results
}
}
}
return new Vertices(\array_reverse($output, true));
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Graphp\Algorithms;
use Fhaculty\Graph\Edge\Directed as EdgeDirected;
use Fhaculty\Graph\Exception\UnexpectedValueException;
use Fhaculty\Graph\Graph;
class TransposeGraph extends BaseGraph
{
/**
* create transpose graph
*
* @throws UnexpectedValueException if input graph has undirected edges
* @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');
}
$newgraph->createEdgeCloneInverted($edge);
}
return $newgraph;
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace Graphp\Algorithms\TravelingSalesmanProblem;
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Set\Edges;
use Fhaculty\Graph\Vertex;
use Fhaculty\Graph\Walk;
use Graphp\Algorithms\Base as AlgorithmBase;
abstract class Base extends AlgorithmBase
{
/**
* get resulting graph with the (first) best circle of edges connecting all vertices
*
* @throws \Exception on error
* @return Graph
* @uses self::getGraph()
* @uses self::getEdges()
* @uses Graph::createGraphCloneEdges()
*/
public function createGraph()
{
return $this->getGraph()->createGraphCloneEdges($this->getEdges());
}
/**
* get graph this algorithm operates on
*
* @return Graph
*/
abstract protected function getGraph();
/**
* get start vertex this algorithm starts on
*
* @return Vertex
*/
abstract protected function getVertexStart();
/**
* get (first) best circle connecting all vertices
*
* @return Walk
* @uses self::getEdges()
* @uses self::getVertexStart()
* @uses Walk::factoryCycleFromEdges()
*/
public function getCycle()
{
return Walk::factoryCycleFromEdges($this->getEdges(), $this->getVertexStart());
}
public function getWeight()
{
$weight = 0;
foreach ($this->getEdges() as $edge) {
$weight += $edge->getWeight();
}
return $weight;
}
/**
* get array of edges connecting all vertices in a circle
*
* @return Edges
*/
abstract public function getEdges();
}

View File

@@ -0,0 +1,220 @@
<?php
namespace Graphp\Algorithms\TravelingSalesmanProblem;
use Fhaculty\Graph\Edge\Base as Edge;
use Fhaculty\Graph\Exception\UnexpectedValueException;
use Fhaculty\Graph\Exception\UnderflowException;
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Set\Edges;
use Fhaculty\Graph\Vertex;
use Graphp\Algorithms\TravelingSalesmanProblem\MinimumSpanningTree as AlgorithmTspMst;
class Bruteforce extends Base
{
/**
*
* @var Graph
*/
private $graph;
/**
* best weight so for (used for branch-and-bound)
*
* @var number|NULL
*/
private $bestWeight;
/**
* reference to start vertex
*
* @var Vertex
*/
private $startVertex;
/**
* total number of edges needed
*
* @var int
*/
private $numEdges;
/**
* upper limit to use for branch-and-bound (BNB)
*
* @var float|NULL
* @see self::setUpperLimit()
*/
private $upperLimit = NULL;
/**
* whether to use branch-and-bound
*
* simply put, there's no valid reason why anybody would want to turn off this flag
*
* @var bool
*/
private $branchAndBound = true;
/**
*
* @param Graph $graph
*/
public function __construct(Graph $graph)
{
$this->graph = $graph;
}
/**
* explicitly set upper limit to use for branch-and-bound
*
* this method can be used to optimize the algorithm by providing an upper
* bound of when to stop branching any further.
*
* @param float $limit
* @return self $this (chainable)
*/
public function setUpperLimit($limit)
{
$this->upperLimit = $limit;
return $this;
}
/**
* automatically sets upper limit to use for branch-and-bound from the MST heuristic
*
* @return self $this (chainable)
* @uses AlgorithmTspMst
*/
public function setUpperLimitMst()
{
$alg = new AlgorithmTspMst($this->graph);
$this->upperLimit = $alg->getWeight();
return $this;
}
protected function getVertexStart()
{
// actual start doesn't really matter as we're only considering complete graphs here
return $this->graph->getVertices()->getVertexFirst();
}
protected function getGraph()
{
return $this->graph;
}
/**
* get resulting (first) best circle of edges connecting all vertices
*
* @throws \Exception on error
* @return Edges
*/
public function getEdges()
{
$this->numEdges = \count($this->graph->getVertices());
if ($this->numEdges < 3) {
throw new UnderflowException('Needs at least 3 vertices');
}
// numEdges 3-12 should work
$this->bestWeight = $this->upperLimit;
$this->startVertex = $this->getVertexStart();
$result = $this->step($this->startVertex,
0,
array(),
array()
);
if ($result === NULL) {
throw new UnexpectedValueException('No resulting solution for TSP found');
}
return new Edges($result);
}
/**
*
* @param Vertex $vertex current point-of-view
* @param number $totalWeight total weight (so far)
* @param bool[] $visitedVertices
* @param Edge[] $visitedEdges
* @return Edge[]|null
*/
private function step(Vertex $vertex, $totalWeight, array $visitedVertices, array $visitedEdges)
{
// stop recursion if best result is exceeded (branch and bound)
if ($this->branchAndBound && $this->bestWeight !== NULL && $totalWeight >= $this->bestWeight) {
return NULL;
}
// kreis geschlossen am Ende
if ($vertex === $this->startVertex && \count($visitedEdges) === $this->numEdges) {
// new best result
$this->bestWeight = $totalWeight;
return $visitedEdges;
}
// only visit each vertex once
if (isset($visitedVertices[$vertex->getId()])) {
return NULL;
}
$visitedVertices[$vertex->getId()] = true;
$bestResult = NULL;
// weiter verzweigen in alle vertices
foreach ($vertex->getEdgesOut() as $edge) {
// get target vertex of this edge
$target = $edge->getVertexToFrom($vertex);
$weight = $edge->getWeight();
if ($weight < 0) {
throw new UnexpectedValueException('Edge with negative weight "' . $weight . '" not supported');
}
$result = $this->step($target,
$totalWeight + $weight,
$visitedVertices,
\array_merge($visitedEdges, array($edge))
);
// new result found
if ($result !== NULL) {
// branch and bound enabled (default): returned result MUST be the new best result
if($this->branchAndBound ||
// this is the first result, just use it anyway
$bestResult === NULL ||
// this is the new best result
$this->sumEdges($result) < $this->sumEdges($bestResult)){
$bestResult = $result;
}
}
}
return $bestResult;
}
/**
* get sum of weight of given edges
*
* no need to optimize this further, as it's only evaluated if branchAndBound is disabled and
* there's no valid reason why anybody would want to do so.
*
* @param Edge[] $edges
* @return float
*/
private function sumEdges(array $edges)
{
$sum = 0;
foreach ($edges as $edge) {
$sum += $edge->getWeight();
}
return $sum;
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace Graphp\Algorithms\TravelingSalesmanProblem;
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Set\Edges;
use Graphp\Algorithms\MinimumSpanningTree\Kruskal as MstKruskal;
use Graphp\Algorithms\Search\BreadthFirst as SearchDepthFirst;
class MinimumSpanningTree extends Base
{
/**
* @var Graph
*/
private $graph;
public function __construct(Graph $inputGraph)
{
$this->graph = $inputGraph;
}
protected function getVertexStart()
{
return $this->graph->getVertices()->getVertexFirst();
}
protected function getGraph()
{
return $this->graph;
}
/**
* @return Edges
*/
public function getEdges()
{
$returnEdges = array();
// Create minimum spanning tree
$minimumSpanningTreeAlgorithm = new MstKruskal($this->graph);
$minimumSpanningTree = $minimumSpanningTreeAlgorithm->createGraph();
$alg = new SearchDepthFirst($minimumSpanningTree->getVertices()->getVertexFirst());
// Depth first search in minmum spanning tree (for the eulerian path)
$startVertex = NULL;
$oldVertex = NULL;
// connect vertices in order of the depth first search
foreach ($alg->getVertices() as $vertex) {
// get vertex from the original graph (not from the depth first search)
$vertex = $this->graph->getVertex($vertex->getId());
// need to clone the edge from the original graph, therefore i need the original edge
if ($startVertex === NULL) {
$startVertex = $vertex;
} else {
// get edge(s) to clone, multiple edges are possible (returns an array if undirected edge)
assert($oldVertex !== null);
$returnEdges[] = $oldVertex->getEdgesTo($vertex)->getEdgeFirst();
}
$oldVertex = $vertex;
}
// connect last vertex with start vertex
// multiple edges are possible (returns an array if undirected edge)
assert($startVertex !== null && $oldVertex !== null);
$returnEdges[] = $oldVertex->getEdgesTo($startVertex)->getEdgeFirst();
return new Edges($returnEdges);
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace Graphp\Algorithms\TravelingSalesmanProblem;
use Fhaculty\Graph\Exception\UnexpectedValueException;
use Fhaculty\Graph\Set\Edges;
use Fhaculty\Graph\Vertex;
use SplPriorityQueue;
class NearestNeighbor extends Base
{
/**
* @var Vertex
*/
private $vertex;
public function __construct(Vertex $startVertex)
{
$this->vertex = $startVertex;
}
protected function getVertexStart()
{
return $this->vertex;
}
protected function getGraph()
{
return $this->vertex->getGraph();
}
/**
* @return Edges
*/
public function getEdges()
{
$returnEdges = array();
$n = \count($this->vertex->getGraph()->getVertices());
$vertex = $nextVertex = $this->vertex;
$visitedVertices = array($vertex->getId() => true);
for ($i = 0; $i < $n - 1; ++$i,
// n-1 steps (spanning tree)
$vertex = $nextVertex) {
// get all edges from the aktuel vertex
$edges = $vertex->getEdgesOut();
$sortedEdges = new SplPriorityQueue();
// sort the edges
foreach ($edges as $edge) {
$sortedEdges->insert($edge, - $edge->getWeight());
}
// Untill first is found: get cheepest edge
foreach ($sortedEdges as $edge) {
// Get EndVertex of this edge
$nextVertex = $edge->getVertexToFrom($vertex);
// is unvisited
if (!isset($visitedVertices[$nextVertex->getId()])) {
break;
}
}
// check if there is a way i can use
if (isset($visitedVertices[$nextVertex->getId()])) {
throw new UnexpectedValueException('Graph is not complete - can\'t find an edge to unconnected vertex');
}
$visitedVertices[$nextVertex->getId()] = TRUE;
// clone edge in new Graph
assert(isset($edge));
$returnEdges[] = $edge;
}
// check if there is a way from end edge to start edge
// get first connecting edge
// connect the last vertex with the start vertex
$returnEdges[] = $vertex->getEdgesTo($this->vertex)->getEdgeFirst();
return new Edges($returnEdges);
}
}

View File

@@ -0,0 +1,102 @@
<?php
namespace Graphp\Algorithms\Tree;
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Set\Vertices;
use Fhaculty\Graph\Vertex;
use Graphp\Algorithms\BaseGraph;
use Graphp\Algorithms\Degree;
/**
* Abstract base class for tree algorithms
*
* This abstract base class provides the base interface for working with
* graphs that represent a tree.
*
* A tree is a connected Graph (single component) with no cycles. Every Tree is
* a Graph, but not every Graph is a Tree. A null Graph (a Graph with no Vertices
* and thus no Edges) is *NOT* considered a valid Tree, as it is not considered
* connected (@see ConnectedComponents and @link)
*
* A
* / \
* B C
* / \
* D E
*
* Special cases are undirected trees (like the one pictured above), handled via
* Tree\Undirected and directed, rooted trees (InTree and OutTree), handled via
* Tree\BaseDirected.
*
* @link http://en.wikipedia.org/wiki/Tree_%28graph_theory%29
* @link http://en.wikipedia.org/wiki/Tree_%28data_structure%29
* @link http://mathoverflow.net/questions/120536/is-the-empty-graph-a-tree
* @see Undirected for an implementation of these algorithms on (undirected) trees
* @see BaseDirected for an abstract implementation of these algorithms on directed, rooted trees
*/
abstract class Base extends BaseGraph
{
/**
* @var Degree
*/
protected $degree;
public function __construct(Graph $graph)
{
parent::__construct($graph);
$this->degree = new Degree($graph);
}
/**
* checks whether the given graph is actually a tree
*
* @return bool
*/
abstract public function isTree();
/**
* checks if the given $vertex is a leaf (outermost vertext)
*
* leaf vertex is also known as leaf node, external node or terminal node
*
* @param Vertex $vertex
* @return bool
*/
abstract public function isVertexLeaf(Vertex $vertex);
/**
* checks if the given $vertex is an internal vertex (somewhere in the "middle" of the tree)
*
* internal vertex is also known as inner node (inode) or branch node
*
* @param Vertex $vertex
* @return bool
*/
abstract public function isVertexInternal(Vertex $vertex);
/**
* get array of leaf vertices (outermost vertices with no children)
*
* @return Vertices
* @uses Graph::getVertices()
* @uses self::isVertexLeaf()
*/
public function getVerticesLeaf()
{
return $this->graph->getVertices()->getVerticesMatch(array($this, 'isVertexLeaf'));
}
/**
* get array of internal vertices
*
* @return Vertices
* @uses Graph::getVertices()
* @uses self::isVertexInternal()
*/
public function getVerticesInternal()
{
return $this->graph->getVertices()->getVerticesMatch(array($this, 'isVertexInternal'));
}
}

View File

@@ -0,0 +1,322 @@
<?php
namespace Graphp\Algorithms\Tree;
use Fhaculty\Graph\Exception\UnderflowException;
use Fhaculty\Graph\Exception\UnexpectedValueException;
use Fhaculty\Graph\Set\Vertices;
use Fhaculty\Graph\Vertex;
use Graphp\Algorithms\Tree\Base as Tree;
/**
* Abstract algorithm base class for working with directed, rooted trees
*
* Directed trees have an designated root Vertex, which is the uppermost Vertex.
* Every other Vertex is either a directed child of this root Vertex or an
* indirect descendant (recursive child).
*
* There are two common implementations of directed trees:
*
* - Usual OutTree implementation where Edges "point away" from root Vertex
*
* ROOT
* / \
* A <--/ \--> B
* \
* \--> C
*
* - Alternative InTree implementation where Edges "point towards" root Vertex
*
* ROOT
* ^ ^
* / \
* A B
* ^
* \
* C
*
* It's your choice on how to direct the edges, but make sure they all point in
* the "same direction", or it will not be a valid tree anymore. However your
* decision may be, in the above example, ROOT is always the root Vertex,
* B is the parent of "C" and A, B are the children of ROOT.
*
* For performance reasons, except for `isTree()`, none of the below methods
* check if the given Graph is actually a valid tree. So make sure to verify
* `isTree()` returns `true` before relying on any of the methods.
*
* @link http://en.wikipedia.org/wiki/Arborescence_%28graph_theory%29
* @link http://en.wikipedia.org/wiki/Spaghetti_stack
* @see OutTree usual implementation where Edges "point away" from root vertex
* @see InTree alternative implementation where Edges "point towards" root vertex
*/
abstract class BaseDirected extends Tree
{
/**
* get root vertex for this in-tree
*
* @return Vertex
* @throws UnderflowException if given graph is empty or no possible root candidate was found (check isTree()!)
* @uses Graph::getVertices() to iterate over each Vertex
* @uses self::isVertexPossibleRoot() to check if any Vertex is a possible root candidate
*/
public function getVertexRoot()
{
foreach ($this->graph->getVertices() as $vertex) {
if ($this->isVertexPossibleRoot($vertex)) {
return $vertex;
}
}
throw new UnderflowException('No possible root found. Either empty graph or no Vertex with proper degree found.');
}
/**
* checks if this is a tree
*
* @return bool
* @uses self::getVertexRoot() to get root Vertex to start search from
* @uses self::getVerticesSubtree() to count number of vertices connected to root
*/
public function isTree()
{
try {
$root = $this->getVertexRoot();
} catch (UnderflowException $e) {
return false;
} catch (UnexpectedValueException $e) {
return false;
}
try {
$num = \count($this->getVerticesSubtree($root));
} catch (UnexpectedValueException $e) {
return false;
}
// check number of vertices reachable from root should match total number of vertices
return ($num === \count($this->graph->getVertices()));
}
/**
* get parent vertex for given $vertex
*
* @param Vertex $vertex
* @throws UnderflowException if vertex has no parent (is a root vertex)
* @throws UnexpectedValueException if vertex has more than one possible parent (check isTree()!)
* @return Vertex
* @uses self::getVerticesParents() to get array of parent vertices
*/
public function getVertexParent(Vertex $vertex)
{
$parents = $this->getVerticesParent($vertex);
if (\count($parents) !== 1) {
if ($parents->isEmpty()) {
throw new UnderflowException('No parents for given vertex found');
} else {
throw new UnexpectedValueException('More than one parent');
}
}
return $parents->getVertexFirst();
}
/**
* get array of child vertices for given $vertex
*
* @param Vertex $vertex
* @return Vertices
* @throws UnexpectedValueException if the given $vertex contains invalid / parallel edges (check isTree()!)
*/
abstract public function getVerticesChildren(Vertex $vertex);
/**
* internal helper to get all parents vertices
*
* a valid tree vertex only ever has a single parent, except for the root,
* which has none.
*
* @param Vertex $vertex
* @return Vertices
* @throws UnexpectedValueException if the given $vertex contains invalid / parallel edges (check isTree()!)
*/
abstract protected function getVerticesParent(Vertex $vertex);
/**
* check if given vertex is a possible root (i.e. has no parent)
*
* @param Vertex $vertex
* @return bool
* @uses self::getVerticesParent()
*/
protected function isVertexPossibleRoot(Vertex $vertex)
{
return (\count($this->getVerticesParent($vertex)) === 0);
}
/**
* checks if the given $vertex is a leaf (outermost vertex with no children)
*
* @param Vertex $vertex
* @return bool
* @uses self::getVerticesChildren() to check given vertex has no children
*/
public function isVertexLeaf(Vertex $vertex)
{
return (\count($this->getVerticesChildren($vertex)) === 0);
}
/**
* checks if the given $vertex is an internal vertex (has children and is not root)
*
* @param Vertex $vertex
* @return bool
* @uses self::getVerticesParent() to check given vertex has a parent (is not root)
* @uses self::getVerticesChildren() to check given vertex has children (is not a leaf)
* @see \Graphp\Algorithms\Tree\Base::isVertexInternal() for more information
*/
public function isVertexInternal(Vertex $vertex)
{
return (!$this->getVerticesParent($vertex)->isEmpty() && !$this->getVerticesChildren($vertex)->isEmpty());
}
/**
* get degree of tree (maximum number of children)
*
* @return int
* @throws UnderflowException for empty graphs
* @uses Graph::getVertices()
* @uses self::getVerticesChildren()
*/
public function getDegree()
{
$max = null;
foreach ($this->graph->getVertices() as $vertex) {
$num = \count($this->getVerticesChildren($vertex));
if ($max === null || $num > $max) {
$max = $num;
}
}
if ($max === null) {
throw new UnderflowException('No vertices found');
}
return $max;
}
/**
* get depth of given $vertex (number of edges between root vertex)
*
* root has depth zero
*
* @param Vertex $vertex
* @return int
* @throws UnderflowException for empty graphs
* @throws UnexpectedValueException if there's no path to root node (check isTree()!)
* @uses self::getVertexRoot()
* @uses self::getVertexParent() for each step
*/
public function getDepthVertex(Vertex $vertex)
{
$root = $this->getVertexRoot();
$depth = 0;
while ($vertex !== $root) {
$vertex = $this->getVertexParent($vertex);
++$depth;
}
return $depth;
}
/**
* get height of this tree (longest downward path to a leaf)
*
* a single vertex graph has height zero
*
* @return int
* @throws UnderflowException for empty graph
* @uses self::getVertexRoot()
* @uses self::getHeightVertex()
*/
public function getHeight()
{
return $this->getHeightVertex($this->getVertexRoot());
}
/**
* get height of given vertex (longest downward path to a leaf)
*
* leafs has height zero
*
* @param Vertex $vertex
* @return int
* @uses self::getVerticesChildren() to get children of given vertex
* @uses self::getHeightVertex() to recurse into sub-children
*/
public function getHeightVertex(Vertex $vertex)
{
$max = 0;
foreach ($this->getVerticesChildren($vertex) as $vertex) {
$height = $this->getHeightVertex($vertex) + 1;
if ($height > $max) {
$max = $height;
}
}
return $max;
}
/**
* get all vertices that are in the subtree of the given $vertex (which IS included)
*
* root vertex will return the whole tree, leaf vertices will only return themselves
*
* @param Vertex $vertex
* @throws UnexpectedValueException if there are invalid edges (check isTree()!)
* @return Vertices
* @uses self::getVerticesSubtreeRecursive()
* @uses self::getVerticesSubtree()
*/
public function getVerticesSubtree(Vertex $vertex)
{
$vertices = array();
$this->getVerticesSubtreeRecursive($vertex, $vertices);
return new Vertices($vertices);
}
/**
* helper method to get recursively get subtree for given $vertex
*
* @param Vertex $vertex
* @param Vertex[] $vertices
* @throws UnexpectedValueException if multiple links were found to the given edge (check isTree()!)
* @uses self::getVerticesChildren()
* @uses self::getVerticesSubtreeRecursive() to recurse into subtrees
*/
private function getVerticesSubtreeRecursive(Vertex $vertex, array &$vertices)
{
$vid = $vertex->getId();
if (isset($vertices[$vid])) {
throw new UnexpectedValueException('Multiple links found');
}
$vertices[$vid] = $vertex;
foreach ($this->getVerticesChildren($vertex) as $vertexChild) {
$this->getVerticesSubtreeRecursive($vertexChild, $vertices);
}
}
/**
* get all vertices below the given $vertex (which is NOT included)
*
* think of this as the recursive version of getVerticesChildren()
*
* @param Vertex $vertex
* @return Vertices
* @throws UnexpectedValueException if there are invalid edges (check isTree()!)
* @uses self::getVerticesSubtree()
*/
public function getVerticesDescendant(Vertex $vertex)
{
$vertices = $this->getVerticesSubtree($vertex)->getMap();
unset($vertices[$vertex->getId()]);
return new Vertices($vertices);
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Graphp\Algorithms\Tree;
use Fhaculty\Graph\Exception\UnexpectedValueException;
use Fhaculty\Graph\Vertex;
use Graphp\Algorithms\Tree\BaseDirected as DirectedTree;
/**
* Alternative InTree implementation where Edges "point towards" root Vertex
*
* ROOT
* ^ ^
* / \
* A B
* ^
* \
* C
*
* @link http://en.wikipedia.org/wiki/Spaghetti_stack
* @see DirectedTree for more information on directed, rooted trees
*/
class InTree extends DirectedTree
{
public function getVerticesChildren(Vertex $vertex)
{
$vertices = $vertex->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;
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Graphp\Algorithms\Tree;
use Fhaculty\Graph\Exception\UnexpectedValueException;
use Fhaculty\Graph\Vertex;
use Graphp\Algorithms\Tree\BaseDirected as DirectedTree;
/**
* Usual OutTree implementation where Edges "point away" from root Vertex
*
* ROOT
* / \
* A <--/ \--> 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;
}
}

View File

@@ -0,0 +1,143 @@
<?php
namespace Graphp\Algorithms\Tree;
use Fhaculty\Graph\Edge\Undirected as UndirectedEdge;
use Fhaculty\Graph\Exception\UnexpectedValueException;
use Fhaculty\Graph\Set\Vertices;
use Fhaculty\Graph\Vertex;
use Graphp\Algorithms\Tree\Base as Tree;
/**
* Undirected tree implementation
*
* An undirected tree is a connected Graph (single component) with no cycles.
* Every undirected Tree is an undirected Graph, but not every undirected Graph
* is an undirected Tree.
*
* A
* / \
* B C
* / \
* D E
*
* Undirected trees do not have special root Vertices (like the above picture
* might suggest). The above tree Graph can also be equivalently be pictured
* like this:
*
* C
* /|\
* / | \
* A D E
* /
* B
*
* If you're looking for a tree with a designated root Vertex, use directed,
* rooted trees (BaseDirected).
*
* @link http://en.wikipedia.org/wiki/Tree_%28graph_theory%29
* @see BaseDirected if you're looking for directed, rooted trees
*/
class Undirected extends Tree
{
/**
* checks if this is a tree
*
* @return bool
* @uses Vertices::isEmpty() to skip null Graphs (a Graph with no Vertices is *NOT* a valid tree)
* @uses Vertices::getVertexFirst() to get get get random "root" Vertex to start search from
* @uses self::getVerticesSubtreeRecursive() to count number of vertices connected to root
*/
public function isTree()
{
if ($this->graph->getVertices()->isEmpty()) {
return false;
}
// every vertex can represent a root vertex, so just pick one
$root = $this->graph->getVertices()->getVertexFirst();
$vertices = array();
try {
$this->getVerticesSubtreeRecursive($root, $vertices, null);
} catch (UnexpectedValueException $e) {
return false;
}
return (\count($vertices) === \count($this->graph->getVertices()));
}
/**
* checks if the given $vertex is a leaf (outermost vertex with exactly one edge)
*
* @param Vertex $vertex
* @return bool
* @uses Degree::getDegreeVertex()
*/
public function isVertexLeaf(Vertex $vertex)
{
return ($this->degree->getDegreeVertex($vertex) === 1);
}
/**
* checks if the given $vertex is an internal vertex (inner vertex with at least 2 edges)
*
* @param Vertex $vertex
* @return bool
* @uses Degree::getDegreeVertex()
*/
public function isVertexInternal(Vertex $vertex)
{
return ($this->degree->getDegreeVertex($vertex) >= 2);
}
/**
* get subtree for given Vertex and ignore path to "parent" ignoreVertex
*
* @param Vertex $vertex
* @param Vertex[] $vertices
* @param Vertex|null $ignore
* @throws UnexpectedValueException for cycles or directed edges (check isTree()!)
* @uses self::getVerticesNeighbor()
* @uses self::getVerticesSubtreeRecursive() to recurse into sub-subtrees
*/
private function getVerticesSubtreeRecursive(Vertex $vertex, array &$vertices, Vertex $ignore = null)
{
if (isset($vertices[$vertex->getId()])) {
// vertex already visited => must be a cycle
throw new UnexpectedValueException('Vertex already visited');
}
$vertices[$vertex->getId()] = $vertex;
foreach ($this->getVerticesNeighbor($vertex) as $vertexNeighboor) {
if ($vertexNeighboor === $ignore) {
// ignore source vertex only once
$ignore = null;
continue;
}
$this->getVerticesSubtreeRecursive($vertexNeighboor, $vertices, $vertex);
}
}
/**
* get neighbor vertices for given start vertex
*
* @param Vertex $vertex
* @return Vertices (might include possible duplicates)
* @throws UnexpectedValueException for directed edges
* @uses Vertex::getEdges()
* @uses Edge::getVertexToFrom()
* @see Vertex::getVerticesEdge()
*/
private function getVerticesNeighbor(Vertex $vertex)
{
$vertices = array();
foreach ($vertex->getEdges() as $edge) {
if (!$edge instanceof UndirectedEdge) {
throw new UnexpectedValueException('Directed edge encountered');
}
$vertices[] = $edge->getVertexToFrom($vertex);
}
return new Vertices($vertices);
}
}

105
vendor/graphp/algorithms/src/Weight.php vendored Normal file
View File

@@ -0,0 +1,105 @@
<?php
namespace Graphp\Algorithms;
use Fhaculty\Graph\Graph;
/**
* Basic algorithms for working with the (total) weight of a Graph/Walk
*
* A weighted graph associates a label (weight) with every edge in the graph.
* Sometimes the word cost is used instead of weight. The term network is a
* synonym for a weighted graph.
*
* @link http://en.wikipedia.org/wiki/Glossary_of_graph_theory#Weighted_graphs_and_networks
*/
class Weight extends BaseDual
{
/**
* checks whether this graph has any weighted edges
*
* edges usually have no weight attached. a weight explicitly set to (int) 0
* will be considered as 'weighted'.
*
* @return bool
* @uses Edge::getWeight()
*/
public function isWeighted()
{
foreach ($this->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;
}
}

View File

@@ -0,0 +1,94 @@
<?php
use Fhaculty\Graph\Graph;
use Graphp\Algorithms\Bipartit as AlgorithmBipartit;
class BipartitTest extends TestCase
{
public function testGraphEmpty()
{
$graph = new Graph();
$alg = new AlgorithmBipartit($graph);
$this->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();
}
}

View File

@@ -0,0 +1,65 @@
<?php
use Fhaculty\Graph\Graph;
use Graphp\Algorithms\Complete as AlgorithmComplete;
class CompleteTest extends TestCase
{
public function testGraphEmptyK0()
{
$graph = new Graph();
$alg = new AlgorithmComplete($graph);
$this->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());
}
}

View File

@@ -0,0 +1,97 @@
<?php
use Fhaculty\Graph\Graph;
use Graphp\Algorithms\ConnectedComponents as AlgorithmConnected;
class ConnectedComponentsTest extends TestCase
{
public function testNullGraph()
{
$graph = new Graph();
$alg = new AlgorithmConnected($graph);
$this->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);
$v1->createEdge($v2);
$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);
}
}

View File

@@ -0,0 +1,94 @@
<?php
use Fhaculty\Graph\Exception\UnderflowException;
use Fhaculty\Graph\Exception\UnexpectedValueException;
use Fhaculty\Graph\Graph;
use Graphp\Algorithms\Degree as AlgorithmDegree;
class DegreeTest extends TestCase
{
public function testGraphEmpty()
{
$graph = new Graph();
$alg = new AlgorithmDegree($graph);
try {
$alg->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));
}
}

View File

@@ -0,0 +1,151 @@
<?php
use Fhaculty\Graph\Graph;
use Graphp\Algorithms\DetectNegativeCycle;
class DetectNegativeCycleTest extends TestCase
{
public function testNullGraph()
{
$graph = new Graph();
$alg = new DetectNegativeCycle($graph);
$this->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);
$v1->createEdgeTo($v2)->setWeight(-1);
$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);
$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);
$v1->createEdgeTo($v2)->setWeight(1);
$v2->createEdgeTo($v3)->setWeight(1);
$v3->createEdgeTo($v4)->setWeight(1);
$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);
$v1->createEdge($v2);
$v3->createEdgeTo($v4)->setWeight(-1);
$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));
}
}

View File

@@ -0,0 +1,58 @@
<?php
use Fhaculty\Graph\Graph;
use Graphp\Algorithms\Directed as AlgorithmDirected;
class DirectedTest extends TestCase
{
public function testGraphEmpty()
{
$graph = new Graph();
$alg = new AlgorithmDirected($graph);
$this->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());
}
}

View File

@@ -0,0 +1,45 @@
<?php
use Fhaculty\Graph\Graph;
use Graphp\Algorithms\Eulerian as AlgorithmEulerian;
class EulerianTest extends TestCase
{
public function testGraphEmpty()
{
$graph = new Graph();
$alg = new AlgorithmEulerian($graph);
$this->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());
}
}

View File

@@ -0,0 +1,98 @@
<?php
use Fhaculty\Graph\Graph;
use Graphp\Algorithms\Flow as AlgorithmFlow;
class FlowaTest extends TestCase
{
public function testGraphEmpty()
{
$graph = new Graph();
$alg = new AlgorithmFlow($graph);
$this->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));
}
}

View File

@@ -0,0 +1,62 @@
<?php
use Fhaculty\Graph\Graph;
use Graphp\Algorithms\Groups as AlgorithmGroups;
class GroupsTest extends TestCase
{
public function testGraphEmpty()
{
$graph = new Graph();
$alg = new AlgorithmGroups($graph);
$this->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());
}
}

View File

@@ -0,0 +1,57 @@
<?php
use Fhaculty\Graph\Graph;
use Graphp\Algorithms\Loop as AlgorithmLoop;
class LoopTest extends TestCase
{
public function testGraphEmpty()
{
$graph = new Graph();
$alg = new AlgorithmLoop($graph);
$this->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));
}
}

View File

@@ -0,0 +1,165 @@
<?php
use Fhaculty\Graph\Graph;
use Graphp\Algorithms\MaxFlow\EdmondsKarp as AlgorithmMaxFlowEdmondsKarp;
use PHPUnit\Framework\TestCase;
class EdmondsKarpTest extends TestCase
{
public function testEdgeDirected()
{
// 0 -[0/10]-> 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);
$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);
}
}

View File

@@ -0,0 +1,63 @@
<?php
use Fhaculty\Graph\Graph;
use Graphp\Algorithms\MaximumMatching\Flow;
use PHPUnit\Framework\TestCase;
class FlowTest extends TestCase
{
// /**
// * run algorithm with small graph and check result against known result
// */
// public function testKnownResult()
// {
// $loader = new EdgeListBipartit(PATH_DATA . 'Matching_100_100.txt');
// $loader->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();
}
}

View File

@@ -0,0 +1,203 @@
<?php
use Fhaculty\Graph\Graph;
use Graphp\Algorithms\MinimumCostFlow\Base;
abstract class BaseMcfTest extends TestCase
{
/**
*
* @param Graph $graph
* @return Base
*/
abstract protected function createAlgorithm(Graph $graph);
public function testNull()
{
$graph = new Graph();
$alg = $this->createAlgorithm($graph);
$this->assertEquals(0, $alg->getWeightFlow());
}
public function testSingleIntermediary()
{
$graph = new Graph();
$graph->createVertex(1);
$alg = $this->createAlgorithm($graph);
$this->assertEquals(0, $alg->getWeightFlow());
}
public function testSimpleEdge()
{
// 1(+2) -[0/2/2]-> 2(-2)
$graph = new Graph();
$v1 = $graph->createVertex(1)->setBalance(2);
$v2 = $graph->createVertex(2)->setBalance(-2);
$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();
$graph->createVertex(1)->setBalance(2);
$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();
}
}

View File

@@ -0,0 +1,12 @@
<?php
use Fhaculty\Graph\Graph;
use Graphp\Algorithms\MinimumCostFlow\CycleCanceling;
class CycleCancellingTest extends BaseMcfTest
{
protected function createAlgorithm(Graph $graph)
{
return new CycleCanceling($graph);
}
}

View File

@@ -0,0 +1,12 @@
<?php
use Fhaculty\Graph\Graph;
use Graphp\Algorithms\MinimumCostFlow\SuccessiveShortestPath;
class SuccessiveShortestPathTest extends BaseMcfTest
{
protected function createAlgorithm(Graph $graph)
{
return new SuccessiveShortestPath($graph);
}
}

View File

@@ -0,0 +1,167 @@
<?php
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Vertex;
use Graphp\Algorithms\MinimumSpanningTree\Base as MstBase;
abstract class BaseMstTest extends TestCase
{
/**
* @param Vertex $vertex
* @return MstBase
*/
abstract protected function createAlg(Vertex $vertex);
public function testIsolatedVertex()
{
$graph = new Graph();
$v1 = $graph->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);
$graph->createVertex(2);
$alg = $this->createAlg($v1);
$alg->getEdges();
}
}

View File

@@ -0,0 +1,24 @@
<?php
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Vertex;
use Graphp\Algorithms\MinimumSpanningTree\Kruskal;
class KruskalTest extends BaseMstTest
{
protected function createAlg(Vertex $vertex)
{
return new Kruskal($vertex->getGraph());
}
/**
* @expectedException UnexpectedValueException
*/
public function testNullGraphIsNotConsideredToBeConnected()
{
$graph = new Graph();
$alg = new Kruskal($graph);
$alg->getEdges();
}
}

View File

@@ -0,0 +1,12 @@
<?php
use Fhaculty\Graph\Vertex;
use Graphp\Algorithms\MinimumSpanningTree\Prim;
class PrimTest extends BaseMstTest
{
protected function createAlg(Vertex $vertex)
{
return new Prim($vertex);
}
}

View File

@@ -0,0 +1,97 @@
<?php
use Fhaculty\Graph\Graph;
use Graphp\Algorithms\Parallel as AlgorithmParallel;
class ParallelTest extends TestCase
{
public function testGraphEmpty()
{
$graph = new Graph();
$alg = new AlgorithmParallel($graph);
$this->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());
}
}

View File

@@ -0,0 +1,30 @@
<?php
use Fhaculty\Graph\Graph;
use Graphp\Algorithms\Property\GraphProperty;
class PropertyGraphTest extends TestCase
{
public function testEmptyIsEdgeless()
{
$graph = new Graph();
$alg = new GraphProperty($graph);
$this->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());
}
}

View File

@@ -0,0 +1,185 @@
<?php
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Walk;
use Graphp\Algorithms\Property\WalkProperty;
class WalkPropertyTest extends TestCase
{
public function testTrivialGraph()
{
$graph = new Graph();
$v1 = $graph->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);
$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());
}
}

View File

@@ -0,0 +1,206 @@
<?php
use Fhaculty\Graph\Graph;
use Graphp\Algorithms\ResidualGraph;
class ResidualGraphTest extends TestCase
{
public function testGraphEmpty()
{
$graph = new Graph();
$alg = new ResidualGraph($graph);
$residual = $alg->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);
}
public function testResidualGraphCanOptionallyKeepNullCapacityForEdgeWithZeroFlow()
{
// 1 -[0/2]-> 2
$graph = new Graph();
$v1 = $graph->createVertex(1);
$v2 = $graph->createVertex(2);
$v1->createEdgeTo($v2)->setFlow(0)->setCapacity(2);
// 1 -[0/2]-> 2
// ^ |
// \--[0/0]---/
$expected = new Graph();
$v1 = $expected->createVertex(1);
$v2 = $expected->createVertex(2);
$v1->createEdgeTo($v2)->setFlow(0)->setCapacity(2);
$v2->createEdgeTo($v1)->setFlow(0)->setCapacity(0);
$alg = new ResidualGraph($graph);
$alg->setKeepNullCapacity(true);
$residual = $alg->createGraph();
$this->assertGraphEquals($expected, $residual);
}
public function testResidualGraphCanOptionallyKeepNullCapacityForEdgeWithZeroCapacityRemaining()
{
// 1 -[2/2]-> 2
$graph = new Graph();
$v1 = $graph->createVertex(1);
$v2 = $graph->createVertex(2);
$v1->createEdgeTo($v2)->setFlow(2)->setCapacity(2);
// 1 -[0/0]-> 2
// ^ |
// \--[0/2]---/
$expected = new Graph();
$v1 = $expected->createVertex(1);
$v2 = $expected->createVertex(2);
$v1->createEdgeTo($v2)->setFlow(0)->setCapacity(0);
$v2->createEdgeTo($v1)->setFlow(0)->setCapacity(2);
$alg = new ResidualGraph($graph);
$alg->setKeepNullCapacity(true);
$residual = $alg->createGraph();
$this->assertGraphEquals($expected, $residual);
}
public function testParallelEdgesCanBeMerged()
{
// 1 -[1/2]-> 2
// | ^
// \--[2/3]---/
$graph = new Graph();
$v1 = $graph->createVertex(1);
$v2 = $graph->createVertex(2);
$v1->createEdgeTo($v2)->setFlow(1)->setCapacity(2);
$v1->createEdgeTo($v2)->setFlow(2)->setCapacity(3);
// 1 -[0/2]-> 2
// ^ |
// \--[0/3]---/
$expected = new Graph();
$v1 = $expected->createVertex(1);
$v2 = $expected->createVertex(2);
$v1->createEdgeTo($v2)->setFlow(0)->setCapacity(2);
$v2->createEdgeTo($v1)->setFlow(0)->setCapacity(3);
$alg = new ResidualGraph($graph);
$alg->setMergeParallelEdges(true);
$residual = $alg->createGraph();
$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();
}
}

View File

@@ -0,0 +1,55 @@
<?php
use Fhaculty\Graph\Graph;
use Graphp\Algorithms\Search\BreadthFirst;
class BreadthFirstSearchTest extends TestCase
{
public function providerMaxDepth()
{
return array(
"simple path (no limit)" => array(
"edges" => array(
array(1, 2), array(2, 3), array(3, 4), array(4, 5),
),
"subject" => 1,
"maxDepth" => null,
"expected" => array(1, 2, 3, 4, 5),
),
"simple path (limit = 0)" => array(
"edges" => array(
array(1, 2), array(2, 3), array(3, 4), array(4, 5),
),
"subject" => 1,
"maxDepth" => 0,
"expected" => array(1),
),
"simple path (limit = 1)" => array(
"edges" => array(
array(1, 2), array(2, 3), array(3, 4), array(4, 5),
),
"subject" => 1,
"maxDepth" => 1,
"expected" => array(1, 2),
),
);
}
/**
* @dataProvider providerMaxDepth
*/
public function testMaxDepth(array $edges, $subject, $maxDepth, array $expected)
{
$g = new Graph();
foreach ($edges as $e) {
$g->createVertex($e[0], true)->createEdgeTo($g->createVertex($e[1], true));
}
$a = new BreadthFirst($g->getVertex($subject));
if ($maxDepth !== null) {
$v = $a->getVertices($maxDepth);
} else {
$v = $a->getVertices(); // Simulate default
}
$this->assertSame($expected, $v->getIds());
}
}

View File

@@ -0,0 +1,169 @@
<?php
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Vertex;
use Graphp\Algorithms\ShortestPath\Base as ShortestPathAlg;
abstract class BaseShortestPathTest extends TestCase
{
/**
*
* @param Vertex $vertex
* @return ShortestPathAlg
*/
abstract protected function createAlg(Vertex $vertex);
abstract public function testGraphParallelNegative();
public function testGraphTrivial()
{
// 1
$graph = new Graph();
$v1 = $graph->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);
$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;
}
}

View File

@@ -0,0 +1,39 @@
<?php
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Vertex;
use Graphp\Algorithms\ShortestPath\BreadthFirst;
class BreadthFirstTest extends BaseShortestPathTest
{
protected function createAlg(Vertex $vertex)
{
return new BreadthFirst($vertex);
}
public function testGraphParallelNegative()
{
// 1 -[10]-> 2
// | ^
// \--[-1]---/
$graph = new Graph();
$v1 = $graph->createVertex(1);
$v2 = $graph->createVertex(2);
$e1 = $v1->createEdgeTo($v2)->setWeight(10);
$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);
}
}

View File

@@ -0,0 +1,32 @@
<?php
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Vertex;
use Graphp\Algorithms\ShortestPath\Dijkstra;
class DijkstraTest extends BaseShortestPathTest
{
protected function createAlg(Vertex $vertex)
{
return new Dijkstra($vertex);
}
/**
* @expectedException UnexpectedValueException
*/
public function testGraphParallelNegative()
{
// 1 -[10]-> 2
// | ^
// \--[-1]---/
$graph = new Graph();
$v1 = $graph->createVertex(1);
$v2 = $graph->createVertex(2);
$v1->createEdgeTo($v2)->setWeight(10);
$v1->createEdgeTo($v2)->setWeight(-1);
$alg = $this->createAlg($v1);
$alg->getEdges();
}
}

View File

@@ -0,0 +1,111 @@
<?php
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Vertex;
use Graphp\Algorithms\ShortestPath\MooreBellmanFord;
class MooreBellmanFordTest extends BaseShortestPathTest
{
protected function createAlg(Vertex $vertex)
{
return new MooreBellmanFord($vertex);
}
public function testGraphParallelNegative()
{
// 1 -[10]-> 2
// | ^
// \--[-1]---/
$graph = new Graph();
$v1 = $graph->createVertex(1);
$v2 = $graph->createVertex(2);
$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);
$v1->createEdge($v2)->setWeight(-10);
$alg = $this->createAlg($v1);
$cycle = $alg->getCycleNegative();
$this->assertInstanceOf('Fhaculty\Graph\Walk', $cycle);
}
public function testLoopNegativeWeightIsCycle()
{
// 1 -[-10]-> 1
$graph = new Graph();
$v1 = $graph->createVertex(1);
$v1->createEdge($v1)->setWeight(-10);
$alg = $this->createAlg($v1);
$cycle = $alg->getCycleNegative();
$this->assertInstanceOf('Fhaculty\Graph\Walk', $cycle);
}
public function testNegativeComponentHasCycle()
{
// 1 -[1]-> 2 3 --[-1]--> 4
// ^ |
// \---[-2]----/
$graph = new Graph();
$v1 = $graph->createVertex(1);
$v2 = $graph->createVertex(2);
$v3 = $graph->createVertex(3);
$v4 = $graph->createVertex(4);
$v1->createEdgeTo($v2)->setWeight(1);
$v3->createEdgeTo($v4)->setWeight(-1);
$v4->createEdgeTo($v3)->setWeight(-2);
// second component has a cycle
$alg = $this->createAlg($v3);
$cycle = $alg->getCycleNegative();
assert(isset($cycle));
// first component does not have a cycle
$alg = $this->createAlg($v1);
$this->expectException('UnderflowException');
$alg->getCycleNegative();
}
public function expectException($class)
{
if (\method_exists($this, 'setExpectedException')) {
$this->setExpectedException($class);
} else {
parent::expectException($class);
}
}
}

View File

@@ -0,0 +1,61 @@
<?php
use Fhaculty\Graph\Graph;
use Graphp\Algorithms\Symmetric as AlgorithmSymmetric;
class SymmetricTest extends TestCase
{
public function testGraphEmpty()
{
$graph = new Graph();
$alg = new AlgorithmSymmetric($graph);
$this->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());
}
}

View File

@@ -0,0 +1,75 @@
<?php
use Fhaculty\Graph\Graph;
use Graphp\Algorithms\TopologicalSort;
class TopologicalSortTest extends TestCase
{
public function testGraphEmpty()
{
$graph = new Graph();
$alg = new TopologicalSort($graph);
$this->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();
}
}

View File

@@ -0,0 +1,41 @@
<?php
use Fhaculty\Graph\Graph;
use Graphp\Algorithms\TravelingSalesmanProblem\Bruteforce;
class BruteforceTest extends TestCase
{
public function testGetWeightReturnsExpectedWeightForSimpleCycle()
{
$graph = new Graph();
$a = $graph->createVertex();
$b = $graph->createVertex();
$c = $graph->createVertex();
$a->createEdgeTo($b)->setWeight(1);
$b->createEdgeTo($c)->setWeight(2);
$c->createEdgeTo($a)->setWeight(3);
$alg = new Bruteforce($graph);
$this->assertEquals(6, $alg->getWeight());
}
public function testSetUpperLimitMstSetsExactLimitForSimpleCycle()
{
$graph = new Graph();
$a = $graph->createVertex();
$b = $graph->createVertex();
$c = $graph->createVertex();
$a->createEdgeTo($b)->setWeight(1);
$b->createEdgeTo($c)->setWeight(2);
$c->createEdgeTo($a)->setWeight(3);
$alg = new Bruteforce($graph);
$alg->setUpperLimitMst();
$ref = new ReflectionProperty($alg, 'upperLimit');
$ref->setAccessible(true);
$this->assertEquals(6, $ref->getValue($alg));
}
}

View File

@@ -0,0 +1,197 @@
<?php
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Set\Vertices;
use Graphp\Algorithms\Tree\BaseDirected;
abstract class BaseDirectedTest extends TestCase
{
/**
*
* @param Graph $graph
* @return BaseDirected
*/
abstract protected function createTreeAlg(Graph $graph);
/**
* @return Graph
*/
abstract protected function createGraphNonTree();
/**
* @return Graph
*/
abstract protected function createGraphTree();
/**
* @return Graph
*/
abstract protected function createGraphParallelEdge();
public function testNullGraph()
{
$graph = new Graph();
$tree = $this->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());
}
}

View File

@@ -0,0 +1,48 @@
<?php
use Fhaculty\Graph\Graph;
use Graphp\Algorithms\Tree\InTree;
class InTreeTest extends BaseDirectedTest
{
protected function createGraphTree()
{
// c1 -> 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;
}
}

View File

@@ -0,0 +1,48 @@
<?php
use Fhaculty\Graph\Graph;
use Graphp\Algorithms\Tree\OutTree;
class OutTreeTest extends BaseDirectedTest
{
protected function createGraphTree()
{
// c1 <- root -> 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;
}
}

View File

@@ -0,0 +1,113 @@
<?php
use Fhaculty\Graph\Graph;
use Graphp\Algorithms\Tree\Undirected;
class UndirectedTest extends TestCase
{
protected function createTree(Graph $graph)
{
return new Undirected($graph);
}
public function testNullGraph()
{
$graph = new Graph();
$tree = $this->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());
}
}

View File

@@ -0,0 +1,58 @@
<?php
use Fhaculty\Graph\Graph;
use Graphp\Algorithms\Weight as AlgorithmWeight;
class WeightTest extends TestCase
{
public function testGraphEmpty()
{
$graph = new Graph();
$alg = new AlgorithmWeight($graph);
$this->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());
}
}

View File

@@ -0,0 +1,98 @@
<?php
use Fhaculty\Graph\Edge\Directed;
use Fhaculty\Graph\Edge\Base as Edge;
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Vertex;
use PHPUnit\Framework\TestCase as BaseTestCase;
(include_once __DIR__ . '/../vendor/autoload.php') OR die(PHP_EOL . 'ERROR: composer autoloader not found, run "composer install" or see README for instructions' . PHP_EOL);
class TestCase extends BaseTestCase
{
protected function assertGraphEquals(Graph $expected, Graph $actual)
{
$f = function(Graph $graph){
$ret = \get_class($graph);
$ret .= PHP_EOL . 'vertices: ' . \count($graph->getVertices());
$ret .= PHP_EOL . 'edges: ' . \count($graph->getEdges());
return $ret;
};
// assert graph base parameters are equal
$this->assertEquals($f($expected), $f($actual));
// next, assert that all vertices in both graphs are the same
// each vertex has a unique ID, therefor it's easy to search a matching partner
// do not use assertVertexEquals() in order to not increase assertion counter
foreach ($expected->getVertices()->getMap() as $vid => $vertex) {
$other = $actual->getVertex($vid);
assert(isset($other));
if ($this->getVertexDump($vertex) !== $this->getVertexDump($vertex)) {
$this->fail();
}
}
// next, assert that all edges in both graphs are the same
// assertEdgeEquals() does not work, as the order of the edges is unknown
// therefor, build an array of edge dump and make sure each entry has a match
$edgesExpected = array();
foreach ($expected->getEdges() as $edge) {
$edgesExpected[] = $this->getEdgeDump($edge);
}
foreach ($actual->getEdges() as $edge) {
$dump = $this->getEdgeDump($edge);
$pos = \array_search($dump, $edgesExpected, true);
if ($pos === false) {
$this->fail('given edge ' . $dump . ' not found');
} else {
unset($edgesExpected[$pos]);
}
}
}
protected function assertVertexEquals(Vertex $expected, Vertex $actual)
{
$this->assertEquals($this->getVertexDump($expected), $this->getVertexDump($actual));
}
protected function assertEdgeEquals(Edge $expected, Edge $actual)
{
$this->assertEquals($this->getEdgeDump($expected), $this->getEdgeDump($actual));
}
private function getVertexDump(Vertex $vertex)
{
$ret = \get_class($vertex);
$ret .= PHP_EOL . 'id: ' . $vertex->getId();
$ret .= PHP_EOL . 'attributes: ' . \json_encode($vertex->getAttributeBag()->getAttributes());
$ret .= PHP_EOL . 'balance: ' . $vertex->getBalance();
$ret .= PHP_EOL . 'group: ' . $vertex->getGroup();
return $ret;
}
private function getEdgeDump(Edge $edge)
{
$ret = \get_class($edge) . ' ';
if ($edge instanceof 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;
}
}

2
vendor/graphp/graphviz/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/vendor
/composer.lock

34
vendor/graphp/graphviz/.travis.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
language: php
php:
# - 5.3 # requires old distro, see below
- 5.4
- 5.5
- 5.6
- 7.0
- 7.1
- 7.2
- hhvm # ignore errors, see below
# lock distro so future defaults will not break the build
dist: trusty
matrix:
include:
- php: 5.3
dist: precise
allow_failures:
- php: hhvm
sudo: false
addons:
apt:
packages:
- graphviz
install:
- composer install --no-interaction
script:
- vendor/bin/phpunit --coverage-text

44
vendor/graphp/graphviz/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,44 @@
# Changelog
## 0.2.2 (2019-10-04)
* Feature: Omit root graph name unless explicitly assigned via `graphviz.name` attribute.
(#28 by @rhelms and @clue)
```php
$graph = new Graph();
$graph->setAttribute('graphviz.name', 'g');
```
* Feature: Remove unneeded dependency on `graphp/algorithms`.
(#39 by @clue)
* Feature / Fix: Use UTF-8 encoding (Unicode) by default and respect charset attribute.
(#27 by @Ithilias and @clue)
* Fix: Fix representing directed loop edges as directed edge
(#37 by @clue)
* Add examples and documentation for GraphViz attributes, labels and record shapes.
(#26 by @clue)
* Update test suite to support PHPUnit 6 and PHPUnit 5 and support running on legacy PHP 5.3 through PHP 7.2 and HHVM.
(#24 by @clue)
## 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))

22
vendor/graphp/graphviz/LICENSE vendored Normal file
View File

@@ -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 <https://github.com/clue/graph/graphs/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.

425
vendor/graphp/graphviz/README.md vendored Normal file
View File

@@ -0,0 +1,425 @@
# graphp/graphviz [![Build Status](https://travis-ci.org/graphp/graphviz.svg?branch=master)](https://travis-ci.org/graphp/graphviz)
GraphViz graph drawing for the mathematical graph/network library GraPHP.
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.
**Table of contents**
* [Quickstart examples](#quickstart-examples)
* [Attributes](#attributes)
* [Graph attributes](#graph-attributes)
* [Vertex attributes](#vertex-attributes)
* [Edge attributes](#edge-attributes)
* [Labels](#labels)
* [Vertex labels](#vertex-labels)
* [Edge labels](#edge-labels)
* [HTML-like labels](#html-like-labels)
* [Record-based nodes](#record-based-nodes)
* [Install](#install)
* [Tests](#tests)
* [License](#license)
## 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->setAttribute('graphviz.color', 'red');
$edge = $blue->createEdgeTo($red);
$edge->setAttribute('graphviz.color', 'grey');
$graphviz = new Graphp\GraphViz\GraphViz();
$graphviz->display($graph);
````
The above code will open your default image viewer with the following image:
![red-blue](examples/01-simple.png)
See also the [examples](examples/).
## Attributes
GraphViz supports a number of attributes on the graph instance itself, each
vertex instance (GraphViz calls these "nodes") and edge instance. Any of these
GraphViz attributes are supported by this library and have to be assigned using
GraPHP attributes as documented below.
For the full list of all GraphViz attributes, please refer to the
[GraphViz documentation](https://graphviz.gitlab.io/_pages/doc/info/attrs.html).
Note that all attributes use UTF-8 encoding (Unicode) and will be quoted and
escaped by default, so a `ö` and `>` will appear as-is and will not be
interpreted as HTML. See also [HTML-like labels](#html-like-labels) below for
more details.
### Graph attributes
GraphViz supports a number of attributes on the graph instance itself. Any of
these GraphViz attributes are supported by this library and have to be assigned
on the graph instance with the `graphviz.graph.` prefix like this:
```php
$graph = new Fhaculty\Graph\Graph();
$graph->setAttribute('graphviz.graph.bgcolor', 'transparent');
```
> Note how this uses the `graphviz.graph.` prefix and not just `graphviz.`. This
is done for consistency reasons with respect to default vertex and edge
attributes as documented below.
For example, the `rankdir` attribute can be used to change the orientation to
horizontal mode (left to right) like this:
```php
$graph = new Fhaculty\Graph\Graph();
$graph->setAttribute('graphviz.graph.rankdir', 'LR');
$hello = $graph->createVertex('hello');
$world = $graph->createVertex('wörld');
$hello->createEdgeTo($world);
```
![html graph example](examples/02-html.png)
See also the [examples](examples/).
Additionally, this library accepts an optional `graphviz.name` attribute that
will be used as the name (or ID) for the root graph object in the DOT output if
given. Unless explicitly assigned, this will be omitted by default. It is common
to assign a `G` here, but usually there should be no need to assign this. Among
others, this may be used as the title or tooltip in SVG output.
```php
$graph = new Fhaculty\Graph\Graph();
$graph->setAttribute('graphviz.name', 'G');
$graph->createVertex('first');
```
### Vertex attributes
GraphViz supports a number of attributes on each vertex instance (GraphViz calls
these "node" attributes). Any of these GraphViz attributes are supported by this
library and have to be assigned on the respective vertex instance with the
`graphviz.` prefix like this:
```php
$graph = new Fhaculty\Graph\Graph();
$blue = $graph->createVertex('blue');
$blue->setAttribute('graphviz.color', 'blue');
```
Additionally, GraphViz also supports default attributes for all vertices. Any of
these GraphViz attributes are supported by this library and have to be assigned
on the graph instance with the `graphviz.node.` prefix like this:
```php
$graph = new Fhaculty\Graph\Graph();
$graph->setAttribute('graphviz.node.color', 'grey');
$grey = $graph->createVertex('grey');
```
These default attributes can be overriden on each vertex instance by explicitly
assigning the same attribute on the respective vertex instance like this:
```php
$graph = new Fhaculty\Graph\Graph();
$graph->setAttribute('graphviz.node.color', 'grey');
$blue = $graph->createVertex('blue');
$blue->setAttribute('graphviz.color', 'blue');
```
> Note how this uses the `graphviz.node.` prefix and not `graphviz.vertex.`. This
is done for consistency reasons with respect to how GraphViz assigns these
default attributes in its DOT output.
### Edge attributes
GraphViz supports a number of attributes on each edge instance. Any of these
GraphViz attributes are supported by this library and have to be assigned on the
respective edge instance with the `graphviz.` prefix like this:
```php
$graph = new Fhaculty\Graph\Graph();
$a = $graph->createVertex('a');
$b = $graph->createVertex('b');
$blue = $a->createEdgeTo($b);
$blue->setAttribute('graphviz.color', 'blue');
```
Additionally, GraphViz also supports default attributes for all edges. Any of
these GraphViz attributes are supported by this library and have to be assigned
on the graph instance with the `graphviz.edge.` prefix like this:
```php
$graph = new Fhaculty\Graph\Graph();
$graph->setAttribute('graphviz.edge.color', 'grey');
$a = $graph->createVertex('a');
$b = $graph->createVertex('b');
$grey = $a->createEdgeTo($b);
```
These default attributes can be overriden on each edge instance by explicitly
assigning the same attribute on the respective edge instance like this:
```php
$graph = new Fhaculty\Graph\Graph();
$graph->setAttribute('graphviz.edge.color', 'grey');
$a = $graph->createVertex('a');
$b = $graph->createVertex('b');
$blue = $a->createEdgeTo($b);
$blue->setAttribute('graphviz.color', 'blue');
```
## Labels
### Vertex labels
By default, GraphViz will always render the vertex ID as the label:
```php
$graph = new Fhaculty\Graph\Graph();
$blue = $graph->createVertex('blue');
```
If you assign a vertex balance, this library will automatically include a
`label` attribute that includes the balance value. The following example will
automatically assign `blue (+10)` as the label:
```php
$graph = new Fhaculty\Graph\Graph();
$blue = $graph->createVertex('blue');
$blue->setBalance(10);
```
You can use [vertex attributes](#vertex-attributes) to explicitly assign a
custom `label` attribute. Note that any balance value will still be appended
like in the previous example.
```php
$graph = new Fhaculty\Graph\Graph();
$blue = $graph->createVertex('blue');
$blue->setAttribute('graphviz.label', 'Hello world!');
```
Note that all [attributes](#attributes) will be quoted and escaped by default,
so a `>` will appear as-is and will not be interpreted as HTML. See also
[HTML-like labels](#html-like-labels) below for more details.
### Edge labels
By default, GraphViz will not render any label on an edge:
```php
$graph = new Fhaculty\Graph\Graph();
$a = $graph->createVertex('a');
$b = $graph->createVertex('b');
$edge = $a->createEdgeTo($b);
```
If you assign an edge flow, capacity or weight, this library will automatically
include a `label` attribute that includes these values. The following example
will automatically assign `100` as the label for the weighted edge:
```php
$graph = new Fhaculty\Graph\Graph();
$a = $graph->createVertex('a');
$b = $graph->createVertex('b');
$edge = $a->createEdgeTo($b);
$edge->setWeight(100);
```
The following example will automatically assign `4/10` as the label for an edge
with both flow and maximum capacity set:
```php
$graph = new Fhaculty\Graph\Graph();
$a = $graph->createVertex('a');
$b = $graph->createVertex('b');
$edge = $a->createEdgeTo($b);
$edge->setFlow(4);
$edge->setCapacity(10);
```
The following example will automatically assign `4/∞/100` as the label for a
weighted edge with a flow and unlimited capacity:
```php
$graph = new Fhaculty\Graph\Graph();
$a = $graph->createVertex('a');
$b = $graph->createVertex('b');
$edge = $a->createEdgeTo($b);
$edge->setFlow(4);
$edge->setCapacity(null);
$edge->setWeight(100);
```
You can use [edge attributes](#edge-attributes) to explicitly assign any
custom `label` attribute. Note that any flow, capacity or weight value will still
be appended like in the previous examples.
```php
$graph = new Fhaculty\Graph\Graph();
$a = $graph->createVertex('a');
$b = $graph->createVertex('b');
$edge = $a->createEdgeTo($b);
$edge->setAttribute('graphviz.label', 'important');
```
### HTML-like labels
Note that all [attributes](#attributes) will be quoted and escaped by default,
so a `>` will appear as-is and will not be interpreted as HTML. GraphViz also
supports [HTML-like labels](https://graphviz.gitlab.io/_pages/doc/info/shapes.html#html)
that support a subset of HTML features.
GraphViz requires any HTML-like label to be wrapped in `<` and `>` and only
supports a limited subset of HTML features as documented above. In order to
prevent automatic quoting and escaping, all attribute values have to be passed
to the static `GraphViz::raw()` helper like this:
```php
$graph = new Fhaculty\Graph\Graph();
$a = $graph->createVertex('Entity');
$a->setAttribute('graphviz.shape', 'none');
$a->setAttribute('graphviz.label', GraphViz::raw('<
<table cellspacing="0" border="0" cellborder="1">
<tr><td bgcolor="#eeeeee"><b>\N</b></td></tr>
<tr><td></td></tr><tr>
<td>+ touch()</td></tr>
</table>>'));
$b = $graph->createVertex('Block');
$b->createEdgeTo($a);
$b->setAttribute('graphviz.shape', 'none');
$b->setAttribute('graphviz.label', GraphViz::raw('<
<table cellspacing="0" border="0" cellborder="1">
<tr><td bgcolor="#eeeeee"><b>\N</b></td></tr>
<tr><td>- size:int</td></tr>
<tr><td>+ touch()</td></tr>
</table>>'));
```
![UML html graph example](examples/11-uml-html.png)
See also the [examples](examples/).
### Record-based nodes
Note that all [attributes](#attributes) will be quoted and escaped by default,
so a `>` will appear as-is and will not be interpreted as HTML. Similar to the
above [HTML-like labels](#html-like-labels), GraphViz also supports simple
[record-based nodes](https://graphviz.gitlab.io/_pages/doc/info/shapes.html#record)
using the `record` and `Mrecord` shape attributes and structured label attributes.
GraphViz requires any record-based node label to be quoted, but uses special
syntax to mark record fields and optional port names. In order to prevent
automatic quoting and escaping, all attribute values have to be quoted manually
and passed to the static `GraphViz::raw()` helper like this:
```php
$graph = new Fhaculty\Graph\Graph();
$a = $graph->createVertex();
$a->setAttribute('graphviz.shape', 'Mrecord');
$a->setAttribute('graphviz.label', GraphViz::raw('"<f0> left |<middle> middle |<f2> right"'));
$b = $graph->createVertex();
$b->setAttribute('graphviz.shape', 'Mrecord');
$b->setAttribute('graphviz.label', GraphViz::raw('"<f0> left |<f1> middle |<right> right"'));
// a:middle -> b:right
$edge = $a->createEdgeTo($b);
$edge->setAttribute('graphviz.tailport', 'middle');
$edge->setAttribute('graphviz.headport', 'right');
```
![records with ports graph example](examples/13-record-ports.png)
See also the [examples](examples/).
## Install
The recommended way to install this library is [through Composer](https://getcomposer.org).
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
This will install the latest supported version:
```bash
$ composer require graphp/graphviz:^0.2.2
```
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
This project aims to run on any platform and thus does not require any PHP
extensions and supports running on legacy PHP 5.3 through current PHP 7+ and
HHVM.
It's *highly recommended to use PHP 7+* for this project.
The graph drawing feature is powered by the excellent [GraphViz](https://www.graphviz.org)
software. This means you'll have to install GraphViz (`dot` executable).
The [Graphviz homepage](https://www.graphviz.org/download/) includes complete
installation instructions for most common platforms, users of Debian/Ubuntu-based
distributions may simply invoke:
```bash
$ sudo apt install graphviz
```
## Tests
To run the test suite, you first need to clone this repo and then install all
dependencies [through Composer](https://getcomposer.org):
```bash
$ composer install
```
To run the test suite, go to the project root and run:
```bash
$ php vendor/bin/phpunit
```
## License
Released under the terms of the permissive [MIT license](http://opensource.org/licenses/MIT).

18
vendor/graphp/graphviz/composer.json vendored Normal file
View File

@@ -0,0 +1,18 @@
{
"name": "graphp/graphviz",
"type": "library",
"description": "GraphViz graph drawing for the mathematical graph/network library GraPHP.",
"keywords": ["GraphViz", "graph drawing", "graph image", "dot output", "GraPHP"],
"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"
},
"require-dev": {
"phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
}
}

View File

@@ -0,0 +1,17 @@
<?php
require __DIR__ . '/../vendor/autoload.php';
$graph = new Fhaculty\Graph\Graph();
$blue = $graph->createVertex('blue');
$blue->setAttribute('graphviz.color', 'blue');
$red = $graph->createVertex('red');
$red->setAttribute('graphviz.color', 'red');
$edge = $blue->createEdgeTo($red);
$edge->setAttribute('graphviz.color', 'grey');
$graphviz = new Graphp\GraphViz\GraphViz();
$graphviz->display($graph);

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1,25 @@
<?php
// $ php -S localhost:8080 examples/02-html.php
require __DIR__ . '/../vendor/autoload.php';
$graph = new Fhaculty\Graph\Graph();
$graph->setAttribute('graphviz.graph.rankdir', 'LR');
$hello = $graph->createVertex('hello');
$world = $graph->createVertex('wörld');
$hello->createEdgeTo($world);
$graphviz = new Graphp\GraphViz\GraphViz();
$graphviz->setFormat('svg');
echo '<!DOCTYPE html>
<html>
<head>
<title>hello wörld</title>
<body>
' . $graphviz->createImageHtml($graph) . '
</body>
</html>
';

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Some files were not shown because too many files have changed in this diff Show More