Skip to content

Commit 2825721

Browse files
williamfisetclaude
andauthored
Add BellmanFord tests for unreachable node relaxation (williamfiset#1250)
* Add CLAUDE.md with build commands and architecture guide Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add BellmanFord tests for unreachable node relaxation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add general case tests for BellmanFord algorithm Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 08aad13 commit 2825721

File tree

2 files changed

+235
-0
lines changed

2 files changed

+235
-0
lines changed

src/test/java/com/williamfiset/algorithms/graphtheory/BUILD

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,5 +167,16 @@ java_test(
167167
deps = TEST_DEPS,
168168
)
169169

170+
# bazel test //src/test/java/com/williamfiset/algorithms/graphtheory:BellmanFordAdjacencyListTest
171+
java_test(
172+
name = "BellmanFordAdjacencyListTest",
173+
srcs = ["BellmanFordAdjacencyListTest.java"],
174+
main_class = "org.junit.platform.console.ConsoleLauncher",
175+
use_testrunner = False,
176+
args = ["--select-class=com.williamfiset.algorithms.graphtheory.BellmanFordAdjacencyListTest"],
177+
runtime_deps = JUNIT5_RUNTIME_DEPS,
178+
deps = TEST_DEPS,
179+
)
180+
170181
# Run all tests
171182
# bazel test //src/test/java/com/williamfiset/algorithms/graphtheory:all
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
package com.williamfiset.algorithms.graphtheory;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
5+
import java.util.List;
6+
import org.junit.jupiter.api.Test;
7+
8+
public class BellmanFordAdjacencyListTest {
9+
10+
// -------------------------------------------------------------------------
11+
// Unreachable node relaxation
12+
// -------------------------------------------------------------------------
13+
14+
/**
15+
* An unreachable intermediate node must not corrupt the distance of a node
16+
* that IS reachable via a separate path.
17+
*
18+
* Graph (start = 0):
19+
* 0 --5--> 2
20+
* 1 --(-100)--> 2 (node 1 is unreachable from 0)
21+
*
22+
* The edge 1→2 has a cheaper cost, but because dist[1] = +Inf the
23+
* relaxation dist[1] + (-100) = +Inf must not update dist[2].
24+
* Expected: dist[2] = 5, not -100 or NaN.
25+
*/
26+
@Test
27+
public void unreachableNodeDoesNotPolluteCostOfReachableNeighbor() {
28+
int V = 4;
29+
List<BellmanFordAdjacencyList.Edge>[] g = BellmanFordAdjacencyList.createGraph(V);
30+
BellmanFordAdjacencyList.addEdge(g, 0, 2, 5);
31+
BellmanFordAdjacencyList.addEdge(g, 1, 2, -100); // 1 unreachable
32+
33+
double[] dist = BellmanFordAdjacencyList.bellmanFord(g, V, 0);
34+
35+
assertThat(dist[0]).isEqualTo(0.0);
36+
assertThat(dist[1]).isPositiveInfinity();
37+
assertThat(dist[2]).isEqualTo(5.0);
38+
assertThat(dist[3]).isPositiveInfinity();
39+
}
40+
41+
/**
42+
* A node reachable only through an unreachable intermediary must itself
43+
* remain unreachable (+Inf).
44+
*
45+
* Graph (start = 0):
46+
* 1 --5--> 2 (node 1 is unreachable from 0)
47+
*
48+
* Expected: dist[1] = +Inf, dist[2] = +Inf.
49+
*/
50+
@Test
51+
public void nodeReachableOnlyThroughUnreachableIntermediaryStaysUnreachable() {
52+
int V = 3;
53+
List<BellmanFordAdjacencyList.Edge>[] g = BellmanFordAdjacencyList.createGraph(V);
54+
BellmanFordAdjacencyList.addEdge(g, 1, 2, 5); // 1 unreachable
55+
56+
double[] dist = BellmanFordAdjacencyList.bellmanFord(g, V, 0);
57+
58+
assertThat(dist[0]).isEqualTo(0.0);
59+
assertThat(dist[1]).isPositiveInfinity();
60+
assertThat(dist[2]).isPositiveInfinity();
61+
}
62+
63+
/**
64+
* An unreachable node involved in a negative cycle must not mark reachable
65+
* nodes as -Inf during the cycle-detection pass.
66+
*
67+
* Graph (start = 0):
68+
* 0 --10--> 3
69+
* 1 --(-1)--> 2 (negative cycle: 1→2→1, but both unreachable)
70+
* 2 --(-1)--> 1
71+
* 2 --5--> 3
72+
*
73+
* Nodes 1 and 2 form a negative cycle but are unreachable from 0.
74+
* Node 3 is reachable with cost 10 and must NOT be marked -Inf.
75+
*/
76+
@Test
77+
public void unreachableNegativeCycleDoesNotTaintReachableNode() {
78+
int V = 4;
79+
List<BellmanFordAdjacencyList.Edge>[] g = BellmanFordAdjacencyList.createGraph(V);
80+
BellmanFordAdjacencyList.addEdge(g, 0, 3, 10);
81+
BellmanFordAdjacencyList.addEdge(g, 1, 2, -1); // unreachable negative cycle
82+
BellmanFordAdjacencyList.addEdge(g, 2, 1, -1);
83+
BellmanFordAdjacencyList.addEdge(g, 2, 3, 5); // path from cycle to node 3
84+
85+
double[] dist = BellmanFordAdjacencyList.bellmanFord(g, V, 0);
86+
87+
assertThat(dist[0]).isEqualTo(0.0);
88+
assertThat(dist[1]).isPositiveInfinity();
89+
assertThat(dist[2]).isPositiveInfinity();
90+
assertThat(dist[3]).isEqualTo(10.0); // must NOT be -Inf
91+
}
92+
93+
// -------------------------------------------------------------------------
94+
// General cases
95+
// -------------------------------------------------------------------------
96+
97+
@Test
98+
public void singleNode() {
99+
int V = 1;
100+
List<BellmanFordAdjacencyList.Edge>[] g = BellmanFordAdjacencyList.createGraph(V);
101+
102+
double[] dist = BellmanFordAdjacencyList.bellmanFord(g, V, 0);
103+
104+
assertThat(dist[0]).isEqualTo(0.0);
105+
}
106+
107+
@Test
108+
public void twoNodesDirectEdge() {
109+
int V = 2;
110+
List<BellmanFordAdjacencyList.Edge>[] g = BellmanFordAdjacencyList.createGraph(V);
111+
BellmanFordAdjacencyList.addEdge(g, 0, 1, 7);
112+
113+
double[] dist = BellmanFordAdjacencyList.bellmanFord(g, V, 0);
114+
115+
assertThat(dist[0]).isEqualTo(0.0);
116+
assertThat(dist[1]).isEqualTo(7.0);
117+
}
118+
119+
@Test
120+
public void shortestPathChosenOverLonger() {
121+
// Two paths from 0 to 2: 0→2 (cost 10) and 0→1→2 (cost 3+4=7)
122+
int V = 3;
123+
List<BellmanFordAdjacencyList.Edge>[] g = BellmanFordAdjacencyList.createGraph(V);
124+
BellmanFordAdjacencyList.addEdge(g, 0, 2, 10);
125+
BellmanFordAdjacencyList.addEdge(g, 0, 1, 3);
126+
BellmanFordAdjacencyList.addEdge(g, 1, 2, 4);
127+
128+
double[] dist = BellmanFordAdjacencyList.bellmanFord(g, V, 0);
129+
130+
assertThat(dist[2]).isEqualTo(7.0);
131+
}
132+
133+
@Test
134+
public void negativeEdgeWeightWithoutCycle() {
135+
// 0 --1--> 1 --(-2)--> 2; shortest path to 2 is -1
136+
int V = 3;
137+
List<BellmanFordAdjacencyList.Edge>[] g = BellmanFordAdjacencyList.createGraph(V);
138+
BellmanFordAdjacencyList.addEdge(g, 0, 1, 1);
139+
BellmanFordAdjacencyList.addEdge(g, 1, 2, -2);
140+
141+
double[] dist = BellmanFordAdjacencyList.bellmanFord(g, V, 0);
142+
143+
assertThat(dist[0]).isEqualTo(0.0);
144+
assertThat(dist[1]).isEqualTo(1.0);
145+
assertThat(dist[2]).isEqualTo(-1.0);
146+
}
147+
148+
@Test
149+
public void reachableNegativeCycleMarkedNegativeInfinity() {
150+
// 0 --1--> 1 --1--> 2 --(-3)--> 1 (negative cycle: 1→2→1)
151+
int V = 3;
152+
List<BellmanFordAdjacencyList.Edge>[] g = BellmanFordAdjacencyList.createGraph(V);
153+
BellmanFordAdjacencyList.addEdge(g, 0, 1, 1);
154+
BellmanFordAdjacencyList.addEdge(g, 1, 2, 1);
155+
BellmanFordAdjacencyList.addEdge(g, 2, 1, -3);
156+
157+
double[] dist = BellmanFordAdjacencyList.bellmanFord(g, V, 0);
158+
159+
assertThat(dist[0]).isEqualTo(0.0);
160+
assertThat(dist[1]).isNegativeInfinity();
161+
assertThat(dist[2]).isNegativeInfinity();
162+
}
163+
164+
@Test
165+
public void nodeDownstreamOfNegativeCycleMarkedNegativeInfinity() {
166+
// Negative cycle 1→2→1, with 2→3 leading out of the cycle.
167+
// Node 3 is downstream and must also be -Inf.
168+
int V = 4;
169+
List<BellmanFordAdjacencyList.Edge>[] g = BellmanFordAdjacencyList.createGraph(V);
170+
BellmanFordAdjacencyList.addEdge(g, 0, 1, 1);
171+
BellmanFordAdjacencyList.addEdge(g, 1, 2, 1);
172+
BellmanFordAdjacencyList.addEdge(g, 2, 1, -3);
173+
BellmanFordAdjacencyList.addEdge(g, 2, 3, 5);
174+
175+
double[] dist = BellmanFordAdjacencyList.bellmanFord(g, V, 0);
176+
177+
assertThat(dist[3]).isNegativeInfinity();
178+
}
179+
180+
@Test
181+
public void disconnectedGraph() {
182+
// Nodes 2 and 3 have no path from node 0.
183+
int V = 4;
184+
List<BellmanFordAdjacencyList.Edge>[] g = BellmanFordAdjacencyList.createGraph(V);
185+
BellmanFordAdjacencyList.addEdge(g, 0, 1, 3);
186+
BellmanFordAdjacencyList.addEdge(g, 2, 3, 1); // separate component
187+
188+
double[] dist = BellmanFordAdjacencyList.bellmanFord(g, V, 0);
189+
190+
assertThat(dist[0]).isEqualTo(0.0);
191+
assertThat(dist[1]).isEqualTo(3.0);
192+
assertThat(dist[2]).isPositiveInfinity();
193+
assertThat(dist[3]).isPositiveInfinity();
194+
}
195+
196+
@Test
197+
public void exampleFromMain() {
198+
// Reproduces the graph and expected output from the main() method.
199+
int V = 9;
200+
List<BellmanFordAdjacencyList.Edge>[] g = BellmanFordAdjacencyList.createGraph(V);
201+
BellmanFordAdjacencyList.addEdge(g, 0, 1, 1);
202+
BellmanFordAdjacencyList.addEdge(g, 1, 2, 1);
203+
BellmanFordAdjacencyList.addEdge(g, 2, 4, 1);
204+
BellmanFordAdjacencyList.addEdge(g, 4, 3, -3);
205+
BellmanFordAdjacencyList.addEdge(g, 3, 2, 1);
206+
BellmanFordAdjacencyList.addEdge(g, 1, 5, 4);
207+
BellmanFordAdjacencyList.addEdge(g, 1, 6, 4);
208+
BellmanFordAdjacencyList.addEdge(g, 5, 6, 5);
209+
BellmanFordAdjacencyList.addEdge(g, 6, 7, 4);
210+
BellmanFordAdjacencyList.addEdge(g, 5, 7, 3);
211+
212+
double[] dist = BellmanFordAdjacencyList.bellmanFord(g, V, 0);
213+
214+
assertThat(dist[0]).isEqualTo(0.0);
215+
assertThat(dist[1]).isEqualTo(1.0);
216+
assertThat(dist[2]).isNegativeInfinity();
217+
assertThat(dist[3]).isNegativeInfinity();
218+
assertThat(dist[4]).isNegativeInfinity();
219+
assertThat(dist[5]).isEqualTo(5.0);
220+
assertThat(dist[6]).isEqualTo(5.0);
221+
assertThat(dist[7]).isEqualTo(8.0);
222+
assertThat(dist[8]).isPositiveInfinity();
223+
}
224+
}

0 commit comments

Comments
 (0)