Skip to content

Added Edmonds-Karp Algorithm Implementation #48

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/AI-Algorithms-Graph-Components/AINetworkFlowEdge.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,9 @@ AINetworkFlowEdge >> flow: anObject [
AINetworkFlowEdge >> initialize [
flow:=0
]

{ #category : 'accessing' }
AINetworkFlowEdge >> residualCapacity [
"Return the residual capacity of this edge"
^ capacity - flow
]
102 changes: 102 additions & 0 deletions src/AI-Algorithms-Graph-Tests/AIEdmondsKarpTest.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
"
Test class for the Edmonds-Karp algorithm
"
Class {
#name : 'AIEdmondsKarpTest',
#superclass : 'TestCase',
#category : 'AI-Algorithms-Graph-Tests',
#package : 'AI-Algorithms-Graph-Tests'
}

{ #category : 'tests' }
AIEdmondsKarpTest >> testBasicMaxFlow [
"Test basic maximum flow computation"

| nodes edges edmondsKarp maxFlow |
nodes := #( $s $a $b $t ).
edges := #( #( $s $a 10 ) #( $s $b 8 ) #( $a $b 5 ) #( $a $t 10 ) #( $b $t 10 ) ).

edmondsKarp := AIEdmondsKarp new.
edmondsKarp nodes: nodes.
edmondsKarp
edges: edges
from: [ :each | each first ]
to: [ :each | each second ]
capacity: [ :each | each third ].

edmondsKarp source: $s sink: $t.
maxFlow := edmondsKarp run.

self assert: maxFlow equals: 18
]

{ #category : 'tests' }
AIEdmondsKarpTest >> testComplexNetwork [
"Test with a more complex flow network"

| nodes edges edmondsKarp maxFlow |
nodes := #( 1 2 3 4 5 6 ).
edges := #(
#( 1 2 16 ) #( 1 3 13 )
#( 2 3 10 ) #( 2 4 12 )
#( 3 2 4 ) #( 3 5 14 )
#( 4 3 9 ) #( 4 6 20 )
#( 5 4 7 ) #( 5 6 4 ) ).

edmondsKarp := AIEdmondsKarp new.
edmondsKarp nodes: nodes.
edmondsKarp
edges: edges
from: [ :each | each first ]
to: [ :each | each second ]
capacity: [ :each | each third ].

edmondsKarp source: 1 sink: 6.
maxFlow := edmondsKarp run.

self assert: maxFlow equals: 23
]

{ #category : 'tests' }
AIEdmondsKarpTest >> testNoPath [
"Test when there's no path from source to sink"

| nodes edges edmondsKarp maxFlow |
nodes := #( $a $b $c $d ).
edges := #( #( $a $b 5 ) #( $c $d 3 ) ).

edmondsKarp := AIEdmondsKarp new.
edmondsKarp nodes: nodes.
edmondsKarp
edges: edges
from: [ :each | each first ]
to: [ :each | each second ]
capacity: [ :each | each third ].

edmondsKarp source: $a sink: $d.
maxFlow := edmondsKarp run.

self assert: maxFlow equals: 0
]

{ #category : 'tests' }
AIEdmondsKarpTest >> testSingleEdge [
"Test with a single edge from source to sink"

| nodes edges edmondsKarp maxFlow |
nodes := #( $s $t ).
edges := #( #( $s $t 15 ) ).

edmondsKarp := AIEdmondsKarp new.
edmondsKarp nodes: nodes.
edmondsKarp
edges: edges
from: [ :each | each first ]
to: [ :each | each second ]
capacity: [ :each | each third ].

edmondsKarp source: $s sink: $t.
maxFlow := edmondsKarp run.

self assert: maxFlow equals: 15
]
175 changes: 175 additions & 0 deletions src/AI-Algorithms-Graph/AIEdmondsKarp.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
"
The Edmonds-Karp algorithm is an implementation of the Ford-Fulkerson method for computing the maximum flow in a flow network. It uses breadth-first search to find the shortest augmenting path from source to sink in terms of the number of edges.

The algorithm works by:
1. Initialize flow to 0 for all edges
2. While there exists an augmenting path from source to sink:
a. Find the shortest augmenting path using BFS
b. Find the minimum residual capacity along this path
c. Update the flow along this path
3. Return the maximum flow value

Time complexity: O(VE²) where V is the number of vertices and E is the number of edges.

Example usage:
|nodes edges edmondsKarp maxFlow|
nodes := #( $s $a $b $t ).
edges := #( #( $s $a 10 ) #( $s $b 8 ) #( $a $b 5 ) #( $a $t 10 ) #( $b $t 10 ) ).

edmondsKarp := AIEdmondsKarp new.
edmondsKarp nodes: nodes.
edmondsKarp
edges: edges
from: [ :each | each first ]
to: [ :each | each second ]
capacity: [ :each | each third ].

edmondsKarp source: $s sink: $t.
maxFlow := edmondsKarp run.
"
Class {
#name : 'AIEdmondsKarp',
#superclass : 'AIGraphAlgorithm',
#instVars : [
'source',
'sink',
'parent'
],
#category : 'AI-Algorithms-Graph-Maximum Flow',
#package : 'AI-Algorithms-Graph',
#tag : 'Maximum Flow'
}

{ #category : 'configuration' }
AIEdmondsKarp >> edgeClass [
^ AINetworkFlowEdge
]

{ #category : 'building - graph' }
AIEdmondsKarp >> edges: aCollection from: source to: target capacity: capacityFunction [
| edge reverseEdge |
aCollection do: [ :eModel |
edge := self addEdge: eModel from: source to: target.
edge ifNotNil: [
edge capacity: (capacityFunction value: eModel).
edge flow: 0 ].

"Add reverse edge with 0 capacity for residual graph"
reverseEdge := self addEdge: eModel from: target to: source.
reverseEdge ifNotNil: [
reverseEdge capacity: 0.
reverseEdge flow: 0 ] ]
]

{ #category : 'private' }
AIEdmondsKarp >> findAugmentingPathBFS [
"Find an augmenting path from source to sink using BFS.
Returns true if a path exists, false otherwise.
Updates the parent array to reconstruct the path."

| queue visited |
queue := LinkedList new.
visited := Set new.
parent := Dictionary new.

queue addLast: source.
visited add: source.

[ queue isNotEmpty ] whileTrue: [
| current |
current := queue removeFirst.

current = sink ifTrue: [ ^ true ].

current outgoingEdges do: [ :edge |
| neighbor residualCapacity |
neighbor := edge to.
residualCapacity := edge capacity - edge flow.

(visited includes: neighbor) not & (residualCapacity > 0) ifTrue: [
queue addLast: neighbor.
visited add: neighbor.
parent at: neighbor put: edge ] ] ].

^ false
]

{ #category : 'private' }
AIEdmondsKarp >> findBottleneckCapacity [
"Find the minimum residual capacity along the augmenting path"

| current minCapacity |
current := sink.
minCapacity := Float infinity.

[ current ~= source ] whileTrue: [
| edge residualCapacity |
edge := parent at: current.
residualCapacity := edge capacity - edge flow.
minCapacity := minCapacity min: residualCapacity.
current := edge from ].

^ minCapacity
]

{ #category : 'private' }
AIEdmondsKarp >> findReverseEdge: anEdge [
"Find the reverse edge for the given edge"

^ anEdge to outgoingEdges
detect: [ :edge | edge to = anEdge from ]
ifNone: [ nil ]
]

{ #category : 'initialization' }
AIEdmondsKarp >> initialize [
super initialize.
parent := Dictionary new
]

{ #category : 'configuration' }
AIEdmondsKarp >> nodeClass [
^ AIPathDistanceNode
]

{ #category : 'running' }
AIEdmondsKarp >> run [
"Execute the Edmonds-Karp algorithm and return the maximum flow value"

| maxFlow pathFlow |
maxFlow := 0.

[ self findAugmentingPathBFS ] whileTrue: [
pathFlow := self findBottleneckCapacity.
self updateFlowAlongPath: pathFlow.
maxFlow := maxFlow + pathFlow ].

^ maxFlow
]

{ #category : 'accessing' }
AIEdmondsKarp >> source: sourceNode sink: sinkNode [
source := self findNode: sourceNode.
sink := self findNode: sinkNode
]

{ #category : 'private' }
AIEdmondsKarp >> updateFlowAlongPath: flowValue [
"Update the flow along the augmenting path found by BFS"

| current |
current := sink.

[ current ~= source ] whileTrue: [
| edge reverseEdge |
edge := parent at: current.

"Update forward edge flow"
edge flow: edge flow + flowValue.

"Update reverse edge flow"
reverseEdge := self findReverseEdge: edge.
reverseEdge flow: reverseEdge flow - flowValue.

current := edge from ]
]