Blender V4.3
uvedit_clipboard_graph_iso.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2019 Stefano Quer
2 * SPDX-FileCopyrightText: 2022 Blender Authors
3 *
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 *
6 * Originally 6846114 from https://github.com/stefanoquer/graphISO/blob/master/v3
7 * graphISO: Tools to compute the Maximum Common Subgraph between two graphs.
8 */
9
11
12#include "BLI_assert.h"
13
14#include "MEM_guardedalloc.h"
15
16#include <algorithm>
17#include <climits>
18
19#define L 0
20#define R 1
21#define LL 2
22#define RL 3
23#define ADJ 4
24#define P 5
25#define W 6
26#define IRL 7
27
28#define BDS 8
29
31{
32 this->n = n;
33 label = static_cast<uint *>(MEM_mallocN(n * sizeof *label, __func__));
34 adjmat = static_cast<uint8_t **>(MEM_mallocN(n * sizeof *adjmat, __func__));
35
36 /* \note Allocation of `n * n` bytes total! */
37
38 for (int i = 0; i < n; i++) {
39 /* Caution, are you trying to change the representation of adjmat?
40 * Consider `blender::Vector<std::pair<int, int>> adjmat;` instead.
41 * Better still is to use a different algorithm. See for example:
42 * https://www.uni-ulm.de/fileadmin/website_uni_ulm/iui.inst.190/Mitarbeiter/toran/beatcs09.pdf
43 */
44 adjmat[i] = static_cast<uchar *>(MEM_callocN(n * sizeof *adjmat[i], __func__));
45 }
46 degree = nullptr;
47}
48
50{
51 for (int i = 0; i < n; i++) {
52 MEM_freeN(adjmat[i]);
53 }
56 if (degree) {
58 }
59}
60
61void GraphISO::add_edge(int v, int w)
62{
63 BLI_assert(v != w);
64 adjmat[v][w] = 1;
65 adjmat[w][v] = 1;
66}
67
68void GraphISO::calculate_degrees() const
69{
70 if (degree) {
71 return;
72 }
73 degree = static_cast<uint *>(MEM_mallocN(n * sizeof *degree, __func__));
74 for (int v = 0; v < n; v++) {
75 int row_count = 0;
76 for (int w = 0; w < n; w++) {
77 if (adjmat[v][w]) {
78 row_count++;
79 }
80 }
81 degree[v] = row_count;
82 }
83}
84
86 public:
88 {
89 this->g = g;
90 }
91 const GraphISO *g;
92
93 bool operator()(int i, int j) const
94 {
95 return g->degree[i] < g->degree[j];
96 }
97};
98
100{
101 calculate_degrees();
102
103 int *vv = static_cast<int *>(MEM_mallocN(n * sizeof *vv, __func__));
104 for (int i = 0; i < n; i++) {
105 vv[i] = i;
106 }
107 /* Currently ordering iso_verts by degree.
108 * Instead should order iso_verts by frequency of degree. */
109 GraphISO_DegreeCompare compare_obj(this);
110 std::sort(vv, vv + n, compare_obj);
111
112 GraphISO *subg = new GraphISO(n);
113 for (int i = 0; i < n; i++) {
114 for (int j = 0; j < n; j++) {
115 subg->adjmat[i][j] = adjmat[vv[i]][vv[j]];
116 }
117 }
118 for (int i = 0; i < n; i++) {
119 subg->label[i] = label[vv[i]];
120 }
121 subg->calculate_degrees();
122
123 MEM_freeN(vv);
124 return subg;
125}
126
127static void update_incumbent(uint8_t cur[][2], int inc[][2], int cur_pos, int *inc_pos)
128{
129 if (cur_pos > *inc_pos) {
130 *inc_pos = cur_pos;
131 for (int i = 0; i < cur_pos; i++) {
132 inc[i][L] = cur[i][L];
133 inc[i][R] = cur[i][R];
134 }
135 }
136}
137
138static void add_bidomain(uint8_t domains[][BDS],
139 int *bd_pos,
140 uint8_t left_i,
141 uint8_t right_i,
142 uint8_t left_len,
143 uint8_t right_len,
144 uint8_t is_adjacent,
145 uint8_t cur_pos)
146{
147 domains[*bd_pos][L] = left_i;
148 domains[*bd_pos][R] = right_i;
149 domains[*bd_pos][LL] = left_len;
150 domains[*bd_pos][RL] = right_len;
151 domains[*bd_pos][ADJ] = is_adjacent;
152 domains[*bd_pos][P] = cur_pos;
153 domains[*bd_pos][W] = UINT8_MAX;
154 domains[*bd_pos][IRL] = right_len;
155 (*bd_pos)++;
156}
157
158static int calc_bound(const uint8_t domains[][BDS], int bd_pos, int cur_pos)
159{
160 int bound = 0;
161 for (int i = bd_pos - 1; i >= 0 && domains[i][P] == cur_pos; i--) {
162 bound += std::min(domains[i][LL], domains[i][IRL]);
163 }
164 return bound;
165}
166
167static int partition(uint8_t *arr, int start, int len, const uint8_t *adjrow)
168{
169 int i = 0;
170 for (int j = 0; j < len; j++) {
171 if (adjrow[arr[start + j]]) {
172 std::swap(arr[start + i], arr[start + j]);
173 i++;
174 }
175 }
176 return i;
177}
178
179static void generate_next_domains(uint8_t domains[][BDS],
180 int *bd_pos,
181 int cur_pos,
182 uint8_t *left,
183 uint8_t *right,
184 uint8_t v,
185 uint8_t w,
186 int inc_pos,
187 uint8_t **adjmat0,
188 uint8_t **adjmat1)
189{
190 int i;
191 int bd_backup = *bd_pos;
192 int bound = 0;
193 uint8_t *bd;
194 for (i = *bd_pos - 1, bd = &domains[i][L]; i >= 0 && bd[P] == cur_pos - 1;
195 i--, bd = &domains[i][L])
196 {
197
198 uint8_t l_len = partition(left, bd[L], bd[LL], adjmat0[v]);
199 uint8_t r_len = partition(right, bd[R], bd[RL], adjmat1[w]);
200
201 if (bd[LL] - l_len && bd[RL] - r_len) {
202 add_bidomain(domains,
203 bd_pos,
204 bd[L] + l_len,
205 bd[R] + r_len,
206 bd[LL] - l_len,
207 bd[RL] - r_len,
208 bd[ADJ],
209 uint8_t(cur_pos));
210 bound += std::min(bd[LL] - l_len, bd[RL] - r_len);
211 }
212 if (l_len && r_len) {
213 add_bidomain(domains, bd_pos, bd[L], bd[R], l_len, r_len, true, uint8_t(cur_pos));
214 bound += std::min(l_len, r_len);
215 }
216 }
217 if (cur_pos + bound <= inc_pos) {
218 *bd_pos = bd_backup;
219 }
220}
221
223{
225 uint8_t idx = UINT8_MAX;
226 if (bd[RL] != bd[IRL]) {
227 return left[bd[L] + bd[LL]];
228 }
229 for (uint8_t i = 0; i < bd[LL]; i++) {
230 if (left[bd[L] + i] < min) {
231 min = left[bd[L] + i];
232 idx = i;
233 }
234 }
235 std::swap(left[bd[L] + idx], left[bd[L] + bd[LL] - 1]);
236 bd[LL]--;
237 bd[RL]--;
238 return min;
239}
240
241static uint8_t find_min_value(const uint8_t *arr, uint8_t start_idx, uint8_t len)
242{
243 uint8_t min_v = UINT8_MAX;
244 for (int i = 0; i < len; i++) {
245 if (arr[start_idx + i] < min_v) {
246 min_v = arr[start_idx + i];
247 }
248 }
249 return min_v;
250}
251
252static void select_bidomain(uint8_t domains[][BDS],
253 int bd_pos,
254 const uint8_t *left,
255 int current_matching_size,
256 bool connected)
257{
258 int i;
259 int min_size = INT_MAX;
260 int min_tie_breaker = INT_MAX;
261 int best = INT_MAX;
262 uint8_t *bd;
263 for (i = bd_pos - 1, bd = &domains[i][L]; i >= 0 && bd[P] == current_matching_size;
264 i--, bd = &domains[i][L])
265 {
266 if (connected && current_matching_size > 0 && !bd[ADJ]) {
267 continue;
268 }
269 int len = bd[LL] > bd[RL] ? bd[LL] : bd[RL];
270 if (len < min_size) {
271 min_size = len;
272 min_tie_breaker = find_min_value(left, bd[L], bd[LL]);
273 best = i;
274 }
275 else if (len == min_size) {
276 int tie_breaker = find_min_value(left, bd[L], bd[LL]);
277 if (tie_breaker < min_tie_breaker) {
278 min_tie_breaker = tie_breaker;
279 best = i;
280 }
281 }
282 }
283 if (best != INT_MAX && best != bd_pos - 1) {
284 uint8_t tmp[BDS];
285 for (i = 0; i < BDS; i++) {
286 tmp[i] = domains[best][i];
287 }
288 for (i = 0; i < BDS; i++) {
289 domains[best][i] = domains[bd_pos - 1][i];
290 }
291 for (i = 0; i < BDS; i++) {
292 domains[bd_pos - 1][i] = tmp[i];
293 }
294 }
295}
296
297static uint8_t select_next_w(const uint8_t *right, uint8_t *bd)
298{
300 uint8_t idx = UINT8_MAX;
301 for (uint8_t i = 0; i < bd[RL] + 1; i++) {
302 if ((right[bd[R] + i] > bd[W] || bd[W] == UINT8_MAX) && right[bd[R] + i] < min) {
303 min = right[bd[R] + i];
304 idx = i;
305 }
306 }
307 if (idx == UINT8_MAX) {
308 bd[RL]++;
309 }
310 return idx;
311}
312
313static void maximum_common_subgraph_internal(int incumbent[][2],
314 int *inc_pos,
315 uint8_t **adjmat0,
316 int n0,
317 uint8_t **adjmat1,
318 int n1,
319 bool *r_search_abandoned)
320{
321 int min = std::min(n0, n1);
322
323 uint8_t(*cur)[2] = (uint8_t(*)[2])MEM_mallocN(min * sizeof(*cur), __func__);
324 uint8_t(*domains)[BDS] = (uint8_t(*)[8])MEM_mallocN(min * min * sizeof(*domains), __func__);
325 uint8_t *left = static_cast<uint8_t *>(MEM_mallocN(n0 * sizeof *left, __func__));
326 uint8_t *right = static_cast<uint8_t *>(MEM_mallocN(n1 * sizeof *right, __func__));
327
328 uint8_t v, w, *bd;
329 int bd_pos = 0;
330 for (int i = 0; i < n0; i++) {
331 left[i] = i;
332 }
333 for (int i = 0; i < n1; i++) {
334 right[i] = i;
335 }
336 add_bidomain(domains, &bd_pos, 0, 0, n0, n1, 0, 0);
337
338 int iteration_count = 0;
339
340 while (bd_pos > 0) {
341 if (iteration_count++ > 10000000) {
342 /* Unlikely to find a solution, may as well give up.
343 * Can occur with moderate sized inputs where the graph has lots of symmetry, e.g. a cube
344 * subdivided 3x times.
345 */
346 *r_search_abandoned = true;
347 *inc_pos = 0;
348 break;
349 }
350 bd = &domains[bd_pos - 1][L];
351 if (calc_bound(domains, bd_pos, bd[P]) + bd[P] <= *inc_pos ||
352 (bd[LL] == 0 && bd[RL] == bd[IRL]))
353 {
354 bd_pos--;
355 }
356 else {
357 const bool connected = false;
358 select_bidomain(domains, bd_pos, left, domains[bd_pos - 1][P], connected);
359 v = select_next_v(left, bd);
360 if ((bd[W] = select_next_w(right, bd)) != UINT8_MAX) {
361 w = right[bd[R] + bd[W]]; /* Swap the W after the bottom of the current right domain. */
362 right[bd[R] + bd[W]] = right[bd[R] + bd[RL]];
363 right[bd[R] + bd[RL]] = w;
364 bd[W] = w; /* Store the W used for this iteration. */
365 cur[bd[P]][L] = v;
366 cur[bd[P]][R] = w;
367 update_incumbent(cur, incumbent, bd[P] + uint8_t(1), inc_pos);
369 domains, &bd_pos, bd[P] + 1, left, right, v, w, *inc_pos, adjmat0, adjmat1);
370 }
371 }
372 }
373
374 MEM_freeN(cur);
375 MEM_freeN(domains);
376 MEM_freeN(right);
377 MEM_freeN(left);
378}
379
380static bool check_automorphism(const GraphISO *g0,
381 const GraphISO *g1,
382 int solution[][2],
383 int *solution_length)
384{
385 if (g0->n != g1->n) {
386 return false;
387 }
388 for (int i = 0; i < g0->n; i++) {
389 if (g0->label[i] != g1->label[i]) {
390 return false;
391 }
392 for (int j = 0; j < g0->n; j++) {
393 if (g0->adjmat[i][j] != g1->adjmat[i][j]) {
394 return false;
395 }
396 }
397 solution[i][0] = i;
398 solution[i][1] = i;
399 }
400 *solution_length = g0->n;
401 return true;
402}
403
405 GraphISO *g1_input,
406 int solution[][2],
407 int *solution_length,
408 bool *r_search_abandoned)
409{
410 if (check_automorphism(g0_input, g1_input, solution, solution_length)) {
411 return true;
412 }
413
414 int n0 = g0_input->n;
415 int n1 = g1_input->n;
416
417 int min_size = std::min(n0, n1);
418 if (min_size >= UINT8_MAX - 2) {
419 return false;
420 }
421
422 GraphISO *g0 = g0_input->sort_vertices_by_degree();
423 GraphISO *g1 = g1_input->sort_vertices_by_degree();
424
425 int sol_len = 0;
427 solution, &sol_len, g0->adjmat, n0, g1->adjmat, n1, r_search_abandoned);
428 *solution_length = sol_len;
429
430 bool result = (sol_len == n0);
431 if (result) {
432 for (int i = 0; i < sol_len; i++) {
433 solution[i][0] = g0->label[solution[i][0]]; /* Index from input. */
434 solution[i][1] = g1->label[solution[i][1]];
435 }
436 }
437
438 delete g1;
439 delete g0;
440
441 return result;
442}
#define BLI_assert(a)
Definition BLI_assert.h:50
unsigned char uchar
unsigned int uint
Read Guarded memory(de)allocation.
ATTR_WARN_UNUSED_RESULT const BMVert * v
SIMD_FORCE_INLINE const btScalar & w() const
Return the w value.
Definition btQuadWord.h:119
GraphISO_DegreeCompare(const GraphISO *g)
bool operator()(int i, int j) const
void add_edge(int v, int w)
GraphISO * sort_vertices_by_degree() const
int len
void *(* MEM_mallocN)(size_t len, const char *str)
Definition mallocn.cc:44
void MEM_freeN(void *vmemh)
Definition mallocn.cc:105
void *(* MEM_callocN)(size_t len, const char *str)
Definition mallocn.cc:42
#define min(a, b)
Definition sort.c:32
unsigned char uint8_t
Definition stdint.h:78
#define UINT8_MAX
Definition stdint.h:140
static void generate_next_domains(uint8_t domains[][BDS], int *bd_pos, int cur_pos, uint8_t *left, uint8_t *right, uint8_t v, uint8_t w, int inc_pos, uint8_t **adjmat0, uint8_t **adjmat1)
static uint8_t select_next_w(const uint8_t *right, uint8_t *bd)
static bool check_automorphism(const GraphISO *g0, const GraphISO *g1, int solution[][2], int *solution_length)
static uint8_t select_next_v(uint8_t *left, uint8_t *bd)
static void update_incumbent(uint8_t cur[][2], int inc[][2], int cur_pos, int *inc_pos)
bool ED_uvedit_clipboard_maximum_common_subgraph(GraphISO *g0_input, GraphISO *g1_input, int solution[][2], int *solution_length, bool *r_search_abandoned)
static void select_bidomain(uint8_t domains[][BDS], int bd_pos, const uint8_t *left, int current_matching_size, bool connected)
static int calc_bound(const uint8_t domains[][BDS], int bd_pos, int cur_pos)
static int partition(uint8_t *arr, int start, int len, const uint8_t *adjrow)
static void maximum_common_subgraph_internal(int incumbent[][2], int *inc_pos, uint8_t **adjmat0, int n0, uint8_t **adjmat1, int n1, bool *r_search_abandoned)
static void add_bidomain(uint8_t domains[][BDS], int *bd_pos, uint8_t left_i, uint8_t right_i, uint8_t left_len, uint8_t right_len, uint8_t is_adjacent, uint8_t cur_pos)
static uint8_t find_min_value(const uint8_t *arr, uint8_t start_idx, uint8_t len)