Blender V5.0
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++) {
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
222static uint8_t select_next_v(uint8_t *left, uint8_t *bd)
223{
224 uint8_t min = UINT8_MAX;
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 min_v = std::min(arr[start_idx + i], min_v);
246 }
247 return min_v;
248}
249
250static void select_bidomain(uint8_t domains[][BDS],
251 int bd_pos,
252 const uint8_t *left,
253 int current_matching_size,
254 bool connected)
255{
256 int i;
257 int min_size = INT_MAX;
258 int min_tie_breaker = INT_MAX;
259 int best = INT_MAX;
260 uint8_t *bd;
261 for (i = bd_pos - 1, bd = &domains[i][L]; i >= 0 && bd[P] == current_matching_size;
262 i--, bd = &domains[i][L])
263 {
264 if (connected && current_matching_size > 0 && !bd[ADJ]) {
265 continue;
266 }
267 int len = bd[LL] > bd[RL] ? bd[LL] : bd[RL];
268 if (len < min_size) {
269 min_size = len;
270 min_tie_breaker = find_min_value(left, bd[L], bd[LL]);
271 best = i;
272 }
273 else if (len == min_size) {
274 int tie_breaker = find_min_value(left, bd[L], bd[LL]);
275 if (tie_breaker < min_tie_breaker) {
276 min_tie_breaker = tie_breaker;
277 best = i;
278 }
279 }
280 }
281 if (best != INT_MAX && best != bd_pos - 1) {
282 uint8_t tmp[BDS];
283 for (i = 0; i < BDS; i++) {
284 tmp[i] = domains[best][i];
285 }
286 for (i = 0; i < BDS; i++) {
287 domains[best][i] = domains[bd_pos - 1][i];
288 }
289 for (i = 0; i < BDS; i++) {
290 domains[bd_pos - 1][i] = tmp[i];
291 }
292 }
293}
294
295static uint8_t select_next_w(const uint8_t *right, uint8_t *bd)
296{
297 uint8_t min = UINT8_MAX;
298 uint8_t idx = UINT8_MAX;
299 for (uint8_t i = 0; i < bd[RL] + 1; i++) {
300 if ((right[bd[R] + i] > bd[W] || bd[W] == UINT8_MAX) && right[bd[R] + i] < min) {
301 min = right[bd[R] + i];
302 idx = i;
303 }
304 }
305 if (idx == UINT8_MAX) {
306 bd[RL]++;
307 }
308 return idx;
309}
310
311static void maximum_common_subgraph_internal(int incumbent[][2],
312 int *inc_pos,
313 uint8_t **adjmat0,
314 int n0,
315 uint8_t **adjmat1,
316 int n1,
317 bool *r_search_abandoned)
318{
319 int min = std::min(n0, n1);
320
321 uint8_t (*cur)[2] = (uint8_t (*)[2])MEM_mallocN(min * sizeof(*cur), __func__);
322 uint8_t (*domains)[BDS] = (uint8_t (*)[8])MEM_mallocN(min * min * sizeof(*domains), __func__);
323 uint8_t *left = static_cast<uint8_t *>(MEM_mallocN(n0 * sizeof *left, __func__));
324 uint8_t *right = static_cast<uint8_t *>(MEM_mallocN(n1 * sizeof *right, __func__));
325
326 uint8_t v, w, *bd;
327 int bd_pos = 0;
328 for (int i = 0; i < n0; i++) {
329 left[i] = i;
330 }
331 for (int i = 0; i < n1; i++) {
332 right[i] = i;
333 }
334 add_bidomain(domains, &bd_pos, 0, 0, n0, n1, 0, 0);
335
336 int iteration_count = 0;
337
338 while (bd_pos > 0) {
339 if (iteration_count++ > 10000000) {
340 /* Unlikely to find a solution, may as well give up.
341 * Can occur with moderate sized inputs where the graph has lots of symmetry, e.g. a cube
342 * subdivided 3x times.
343 */
344 *r_search_abandoned = true;
345 *inc_pos = 0;
346 break;
347 }
348 bd = &domains[bd_pos - 1][L];
349 if (calc_bound(domains, bd_pos, bd[P]) + bd[P] <= *inc_pos ||
350 (bd[LL] == 0 && bd[RL] == bd[IRL]))
351 {
352 bd_pos--;
353 }
354 else {
355 const bool connected = false;
356 select_bidomain(domains, bd_pos, left, domains[bd_pos - 1][P], connected);
357 v = select_next_v(left, bd);
358 if ((bd[W] = select_next_w(right, bd)) != UINT8_MAX) {
359 w = right[bd[R] + bd[W]]; /* Swap the W after the bottom of the current right domain. */
360 right[bd[R] + bd[W]] = right[bd[R] + bd[RL]];
361 right[bd[R] + bd[RL]] = w;
362 bd[W] = w; /* Store the W used for this iteration. */
363 cur[bd[P]][L] = v;
364 cur[bd[P]][R] = w;
365 update_incumbent(cur, incumbent, bd[P] + uint8_t(1), inc_pos);
367 domains, &bd_pos, bd[P] + 1, left, right, v, w, *inc_pos, adjmat0, adjmat1);
368 }
369 }
370 }
371
372 MEM_freeN(cur);
373 MEM_freeN(domains);
374 MEM_freeN(right);
376}
377
378static bool check_automorphism(const GraphISO *g0,
379 const GraphISO *g1,
380 int solution[][2],
381 int *solution_length)
382{
383 if (g0->n != g1->n) {
384 return false;
385 }
386 for (int i = 0; i < g0->n; i++) {
387 if (g0->label[i] != g1->label[i]) {
388 return false;
389 }
390 for (int j = 0; j < g0->n; j++) {
391 if (g0->adjmat[i][j] != g1->adjmat[i][j]) {
392 return false;
393 }
394 }
395 solution[i][0] = i;
396 solution[i][1] = i;
397 }
398 *solution_length = g0->n;
399 return true;
400}
401
403 GraphISO *g1_input,
404 int solution[][2],
405 int *solution_length,
406 bool *r_search_abandoned)
407{
408 if (check_automorphism(g0_input, g1_input, solution, solution_length)) {
409 return true;
410 }
411
412 int n0 = g0_input->n;
413 int n1 = g1_input->n;
414
415 int min_size = std::min(n0, n1);
416 if (min_size >= UINT8_MAX - 2) {
417 return false;
418 }
419
420 GraphISO *g0 = g0_input->sort_vertices_by_degree();
421 GraphISO *g1 = g1_input->sort_vertices_by_degree();
422
423 int sol_len = 0;
425 solution, &sol_len, g0->adjmat, n0, g1->adjmat, n1, r_search_abandoned);
426 *solution_length = sol_len;
427
428 bool result = (sol_len == n0);
429 if (result) {
430 for (int i = 0; i < sol_len; i++) {
431 solution[i][0] = g0->label[solution[i][0]]; /* Index from input. */
432 solution[i][1] = g1->label[solution[i][1]];
433 }
434 }
435
436 delete g1;
437 delete g0;
438
439 return result;
440}
#define BLI_assert(a)
Definition BLI_assert.h:46
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
#define UINT8_MAX
#define partition
void * MEM_mallocN(size_t len, const char *str)
Definition mallocn.cc:128
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
static int left
#define R
#define L
#define min(a, b)
Definition sort.cc:36
i
Definition text_draw.cc:230
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 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)
uint len