LAL: Linear Arrangement Library 23.01.00
A library focused on algorithms on linear arrangements of graphs.
Loading...
Searching...
No Matches
C_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 - 2023
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 (lalemany@cs.upc.edu)
28 * LARCA (Laboratory for Relational Algorithmics, Complexity and Learning)
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 * LARCA (Laboratory for Relational Algorithmics, Complexity and Learning)
35 * CQL (Complexity and Quantitative Linguistics Lab)
36 * Office S124, 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/identity_arrangement.hpp>
52#include <lal/detail/graphs/utils.hpp>
53#include <lal/detail/data_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, linarr_type arr_type>
94uint64_t compute
95(
96 const graph_t& g, const linarr_wrapper<arr_type>& arr,
97 unsigned char * const __restrict__ bn,
98 uint64_t * const __restrict__ M,
99 uint64_t * const __restrict__ K,
100 uint64_t upper_bound
101)
102noexcept
103{
104 const uint64_t n = g.get_num_nodes();
105 std::fill(bn, &bn[n], 0);
106 std::fill(K, &K[(n - 3)*(n - 3)], 0);
107
108 const node u0 = arr[position_t{0ull}];
109 const node u1 = arr[position_t{1ull}];
110
111 /* fill matrix M */
112
113 for (position_t pu = 0ull; pu < n - 3; ++pu) {
114 // node at position pu + 1
115 const node u = arr[pu + 1ull];
116
117 detail::get_bool_neighbours<graph_t>(g, u, bn);
118
119 uint64_t k = g.get_degree(u);
120
121 // check existence of edges between node u
122 // and the nodes in positions 0 and 1 of
123 // the arrangement
124 k -= bn[u0] + bn[u1];
125 bn[u0] = bn[u1] = 0;
126
127 // this is done because there is no need to
128 // fill the first two columns.
129
130 // Now we start filling M at the third column
131 for (position_t i = 3ull; i < n; ++i) {
132 // node at position i - 1
133 const node ui = arr[i - 1ull];
134 k -= bn[ui];
135
136 // the row corresponding to node 'u' in M is
137 // the same as its position in the sequence.
138 // This explains M[pu][*]
139
140 //M[pu][i - 3] = k;
141 M[ idx(*pu, *i - 3, n-3) ] = k;
142
143 // clear boolean neighbours so that at the next
144 // iteration all its values are valid
145 bn[ui] = 0;
146 }
147 }
148
149 /* fill matrix K */
150
151 // special case for ii = 0 (see next for loop)
152 K[idx(n-4,n-4, n-3)] = M[idx(n-4,n-4, n-3)];
153
154 // pointer for next row in K
155 uint64_t * __restrict__ next_k_it;
156 // pointer for M
157 uint64_t * __restrict__ m_it;
158 // pointer for K
159 uint64_t * __restrict__ k_it;
160
161 for (uint64_t ii = 1; ii < n - 3; ++ii) {
162 const uint64_t i = n - 3 - ii - 1;
163
164 //m_it = &M[i][i];
165 m_it = &M[ idx(i,i, n-3) ];
166
167 // place k_it at the beginning of i-th row ("beginning" here means
168 // the first column with non-redundant information: the upper half
169 // of the matrix)
170 k_it = &K[ idx(i,i, n-3) ];
171
172 // place next_k_it at the same column as k_it but at the next row
173 next_k_it = k_it + n - 3;
174
175 for (uint64_t j = i; j < n - 3; ++j) {
176 //K[i][j] = M[i][j] + K[i + 1][j];
177
178 *k_it++ = *m_it++ + *next_k_it++;
179 }
180 }
181
182 /* compute number of crossings */
183
184 uint64_t C = 0;
185
186 const auto process_neighbours =
187 [&](const position pu, const node_t v) -> void {
188 const position pv = arr[v];
189 // 'u' and 'v' is an edge of the graph.
190 // In case that arr[u] < arr[v], or, equivalently, pu < arr[v],
191 // then 'u' is "in front of" 'v' in the linear arrangement.
192 // This explains the first condition 'pu < arr[v]'.
193 // The second condition '2 <= arr[v] and arr[v] < n -1' is used
194 // to avoid making illegal memory accesses.
195 // --
196 /*if (pu < arr[v] and 2 <= arr[v] and arr[v] < n - 1) {
197 C += K[idx(pu,arr[v]-2, n-3)];
198 }*/
199 // --
200 if (pu < pv and 2 <= pv and pv < n - 1) {
201 C += K[idx(pu,pv-2, n-3)];
202 }
203 };
204
205 for (position pu = 0; pu < n - 3; ++pu) {
206 const node u = arr[position_t{pu}];
207
208 if constexpr (std::is_base_of_v<graphs::directed_graph, graph_t>) {
209 const neighbourhood& Nu = g.get_out_neighbours(u);
210 for (node_t v : Nu) {
211 process_neighbours(pu, v);
212 if constexpr (decide_upper_bound) {
213 if (C > upper_bound) { return DECIDED_C_GT; }
214 }
215 }
216 const neighbourhood& Nu_in = g.get_in_neighbours(u);
217 for (node_t v : Nu_in) {
218 process_neighbours(pu, v);
219 if constexpr (decide_upper_bound) {
220 if (C > upper_bound) { return DECIDED_C_GT; }
221 }
222 }
223 }
224 else {
225 const neighbourhood& Nu = g.get_neighbours(u);
226 for (node_t v : Nu) {
227 process_neighbours(pu, v);
228 if constexpr (decide_upper_bound) {
229 if (C > upper_bound) { return DECIDED_C_GT; }
230 }
231 }
232 }
233 }
234
235 // none of the conditions above were true, so we must have
236 // C <= upper_bound
237 return C;
238}
239
240} // -- namespace dyn_prog
241
242// =============================================================================
243// CALLS TO THE ALGORITHM
244// =============================================================================
245
246// ------------------
247// single arrangement
248
257template <class graph_t, class arrangement_t>
259(const graph_t& g, const arrangement_t& arr)
260noexcept
261{
262 const uint64_t n = g.get_num_nodes();
263
264#if defined DEBUG
265 assert(arr.m_arr.size() == 0 or arr.m_arr.size() == n);
266#endif
267
268 if (n < 4) { return 0; }
269
270 // boolean neighbourhood of nodes
271 data_array<unsigned char> bool_neighs(n);
272
273 const std::size_t n_elems = 2*(n - 3)*(n - 3);
274 data_array<uint64_t> all_memory(n_elems);
275
276 // matrix M (without 3 of its columns and rows) ( size (n-3)*(n-3) )
277 uint64_t * const __restrict__ M = &all_memory[0];
278 // matrix K (without 3 of its columns and rows) ( size (n-3)*(n-3) )
279 uint64_t * const __restrict__ K = &all_memory[0 + (n - 3)*(n - 3)];
280
281 return dyn_prog::compute<false>(g, arr, bool_neighs.begin(), M,K, 0);
282}
283
284// --------------------
285// list of arrangements
286
294template <class graph_t>
295std::vector<uint64_t> n_C_dynamic_programming
296(const graph_t& g, const std::vector<linear_arrangement>& arrs)
297noexcept
298{
299 const uint64_t n = g.get_num_nodes();
300
301 std::vector<uint64_t> cs(arrs.size(), 0);
302 if (n < 4) { return cs; }
303
304 /* allocate memory */
305 const std::size_t n_elems = 2*(n - 3)*(n - 3);
306 data_array<uint64_t> all_memory(n_elems);
307
308 // matrix M (without 3 of its columns and rows) ( size (n-3)*(n-3) )
309 uint64_t * const __restrict__ M = &all_memory[0];
310 // matrix K (without 3 of its columns and rows) ( size (n-3)*(n-3) )
311 uint64_t * const __restrict__ K = &all_memory[0 + (n - 3)*(n - 3)];
312
313 // boolean neighbourhood of nodes
314 data_array<unsigned char> bool_neighs(n);
315
316 /* compute C for every linear arrangement */
317 for (std::size_t i = 0; i < arrs.size(); ++i) {
318#if defined DEBUG
319 // ensure that no linear arrangement is empty
320 assert(arrs[i].size() == n);
321#endif
322
323 // compute C
324 cs[i] = dyn_prog::compute<false>
325 (g, nonident_arr(arrs[i]), bool_neighs.begin(), M,K, 0);
326
327 // contents of 'bool_neighs' is set to 0 inside the function
328 //bool_neighs.assign(n, false);
329 }
330
331 return cs;
332}
333
334// -----------------------------------------------------------------------------
335// DECISION
336
337// ------------------
338// single arrangement
339
350template <class graph_t, class arrangement_t>
352 const graph_t& g,
353 const arrangement_t& arr,
354 uint64_t upper_bound
355)
356noexcept
357{
358 const uint64_t n = g.get_num_nodes();
359
360#if defined DEBUG
361 assert(arr.m_arr.size() == 0 or arr.m_arr.size() == n);
362#endif
363
364 // boolean neighbourhood of nodes
365 data_array<unsigned char> bool_neighs(n);
366
367 const std::size_t n_elems = 2*(n - 3)*(n - 3);
368 data_array<uint64_t> all_memory(n_elems);
369
370 // matrix M (without 3 of its columns and rows) ( size (n-3)*(n-3) )
371 uint64_t * const __restrict__ M = &all_memory[0];
372 // matrix K (without 3 of its columns and rows) ( size (n-3)*(n-3) )
373 uint64_t * const __restrict__ K = &all_memory[0 + (n - 3)*(n - 3)];
374
375 return dyn_prog::compute<true>(g, arr, bool_neighs.begin(), M,K, upper_bound);
376}
377
378// --------------------
379// list of arrangements
380
390template <class graph_t>
392 const graph_t& g,
393 const std::vector<linear_arrangement>& arrs,
394 uint64_t upper_bound
395)
396noexcept
397{
398 const uint64_t n = g.get_num_nodes();
399
400 std::vector<uint64_t> cs(arrs.size(), 0);
401 if (n < 4) { return cs; }
402
403 /* allocate memory */
404 const std::size_t n_elems = 2*(n - 3)*(n - 3);
405 data_array<uint64_t> all_memory(n_elems);
406
407 // matrix M (without 3 of its columns and rows) ( size (n-3)*(n-3) )
408 uint64_t * const __restrict__ M = &all_memory[0];
409 // matrix K (without 3 of its columns and rows) ( size (n-3)*(n-3) )
410 uint64_t * const __restrict__ K = &all_memory[0 + (n - 3)*(n - 3)];
411
412 // boolean neighbourhood of nodes
413 data_array<unsigned char> bool_neighs(n);
414
415 /* compute C for every linear arrangement */
416 for (std::size_t i = 0; i < arrs.size(); ++i) {
417#if defined DEBUG
418 // ensure that no linear arrangement is empty
419 assert(arrs[i].size() == n);
420#endif
421
422 // compute C
423 cs[i] = dyn_prog::compute<true>
424 (g, nonident_arr(arrs[i]), bool_neighs.begin(), M,K, upper_bound);
425
426 // contents of 'bool_neighs' is set to 0 inside the function
427 //bool_neighs.assign(n, false);
428 }
429
430 /* free memory */
431 return cs;
432}
433
444template <class graph_t>
446 const graph_t& g,
447 const std::vector<linear_arrangement>& arrs,
448 const std::vector<uint64_t>& upper_bounds
449)
450noexcept
451{
452#if defined DEBUG
453 // ensure that there are as many linear arrangements as upper bounds
454 assert(arrs.size() == upper_bounds.size());
455#endif
456
457 const uint64_t n = g.get_num_nodes();
458
459 std::vector<uint64_t> cs(arrs.size(), 0);
460 if (n < 4) { return cs; }
461
462 /* allocate memory */
463 const std::size_t n_elems = 2*(n - 3)*(n - 3);
464 data_array<uint64_t> all_memory(n_elems);
465
466 // matrix M (without 3 of its columns and rows) ( size (n-3)*(n-3) )
467 uint64_t * const __restrict__ M = &all_memory[0];
468 // matrix K (without 3 of its columns and rows) ( size (n-3)*(n-3) )
469 uint64_t * const __restrict__ K = &all_memory[0 + (n - 3)*(n - 3)];
470
471 // boolean neighbourhood of nodes
472 data_array<unsigned char> bool_neighs(n);
473
474 /* compute C for every linear arrangement */
475 for (std::size_t i = 0; i < arrs.size(); ++i) {
476#if defined DEBUG
477 // ensure that no linear arrangement is empty
478 assert(arrs[i].size() == n);
479#endif
480
481 // compute C
482 cs[i] = dyn_prog::compute<true>
483 (g, nonident_arr(arrs[i]), bool_neighs.begin(), M,K, upper_bounds[i]);
484
485 // contents of 'bool_neighs' is set to 0 inside the function
486 //bool_neighs.assign(n, false);
487 }
488
489 /* free memory */
490 return cs;
491}
492
493} // -- namespace crossings
494} // -- namespace detail
495} // -- namespace lal
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511/*
512// This is a basic, straightforward and easy-to-understand
513// implementation of the above dynamic programming algorithm.
514
515inline void compute_M
516(
517 std::size_t L,
518 const graph& G, const vector<std::size_t>& seq,
519 vector<vector<std::size_t> >& M
520)
521{
522 for (std::size_t pu = 0; pu < L; ++pu) {
523 std::size_t u = seq[pu];
524
525 // the row corresponding to node 'u' in M is
526 // the same as its position in the sequence
527
528 std::size_t k = G.get_degree(u);
529 M[pu][0] = k;
530 for (std::size_t i = 1; i < L and k > 0; ++i) {
531 if (G.has_edge(u, seq[i - 1])) --k;
532 M[pu][i] = k;
533 }
534 }
535}
536
537inline void compute_K
538(
539 std::size_t L,
540 const graph& G, const vector<std::size_t>& seq,
541 vector<vector<std::size_t> >& K
542)
543{
544 // Fill M matrix
545 vector<vector<std::size_t> > M(L, vector<std::size_t>(L));
546 compute_M(L, G, seq, M);
547
548 // Fill K matrix
549 for (std::size_t i = L - 3 - 1; i > 0; --i) {
550 for (std::size_t j = L - 1 - 1; j >= i + 2; --j) {
551 K[i][j] = M[i + 1][j + 1] + K[i + 1][j];
552 }
553 }
554 for (std::size_t j = L - 1 - 1; j >= 2; --j) {
555 K[0][j] = M[1][j + 1] + K[1][j];
556 }
557}
558
559std::size_t crossings_sequence_n2_n2(const graph& G, const vector<std::size_t>& seq) {
560 const std::size_t L = seq.size();
561 if (L < 4) return 0;
562
563 // translation table
564 // T[u] = p <-> node u is at position p
565 vector<std::size_t> T(G.get_num_nodes(), L + 1);
566 for (std::size_t i = 0; i < L; ++i) {
567 T[ seq[i] ] = i;
568 }
569
570 vector<vector<std::size_t> > K(L, vector<std::size_t>(L, 0));
571 compute_K(L, G, seq, K);
572
573 std::size_t C = 0;
574
575 for (std::size_t pu = 0; pu < L; ++pu) {
576 std::size_t u = seq[pu];
577
578 const neighbourhood& Nu = G.get_neighbours(u);
579 for (const std::size_t& v : Nu) {
580
581 if (pu < T[v]) {
582 // 'u' and 'v' is a pair of connected nodes such that
583 // 'u' is "in front of" 'v' in the random seq 'seq'.
584
585 C += K[pu][ T[v] ];
586 }
587 }
588 }
589
590 return C;
591}
592*/
uint64_t compute(const graph_t &g, const linarr_wrapper< arr_type > &arr, unsigned char *const __restrict__ bn, uint64_t *const __restrict__ M, uint64_t *const __restrict__ K, uint64_t upper_bound) noexcept
Dynamic programming computation of for undirected graphs.
Definition: C_dyn_prog.hpp:95
uint64_t n_C_dynamic_programming(const graph_t &g, const arrangement_t &arr) noexcept
Dynamic programming computation of .
Definition: C_dyn_prog.hpp:259
uint64_t is_n_C_dynamic_programming_lesseq_than(const graph_t &g, const arrangement_t &arr, uint64_t upper_bound) noexcept
Dynamic programming computation of .
Definition: C_dyn_prog.hpp:351
linarr_wrapper< linarr_type::nonident > nonident_arr(const linear_arrangement &arr) noexcept
Shorthand for a nonidentity arrangement.
Definition: identity_arrangement.hpp:121
Main namespace of the library.
Definition: basic_types.hpp:50
uint64_t position
Node's position type.
Definition: basic_types.hpp:55
uint64_t node
Node type. See Node / Vertex page for further details.
Definition: basic_types.hpp:53
std::vector< node > neighbourhood
List of nodes.
Definition: basic_types.hpp:62
Wrapper of a C array for autmatic deallocation of memory.
Definition: data_array.hpp:59
T * begin() noexcept
Non-constant raw pointer to first element.
Definition: data_array.hpp:291
A wrapper to easily use identity arrangements.
Definition: identity_arrangement.hpp:72
Typesafe node type.
Definition: basic_types.hpp:67
Typesafe position type.
Definition: basic_types.hpp:168