Skip to content

Commit

Permalink
Merge pull request #8275 from yuvalr1neo/longest-path-path-docs
Browse files Browse the repository at this point in the history
Longest path - actual path docs
  • Loading branch information
jjaderberg authored Oct 24, 2023
2 parents cdc3eac + d492135 commit 73501bf
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.gds.doc;

import org.neo4j.gds.functions.AsNodeFunc;
import org.neo4j.gds.paths.dag.longestPath.DagLongestPathStreamProc;

import java.util.List;

class DagLongestPathDocTest extends SingleFileDocTestBase {

@Override
protected List<Class<?>> functions() {
return List.of(AsNodeFunc.class);
}

@Override
protected List<Class<?>> procedures() {
return List.of(
DagLongestPathStreamProc.class
);
}

@Override
protected String adocFile() {
return "pages/algorithms/dag/longest-path.adoc";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.gds.doc.syntax;

import java.util.List;

import static org.neo4j.gds.doc.syntax.SyntaxMode.STREAM;

class DagLongestPathSyntaxTest extends SyntaxTestBase {

protected Iterable<SyntaxModeMeta> syntaxModes() {
return List.of(
SyntaxModeMeta.of(STREAM)
);
}

@Override
protected String adocFile() {
return "pages/algorithms/dag/longest-path.adoc";
}
}
6 changes: 4 additions & 2 deletions doc/modules/ROOT/pages/algorithms/dag/dag-algorithms.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
Directed Acyclic Graphs (DAGs) are directed graphs that do not contain cycles.
These kind of graphs are commonly used to model dependencies between entities.

The canonical algorithm that goes hand in hand with DAGs is topological sort, for which GDS provides an efficient parallel implementation. Running topological sort is the best way to make sure the graph is a DAG.
The canonical algorithm that goes hand in hand with DAGs is topological sort, for which GDS provides an efficient parallel implementation.
Running topological sort is the best way to make sure the graph is a DAG.

Some of the problems that are computationally hard to solve in the general case can be solved efficiently when the scope is limited to DAGs. One of these is the longest path problem, for which GDS provides an efficient algorithm.
Some of the problems that are computationally hard to solve in the general case can be solved efficiently when the scope is limited to DAGs.
One of these is the longest path problem, for which GDS provides an efficient algorithm.

The Neo4j GDS library includes the following DAG algorithms:

Expand Down
69 changes: 47 additions & 22 deletions doc/modules/ROOT/pages/algorithms/dag/longest-path.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,18 @@ include::partial$/operations-reference/alpha-note.adoc[]

Finding the longest path that leads to a node in a graph is possible to do in linear time for the special case of DAGs.

GDS implementation for this algorithm is based on topological sort and takes linear time. If the graph is not a DAG, the runtime is still linear, but the results cannot be trusted. You can use xref:algorithms/dag/topological-sort.adoc[topological sort] to make sure the graph is a DAG.
GDS implementation for this algorithm is based on topological sort and takes linear time.
If the graph is not a DAG, the runtime is still linear, but the results cannot be trusted.
You can use xref:algorithms/dag/topological-sort.adoc[topological sort] to make sure the graph is a DAG.

The algorithm supports unweighted graphs and graphs with non negative weights.
The algorithm supports weighted and unweighted graphs.
Negative weights are currently unsupported.


=== Usage

One example for usage of this algorithm is in the context of a supply chain graph. If edges indicate the time to supply, then the distance of the longest path to a target node is the time required to manufacture the node from decision to completion.
One example for usage of this algorithm is in the context of a supply chain graph.
If edges indicate the time to supply, then the distance of the longest path to a target node is the time required to manufacture the node from decision to completion.


== Syntax
Expand All @@ -46,7 +50,13 @@ CALL gds.dag.longestPath.stream(
graphName: String,
configuration: Map
) YIELD
nodeId: Integer
index: Integer,
sourceNode: Integer,
targetNode: Integer,
totalCost: Float,
nodeIds: List of Integer,
costs: List of Float,
path: Path
----

include::partial$/algorithms/common-configuration/common-parameters.adoc[]
Expand All @@ -62,9 +72,14 @@ include::partial$/algorithms/common-configuration/common-stream-stats-configurat
.Results
[opts="header"]
|===
| Name | Type | Description
| targetNodeId | Integer | The ID of the node for which the path is computed
| distance | Double | The distance of the longest path that ends in the target node
| Name | Type | Description
| index | Integer | 0-based index of the found path.
| sourceNode | Integer | Source node of the path.
| targetNode | Integer | Target node of the path.
| totalCost | Float | Total cost from source to target.
| nodeIds | List of Integer | Node ids on the path in traversal order.
| costs | List of Float | Accumulated costs for each node on the path.
| path | Path | The path represented as Cypher entity.
|===

// include-with-stream
Expand All @@ -73,9 +88,6 @@ include::partial$/algorithms/common-configuration/common-stream-stats-configurat
// tabbed-example
====

[NOTE]
Current result does not contain the longest path itself, but only the distance of the path.


== Examples

Expand All @@ -91,7 +103,7 @@ CREATE
(n0:Goods {name: 'Timber'}),
(n1:Goods {name: 'Lumber'}),
(n2:Goods {name: 'Screws'}),
(n3:Workshop {name: 'Table Maker Inc.'}),
(n3:Workshop {name: 'Table Maker'}),
(n4:Product {name: 'Table'}),
(n0)-[:Processing {time: 1}]->(n1),
Expand All @@ -101,9 +113,13 @@ CREATE
----

This graph describes a simple supply chain of constructing a table in the Table Maker workshop.
In order to have lumber for the table, the workshop processes timber, which takes 1 day to complete. Once the lumber is ready, it is already in the workshop, therefor it takes zero time to ship it. However, the screws take 3 days to be shipped to the workshop. Only after the workshop has all the requirements met, the table can be constructed, a process that takes 1 day.
In order to have lumber for the table, the workshop processes timber, which takes 1 day to complete.
Once the lumber is ready, it is already in the workshop, therefor it takes zero time to ship it.
However, the screws take 3 days to be shipped to the workshop.
Only after the workshop has all the requirements met, the table can be constructed, a process that takes 1 day.

The longest path to the table node starts with the screws, then the workshop and then the table, in total: 4 days. This is the bottleneck path, and total time that takes to manufacture the table.
The longest path to the table node starts with the screws, then the workshop and then the table, in total: 4 days.
This is the bottleneck path, and total time that takes to manufacture the table.

.The following Cypher statement will project the graph to GDS:
[source, cypher, role=noplay setup-query]
Expand All @@ -127,19 +143,28 @@ For more details on the stream mode in general, see xref:common-usage/running-al
[source, cypher, role=noplay setup-query]
----
CALL gds.dag.longestPath.stream("g", {relationshipWeightProperty: "time"})
YIELD targetNodeId as target, distance
RETURN gds.util.asNode(target).name, distance
YIELD index, sourceNode, targetNode, totalCost, nodeIds, costs, path
RETURN
index,
gds.util.asNode(sourceNode).name AS sourceNode,
gds.util.asNode(targetNode).name AS targetNode,
totalCost,
[nodeId IN nodeIds | gds.util.asNode(nodeId).name] AS nodeNames,
costs,
nodes(path) as path
ORDER BY index
----
We use the utility function asNode to return the name of node instead of its ID to make results more readable.

We use the utility function `asNode` to return the name of node instead of its ID to make results more readable.

.Results
[opts="header"]
|===
| name | distance
|"Timber" | 0.0
|"Screws" | 0.0
|"Lumber" | 1.0
|"Table Maker" | 3.0
|"Table" | 4.0
| index | sourceNode | targetNode | totalCost | nodeNames | costs | path
| 0 | "Timber" | "Timber" | 0.0 | [Timber] | [0.0] | [Node[0]]
| 1 | "Timber" | "Lumber" | 1.0 | [Timber, Lumber] | [0.0, 1.0] | [Node[0], Node[1]]
| 2 | "Screws" | "Table Maker" | 3.0 | [Screws, Table Maker] | [0.0, 3.0] | [Node[2], Node[3]]
| 3 | "Screws" | "Screws" | 0.0 | [Screws] | [0.0] | [Node[2]]
| 4 | "Screws" | "Table" | 4.0 | [Screws, Table Maker, Table] | [0.0, 3.0, 4.0] | [Node[2], Node[3], Node[4]]
|===
--
26 changes: 20 additions & 6 deletions doc/modules/ROOT/pages/algorithms/dag/topological-sort.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,17 @@ include::partial$/operations-reference/alpha-note.adoc[]
A topological sorting of nodes in a graph is an ordering of the nodes in the graph where every node appears only after all the nodes pointing to it have appeared.
For example, for a graph with 4 nodes and these relations: `a->b`, `a->c`, `b->d`, `c->d`, there are two acceptable topological sorts: `a, b, c, d` and `a, c, b, d`.

The topological order of the nodes is defined only for directed acyclic graphs (DAGs). See xref:#topological-sort-cycles[below] for the expected result for graphs with cycles.
The topological order of the nodes is defined only for directed acyclic graphs (DAGs).
See xref:#topological-sort-cycles[below] for the expected result for graphs with cycles.

GDS provides an efficient parallel implementation for this algorithm.


[[topological-sort-cycles]]
=== Cycles

Running the algorithm on a graph with cycles will cause the omitting of part of the nodes from the sorting. The omitted nodes are:
Running the algorithm on a graph with cycles will cause the omitting of part of the nodes from the sorting.
The omitted nodes are:

1. Nodes that are part of a cycle (including self cycles)

Expand All @@ -43,11 +45,17 @@ image::example-graphs/{image-file}[Visualization of the example graph,align="cen

=== Usage

Topological ordering of the nodes is beneficial when you want to guarantee a node will only be processed after its dependencies were processed. This is very useful for dependency related tasks such as scheduling or calculations that derive values from their dependencies.
Topological ordering of the nodes is beneficial when you want to guarantee a node will only be processed after its dependencies were processed.
This is very useful for dependency related tasks such as scheduling or calculations that derive values from their dependencies.


==== Cycles detection

The algorithm can also be used to determine if the graph contains a cycle or not. If all the nodes in the graph appear in the sorting, there is no cycle in the graph. If some of the nodes are missing from the sorting, there is a cycle. It does not tell which nodes constitute the cycle, but it does give a clue, as described in the xref:#topological-sort-cycles[cycles] section.
The algorithm can also be used to determine if the graph contains a cycle or not.
If all the nodes in the graph appear in the sorting, there is no cycle in the graph.
If some of the nodes are missing from the sorting, there is a cycle.
It does not tell which nodes constitute the cycle, but it does give a clue, as described in the xref:#topological-sort-cycles[cycles] section.


==== Maximum distance from source

Expand Down Expand Up @@ -106,6 +114,7 @@ include::partial$/algorithms/topological-sort/specific-configuration.adoc[]
// tabbed-example
====


== Examples

:algorithm-name: Topological Sort
Expand Down Expand Up @@ -133,7 +142,8 @@ CREATE
(n5)-[:REQUIRED]->(n6)
----

This graph describes a simplified supply chain of building a house. Each part of the house cannot be worked on before its requirements are met.
This graph describes a simplified supply chain of building a house.
Each part of the house cannot be worked on before its requirements are met.
For example, we cannot build support before getting the steel, the skeleton is not ready until both support and base are ready.

.The following Cypher statement will project the graph to GDS:
Expand All @@ -145,8 +155,11 @@ WITH gds.graph.project("g", n, target, {}) AS g
RETURN g
----


=== Stream
The stream procedure streams the nodes in the graph ordered by a valid topological order. The nodes can then be processed one by one, guaranteeing that each node is processed only after its dependencies were processed.

The stream procedure streams the nodes in the graph ordered by a valid topological order.
The nodes can then be processed one by one, guaranteeing that each node is processed only after its dependencies were processed.

For more details on the stream mode in general, see xref:common-usage/running-algos.adoc#running-algos-stream[Stream].

Expand All @@ -160,6 +173,7 @@ YIELD nodeId, maxDistanceFromSource
RETURN gds.util.asNode(nodeId).name AS name, maxDistanceFromSource
ORDER BY maxDistanceFromSource, name
----

We use the utility function `asNode` to return the name of node instead of its ID to make results more readable.

.Results
Expand Down

0 comments on commit 73501bf

Please sign in to comment.