Add clav-check-graph-isomorphism
[clav.git] / check-graph-iso.c
blob62212ff2cc2075c5e40fb5be31d35524f791940d
1 /*
2 * Copyright (c) 2018, S. Gilles <sgilles@math.umd.edu>
4 * Permission to use, copy, modify, and/or distribute this software
5 * for any purpose with or without fee is hereby granted, provided
6 * that the above copyright notice and this permission notice appear
7 * in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS *ALL
10 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING AL*L IMPLIED
11 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EV*ENT SHALL THE
12 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
13 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
14 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
15 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
16 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #include <errno.h>
19 #include <limits.h>
20 #include <stdint.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
26 #include "macros.h"
27 #include "quiver.h"
29 #define zmax(x, y) (((x) > (y)) ? (x) : (y))
31 static int cmp(const void *a, const void *b)
33 const size_t *as = (const size_t *) a;
34 const size_t *bs = (const size_t *) b;
36 if (*as < *bs) {
37 return -1;
38 } else if (*as > *bs) {
39 return 1;
42 return 0;
45 int check_iso(struct quiver *a, struct quiver *b, const char **fix, size_t
46 fix_len)
48 int ret = -1;
49 size_t n = a->v_num;
50 size_t *a_i = 0;
51 size_t *b_i = 0;
52 size_t i_idx = 0;
53 size_t ia = 0;
54 size_t ib = 0;
55 size_t a_next = 0;
56 size_t b_next = 0;
57 size_t next_highest = 0;
58 size_t next_highest_idx = 0;
59 size_t t = 0;
62 a_i is { f₁, f₂, …, fₘ, u₁, u₂, …, uₙ₋ₘ }, where f's are
63 fixed indices and u₁ < u₂ < … < uₙ₋ₘ .
65 b_i will be the same, but as the sequence progresses the
66 u's will be permuted, and we require always that a and
67 b, when restricted to indices less than i_idx, are
68 isomorphic.
70 if (!(a_i = calloc(n, sizeof *a_i))) {
71 perror(L("calloc"));
72 goto done;
75 if (!(b_i = calloc(n, sizeof *b_i))) {
76 perror(L("calloc"));
77 goto done;
80 for (size_t j = 0; j < fix_len; ++j) {
81 const char *name = fix[j];
83 for (size_t k = 0; k < n; ++k) {
84 if (!(strcmp(a->v[k].name, name))) {
85 printf("Setting a_i[%zu] = %zu\n", i_idx, k);
86 a_i[j] = k;
89 if (!(strcmp(b->v[k].name, name))) {
90 printf("Setting b_i[%zu] = %zu\n", i_idx, k);
91 b_i[j] = k;
96 /* Fill out the permutation to make u's increasing */
97 for (i_idx = fix_len; i_idx < n; ++i_idx) {
98 /* This is incredibly inefficient, but it's readable */
99 int seen_a_next = 0;
100 int seen_b_next = 0;
102 /* Find smallest a_next value that's not already used */
103 for (a_next = 0; a_next < n; ++a_next) {
104 seen_a_next = 0;
106 for (size_t k = 0; k < i_idx; ++k) {
107 if (a_i[k] == a_next) {
108 seen_a_next = 1;
112 if (!seen_a_next) {
113 a_i[i_idx] = a_next;
114 break;
117 /* The same; for b */
118 for (b_next = 0; b_next < n; ++b_next) {
119 seen_b_next = 0;
121 for (size_t k = 0; k < i_idx; ++k) {
122 if (b_i[k] == b_next) {
123 seen_b_next = 1;
127 if (!seen_b_next) {
128 b_i[i_idx] = b_next;
129 break;
135 i_idx = fix_len;
138 * Require that, restricted to the fixed parts, we have a
139 * graph isomorphism.
141 for (size_t j = 0; j < fix_len; ++j) {
142 size_t ja = a_i[j];
143 size_t jb = b_i[j];
145 if (a->v[ja].fatness != b->v[jb].fatness) {
146 fprintf(stderr,
147 "Fixed vertex \u00ab%s\u00bb has inconsistent fatness %d, %d\n",
148 a->v[ja].name, a->v[ja].fatness,
149 b->v[ja].fatness);
150 goto done;
153 for (size_t k = 0; k < fix_len; ++k) {
154 size_t ka = a_i[k];
155 size_t kb = b_i[k];
156 struct rational *ea = &a->e[ja * a->v_len + ka];
157 struct rational *eb = &b->e[jb * b->v_len + kb];
159 if (ea->p != eb->p ||
160 ea->q != eb->q) {
162 printf("a_i: [ ");
163 for (size_t l = 0; l < n; ++l) {
164 printf("%zu ", a_i[l]);
165 if (l + 1 == fix_len) {
166 printf("⏐ ");
169 printf("]\n");
171 printf("b_i: [ ");
172 for (size_t l = 0; l < n; ++l) {
173 printf("%zu ", b_i[l]);
174 if (l + 1 == fix_len) {
175 printf("⏐ ");
178 printf("]\n");
180 fprintf(stderr,
181 "S: \u00ab%s\u00bb \u2192 \u00ab%s\u00bb, %d/%d\n",
182 a->v[ja].name, a->v[ka].name, ea->p,
183 ea->q);
184 fprintf(stderr,
185 "T: \u00ab%s\u00bb \u2192 \u00ab%s\u00bb, %d/%d\n",
186 b->v[jb].name, b->v[kb].name, eb->p,
187 eb->q);
188 fprintf(stderr, "These are fixed vertices; so ");
189 fprintf(stderr, "no isomorphism is possible\n");
190 goto done;
195 /* The long haul */
196 while (1) {
197 check_partial_isomorphism:
200 * At this point, a_i and b_i are valid permutations
201 * (completely, up to the last position). Furthermore, the
202 * map Gₐ[a_i[k] <-> Gᵦ[b_i[k]], restricted to k < i_idx,
203 * gives a graph isomorphism. We seek to check if incrementing
204 * i_idx by one works.
206 ia = a_i[i_idx];
207 ib = b_i[i_idx];
209 if (a->v[ia].fatness != b->v[ib].fatness) {
210 goto increment_i_idxth_place;
213 for (size_t j = 0; j < i_idx; ++j) {
214 size_t ja = a_i[j];
215 size_t jb = b_i[j];
216 struct rational *ea = &a->e[ia * a->v_len + ja];
217 struct rational *eb = &b->e[ib * b->v_len + jb];
218 struct rational *fa = &a->e[ja * a->v_len + ia];
219 struct rational *fb = &b->e[jb * b->v_len + ib];
221 if (ea->p != eb->p ||
222 ea->q != eb->q ||
223 fa->p != fb->p ||
224 fa->q != fb->q) {
225 goto increment_i_idxth_place;
230 * Expanding the isomorphism works. Good, let's try
231 * to expand a bit more
233 i_idx++;
235 if (i_idx < n) {
236 goto check_partial_isomorphism;
239 /* The whole thing is an isomorphism. Good, we're done. */
240 printf("Isomorphism found\n");
241 printf(" S | T \n");
242 printf("------------|------------\n");
244 for (size_t j = 0; j < n; ++j) {
245 size_t ja = a_i[j];
246 size_t jb = b_i[j];
248 printf("%11s | %s\n", a->v[ja].name, b->v[jb].name);
251 ret = 0;
252 goto done;
253 increment_i_idxth_place:
255 * Try to get the next in the orderings of b_i.
256 * It's a permutation, so we're in the easy case
257 * of the incrementation algorithm.
259 next_highest = (size_t) -1;
260 next_highest_idx = i_idx;
262 for (size_t j = i_idx + 1; j < n; ++j) {
263 if (b_i[j] < next_highest &&
264 b_i[j] > b_i[i_idx]) {
265 next_highest = b_i[j];
266 next_highest_idx = j;
270 if (next_highest == (size_t) -1) {
271 if (i_idx <= fix_len) {
272 /* We've checked all we can */
273 printf("No isomorphism possible\n");
274 goto done;
277 i_idx--;
278 goto increment_i_idxth_place;
281 t = b_i[i_idx];
282 b_i[i_idx] = b_i[next_highest_idx];
283 b_i[next_highest_idx] = t;
284 qsort(b_i + i_idx + 1, n - i_idx - 1, sizeof *b_i, cmp);
287 done:
288 free(a_i);
289 free(b_i);
291 return ret;
294 static void usage(char *argv0)
296 printf("Usage: %s -s <file> -t <file> [ -f <v> [ -f <v> [ ... ] ] ]\n",
297 argv0);
298 printf(" <file>: a quiver file output by clav\n");
299 printf(" <v>: a name of a vertex in both files\n");
300 printf("\n");
301 printf("Find (and possibly output) some graph isomorphism between s\n");
302 printf("and t, fixing every vertex named by ‘-f’.\n");
303 printf("\n");
304 printf("Returns 0 if such an isomorphism was found, < 0 if not.\n");
307 /* Run the thing */
308 int main(int argc, char **argv)
310 int ret = -1;
311 int opt = 0;
312 const char *sarg = 0;
313 const char *targ = 0;
314 struct quiver qs = { 0 };
315 struct quiver qt = { 0 };
316 FILE *fs = 0;
317 FILE *ft = 0;
318 const char **fixnames = 0;
319 const char *errstr = 0;
320 size_t fidx = 0;
322 if (!(fixnames = calloc(1 + (argc / 2), sizeof *fixnames))) {
323 perror(L("calloc"));
324 goto done;
327 while ((opt = getopt(argc, argv, "hs:t:f:")) != -1) {
328 switch (opt) {
329 case 's':
330 sarg = optarg;
331 break;
332 case 't':
333 targ = optarg;
334 break;
335 case 'f':
336 fixnames[fidx] = optarg;
337 fidx++;
338 break;
339 case 'h':
340 ret = 0;
342 /* fall through */
343 default:
344 usage(argv[0]);
345 goto done;
349 if (!sarg) {
350 fprintf(stderr, "\u2018-s\u2019 is required\n");
351 usage(argv[0]);
352 goto done;
355 if (!targ) {
356 fprintf(stderr, "\u2018-t\u2019 is required\n");
357 usage(argv[0]);
358 goto done;
361 if (!(fs = fopen(sarg, "r"))) {
362 perror(L("fopen"));
363 goto done;
366 if ((ret = quiver_load_from_file(&qs, fs, &errstr))) {
367 fprintf(stderr, "cannot load \u00ab%s\u00bb: %s\n", sarg,
368 errstr);
369 goto done;
372 fclose(fs);
373 fs = 0;
375 if (!(ft = fopen(targ, "r"))) {
376 perror(L("fopen"));
377 goto done;
380 if ((ret = quiver_load_from_file(&qt, ft, &errstr))) {
381 fprintf(stderr, "cannot load \u00ab%s\u00bb: %s\n", sarg,
382 errstr);
383 goto done;
386 fclose(ft);
387 ft = 0;
389 if (qs.v_num != qt.v_num) {
390 fprintf(stderr,
391 "\u00ab%s\u00bb and \u00ab%s\u00bb have different |V|\n",
392 sarg,
393 targ);
394 goto done;
397 /* Make sure all the fixed vertices are present exactly once */
398 for (size_t j = 0; j < fidx; ++j) {
399 const char *f = fixnames[j];
400 int seen_in_s = 0;
401 int seen_in_t = 0;
403 for (size_t k = 0; k < qs.v_num; ++k) {
404 seen_in_s += !strcmp(qs.v[k].name, f);
405 seen_in_t += !strcmp(qt.v[k].name, f);
408 if (seen_in_s != 1 ||
409 seen_in_t != 1) {
410 fprintf(stderr, "\u00ab%s\u00bb appears\n", f);
411 fprintf(stderr, " %d time%s in \u00ab%s\u00bb\n",
412 seen_in_s, (seen_in_s == 1) ? " " : "s", sarg);
413 fprintf(stderr, " %d time%s in \u00ab%s\u00bb\n",
414 seen_in_t, (seen_in_t == 1) ? " " : "s", targ);
415 goto done;
419 /* Make sure all vertices in all files are distinct */
420 for (size_t j = 0; j < qs.v_num; ++j) {
421 for (size_t k = 0; k < j; ++k) {
422 if (!(strcmp(qs.v[j].name, qs.v[k].name))) {
423 fprintf(stderr,
424 "\u00ab%s\u00bb is not distinct in \u00ab%s\u00bb\n",
425 qs.v[j].name, sarg);
428 if (!(strcmp(qt.v[j].name, qt.v[k].name))) {
429 fprintf(stderr,
430 "\u00ab%s\u00bb is not distinct in \u00ab%s\u00bb\n",
431 qs.v[j].name, sarg);
437 * No immediate reason why there shouldn't be an isomorphism.
438 * Let's try it.
440 ret = check_iso(&qs, &qt, fixnames, fidx);
441 done:
442 quiver_clean(&qs);
443 quiver_clean(&qt);
445 if (fs) {
446 fclose(fs);
449 if (ft) {
450 fclose(ft);
453 free(fixnames);
455 return ret;