diff options
Diffstat (limited to 'source/blender/editors/uvedit/uvedit_clipboard_graph_iso.cc')
-rw-r--r-- | source/blender/editors/uvedit/uvedit_clipboard_graph_iso.cc | 419 |
1 files changed, 419 insertions, 0 deletions
diff --git a/source/blender/editors/uvedit/uvedit_clipboard_graph_iso.cc b/source/blender/editors/uvedit/uvedit_clipboard_graph_iso.cc new file mode 100644 index 00000000000..43975d0d382 --- /dev/null +++ b/source/blender/editors/uvedit/uvedit_clipboard_graph_iso.cc @@ -0,0 +1,419 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (c) 2019 Stefano Quer. + * Additional code, copyright 2022 Blender Foundation. All rights reserved. + * + * Originally 6846114 from https://github.com/stefanoquer/graphISO/blob/master/v3 + * graphISO: Tools to compute the Maximum Common Subgraph between two graphs. + */ + +#include "uvedit_clipboard_graph_iso.hh" + +#include "BLI_assert.h" + +#include "MEM_guardedalloc.h" + +#include <algorithm> +#include <limits.h> + +#define L 0 +#define R 1 +#define LL 2 +#define RL 3 +#define ADJ 4 +#define P 5 +#define W 6 +#define IRL 7 + +#define BDS 8 + +GraphISO::GraphISO(int n) +{ + this->n = n; + label = static_cast<uint *>(MEM_mallocN(n * sizeof *label, __func__)); + adjmat = static_cast<uint8_t **>(MEM_mallocN(n * sizeof *adjmat, __func__)); + + /* \note Allocation of `n * n` bytes total! */ + + for (int i = 0; i < n; i++) { + /* Caution, are you trying to change the representation of adjmat? + * Consider `blender::Vector<std::pair<int, int>> adjmat;` instead. + * Better still is to use a different algorithm. See for example: + * https://www.uni-ulm.de/fileadmin/website_uni_ulm/iui.inst.190/Mitarbeiter/toran/beatcs09.pdf + */ + adjmat[i] = static_cast<unsigned char *>(MEM_callocN(n * sizeof *adjmat[i], __func__)); + } + degree = nullptr; +} + +GraphISO::~GraphISO() +{ + for (int i = 0; i < n; i++) { + MEM_freeN(adjmat[i]); + } + MEM_freeN(adjmat); + MEM_freeN(label); + if (degree) { + MEM_freeN(degree); + } +} + +void GraphISO::add_edge(int v, int w) +{ + BLI_assert(v != w); + adjmat[v][w] = 1; + adjmat[w][v] = 1; +} + +void GraphISO::calculate_degrees() const +{ + if (degree) { + return; + } + degree = static_cast<unsigned int *>(MEM_mallocN(n * sizeof *degree, __func__)); + for (int v = 0; v < n; v++) { + int row_count = 0; + for (int w = 0; w < n; w++) { + if (adjmat[v][w]) { + row_count++; + } + } + degree[v] = row_count; + } +} + +class GraphISO_DegreeCompare { + public: + GraphISO_DegreeCompare(const GraphISO *g) + { + this->g = g; + } + const GraphISO *g; + + bool operator()(int i, int j) const + { + return g->degree[i] < g->degree[j]; + } +}; + +GraphISO *GraphISO::sort_vertices_by_degree() const +{ + calculate_degrees(); + + int *vv = static_cast<int *>(MEM_mallocN(n * sizeof *vv, __func__)); + for (int i = 0; i < n; i++) { + vv[i] = i; + } + /* Currently ordering iso_verts by degree. + * Instead should order iso_verts by frequency of degree. */ + GraphISO_DegreeCompare compare_obj(this); + std::sort(vv, vv + n, compare_obj); + + GraphISO *subg = new GraphISO(n); + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + subg->adjmat[i][j] = adjmat[vv[i]][vv[j]]; + } + } + for (int i = 0; i < n; i++) { + subg->label[i] = label[vv[i]]; + } + subg->calculate_degrees(); + + MEM_freeN(vv); + return subg; +} + +static void update_incumbent(uint8_t cur[][2], int inc[][2], int cur_pos, int *inc_pos) +{ + if (cur_pos > *inc_pos) { + *inc_pos = cur_pos; + for (int i = 0; i < cur_pos; i++) { + inc[i][L] = cur[i][L]; + inc[i][R] = cur[i][R]; + } + } +} + +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) +{ + domains[*bd_pos][L] = left_i; + domains[*bd_pos][R] = right_i; + domains[*bd_pos][LL] = left_len; + domains[*bd_pos][RL] = right_len; + domains[*bd_pos][ADJ] = is_adjacent; + domains[*bd_pos][P] = cur_pos; + domains[*bd_pos][W] = UINT8_MAX; + domains[*bd_pos][IRL] = right_len; + (*bd_pos)++; +} + +static int calc_bound(uint8_t domains[][BDS], int bd_pos, int cur_pos) +{ + int bound = 0; + for (int i = bd_pos - 1; i >= 0 && domains[i][P] == cur_pos; i--) { + bound += std::min(domains[i][LL], domains[i][IRL]); + } + return bound; +} + +static int partition(uint8_t *arr, int start, int len, const uint8_t *adjrow) +{ + int i = 0; + for (int j = 0; j < len; j++) { + if (adjrow[arr[start + j]]) { + std::swap(arr[start + i], arr[start + j]); + i++; + } + } + return i; +} + +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) +{ + int i; + int bd_backup = *bd_pos; + int bound = 0; + uint8_t *bd; + for (i = *bd_pos - 1, bd = &domains[i][L]; i >= 0 && bd[P] == cur_pos - 1; + i--, bd = &domains[i][L]) { + + uint8_t l_len = partition(left, bd[L], bd[LL], adjmat0[v]); + uint8_t r_len = partition(right, bd[R], bd[RL], adjmat1[w]); + + if (bd[LL] - l_len && bd[RL] - r_len) { + add_bidomain(domains, + bd_pos, + bd[L] + l_len, + bd[R] + r_len, + bd[LL] - l_len, + bd[RL] - r_len, + bd[ADJ], + (uint8_t)(cur_pos)); + bound += std::min(bd[LL] - l_len, bd[RL] - r_len); + } + if (l_len && r_len) { + add_bidomain(domains, bd_pos, bd[L], bd[R], l_len, r_len, true, (uint8_t)(cur_pos)); + bound += std::min(l_len, r_len); + } + } + if (cur_pos + bound <= inc_pos) { + *bd_pos = bd_backup; + } +} + +static uint8_t select_next_v(uint8_t *left, uint8_t *bd) +{ + uint8_t min = UINT8_MAX; + uint8_t idx = UINT8_MAX; + if (bd[RL] != bd[IRL]) { + return left[bd[L] + bd[LL]]; + } + for (uint8_t i = 0; i < bd[LL]; i++) { + if (left[bd[L] + i] < min) { + min = left[bd[L] + i]; + idx = i; + } + } + std::swap(left[bd[L] + idx], left[bd[L] + bd[LL] - 1]); + bd[LL]--; + bd[RL]--; + return min; +} + +static uint8_t find_min_value(uint8_t *arr, uint8_t start_idx, uint8_t len) +{ + uint8_t min_v = UINT8_MAX; + for (int i = 0; i < len; i++) { + if (arr[start_idx + i] < min_v) { + min_v = arr[start_idx + i]; + } + } + return min_v; +} + +static void select_bidomain( + uint8_t domains[][BDS], int bd_pos, uint8_t *left, int current_matching_size, bool connected) +{ + int i; + int min_size = INT_MAX; + int min_tie_breaker = INT_MAX; + int best = INT_MAX; + uint8_t *bd; + for (i = bd_pos - 1, bd = &domains[i][L]; i >= 0 && bd[P] == current_matching_size; + i--, bd = &domains[i][L]) { + if (connected && current_matching_size > 0 && !bd[ADJ]) { + continue; + } + int len = bd[LL] > bd[RL] ? bd[LL] : bd[RL]; + if (len < min_size) { + min_size = len; + min_tie_breaker = find_min_value(left, bd[L], bd[LL]); + best = i; + } + else if (len == min_size) { + int tie_breaker = find_min_value(left, bd[L], bd[LL]); + if (tie_breaker < min_tie_breaker) { + min_tie_breaker = tie_breaker; + best = i; + } + } + } + if (best != INT_MAX && best != bd_pos - 1) { + uint8_t tmp[BDS]; + for (i = 0; i < BDS; i++) { + tmp[i] = domains[best][i]; + } + for (i = 0; i < BDS; i++) { + domains[best][i] = domains[bd_pos - 1][i]; + } + for (i = 0; i < BDS; i++) { + domains[bd_pos - 1][i] = tmp[i]; + } + } +} + +static uint8_t select_next_w(uint8_t *right, uint8_t *bd) +{ + uint8_t min = UINT8_MAX; + uint8_t idx = UINT8_MAX; + for (uint8_t i = 0; i < bd[RL] + 1; i++) { + if ((right[bd[R] + i] > bd[W] || bd[W] == UINT8_MAX) && right[bd[R] + i] < min) { + min = right[bd[R] + i]; + idx = i; + } + } + if (idx == UINT8_MAX) { + bd[RL]++; + } + return idx; +} + +static void maximum_common_subgraph_internal( + int incumbent[][2], int *inc_pos, uint8_t **adjmat0, int n0, uint8_t **adjmat1, int n1) +{ + int min = std::min(n0, n1); + + uint8_t cur[min][2]; + uint8_t domains[min * min][BDS]; + uint8_t left[n0], right[n1]; + uint8_t v, w, *bd; + int bd_pos = 0; + for (int i = 0; i < n0; i++) { + left[i] = i; + } + for (int i = 0; i < n1; i++) { + right[i] = i; + } + add_bidomain(domains, &bd_pos, 0, 0, n0, n1, 0, 0); + + int iteration_count = 0; + + while (bd_pos > 0) { + if (iteration_count++ > 10000000) { + /* Unlikely to find a solution, may as well give up. + * Can occur with moderate sized inputs where the graph has lots of symmetry, e.g. a cube + * subdivided 3x times. + */ + return; + } + bd = &domains[bd_pos - 1][L]; + if (calc_bound(domains, bd_pos, bd[P]) + bd[P] <= *inc_pos || + (bd[LL] == 0 && bd[RL] == bd[IRL])) { + bd_pos--; + } + else { + const bool connected = false; + select_bidomain(domains, bd_pos, left, domains[bd_pos - 1][P], connected); + v = select_next_v(left, bd); + if ((bd[W] = select_next_w(right, bd)) != UINT8_MAX) { + w = right[bd[R] + bd[W]]; /* Swap the W after the bottom of the current right domain. */ + right[bd[R] + bd[W]] = right[bd[R] + bd[RL]]; + right[bd[R] + bd[RL]] = w; + bd[W] = w; /* Store the W used for this iteration. */ + cur[bd[P]][L] = v; + cur[bd[P]][R] = w; + update_incumbent(cur, incumbent, bd[P] + (uint8_t)1, inc_pos); + generate_next_domains( + domains, &bd_pos, bd[P] + 1, left, right, v, w, *inc_pos, adjmat0, adjmat1); + } + } + } +} + +static bool check_automorphism(const GraphISO *g0, + const GraphISO *g1, + int solution[][2], + int *solution_length) +{ + if (g0->n != g1->n) { + return false; + } + for (int i = 0; i < g0->n; i++) { + if (g0->label[i] != g1->label[i]) { + return false; + } + for (int j = 0; j < g0->n; j++) { + if (g0->adjmat[i][j] != g1->adjmat[i][j]) { + return false; + } + } + solution[i][0] = i; + solution[i][1] = i; + } + *solution_length = g0->n; + return true; +} + +bool ED_uvedit_clipboard_maximum_common_subgraph(GraphISO *g0_input, + GraphISO *g1_input, + int solution[][2], + int *solution_length) +{ + if (check_automorphism(g0_input, g1_input, solution, solution_length)) { + return true; + } + + int n0 = g0_input->n; + int n1 = g1_input->n; + + int min_size = std::min(n0, n1); + if (min_size >= UINT8_MAX - 2) { + return false; + } + + GraphISO *g0 = g0_input->sort_vertices_by_degree(); + GraphISO *g1 = g1_input->sort_vertices_by_degree(); + + int sol_len = 0; + maximum_common_subgraph_internal(solution, &sol_len, g0->adjmat, n0, g1->adjmat, n1); + *solution_length = sol_len; + + bool result = (sol_len == n0); + if (result) { + for (int i = 0; i < sol_len; i++) { + solution[i][0] = g0->label[solution[i][0]]; /* Index from input. */ + solution[i][1] = g1->label[solution[i][1]]; + } + } + + delete g1; + delete g0; + + return result; +} |