Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
tpl_multicommodity_test.cc
Go to the documentation of this file.
1
2/*
3 Aleph_w
4
5 Data structures & Algorithms
6 version 2.0.0b
7 https://github.com/lrleon/Aleph-w
8
9 This file is part of Aleph-w library
10
11 Copyright (c) 2002-2026 Leandro Rabindranath Leon
12
13 Permission is hereby granted, free of charge, to any person obtaining a copy
14 of this software and associated documentation files (the "Software"), to deal
15 in the Software without restriction, including without limitation the rights
16 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 copies of the Software, and to permit persons to whom the Software is
18 furnished to do so, subject to the following conditions:
19
20 The above copyright notice and this permission notice shall be included in all
21 copies or substantial portions of the Software.
22
23 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 SOFTWARE.
30*/
31
32
38#include <gtest/gtest.h>
39#include <tpl_multicommodity.H>
40#include <cmath>
41#include <vector>
42
43using namespace Aleph;
44
45// Type aliases at file scope
49
50//==============================================================================
51// Test Fixture
52//==============================================================================
53
54class MultiCommodityTest : public ::testing::Test
55{
56protected:
57 static constexpr double EPSILON = 1e-6;
58
59 bool nearly_equal(double a, double b, double eps = EPSILON)
60 {
61 return std::fabs(a - b) < eps;
62 }
63
64 // Build a simple 2-node network
65 TestNet build_single_arc_network(double capacity, double cost)
66 {
67 TestNet net;
68 auto s = net.insert_node();
69 auto t = net.insert_node();
70 net.insert_arc(s, t, capacity, cost);
71 return net;
72 }
73
74 // Build a diamond network
76 {
77 TestNet net;
78 auto s = net.insert_node();
79 auto a = net.insert_node();
80 auto b = net.insert_node();
81 auto t = net.insert_node();
82
83 net.insert_arc(s, a, 10.0, 1.0);
84 net.insert_arc(s, b, 10.0, 2.0);
85 net.insert_arc(a, t, 10.0, 1.0);
86 net.insert_arc(b, t, 10.0, 2.0);
87
88 return net;
89 }
90
91 // Build a parallel paths network
93 {
94 TestNet net;
95 auto s = net.insert_node();
96 auto a = net.insert_node();
97 auto b = net.insert_node();
98 auto t = net.insert_node();
99
100 net.insert_arc(s, a, 5.0, 1.0);
101 net.insert_arc(a, t, 5.0, 1.0);
102 net.insert_arc(s, b, 5.0, 2.0);
103 net.insert_arc(b, t, 5.0, 2.0);
104
105 return net;
106 }
107};
108
109//==============================================================================
110// Basic Structure Tests
111//==============================================================================
112
114{
115 TestNet net;
116 EXPECT_EQ(net.vsize(), 0);
117 EXPECT_EQ(net.esize(), 0);
118 EXPECT_EQ(net.num_commodities(), 0);
119}
120
122{
123 TestNet net;
124 auto s = net.insert_node();
125 auto t = net.insert_node();
126 auto arc = net.insert_arc(s, t, 10.0, 5.0);
127
128 EXPECT_EQ(net.vsize(), 2);
129 EXPECT_EQ(net.esize(), 1);
130 EXPECT_EQ(arc->capacity, 10.0);
131 EXPECT_EQ(arc->base_cost, 5.0);
132}
133
135{
136 auto net = build_single_arc_network(10.0, 1.0);
137
138 auto nodes = net.nodes();
139 auto it = nodes.get_it();
140 auto s = it.get_curr(); it.next();
141 auto t = it.get_curr();
142
143 size_t k = net.add_commodity(s, t, 5.0, "Commodity 1");
144
145 EXPECT_EQ(k, 0);
146 EXPECT_EQ(net.num_commodities(), 1);
147
148 const auto& comm = net.get_commodity(0);
149 EXPECT_EQ(comm.source, s);
150 EXPECT_EQ(comm.sink, t);
151 EXPECT_EQ(comm.demand, 5.0);
152 EXPECT_EQ(comm.name, "Commodity 1");
153}
154
156{
157 TestNet net;
158 auto s = net.insert_node();
159 auto t = net.insert_node();
160 auto arc = net.insert_arc(s, t, 20.0, 1.0);
161
162 net.add_commodity(s, t, 5.0);
163 net.add_commodity(s, t, 8.0);
164
165 arc->set_flow(0, 5.0);
166 arc->set_flow(1, 8.0);
167
168 EXPECT_EQ(arc->flow(0), 5.0);
169 EXPECT_EQ(arc->flow(1), 8.0);
170 EXPECT_EQ(arc->total_flow(), 13.0);
171 EXPECT_EQ(arc->residual(), 7.0);
172}
173
175{
176 auto net = build_diamond_network();
177
178 auto nodes = net.nodes();
179 auto it = nodes.get_it();
180 auto s = it.get_curr(); it.next();
181 it.next(); it.next();
182 auto t = it.get_curr();
183
184 net.add_commodity(s, t, 5.0, "K1");
185 net.add_commodity(s, t, 3.0, "K2");
186
187 EXPECT_EQ(net.num_commodities(), 2);
188 EXPECT_EQ(net.get_commodity(0).demand, 5.0);
189 EXPECT_EQ(net.get_commodity(1).demand, 3.0);
190}
191
192//==============================================================================
193// Solver Tests - Single Commodity
194//==============================================================================
195
197{
198 auto net = build_single_arc_network(10.0, 2.0);
199
200 auto nodes = net.nodes();
201 auto it = nodes.get_it();
202 auto s = it.get_curr(); it.next();
203 auto t = it.get_curr();
204
205 net.add_commodity(s, t, 5.0);
206
207 auto result = solve_multicommodity_flow(net);
208
209 EXPECT_TRUE(result.is_optimal());
210 EXPECT_NEAR(result.total_cost, 10.0, 0.1); // 5 * 2 = 10
211}
212
214{
215 auto net = build_diamond_network();
216
217 auto nodes = net.nodes();
218 auto it = nodes.get_it();
219 auto s = it.get_curr(); it.next();
220 it.next(); it.next();
221 auto t = it.get_curr();
222
223 net.add_commodity(s, t, 5.0);
224
225 auto result = solve_multicommodity_flow(net);
226
227 EXPECT_TRUE(result.is_optimal());
228 EXPECT_NEAR(result.total_cost, 10.0, 1.0);
229}
230
232{
233 auto net = build_parallel_paths();
234
235 auto nodes = net.nodes();
236 auto it = nodes.get_it();
237 auto s = it.get_curr(); it.next();
238 it.next(); it.next();
239 auto t = it.get_curr();
240
241 net.add_commodity(s, t, 8.0);
242
243 auto result = solve_multicommodity_flow(net);
244
245 EXPECT_TRUE(result.is_optimal());
246 EXPECT_GT(result.total_cost, 0.0);
247}
248
249//==============================================================================
250// Solver Tests - Multiple Commodities
251//==============================================================================
252
254{
255 TestNet net;
256 auto s1 = net.insert_node();
257 auto s2 = net.insert_node();
258 auto m = net.insert_node();
259 auto t1 = net.insert_node();
260 auto t2 = net.insert_node();
261
262 net.insert_arc(s1, m, 10.0, 1.0);
263 net.insert_arc(s2, m, 10.0, 1.0);
264 net.insert_arc(m, t1, 5.0, 1.0);
265 net.insert_arc(m, t2, 5.0, 1.0);
266
267 net.add_commodity(s1, t1, 3.0, "K1");
268 net.add_commodity(s2, t2, 3.0, "K2");
269
270 auto result = solve_multicommodity_flow(net);
271
272 EXPECT_TRUE(result.is_optimal());
273 EXPECT_GT(result.total_cost, 0.0);
274}
275
277{
278 TestNet net;
279 auto s = net.insert_node();
280 auto t = net.insert_node();
281
282 auto arc = net.insert_arc(s, t, 10.0, 1.0);
283
284 net.add_commodity(s, t, 4.0, "K1");
285 net.add_commodity(s, t, 4.0, "K2");
286
287 auto result = solve_multicommodity_flow(net);
288
289 EXPECT_TRUE(result.is_optimal());
290 EXPECT_NEAR(result.total_cost, 8.0, 0.1);
291
292 EXPECT_NEAR(arc->flow(0), 4.0, 0.1);
293 EXPECT_NEAR(arc->flow(1), 4.0, 0.1);
294 EXPECT_NEAR(arc->total_flow(), 8.0, 0.1);
295}
296
298{
299 auto net = build_diamond_network();
300
301 auto nodes = net.nodes();
302 auto it = nodes.get_it();
303 auto s = it.get_curr(); it.next();
304 it.next(); it.next();
305 auto t = it.get_curr();
306
307 net.add_commodity(s, t, 2.0, "K1");
308 net.add_commodity(s, t, 3.0, "K2");
309 net.add_commodity(s, t, 4.0, "K3");
310
311 auto result = solve_multicommodity_flow(net);
312
313 EXPECT_TRUE(result.is_optimal());
314 EXPECT_GT(result.total_cost, 0.0);
315}
316
317//==============================================================================
318// Edge Cases
319//==============================================================================
320
322{
323 auto net = build_single_arc_network(10.0, 1.0);
324
325 auto nodes = net.nodes();
326 auto it = nodes.get_it();
327 auto s = it.get_curr(); it.next();
328 auto t = it.get_curr();
329
330 net.add_commodity(s, t, 0.0);
331
332 auto result = solve_multicommodity_flow(net);
333
334 EXPECT_TRUE(result.is_optimal());
335 EXPECT_NEAR(result.total_cost, 0.0, 0.1);
336}
337
339{
340 auto net = build_diamond_network();
341
342 auto result = solve_multicommodity_flow(net);
343
344 EXPECT_TRUE(result.is_optimal());
345 EXPECT_NEAR(result.total_cost, 0.0, 0.1);
346}
347
348//==============================================================================
349// Validation Tests
350//==============================================================================
351
353{
354 TestNet net;
355 auto s = net.insert_node();
356 auto t = net.insert_node();
357 net.insert_arc(s, t, 10.0, 1.0);
358
359 net.add_commodity(s, t, 4.0);
360 net.add_commodity(s, t, 5.0);
361
362 auto result = solve_multicommodity_flow(net);
363
364 EXPECT_TRUE(result.is_optimal());
365 EXPECT_TRUE(net.capacities_respected());
366}
367
369{
370 TestNet net;
371 auto s = net.insert_node();
372 auto t = net.insert_node();
373 auto arc = net.insert_arc(s, t, 20.0, 1.0); // base cost = 1
374
375 // Add commodities FIRST, then set per-commodity costs
376 net.add_commodity(s, t, 3.0, "Cheap"); // k=0
377 net.add_commodity(s, t, 2.0, "Expensive"); // k=1
378
379 // Now set different costs per commodity
380 arc->set_cost(0, 1.0); // Commodity 0: cost 1
381 arc->set_cost(1, 5.0); // Commodity 1: cost 5
382
383 auto result = solve_multicommodity_flow(net);
384
385 // Expected: 3 * 1 + 2 * 5 = 13
386 EXPECT_TRUE(result.is_optimal());
387 EXPECT_NEAR(result.total_cost, 13.0, 0.1);
388}
389
390//==============================================================================
391// Performance Test
392//==============================================================================
393
395{
396 const int grid_size = 4;
398
399 std::vector<TestNode*> grid_nodes;
400 grid_nodes.reserve(grid_size * grid_size);
401
402 for (int i = 0; i < grid_size * grid_size; ++i)
403 grid_nodes.push_back(mcf_net.insert_node());
404
405 auto node_at = [&](int i, int j) { return grid_nodes[i * grid_size + j]; };
406
407 for (int i = 0; i < grid_size; ++i)
408 {
409 for (int j = 0; j < grid_size; ++j)
410 {
411 if (j + 1 < grid_size)
412 mcf_net.insert_arc(node_at(i, j), node_at(i, j + 1), 5.0, 1.0);
413 if (i + 1 < grid_size)
414 mcf_net.insert_arc(node_at(i, j), node_at(i + 1, j), 5.0, 1.0);
415 }
416 }
417
418 mcf_net.add_commodity(node_at(0, 0), node_at(grid_size-1, grid_size-1),
419 2.0, "TopLeft-BottomRight");
420 mcf_net.add_commodity(node_at(0, grid_size-1), node_at(grid_size-1, 0),
421 2.0, "TopRight-BottomLeft");
422
423 auto result = solve_multicommodity_flow(mcf_net);
424
425 std::cout << "Grid " << grid_size << "x" << grid_size << ": "
426 << "cost=" << result.total_cost
427 << ", time=" << result.solve_time_ms << " ms"
428 << ", iterations=" << result.iterations << "\n";
429
430 EXPECT_TRUE(result.is_optimal());
431 EXPECT_GT(result.total_cost, 0.0);
432}
433
434//==============================================================================
435// Result Structure Tests
436//==============================================================================
437
439{
440 TestNet net;
441 auto s = net.insert_node();
442 auto a = net.insert_node();
443 auto b = net.insert_node();
444 auto t = net.insert_node();
445
446 net.insert_arc(s, a, 10.0, 1.0);
447 net.insert_arc(s, b, 10.0, 2.0);
448 net.insert_arc(a, t, 10.0, 1.0);
449 net.insert_arc(b, t, 10.0, 2.0);
450
451 net.add_commodity(s, t, 5.0, "K1");
452 net.add_commodity(s, t, 3.0, "K2");
453
454 auto result = solve_multicommodity_flow(net);
455
456 EXPECT_TRUE(result.is_optimal());
457 EXPECT_EQ(result.commodity_costs.size(), 2u);
458 EXPECT_GE(result.solve_time_ms, 0.0);
459 EXPECT_GT(result.iterations, 0u);
460
461 double sum = 0;
462 for (auto c : result.commodity_costs)
463 sum += c;
464 EXPECT_NEAR(sum, result.total_cost, 0.1);
465}
466
467int main(int argc, char **argv)
468{
469 ::testing::InitGoogleTest(&argc, argv);
470 return RUN_ALL_TESTS();
471}
int main()
Node of Array_Graph
Definition tpl_agraph.H:64
Multi-commodity flow network (directed graph).
Arc * insert_arc(Node *src, Node *tgt, Flow_Type capacity, Flow_Type cost=0)
Insert arc with capacity and cost.
constexpr size_t vsize() const noexcept
Definition graph-dry.H:698
size_t esize() const noexcept
Return the total of arcs of graph.
Definition graph-dry.H:792
TestNet build_single_arc_network(double capacity, double cost)
static constexpr double EPSILON
bool nearly_equal(double a, double b, double eps=EPSILON)
DynArray< Graph::Node * > nodes
Definition graphpic.C:406
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
MCF_Result< typename Net::Flow_Type > solve_multicommodity_flow(Net &net)
Solve multi-commodity flow problem.
DynList< T > maps(const C &c, Op op)
Classic map operation.
T sum(const Container &container, const T &init=T{})
Compute sum of all elements.
Arc information for multi-commodity flow.
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
void set_flow(Arc *arc, const Flow_Type &flow)
Set the flow of an arc. Throws if flow exceeds capacity.
Definition tpl_net.H:780
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
Multi-commodity flow algorithms.
Graph_Anode< Empty_Class > TestNode
TEST_F(MultiCommodityTest, EmptyNetwork)