Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
dp_optimizations_test.cc
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
31
37# include <gtest/gtest.h>
38
39# include <limits>
40# include <random>
41# include <chrono>
42# include <numeric>
43# include <ctime>
44# include <cstdlib>
45
46# include <DP_Optimizations.H>
47
48using namespace Aleph;
49
50namespace
51{
52 constexpr long long INF64 = std::numeric_limits<long long>::max() / 4;
53
55 const size_t n,
56 const auto & cost)
57 {
59 dp.reserve(groups + 1);
60 for (size_t g = 0; g <= groups; ++g)
61 {
63 for (size_t i = 0; i <= n; ++i)
64 row(i) = INF64;
65 dp.append(std::move(row));
66 }
67
68 dp(0)(0) = 0;
69
70 for (size_t g = 1; g <= groups; ++g)
71 for (size_t i = g; i <= n; ++i)
72 for (size_t k = g - 1; k < i; ++k)
73 {
74 if (dp[g - 1][k] == INF64)
75 continue;
76 const long long cand = dp[g - 1][k] + cost(k, i);
77 if (cand < dp[g][i])
78 dp(g)(i) = cand;
79 }
80
81 return dp;
82 }
83
84 size_t brute_optimal_merge(const Array<size_t> & w)
85 {
86 const size_t n = w.size();
87 if (n <= 1)
88 return 0;
89
91 pref(0) = 0;
92 for (size_t i = 0; i < n; ++i)
93 pref(i + 1) = pref[i] + w[i];
94
96 dp.reserve(n + 1);
97 for (size_t i = 0; i <= n; ++i)
98 {
100 for (size_t j = 0; j <= n; ++j)
101 row(j) = 0;
102 dp.append(std::move(row));
103 }
104
105 for (size_t len = 2; len <= n; ++len)
106 for (size_t i = 0; i + len <= n; ++i)
107 {
108 const size_t j = i + len;
109 size_t best = std::numeric_limits<size_t>::max();
110 const size_t seg = pref[j] - pref[i];
111 for (size_t k = i + 1; k < j; ++k)
112 {
113 const size_t cand = dp[i][k] + dp[k][j] + seg;
114 if (cand < best)
115 best = cand;
116 }
117 dp(i)(j) = best;
118 }
119
120 return dp[0][n];
121 }
122
123 long long brute_windowed_dp(const Array<long long> & base, const size_t w)
124 {
125 if (base.size() == 0)
126 return 0;
127 if (base.size() > 1 and w == 0)
128 return INF64;
129
131 dp(0) = base[0];
132
133 for (size_t i = 1; i < base.size(); ++i)
134 {
135 const size_t l = (i > w) ? (i - w) : 0;
136 long long best = INF64;
137 for (size_t j = l; j < i; ++j)
138 if (dp[j] < best)
139 best = dp[j];
140 dp(i) = best + base[i];
141 }
142
143 return dp[base.size() - 1];
144 }
145
147 const Array<long long> & ws,
148 const size_t i)
149 {
150 long long best = INF64;
151 for (size_t j = 0; j < xs.size(); ++j)
152 {
153 const long long d = xs[i] - xs[j];
154 const long long cand = d * d + ws[j];
155 if (cand < best)
156 best = cand;
157 }
158 return best;
159 }
160} // namespace
161
162
164{
165 std::mt19937 rng(20260226);
166
167 for (size_t trial = 0; trial < 120; ++trial)
168 {
169 const size_t n = 1 + (rng() % 36);
170 const size_t groups = 1 + (rng() % std::min<size_t>(6, n));
171
173 a.reserve(n);
174 for (size_t i = 0; i < n; ++i)
175 a.append(1 + static_cast<long long>(rng() % 11));
176
178 pref(0) = 0;
179 for (size_t i = 0; i < n; ++i)
180 pref(i + 1) = pref[i] + a[i];
181
182 const auto cost = [&] (const size_t k, const size_t i) -> long long
183 {
184 const long long d = pref[i] - pref[k];
185 return d * d;
186 };
187
189 groups, n, cost, INF64
190 );
191 const auto slow = brute_partition_dp(groups, n, cost);
192
193 EXPECT_EQ(fast.optimal_cost, slow[groups][n]);
194
195 for (size_t g = 1; g <= groups; ++g)
196 for (size_t i = g + 1; i <= n; ++i)
197 EXPECT_LE(fast.split[g][i - 1], fast.split[g][i]);
198 }
199}
200
202{
203 const auto cost = [] (const size_t k, const size_t i) -> long long
204 {
205 const long long d = static_cast<long long>(i - k);
206 return d * d;
207 };
208
209 const auto r0 = divide_and_conquer_partition_dp<long long>(0, 0, cost, INF64);
210 EXPECT_EQ(r0.optimal_cost, 0);
211
212 const auto r1 = divide_and_conquer_partition_dp<long long>(0, 5, cost, INF64);
213 EXPECT_EQ(r1.optimal_cost, INF64);
214
215 const auto r2 = divide_and_conquer_partition_dp<long long>(7, 3, cost, INF64);
216 EXPECT_EQ(r2.optimal_cost, INF64);
217}
218
220{
221 std::mt19937 rng(20260226 + 7);
222
223 for (size_t trial = 0; trial < 150; ++trial)
224 {
225 const size_t n = rng() % 35;
227 w.reserve(n);
228 for (size_t i = 0; i < n; ++i)
229 w.append(static_cast<size_t>(rng() % 25));
230
231 const auto fast = optimal_merge_knuth(w);
232 const size_t slow = brute_optimal_merge(w);
233 EXPECT_EQ(fast.optimal_cost, slow);
234
235 if (n >= 2)
236 for (size_t len = 3; len <= n; ++len)
237 for (size_t i = 0; i + len <= n; ++i)
238 {
239 const size_t j = i + len;
240 EXPECT_LE(fast.opt[i][j - 1], fast.opt[i][j]);
241 EXPECT_LE(fast.opt[i][j], fast.opt[i + 1][j]);
242 }
243 }
244}
245
247{
248 Array<size_t> empty;
249 auto e = optimal_merge_knuth(empty);
250 EXPECT_EQ(e.optimal_cost, 0u);
251
252 Array<size_t> one = {42};
253 auto o = optimal_merge_knuth(one);
254 EXPECT_EQ(o.optimal_cost, 0u);
255}
256
258{
261
262 const Array<long long> slopes = {7, 4, 2, -1, -3, -5};
263 const Array<long long> intercepts = {-10, -4, -3, 0, 5, 9};
264
265 for (size_t i = 0; i < slopes.size(); ++i)
266 {
267 cht.add_line(slopes[i], intercepts[i]);
269 }
270
271 for (long long x = -30; x <= 30; ++x)
272 {
273 long long best = INF64;
274 for (size_t i = 0; i < lines.size(); ++i)
275 {
276 const long long v = lines[i].value_at(x);
277 if (v < best)
278 best = v;
279 }
280 EXPECT_EQ(cht.query(x), best);
281 }
282
283 cht.reset_query_cursor();
284 for (long long x = -30; x <= 30; ++x)
285 {
286 long long best = INF64;
287 for (size_t i = 0; i < lines.size(); ++i)
288 {
289 const long long v = lines[i].value_at(x);
290 if (v < best)
291 best = v;
292 }
293 EXPECT_EQ(cht.query_monotone(x), best);
294 }
295}
296
298{
300
301 EXPECT_THROW(cht.query(0), std::runtime_error);
302
303 cht.add_line(1, 5);
304 cht.add_line(1, 7); // dominated (same slope, higher intercept)
305 EXPECT_EQ(cht.size(), 1u);
306
307 cht.add_line(1, 3); // better same slope should replace
308 EXPECT_EQ(cht.size(), 1u);
309 EXPECT_EQ(cht.query(10), 13);
310
311 EXPECT_THROW(cht.add_line(2, 0), std::domain_error);
312}
313
315{
316 Li_Chao_Tree<long long> lc(-200, 200);
318
319 std::mt19937 rng(20260226 + 17);
320 for (size_t i = 0; i < 80; ++i)
321 {
322 const long long m = static_cast<long long>(rng() % 51) - 25;
323 const long long b = static_cast<long long>(rng() % 401) - 200;
324 lc.add_line(m, b);
326 }
327
328 for (long long x = -200; x <= 200; x += 3)
329 {
330 long long best = INF64;
331 for (size_t i = 0; i < lines.size(); ++i)
332 {
333 const long long cand = lines[i].value_at(x);
334 if (cand < best)
335 best = cand;
336 }
337 EXPECT_EQ(lc.query(x), best);
338 }
339}
340
342{
343 EXPECT_THROW(Li_Chao_Tree<long long>(5, 2), std::domain_error);
344
346 EXPECT_THROW(lc.query(0), std::runtime_error);
347
348 lc.add_line(3, 1);
349 EXPECT_THROW(lc.query(11), std::out_of_range);
350}
351
353{
354 std::mt19937 rng(20260226 + 31);
355
356 for (size_t trial = 0; trial < 220; ++trial)
357 {
358 const size_t n = 1 + (rng() % 200);
359 const size_t w = 1 + (rng() % 20);
360
361 Array<long long> base;
362 base.reserve(n);
363 for (size_t i = 0; i < n; ++i)
364 base.append(1 + static_cast<long long>(rng() % 50));
365
366 const auto fast = monotone_queue_min_dp<long long>(base, w);
367 const long long slow = brute_windowed_dp(base, w);
368
369 EXPECT_EQ(fast.dp[base.size() - 1], slow);
370
371 for (size_t i = 1; i < n; ++i)
372 {
373 EXPECT_LT(fast.parent[i], i);
374 EXPECT_LE(i - fast.parent[i], w);
375 }
376 }
377}
378
380{
381 Array<long long> empty;
382 auto re = monotone_queue_min_dp<long long>(empty, 5);
383 EXPECT_EQ(re.dp.size(), 0u);
384
385 Array<long long> one = {17};
387 EXPECT_EQ(r1.dp.size(), 1u);
388 EXPECT_EQ(r1.dp[0], 17);
389
390 Array<long long> two = {1, 2};
391 EXPECT_THROW(monotone_queue_min_dp<long long>(two, 0), std::domain_error);
392}
393
395{
396 Array<long long> xs = {-9, -4, -1, 0, 3, 8, 11};
397 Array<long long> ws = {5, 2, 7, 4, 1, 9, 6};
398
400 ASSERT_EQ(got.size(), xs.size());
401
402 for (size_t i = 0; i < xs.size(); ++i)
404}
405
407{
408 std::mt19937 rng(20260226 + 101);
409
410 for (size_t trial = 0; trial < 120; ++trial)
411 {
412 const size_t n = 1 + (rng() % 60);
413
416 xs.reserve(n);
417 ws.reserve(n);
418
419 for (size_t i = 0; i < n; ++i)
420 {
421 xs.append(static_cast<long long>(rng() % 101) - 50);
422 ws.append(static_cast<long long>(rng() % 80));
423 }
424
426 for (size_t i = 0; i < n; ++i)
428 }
429}
430
437
439{
440 if (std::getenv("RUN_PERF_TESTS") == nullptr)
441 GTEST_SKIP() << "Skipping performance test; set RUN_PERF_TESTS=1 to enable";
442
443 // Deterministic performance check for D&C DP optimizer.
444 // Complexity O(groups * n * log n). For these parameters, ~2.2M iterations.
445 const size_t groups = 100;
446 const size_t n = 2000;
447
448 // Cost function lambda matching the pattern in correctness tests
449 auto cost = [](size_t i, size_t j) -> long long
450 {
451 const long long diff = static_cast<long long>(j - i);
452 return diff * diff;
453 };
454
455 const std::clock_t start_clock = std::clock();
457 const std::clock_t end_clock = std::clock();
458
459 const double cpu_elapsed_ms = (static_cast<double>(end_clock - start_clock) * 1000.0) / CLOCKS_PER_SEC;
460
461 // Conservative 500ms CPU-time threshold to avoid CI flakiness.
463 << "Performance regression: divide_and_conquer_partition_dp took " << cpu_elapsed_ms << "ms CPU time";
464
465 // We split the range [0, n) into groups intervals. Given the cost(i, j) = (j-i)^2
466 // model, the optimal cost res.optimal_cost is achieved with equal-sized splits,
467 // yielding exactly cost(0, n) / groups, which is roughly n^2 / groups.
468 EXPECT_EQ(res.optimal_cost, cost(0, n) / groups);
469}
Classical DP optimization toolkit: D&C DP, Knuth, CHT, Li Chao, and monotone-queue transitions.
long double w
Definition btreepic.C:153
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:139
static Array create(size_t n)
Create an array with n logical elements.
Definition tpl_array.H:194
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
Definition tpl_array.H:351
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:245
void reserve(size_t cap)
Reserves cap cells into the array.
Definition tpl_array.H:315
Convex Hull Trick for minimum queries.
Li Chao tree for min line queries on an integral x-domain.
Key * append(const Key &key)
Alias for insert() (copy version).
Definition hashDry.H:389
#define TEST(name)
static mt19937 rng
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
and
Check uniqueness with explicit hash + equality functors.
Array< T > min_weighted_squared_distance_1d(const Array< T > &xs, const Array< T > &weights)
Weighted squared-distance lower envelope on a line.
Divide_Conquer_DP_Result< Cost > divide_and_conquer_partition_dp(const size_t groups, const size_t n, Transition_Cost_Fn transition_cost, const Cost inf=dp_optimization_detail::default_inf< Cost >())
Optimize partition DP using divide-and-conquer optimization.
Knuth_Optimization_Result< size_t > optimal_merge_knuth(const Array< size_t > &weights)
Optimal adjacent merge cost via Knuth optimization.
bool diff(const C1 &c1, const C2 &c2, Eq e=Eq())
Check if two containers differ.
Affine line y = m*x + b.
Affine line y = m*x + b.
FooMap m(5, fst_unit_pair_hash, snd_unit_pair_hash)
static int * k
DynList< int > l