LAL: Linear Arrangement Library 24.10.00
A library focused on algorithms on linear arrangements of graphs.
Loading...
Searching...
No Matches
dyn_prog.hpp
1/*********************************************************************
2 *
3 * Linear Arrangement Library - A library that implements a collection
4 * algorithms for linear arrangments of graphs.
5 *
6 * Copyright (C) 2019 - 2024
7 *
8 * This file is part of Linear Arrangement Library. The full code is available
9 * at:
10 * https://github.com/LAL-project/linear-arrangement-library.git
11 *
12 * Linear Arrangement Library is free software: you can redistribute it
13 * and/or modify it under the terms of the GNU Affero General Public License
14 * as published by the Free Software Foundation, either version 3 of the
15 * License, or (at your option) any later version.
16 *
17 * Linear Arrangement Library is distributed in the hope that it will be
18 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU Affero General Public License for more details.
21 *
22 * You should have received a copy of the GNU Affero General Public License
23 * along with Linear Arrangement Library. If not, see <http://www.gnu.org/licenses/>.
24 *
25 * Contact:
26 *
27 * LluĂ­s Alemany Puig (lluis.alemany.puig@upc.edu)
28 * LQMC (Quantitative, Mathematical, and Computational Linguisitcs)
29 * CQL (Complexity and Quantitative Linguistics Lab)
30 * Jordi Girona St 1-3, Campus Nord UPC, 08034 Barcelona. CATALONIA, SPAIN
31 * Webpage: https://cqllab.upc.edu/people/lalemany/
32 *
33 * Ramon Ferrer i Cancho (rferrericancho@cs.upc.edu)
34 * LQMC (Quantitative, Mathematical, and Computational Linguisitcs)
35 * CQL (Complexity and Quantitative Linguistics Lab)
36 * Office 220, Omega building
37 * Jordi Girona St 1-3, Campus Nord UPC, 08034 Barcelona. CATALONIA, SPAIN
38 * Webpage: https://cqllab.upc.edu/people/rferrericancho/
39 *
40 ********************************************************************/
41
42// C++ includes
43#if defined DEBUG
44#include <cassert>
45#endif
46#include <vector>
47
48// lal includes
49#include <lal/graphs/directed_graph.hpp>
50#include <lal/graphs/undirected_graph.hpp>
51#include <lal/detail/arrangement_wrapper.hpp>
52#include <lal/detail/graphs/utils.hpp>
53#include <lal/detail/array.hpp>
54
55#define idx(i,j, C) ((i)*(C) + (j))
56#define DECIDED_C_GT (upper_bound + 1)
57
58namespace lal {
59namespace detail {
60namespace crossings {
61
67namespace dyn_prog {
68
69// =============================================================================
70// ACTUAL ALGORITHM
71// =============================================================================
72
93template <bool decide_upper_bound, class graph_t, class arrangement_t>
94[[nodiscard]] uint64_t compute
95(
96 const graph_t& g,
97 const arrangement_t& arr,
98 unsigned char * const __restrict__ bn,
99 uint64_t * const __restrict__ M,
100 uint64_t * const __restrict__ K,
101 const uint64_t upper_bound
102)
103noexcept
104{
105 const uint64_t n = g.get_num_nodes();
106 std::fill(bn, &bn[n], 0);
107 std::fill(K, &K[(n - 3)*(n - 3)], 0);
108
109 const node u0 = arr[position_t{0ull}];
110 const node u1 = arr[position_t{1ull}];
111
112 /* fill matrix M */
113
114 for (position_t pu = 0ull; pu < n - 3; ++pu) {
115 // node at position pu + 1
116 const node u = arr[pu + 1ull];
117
119
120 uint64_t k = g.get_degree(u);
121
122 // check existence of edges between node u
123 // and the nodes in positions 0 and 1 of
124 // the arrangement
125 k -= bn[u0] + bn[u1];
126 bn[u0] = bn[u1] = 0;
127
128 // this is done because there is no need to
129 // fill the first two columns.
130
131 // Now we start filling M at the third column
132 for (position_t i = 3ull; i < n; ++i) {
133 // node at position i - 1
134 const node ui = arr[i - 1ull];
135 k -= bn[ui];
136
137 // the row corresponding to node 'u' in M is
138 // the same as its position in the sequence.
139 // This explains M[pu][*]
140
141 //M[pu][i - 3] = k;
142 M[ idx(*pu, *i - 3, n-3) ] = k;
143
144 // clear boolean neighbors so that at the next
145 // iteration all its values are valid
146 bn[ui] = 0;
147 }
148 }
149
150 /* fill matrix K */
151
152 // special case for ii = 0 (see next for loop)
153 K[idx(n-4,n-4, n-3)] = M[idx(n-4,n-4, n-3)];
154
155 // pointer for next row in K
156 uint64_t * __restrict__ next_k_it;
157 // pointer for M
158 uint64_t * __restrict__ m_it;
159 // pointer for K
160 uint64_t * __restrict__ k_it;
161
162 for (uint64_t ii = 1; ii < n - 3; ++ii) {
163 const uint64_t i = n - 3 - ii - 1;
164
165 //m_it = &M[i][i];
166 m_it = &M[ idx(i,i, n-3) ];
167
168 // place k_it at the beginning of i-th row ("beginning" here means
169 // the first column with non-redundant information: the upper half
170 // of the matrix)
171 k_it = &K[ idx(i,i, n-3) ];
172
173 // place next_k_it at the same column as k_it but at the next row
174 next_k_it = k_it + n - 3;
175
176 for (uint64_t j = i; j < n - 3; ++j) {
177 //K[i][j] = M[i][j] + K[i + 1][j];
178
179 *k_it++ = *m_it++ + *next_k_it++;
180 }
181 }
182
183 /* compute number of crossings */
184
185 uint64_t C = 0;
186
187 const auto process_neighbors =
188 [&](const position pu, const node_t v) -> void {
189 const position pv = arr[v];
190 // 'u' and 'v' is an edge of the graph.
191 // In case that arr[u] < arr[v], or, equivalently, pu < arr[v],
192 // then 'u' is "in front of" 'v' in the linear arrangement.
193 // This explains the first condition 'pu < arr[v]'.
194 // The second condition '2 <= arr[v] and arr[v] < n -1' is used
195 // to avoid making illegal memory accesses.
196 // --
197 /*if (pu < arr[v] and 2 <= arr[v] and arr[v] < n - 1) {
198 C += K[idx(pu,arr[v]-2, n-3)];
199 }*/
200 // --
201 if (pu < pv and 2 <= pv and pv < n - 1) {
202 C += K[idx(pu,pv-2, n-3)];
203 }
204 };
205
206 for (position pu = 0; pu < n - 3; ++pu) {
207 const node u = arr[position_t{pu}];
208
209 if constexpr (std::is_base_of_v<graphs::directed_graph, graph_t>) {
210 const neighbourhood& Nu = g.get_out_neighbors(u);
211 for (node_t v : Nu) {
212 process_neighbors(pu, v);
213 if constexpr (decide_upper_bound) {
214 if (C > upper_bound) { return DECIDED_C_GT; }
215 }
216 }
217 const neighbourhood& Nu_in = g.get_in_neighbors(u);
218 for (node_t v : Nu_in) {
219 process_neighbors(pu, v);
220 if constexpr (decide_upper_bound) {
221 if (C > upper_bound) { return DECIDED_C_GT; }
222 }
223 }
224 }
225 else {
226 const neighbourhood& Nu = g.get_neighbors(u);
227 for (node_t v : Nu) {
228 process_neighbors(pu, v);
229 if constexpr (decide_upper_bound) {
230 if (C > upper_bound) { return DECIDED_C_GT; }
231 }
232 }
233 }
234 }
235
236 // none of the conditions above were true, so we must have
237 // C <= upper_bound
238 return C;
239}
240
241} // -- namespace dyn_prog
242
243// =============================================================================
244// CALLS TO THE ALGORITHM
245// =============================================================================
246
247// ------------------
248// single arrangement
249
258template <class graph_t, class arrangement_t>
259[[nodiscard]] uint64_t n_C_dynamic_programming
260(const graph_t& g, const arrangement_t& arr)
261noexcept
262{
263 const uint64_t n = g.get_num_nodes();
264
265#if defined DEBUG
266 assert(arr.size() == 0 or arr.size() == n);
267#endif
268
269 if (n < 4) { return 0; }
270
271 // boolean neighbourhood of nodes
272 array<unsigned char> bool_neighs(n);
273
274 const std::size_t n_elems = 2*(n - 3)*(n - 3);
275 array<uint64_t> all_memory(n_elems);
276
277 // matrix M (without 3 of its columns and rows) ( size (n-3)*(n-3) )
278 uint64_t * const __restrict__ M = &all_memory[0];
279 // matrix K (without 3 of its columns and rows) ( size (n-3)*(n-3) )
280 uint64_t * const __restrict__ K = &all_memory[0 + (n - 3)*(n - 3)];
281
282 return dyn_prog::compute<false>(g, arr, bool_neighs.begin(), M,K, 0);
283}
284
285// --------------------
286// list of arrangements
287
295template <class graph_t>
296[[nodiscard]] std::vector<uint64_t> n_C_dynamic_programming
297(const graph_t& g, const std::vector<linear_arrangement>& arrs)
298noexcept
299{
300 const uint64_t n = g.get_num_nodes();
301
302 std::vector<uint64_t> cs(arrs.size(), 0);
303 if (n < 4) { return cs; }
304
305 /* allocate memory */
306 const std::size_t n_elems = 2*(n - 3)*(n - 3);
307 array<uint64_t> all_memory(n_elems);
308
309 // matrix M (without 3 of its columns and rows) ( size (n-3)*(n-3) )
310 uint64_t * const __restrict__ M = &all_memory[0];
311 // matrix K (without 3 of its columns and rows) ( size (n-3)*(n-3) )
312 uint64_t * const __restrict__ K = &all_memory[0 + (n - 3)*(n - 3)];
313
314 // boolean neighbourhood of nodes
315 array<unsigned char> bool_neighs(n);
316
317 /* compute C for every linear arrangement */
318 for (std::size_t i = 0; i < arrs.size(); ++i) {
319#if defined DEBUG
320 // ensure that no linear arrangement is empty
321 assert(arrs[i].size() == n);
322#endif
323
324 // compute C
326 (g, nonidentity_arr(arrs[i]), bool_neighs.begin(), M,K, 0);
327
328 // contents of 'bool_neighs' is set to 0 inside the function
329 //bool_neighs.assign(n, false);
330 }
331
332 return cs;
333}
334
335// -----------------------------------------------------------------------------
336// DECISION
337
338// ------------------
339// single arrangement
340
351template <class graph_t, class arrangement_t>
353(
354 const graph_t& g,
355 const arrangement_t& arr,
356 const uint64_t upper_bound
357)
358noexcept
359{
360 const uint64_t n = g.get_num_nodes();
361
362#if defined DEBUG
363 assert(arr.size() == 0 or arr.size() == n);
364#endif
365
366 // boolean neighbourhood of nodes
367 array<unsigned char> bool_neighs(n);
368
369 const std::size_t n_elems = 2*(n - 3)*(n - 3);
370 array<uint64_t> all_memory(n_elems);
371
372 // matrix M (without 3 of its columns and rows) ( size (n-3)*(n-3) )
373 uint64_t * const __restrict__ M = &all_memory[0];
374 // matrix K (without 3 of its columns and rows) ( size (n-3)*(n-3) )
375 uint64_t * const __restrict__ K = &all_memory[0 + (n - 3)*(n - 3)];
376
377 return dyn_prog::compute<true>(g, arr, bool_neighs.begin(), M,K, upper_bound);
378}
379
380// --------------------
381// list of arrangements
382
392template <class graph_t>
393[[nodiscard]] std::vector<uint64_t> is_n_C_dynamic_programming_lesseq_than
394(
395 const graph_t& g,
396 const std::vector<linear_arrangement>& arrs,
397 const uint64_t upper_bound
398)
399noexcept
400{
401 const uint64_t n = g.get_num_nodes();
402
403 std::vector<uint64_t> cs(arrs.size(), 0);
404 if (n < 4) { return cs; }
405
406 /* allocate memory */
407 const std::size_t n_elems = 2*(n - 3)*(n - 3);
408 array<uint64_t> all_memory(n_elems);
409
410 // matrix M (without 3 of its columns and rows) ( size (n-3)*(n-3) )
411 uint64_t * const __restrict__ M = &all_memory[0];
412 // matrix K (without 3 of its columns and rows) ( size (n-3)*(n-3) )
413 uint64_t * const __restrict__ K = &all_memory[0 + (n - 3)*(n - 3)];
414
415 // boolean neighbourhood of nodes
416 array<unsigned char> bool_neighs(n);
417
418 /* compute C for every linear arrangement */
419 for (std::size_t i = 0; i < arrs.size(); ++i) {
420#if defined DEBUG
421 // ensure that no linear arrangement is empty
422 assert(arrs[i].size() == n);
423#endif
424
425 // compute C
427 (g, nonidentity_arr(arrs[i]), bool_neighs.begin(), M,K, upper_bound);
428
429 // contents of 'bool_neighs' is set to 0 inside the function
430 //bool_neighs.assign(n, false);
431 }
432
433 /* free memory */
434 return cs;
435}
436
447template <class graph_t>
448[[nodiscard]] std::vector<uint64_t> is_n_C_dynamic_programming_lesseq_than
449(
450 const graph_t& g,
451 const std::vector<linear_arrangement>& arrs,
452 const std::vector<uint64_t>& upper_bounds
453)
454noexcept
455{
456#if defined DEBUG
457 // ensure that there are as many linear arrangements as upper bounds
458 assert(arrs.size() == upper_bounds.size());
459#endif
460
461 const uint64_t n = g.get_num_nodes();
462
463 std::vector<uint64_t> cs(arrs.size(), 0);
464 if (n < 4) { return cs; }
465
466 /* allocate memory */
467 const std::size_t n_elems = 2*(n - 3)*(n - 3);
468 array<uint64_t> all_memory(n_elems);
469
470 // matrix M (without 3 of its columns and rows) ( size (n-3)*(n-3) )
471 uint64_t * const __restrict__ M = &all_memory[0];
472 // matrix K (without 3 of its columns and rows) ( size (n-3)*(n-3) )
473 uint64_t * const __restrict__ K = &all_memory[0 + (n - 3)*(n - 3)];
474
475 // boolean neighbourhood of nodes
476 array<unsigned char> bool_neighs(n);
477
478 /* compute C for every linear arrangement */
479 for (std::size_t i = 0; i < arrs.size(); ++i) {
480#if defined DEBUG
481 // ensure that no linear arrangement is empty
482 assert(arrs[i].size() == n);
483#endif
484
485 // compute C
487 (g, nonidentity_arr(arrs[i]), bool_neighs.begin(), M,K, upper_bounds[i]);
488
489 // contents of 'bool_neighs' is set to 0 inside the function
490 //bool_neighs.assign(n, false);
491 }
492
493 /* free memory */
494 return cs;
495}
496
497} // -- namespace crossings
498} // -- namespace detail
499} // -- namespace lal
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515/*
516// This is a basic, straightforward and easy-to-understand
517// implementation of the above dynamic programming algorithm.
518
519inline void compute_M
520(
521 std::size_t L,
522 const graph& G, const vector<std::size_t>& seq,
523 vector<vector<std::size_t> >& M
524)
525{
526 for (std::size_t pu = 0; pu < L; ++pu) {
527 std::size_t u = seq[pu];
528
529 // the row corresponding to node 'u' in M is
530 // the same as its position in the sequence
531
532 std::size_t k = G.get_degree(u);
533 M[pu][0] = k;
534 for (std::size_t i = 1; i < L and k > 0; ++i) {
535 if (G.has_edge(u, seq[i - 1])) --k;
536 M[pu][i] = k;
537 }
538 }
539}
540
541inline void compute_K
542(
543 std::size_t L,
544 const graph& G, const vector<std::size_t>& seq,
545 vector<vector<std::size_t> >& K
546)
547{
548 // Fill M matrix
549 vector<vector<std::size_t> > M(L, vector<std::size_t>(L));
550 compute_M(L, G, seq, M);
551
552 // Fill K matrix
553 for (std::size_t i = L - 3 - 1; i > 0; --i) {
554 for (std::size_t j = L - 1 - 1; j >= i + 2; --j) {
555 K[i][j] = M[i + 1][j + 1] + K[i + 1][j];
556 }
557 }
558 for (std::size_t j = L - 1 - 1; j >= 2; --j) {
559 K[0][j] = M[1][j + 1] + K[1][j];
560 }
561}
562
563std::size_t crossings_sequence_n2_n2(const graph& G, const vector<std::size_t>& seq) {
564 const std::size_t L = seq.size();
565 if (L < 4) return 0;
566
567 // translation table
568 // T[u] = p <-> node u is at position p
569 vector<std::size_t> T(G.get_num_nodes(), L + 1);
570 for (std::size_t i = 0; i < L; ++i) {
571 T[ seq[i] ] = i;
572 }
573
574 vector<vector<std::size_t> > K(L, vector<std::size_t>(L, 0));
575 compute_K(L, G, seq, K);
576
577 std::size_t C = 0;
578
579 for (std::size_t pu = 0; pu < L; ++pu) {
580 std::size_t u = seq[pu];
581
582 const neighbourhood& Nu = G.get_neighbors(u);
583 for (const std::size_t& v : Nu) {
584
585 if (pu < T[v]) {
586 // 'u' and 'v' is a pair of connected nodes such that
587 // 'u' is "in front of" 'v' in the random seq 'seq'.
588
589 C += K[pu][ T[v] ];
590 }
591 }
592 }
593
594 return C;
595}
596*/
uint64_t compute(const graph_t &g, const arrangement_t &arr, unsigned char *const __restrict__ bn, uint64_t *const __restrict__ M, uint64_t *const __restrict__ K, const uint64_t upper_bound) noexcept
Dynamic programming computation of for undirected graphs.
Definition dyn_prog.hpp:95
uint64_t n_C_dynamic_programming(const graph_t &g, const arrangement_t &arr) noexcept
Dynamic programming computation of .
Definition dyn_prog.hpp:260
uint64_t is_n_C_dynamic_programming_lesseq_than(const graph_t &g, const arrangement_t &arr, const uint64_t upper_bound) noexcept
Dynamic programming computation of .
Definition dyn_prog.hpp:353
void get_bool_neighbors(const graph_t &g, const node u, char_type *const neighs) noexcept
Retrieves the neighbors of a node in an undirected graph as a list of 0-1 values.
Definition utils.hpp:73
arrangement_wrapper< arrangement_type::nonidentity > nonidentity_arr(const linear_arrangement &arr) noexcept
Shorthand for a nonidentity arrangement.
Definition arrangement_wrapper.hpp:133
Main namespace of the library.
Definition basic_types.hpp:48
uint64_t position
Node's position type.
Definition basic_types.hpp:53
uint64_t node
Node type. See Node / Vertex page for further details.
Definition basic_types.hpp:51
std::vector< node > neighbourhood
List of nodes.
Definition basic_types.hpp:64
Wrapper of a C array for automatic deallocation of memory.
Definition array.hpp:59
T * begin() noexcept
Non-constant raw pointer to first element.
Definition array.hpp:300
Typesafe node type.
Definition basic_types.hpp:70
Typesafe position type.
Definition basic_types.hpp:244