Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
net_apps_test.cc
Go to the documentation of this file.
1/* Tests for net_apps.H - Network flow applications
2 *
3 * Tests cover:
4 * - Circulation with demands
5 * - Project selection
6 * - Baseball elimination
7 * - Image segmentation
8 * - Survey design
9 */
10
11
12#include <array>
13
14#include <gtest/gtest.h>
15#include <map>
16#include <net_apps.H>
17
18using namespace Aleph;
19
20
21//==============================================================================
22// Project Selection Tests
23//==============================================================================
24
25class ProjectSelectionTest : public ::testing::Test
26{
27protected:
28};
29
31{
32 std::vector<Project<double>> projects = {
33 {0, 100, {}, "Profitable A"}, // Pure profit
34 {1, 50, {}, "Profitable B"}, // Pure profit
35 };
36
37 auto result = solve_project_selection(projects);
38
39 EXPECT_DOUBLE_EQ(result.max_profit, 150.0); // Select both
40 EXPECT_EQ(result.selected.size(), 2);
41}
42
44{
45 std::vector<Project<double>> projects = {
46 {0, 100, {}, "Project A"}, // Profit 100
47 {1, -30, {}, "Infra"}, // Cost 30
48 {2, 50, {1}, "Project B"}, // Profit 50, needs Infra
49 };
50
51 auto result = solve_project_selection(projects);
52
53 // Should select A (100) + Infra (-30) + B (50) = 120
54 // Or just A (100) if B's dependency makes it not worth it
55 // 50 - 30 = 20 > 0, so B + Infra is profitable
56 // Total: 100 + 50 - 30 = 120
57 EXPECT_GE(result.max_profit, 120.0);
58}
59
61{
62 std::vector<Project<double>> projects = {
63 {0, -50, {}, "Cost A"},
64 {1, -30, {}, "Cost B"},
65 };
66
67 auto result = solve_project_selection(projects);
68
69 EXPECT_DOUBLE_EQ(result.max_profit, 0.0);
70 EXPECT_TRUE(result.selected.empty());
71}
72
74{
75 std::vector<Project<double>> projects;
76
77 auto result = solve_project_selection(projects);
78
79 EXPECT_DOUBLE_EQ(result.max_profit, 0.0);
80}
81
83{
84 // Note: Circular dependencies are handled but don't make practical sense
85 std::vector<Project<double>> projects = {
86 {0, 100, {1}, "A needs B"},
87 {1, 50, {0}, "B needs A"},
88 };
89
90 auto result = solve_project_selection(projects);
91
92 // Both are selected together due to mutual dependency
93 EXPECT_GE(result.max_profit, 150.0);
94}
95
97{
98 std::vector<Project<double>> projects = {
99 {0, -10, {}, "Foundation"},
100 {1, -10, {0}, "Level 1"},
101 {2, -10, {1}, "Level 2"},
102 {3, 100, {2}, "Payoff"}, // Profit 100, needs all previous
103 };
104
105 auto result = solve_project_selection(projects);
106
107 // Net profit: 100 - 10 - 10 - 10 = 70
108 EXPECT_GE(result.max_profit, 70.0);
109}
110
111
112//==============================================================================
113// Baseball Elimination Tests
114//==============================================================================
115
116class BaseballEliminationTest : public ::testing::Test
117{
118protected:
119 std::vector<Team> create_simple_division()
120 {
121 // 4-team division
122 std::vector<Team> teams(4);
123
124 teams[0].name = "Atlanta";
125 teams[0].wins = 83;
126 teams[0].losses = 71;
127 teams[0].remaining = 8;
128 teams[0].against = {0, 1, 6, 1};
129
130 teams[1].name = "Philly";
131 teams[1].wins = 80;
132 teams[1].losses = 79;
133 teams[1].remaining = 3;
134 teams[1].against = {1, 0, 0, 2};
135
136 teams[2].name = "New York";
137 teams[2].wins = 78;
138 teams[2].losses = 78;
139 teams[2].remaining = 6;
140 teams[2].against = {6, 0, 0, 0};
141
142 teams[3].name = "Montreal";
143 teams[3].wins = 77;
144 teams[3].losses = 82;
145 teams[3].remaining = 3;
146 teams[3].against = {1, 2, 0, 0};
147
148 return teams;
149 }
150};
151
153{
154 auto teams = create_simple_division();
155
156 // Atlanta (team 0) is not eliminated
157 auto result = check_baseball_elimination(teams, 0);
158
159 EXPECT_FALSE(result.eliminated);
160 EXPECT_EQ(result.max_possible_wins, 91);
161}
162
164{
165 std::vector<Team> teams(3);
166
167 teams[0].name = "Leader";
168 teams[0].wins = 100;
169 teams[0].remaining = 0;
170 teams[0].against = {0, 0, 0};
171
172 teams[1].name = "Middle";
173 teams[1].wins = 50;
174 teams[1].remaining = 40;
175 teams[1].against = {0, 0, 40};
176
177 teams[2].name = "Loser";
178 teams[2].wins = 10;
179 teams[2].remaining = 50;
180 teams[2].against = {0, 40, 0};
181
182 // Team 2 max wins = 60, Leader already has 100
183 auto result = check_baseball_elimination(teams, 2);
184
185 EXPECT_TRUE(result.eliminated);
186}
187
189{
190 auto teams = create_simple_division();
191
192 // Montreal (team 3) might be eliminated non-trivially
193 auto result = check_baseball_elimination(teams, 3);
194
195 // Montreal max wins = 77 + 3 = 80
196 // Atlanta can reach 83 + 8 = 91
197 // But we need to check if there's any scenario where Montreal can win
198}
199
201{
202 auto teams = create_simple_division();
203
204 auto result = check_baseball_elimination(teams, 100);
205
206 // Should handle gracefully
207 EXPECT_FALSE(result.eliminated);
208}
209
210
211//==============================================================================
212// Image Segmentation Tests
213//==============================================================================
214
215class ImageSegmentationTest : public ::testing::Test
216{
217protected:
218};
219
221{
222 // 2x2 image
223 std::vector<std::vector<std::array<double, 2>>> data(2,
224 std::vector<std::array<double, 2>>(2));
225
226 // Top-left strongly prefers foreground (label 1)
227 data[0][0] = {100, 10}; // cost[0]=100 (background), cost[1]=10 (foreground)
228
229 // Others prefer background
230 data[0][1] = {10, 100};
231 data[1][0] = {10, 100};
232 data[1][1] = {10, 100};
233
234 auto result = segment_image(2, 2, data, 50.0);
235
236 EXPECT_EQ(result.labels.size(), 2);
237 EXPECT_EQ(result.labels[0].size(), 2);
238
239 // Top-left should be foreground if smoothness cost allows
240 // With smoothness=50, need to evaluate trade-off
241}
242
244{
245 // 3x3 image all preferring foreground
246 std::vector<std::vector<std::array<double, 2>>> data(3,
247 std::vector<std::array<double, 2>>(3));
248
249 for (int i = 0; i < 3; ++i)
250 for (int j = 0; j < 3; ++j)
251 data[i][j] = {100, 10}; // All prefer foreground
252
253 auto result = segment_image(3, 3, data, 10.0);
254
255 // All should be foreground
256 for (int i = 0; i < 3; ++i)
257 for (int j = 0; j < 3; ++j)
258 EXPECT_EQ(result.labels[i][j], 1);
259}
260
262{
263 std::vector<std::vector<std::array<double, 2>>> data;
264
265 auto result = segment_image(0, 0, data, 10.0);
266
267 EXPECT_TRUE(result.labels.empty());
268}
269
271{
272 std::vector<std::vector<std::array<double, 2>>> data(1,
273 std::vector<std::array<double, 2>>(1));
274
275 data[0][0] = {5, 10}; // Prefers background (lower cost)
276
277 auto result = segment_image(1, 1, data, 100.0);
278
279 EXPECT_EQ(result.labels[0][0], 0); // Background
280}
281
282
283//==============================================================================
284// Survey Design Tests
285//==============================================================================
286
287class SurveyDesignTest : public ::testing::Test
288{
289protected:
290};
291
293{
294 std::vector<SurveyQuestion> questions = {
295 {0, 1, 2}, // Question 0: needs 1-2 responses
296 {1, 1, 2}, // Question 1: needs 1-2 responses
297 };
298
299 std::vector<SurveyRespondent> respondents = {
300 {0, 1, 2, {0, 1}}, // Respondent 0: answers 1-2 questions, eligible for both
301 {1, 1, 2, {0, 1}}, // Respondent 1: answers 1-2 questions, eligible for both
302 };
303
304 auto result = design_survey(questions, respondents);
305
306 EXPECT_TRUE(result.feasible);
307 EXPECT_GE(result.assignments.size(), 2); // At least 2 assignments
308}
309
311{
312 std::vector<SurveyQuestion> questions = {
313 {0, 5, 10}, // Needs at least 5 responses
314 };
315
316 std::vector<SurveyRespondent> respondents = {
317 {0, 1, 1, {0}}, // Only 1 respondent who can answer 1 question
318 };
319
320 auto result = design_survey(questions, respondents);
321
322 EXPECT_FALSE(result.feasible);
323}
324
326{
327 std::vector<SurveyQuestion> questions = {
328 {0, 1, 3}, // Question 0
329 {1, 1, 3}, // Question 1
330 };
331
332 std::vector<SurveyRespondent> respondents = {
333 {0, 1, 2, {0}}, // Only eligible for Q0
334 {1, 1, 2, {1}}, // Only eligible for Q1
335 {2, 1, 2, {0, 1}}, // Eligible for both
336 };
337
338 auto result = design_survey(questions, respondents);
339
340 EXPECT_TRUE(result.feasible);
341
342 // Check assignments respect eligibility
343 for (const auto& [r, q] : result.assignments)
344 {
345 bool eligible = false;
346 for (size_t eq : respondents[r].eligible_questions)
347 if (eq == q)
348 eligible = true;
350 }
351}
352
354{
355 std::vector<SurveyQuestion> questions;
356 std::vector<SurveyRespondent> respondents;
357
358 auto result = design_survey(questions, respondents);
359
360 // Empty survey is trivially feasible but not considered feasible here
361 EXPECT_FALSE(result.feasible);
362}
363
364
365//==============================================================================
366// Circulation Tests (Basic)
367//==============================================================================
368
369class CirculationTest : public ::testing::Test
370{
371protected:
374};
375
377{
378 TestNet net;
379 auto a = net.insert_node();
380 auto b = net.insert_node();
381 net.insert_arc(a, b, 10);
382
383 auto result = solve_circulation(net,
384 [](auto*) { return 0.0; },
385 [](auto*) { return 0.0; });
386
387 EXPECT_TRUE(result.feasible);
388}
389
391{
392 // Simple network: source produces 5 units, sink consumes 5 units
393 // s ---(10)---> t
394 // demand(s) = -5 (produces), demand(t) = +5 (consumes)
395 TestNet net;
396 auto s = net.insert_node();
397 auto t = net.insert_node();
398 auto arc = net.insert_arc(s, t, 10.0); // capacity 10
399
400 std::map<TestNet::Node*, double> demands;
401 demands[s] = -5.0; // produces 5
402 demands[t] = 5.0; // consumes 5
403
404 auto result = solve_circulation(net,
405 [&](auto* n) { return demands.count(n) ? demands[n] : 0.0; },
406 [](auto*) { return 0.0; });
407
408 EXPECT_TRUE(result.feasible);
409 EXPECT_DOUBLE_EQ(result.excess_flow, 5.0);
410
411 // Check flow on the arc
412 ASSERT_TRUE(result.flow.contains(arc));
413 EXPECT_DOUBLE_EQ(result.flow[arc], 5.0);
414}
415
417{
418 // Demand exceeds capacity
419 // s ---(5)---> t
420 // demand(t) = +10 (needs 10), but capacity is only 5
421 TestNet net;
422 auto s = net.insert_node();
423 auto t = net.insert_node();
424 net.insert_arc(s, t, 5.0);
425
426 std::map<TestNet::Node*, double> demands;
427 demands[s] = -10.0; // wants to produce 10
428 demands[t] = 10.0; // needs 10
429
430 auto result = solve_circulation(net,
431 [&](auto* n) { return demands.count(n) ? demands[n] : 0.0; },
432 [](auto*) { return 0.0; });
433
434 EXPECT_FALSE(result.feasible);
435}
436
438{
439 // Network with lower bounds on arcs
440 // s ---(cap=10, lower=3)---> t
441 // Flow must be at least 3, at most 10
442 TestNet net;
443 auto s = net.insert_node();
444 auto t = net.insert_node();
445 auto arc = net.insert_arc(s, t, 10.0);
446
447 std::map<TestNet::Node*, double> demands;
448 demands[s] = -5.0; // produces 5
449 demands[t] = 5.0; // consumes 5
450
451 std::map<TestNet::Arc*, double> lower_bounds;
452 lower_bounds[arc] = 3.0; // minimum flow of 3
453
454 auto result = solve_circulation(net,
455 [&](auto* n) { return demands.count(n) ? demands[n] : 0.0; },
456 [&](auto* a) { return lower_bounds.count(a) ? lower_bounds[a] : 0.0; });
457
458 EXPECT_TRUE(result.feasible);
459
460 // Flow should be 5 (satisfies demand and >= lower bound of 3)
461 ASSERT_TRUE(result.flow.contains(arc));
462 EXPECT_GE(result.flow[arc], 3.0); // At least lower bound
463 EXPECT_LE(result.flow[arc], 10.0); // At most capacity
464}
465
467{
468 // Lower bound exceeds what's possible given demands
469 // s ---(cap=10, lower=8)---> t
470 // But we only need 5 flow, and lower bound requires 8
471 // This should still be feasible if capacity allows
472 TestNet net;
473 auto s = net.insert_node();
474 auto t = net.insert_node();
475 auto arc = net.insert_arc(s, t, 10.0);
476
477 std::map<TestNet::Node*, double> demands;
478 demands[s] = -5.0;
479 demands[t] = 5.0;
480
481 std::map<TestNet::Arc*, double> lower_bounds;
482 lower_bounds[arc] = 8.0; // minimum 8, but only 5 demanded
483
484 auto result = solve_circulation(net,
485 [&](auto* n) { return demands.count(n) ? demands[n] : 0.0; },
486 [&](auto* a) { return lower_bounds.count(a) ? lower_bounds[a] : 0.0; });
487
488 // This might or might not be feasible depending on exact algorithm
489 // Lower bound forces more flow than demand requires
490 // With proper circulation, excess flow needs to go somewhere
491}
492
494{
495 // Three nodes in a triangle, balanced demands
496 // a ---> b ---> c ---> a
497 // All demands are 0, so any circulation is valid
498 TestNet net;
499 auto a = net.insert_node();
500 auto b = net.insert_node();
501 auto c = net.insert_node();
502
503 auto ab = net.insert_arc(a, b, 10.0);
504 auto bc = net.insert_arc(b, c, 10.0);
505 auto ca = net.insert_arc(c, a, 10.0);
506
507 auto result = solve_circulation(net,
508 [](auto*) { return 0.0; },
509 [](auto*) { return 0.0; });
510
511 EXPECT_TRUE(result.feasible);
512
513 // With zero demands and zero lower bounds, zero flow is a valid circulation
514 EXPECT_DOUBLE_EQ(result.flow[ab], 0.0);
515 EXPECT_DOUBLE_EQ(result.flow[bc], 0.0);
516 EXPECT_DOUBLE_EQ(result.flow[ca], 0.0);
517}
518
520{
521 // Verify that the network is not modified after solve_circulation
522 TestNet net;
523 auto s = net.insert_node();
524 auto t = net.insert_node();
525 auto arc = net.insert_arc(s, t, 10.0);
526
527 size_t num_nodes_before = net.get_num_nodes();
528 size_t num_arcs_before = net.get_num_arcs();
529 double cap_before = arc->cap;
530
531 std::map<TestNet::Node*, double> demands;
532 demands[s] = -5.0;
533 demands[t] = 5.0;
534
535 auto result = solve_circulation(net,
536 [&](auto* n) { return demands.count(n) ? demands[n] : 0.0; },
537 [](auto*) { return 0.0; });
538
539 // Network should be unchanged
542 EXPECT_DOUBLE_EQ(arc->cap, cap_before);
543 EXPECT_DOUBLE_EQ(arc->flow, 0.0); // Flow should be reset
544}
545
547{
548 // Network where multiple paths are needed to satisfy demand:
549 // s -> b -> t
550 // s -> c -> t
551 // (Diamond shape with s as source, t as sink, b and c as intermediate)
552 // s produces 10, t consumes 10
553 // Each path has capacity 6, so both paths needed
554 TestNet net;
555 auto s = net.insert_node();
556 auto b = net.insert_node();
557 auto c = net.insert_node();
558 auto t = net.insert_node();
559
560 auto sb = net.insert_arc(s, b, 6.0);
561 auto sc = net.insert_arc(s, c, 6.0);
562 auto bt = net.insert_arc(b, t, 6.0);
563 auto ct = net.insert_arc(c, t, 6.0);
564
565 std::map<TestNet::Node*, double> demands;
566 demands[s] = -10.0; // produces 10
567 demands[t] = 10.0; // consumes 10
568
569 auto result = solve_circulation(net,
570 [&](auto* n) { return demands.count(n) ? demands[n] : 0.0; },
571 [](auto*) { return 0.0; });
572
573 EXPECT_TRUE(result.feasible);
574 EXPECT_DOUBLE_EQ(result.excess_flow, 10.0);
575
576 // Both paths should be used
577 double total_to_t = result.flow[bt] + result.flow[ct];
579
580 // Verify flow on all arcs
581 double total_from_s = result.flow[sb] + result.flow[sc];
583}
584
586{
587 TestNet net;
588
589 auto result = solve_circulation(net,
590 [](auto*) { return 0.0; },
591 [](auto*) { return 0.0; });
592
593 EXPECT_TRUE(result.feasible);
594}
595
596
597int main(int argc, char** argv)
598{
599 ::testing::InitGoogleTest(&argc, argv);
600 return RUN_ALL_TESTS();
601}
int main()
std::vector< Team > create_simple_division()
constexpr size_t get_num_nodes() const noexcept
Return the total of nodes of graph.
Definition graph-dry.H:737
constexpr size_t get_num_arcs() const noexcept
Definition graph-dry.H:826
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
bool eq(const C1 &c1, const C2 &c2, Eq e=Eq())
Check equality of two containers using a predicate.
BaseballEliminationResult check_baseball_elimination(const std::vector< Team > &teams, size_t team_idx)
Check if a team is mathematically eliminated from winning.
Definition net_apps.H:615
ProjectSelectionResult< Value_Type > solve_project_selection(const std::vector< Project< Value_Type > > &projects)
Solve project selection problem using max-flow.
Definition net_apps.H:450
SegmentationResult segment_image(size_t rows, size_t cols, const std::vector< std::vector< std::array< Value_Type, 2 > > > &data_cost, Value_Type smoothness)
Segment image using graph cuts.
Definition net_apps.H:780
SurveyDesignResult design_survey(const std::vector< SurveyQuestion > &questions, const std::vector< SurveyRespondent > &respondents)
Design survey assignment using network flow.
Definition net_apps.H:931
CirculationResult< typename Net::Flow_Type > solve_circulation(Net &net, GetDemand get_demand, GetLower get_lower)
Solve a circulation problem with demands.
Definition net_apps.H:135
Network flow applications.
TEST_F(ProjectSelectionTest, SimpleProjects)
Arc of a flow network implemented with adjacency lists.
Definition tpl_net.H:115
Flow network implemented with adjacency lists.
Definition tpl_net.H:261
Node * insert_node(const Node_Type &node_info)
Insert a new node by copying node_info.
Definition tpl_net.H:559
Arc * insert_arc(Node *src_node, Node *tgt_node, const Flow_Type &cap, const Flow_Type &flow, const typename Arc::Arc_Type &arc_info=Arc_Type())
Insert a capacitated arc with an initial flow.
Definition tpl_net.H:607
gsl_rng * r