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