//
// homos.c di/graph homomorphisms Julius Jonusas
// J. D. Mitchell
//
// Copyright (C) 2014-19 - Julius Jonusas and J. D. Mitchell
//
// This file is free software, see the digraphs/LICENSE.
////////////////////////////////////////////////////////////////////////////////
//
// This file is organised as follows:
//
// 1. Macros
// 2. Forward declarations
// 3. Global variables
// 4. Hook functions
// 5. Static helper functions
// 6. The main recursive functions (and helpers)
// 7. The GAP-level function (and helpers)
//
////////////////////////////////////////////////////////////////////////////////
// TODO(later)
// 0. remove final arg from push_conditions
// 1. Try other bit hacks for iterating through set bits
#include "homos.h"
// C headers
#include <setjmp.h>
// for longjmp, setjmp, jmp_buf
#include <stdbool.h>
// for true, false, bool
#include <stddef.h>
// for NULL
#include <stdint.h>
// for uint16_t, uint64_t
#include <stdlib.h>
// for malloc, NULL
#ifdef DIGRAPHS_ENABLE_STATS
#include <cstdio>
// for printf
#endif
// GAP headers
#include "gap-includes.h"
// Digraphs package headers
#include "bitarray.h" // for BitArray
#include "bliss-includes.h" // for bliss stuff
#include "conditions.h" // for Conditions
#include "digraphs-config.h" // for DIGRAPHS_HAVE___BUILTIN_CTZLL
#include "digraphs-debug.h" // for DIGRAPHS_ASSERT
#include "globals.h" // for UNDEFINED...
#include "homos-graphs.h" // for Digraph, Graph, . . .
#include "perms.h" // for UNDEFINED, PermColl, Perm
#include "safemalloc.h" // for safe_mallov
#include "schreier-sims.h" // for PermColl, . . .
#ifdef DIGRAPHS_ENABLE_STATS
// This include has to come after digraphs-config.h since that's where
// DIGRAPHS_ENABLE_STATS is defined
#include <time.h>
// for time
#endif
////////////////////////////////////////////////////////////////////////////////
// 1. Macros
////////////////////////////////////////////////////////////////////////////////
#define MAX(a, b) (a < b ? b : a)
#define MIN(a, b) (a < b ? a : b)
#ifndef HOMOS_STRUCTURE_SIZE
uint16_t HOMOS_STRUCTURE_SIZE = 0;
#endif
#ifndef UNDEFINED
uint16_t UNDEFINED = 65535;
#endif
// The next line can be used instead of the first line of STORE_MIN to
// randomise which vertex of minimum degree is used next, but I didn't find any
// cases where this made things faster.
// if (x < current_x || (x == current_x && (rand() & 1))) {
#define STORE_MIN(current_x, current_y, x, y) \
if (x < current_x) { \
current_x = x; \
current_y = y; \
}
// See the comment before STORE_MIN
// if (x < current_x || (x == current_x && (rand() & 1))) {
#define STORE_MIN_BREAK(current_x, current_y, x, y) \
if (x < current_x) { \
current_x = x; \
current_y = y; \
if (current_x == 1) { \
break; \
} \
}
// The following macro is bitmap_decode_ctz_callpack from https://git.io/fho4p
#if SYS_IS_64_BIT &&
defined(DIGRAPHS_HAVE___BUILTIN_CTZLL)
#define FOR_SET_BITS(__bit_array, __nr_bits, __variable) \
for (size_t k = 0; k < number_of_blocks(__nr_bits); ++k) { \
Block block = __bit_array->blocks[k]; \
while (block != 0) { \
uint64_t t = block & -block; \
int r = __builtin_ctzll(block); \
__variable = k * 64 + r; \
if (__variable >= __nr_bits) { \
break; \
}
#define END_FOR_SET_BITS \
block ^= t; \
} \
}
#else
#define FOR_SET_BITS(__bit_array, __nr_bits, __variable) \
for (__variable = 0; __variable < __nr_bits; ++__variable) { \
if (get_bit_array(__bit_array, __variable)) {
#define END_FOR_SET_BITS \
} \
}
#endif
////////////////////////////////////////////////////////////////////////////////
// 2. Forward declarations
////////////////////////////////////////////////////////////////////////////////
// Defined in digraphs.h
Int DigraphNrVertices(Obj);
Obj FuncOutNeighbours(Obj, Obj);
// GAP level things, imported in digraphs.c
extern Obj IsDigraph;
extern Obj IsMultiDigraph;
extern Obj DIGRAPHS_ValidateVertexColouring;
extern Obj Infinity;
extern Obj IsSymmetricDigraph;
extern Obj GeneratorsOfGroup;
extern Obj AutomorphismGroup;
extern Obj IsPermGroup;
extern Obj IsDigraphAutomorphism;
extern Obj LargestMovedPointPerms;
extern Obj InfoWarning;
////////////////////////////////////////////////////////////////////////////////
// 3. Global variables
////////////////////////////////////////////////////////////////////////////////
static Obj GAP_FUNC;
// Variable to hold a GAP level hook function
static Obj (*HOOK)(
void*,
// HOOK function applied to every homo found
const uint16_t,
const uint16_t*);
static void* USER_PARAM;
// a USER_PARAM for the hook
static jmp_buf OUTOFHERE;
// so we can jump out of the deepest
static bool ORDERED;
// true if the vertices of the domain/source digraph
// should be considered in a different order than they are
// given, false otherwise.
static BitArray** BIT_ARRAY_BUFFER = NULL;
// A buffer
static BitArray* IMAGE_RESTRICT;
// Values in MAP must be in this
static BitArray** MAP_UNDEFINED = NULL;
// UNDEFINED positions in MAP
static BitArray* ORB_LOOKUP;
// points in orbit
static BitArray* VALS;
// Values in MAP already
static BitArray** REPS;
// orbit reps organised by depth
static Conditions* CONDITIONS;
static Digraph* DIGRAPH1;
// Digraphs to hold incoming GAP digraphs
static Digraph* DIGRAPH2;
static Graph* GRAPH1;
// Graphs to hold incoming GAP symmetric digraphs
static Graph* GRAPH2;
static BlissGraph** BLISS_GRAPH = NULL;
static uint16_t* MAP = NULL;
// partial image list
static uint16_t* COLORS2 = NULL;
// colors of range (di)graph
static uint16_t* INVERSE_ORDER = NULL;
// external -> internal
static uint16_t* MAP_BUFFER = NULL;
// For converting from internal ->
// external and back when calling the
// hook functions.
static uint16_t* ORB = NULL;
// Array for containing nodes in an orbit.
static uint16_t* ORDER = NULL;
// internal -> external
static PermColl** STAB_GENS = NULL;
// stabiliser generators
static SchreierSims* SCHREIER_SIMS;
#ifdef DIGRAPHS_ENABLE_STATS
struct homo_stats_struct {
time_t last_print;
size_t max_depth;
double mean_depth;
size_t nr_calls;
size_t nr_dead_branches;
time_t start_time;
};
typedef struct homo_stats_struct HomoStats;
static HomoStats* STATS;
static inline void clear_stats(HomoStats* stats) {
stats->last_print = time(0);
stats->max_depth = 0;
stats->mean_depth = 0;
stats->nr_calls = 0;
stats->nr_dead_branches = 0;
stats->start_time = time(0);
}
static void print_stats(HomoStats* stats) {
printf(
"Running for %0.0fs . . .\n", difftime(time(0), stats->start_time));
printf(
"Number of function calls = %*lu\n", 20, stats->nr_calls);
printf(
"Mean depth = %*.2f\n", 20, stats->mean_depth);
printf(
"Max depth = %*lu\n", 20, stats->max_depth);
printf(
"Number of dead branches = %*lu\n\n", 20, stats->nr_dead_branches);
}
static inline void update_stats(HomoStats* stats, uint64_t depth) {
if (depth > stats->max_depth) {
stats->max_depth = depth;
}
stats->nr_calls++;
stats->mean_depth += (depth - stats->mean_depth) / stats->nr_calls;
if (difftime(time(0), stats->last_print) > 0.9) {
print_stats(stats);
stats->last_print = time(0);
}
}
#endif // DIGRAPHS_ENABLE_STATS
////////////////////////////////////////////////////////////////////////////////
// 4. Hook functions
////////////////////////////////////////////////////////////////////////////////
static Obj
homo_hook_gap(
void* user_param, uint16_t
const nr, uint16_t
const* map) {
UInt2* ptr;
Obj t;
UInt i;
// copy map into new trans2
t = NEW_TRANS2(nr);
ptr = ADDR_TRANS2(t);
for (i = 0; i < nr; i++) {
ptr[i] = map[i];
}
return CALL_2ARGS(GAP_FUNC, user_param, t);
}
static Obj
homo_hook_collect(
void* user_param, uint16_t
const nr, uint16_t
const* map) {
UInt2* ptr;
Obj t;
UInt i;
// copy map into new trans2
t = NEW_TRANS2(nr);
ptr = ADDR_TRANS2(t);
for (i = 0; i < nr; i++) {
ptr[i] = map[i];
}
ASS_LIST(user_param, LEN_LIST(user_param) + 1, t);
return False;
}
////////////////////////////////////////////////////////////////////////////////
// 5. Static helper functions
////////////////////////////////////////////////////////////////////////////////
// print_array is not used, but can be useful for debugging.
// static void print_array(uint16_t const* const array, uint16_t const len) {
// if (array == NULL) {
// printf("NULL");
// return;
// }
// printf("<array {");
// for (uint16_t i = 0; i < len; i++) {
// printf(" %d", array[i]);
// }
// printf(" }>");
// }
bool homos_data_initialized =
false;
// did we call this method before?
Obj FuncDIGRAPHS_FREE_HOMOS_DATA(Obj self) {
if (homos_data_initialized) {
free_digraph(DIGRAPH1);
free_digraph(DIGRAPH2);
free_graph(GRAPH1);
free_graph(GRAPH2);
free_bit_array(IMAGE_RESTRICT);
free_bit_array(ORB_LOOKUP);
free(MAP);
free(COLORS2);
free(INVERSE_ORDER);
free(MAP_BUFFER);
free(ORB);
free(ORDER);
for (uint16_t i = 0; i < HOMOS_STRUCTURE_SIZE * 3; i++) {
bliss_digraphs_release(BLISS_GRAPH[i]);
}
for (uint16_t i = 0; i < HOMOS_STRUCTURE_SIZE; i++) {
free_bit_array(REPS[i]);
free_bit_array(BIT_ARRAY_BUFFER[i]);
free_bit_array(MAP_UNDEFINED[i]);
free_perm_coll(STAB_GENS[i]);
}
free(BLISS_GRAPH);
free(REPS);
free(BIT_ARRAY_BUFFER);
free(MAP_UNDEFINED);
free(STAB_GENS);
free_bit_array(VALS);
free_conditions(CONDITIONS);
free_schreier_sims(SCHREIER_SIMS);
homos_data_initialized =
false;
}
return 0L;
}
static void get_automorphism_group_from_gap(Obj digraph_obj, PermColl* out) {
DIGRAPHS_ASSERT(CALL_1ARGS(IsDigraph, digraph_obj) ==
True);
if (CALL_1ARGS(IsMultiDigraph, digraph_obj) ==
True) {
ErrorQuit(
"expected a digraph without multiple edges!", 0L, 0L);
}
Obj o = CALL_1ARGS(AutomorphismGroup, digraph_obj);
o = CALL_1ARGS(GeneratorsOfGroup, o);
DIGRAPHS_ASSERT(IS_LIST(o));
clear_perm_coll(out);
out->degree = PERM_DEGREE;
DIGRAPHS_ASSERT(out->capacity >= LEN_LIST(o));
Int k = 0;
// perm coll index
for (
Int i = 1; i <= LEN_LIST(o); ++i) {
DIGRAPHS_ASSERT(ISB_LIST(o, i));
DIGRAPHS_ASSERT(IS_PERM2(ELM_LIST(o, i)) || IS_PERM4(ELM_LIST(o, i)));
Obj p = ELM_LIST(o, i);
DIGRAPHS_ASSERT(LargestMovedPointPerm(p) <= PERM_DEGREE);
if (LargestMovedPointPerm(p) == 0) {
// the index k must be separate from the index i because of this continue
continue;
}
Perm q = out->perms[k++];
out->size++;
DIGRAPHS_ASSERT(out->size == k);
size_t dep;
if (IS_PERM2(p)) {
UInt2
const* ptp2 = CONST_ADDR_PERM2(p);
dep = MIN((uint16_t) DEG_PERM2(p), PERM_DEGREE);
for (uint16_t j = 0; j < dep; ++j) {
q[j] = (uint16_t) ptp2[j];
}
}
else {
DIGRAPHS_ASSERT(IS_PERM4(p));
UInt4
const* ptp4 = CONST_ADDR_PERM4(p);
dep = MIN((uint16_t) DEG_PERM4(p), PERM_DEGREE);
for (uint16_t j = 0; j < dep; ++j) {
q[j] = (uint16_t) ptp4[j];
}
}
for (uint16_t j = dep; j < PERM_DEGREE; ++j) {
q[j] = j;
}
}
}
static void init_digraph_from_digraph_obj(Digraph*
const digraph,
Obj digraph_obj,
bool const reorder) {
DIGRAPHS_ASSERT(digraph != NULL);
DIGRAPHS_ASSERT(CALL_1ARGS(IsDigraph, digraph_obj) ==
True);
UInt
const nr = DigraphNrVertices(digraph_obj);
Obj out = FuncOutNeighbours(0L, digraph_obj);
DIGRAPHS_ASSERT(nr < MAXVERTS);
DIGRAPHS_ASSERT(IS_PLIST(out));
clear_digraph(digraph, nr);
if (!reorder) {
for (uint16_t i = 1; i <= nr; i++) {
Obj nbs = ELM_PLIST(out, i);
DIGRAPHS_ASSERT(IS_LIST(nbs));
for (uint16_t j = 1; j <= LEN_LIST(nbs); j++) {
DIGRAPHS_ASSERT(IS_INTOBJ(ELM_LIST(nbs, j)));
add_edge_digraph(digraph, i - 1, INT_INTOBJ(ELM_LIST(nbs, j)) - 1);
}
}
}
else {
DIGRAPHS_ASSERT(ORDERED);
for (uint16_t i = 1; i <= nr; i++) {
Obj nbs = ELM_PLIST(out, ORDER[i - 1] + 1);
DIGRAPHS_ASSERT(IS_LIST(nbs));
for (uint16_t j = 1; j <= LEN_LIST(nbs); j++) {
DIGRAPHS_ASSERT(IS_INTOBJ(ELM_LIST(nbs, j)));
add_edge_digraph(
digraph, i - 1, INVERSE_ORDER[INT_INTOBJ(ELM_LIST(nbs, j)) - 1]);
}
}
}
}
static void init_graph_from_digraph_obj(Graph*
const graph,
Obj digraph_obj,
bool const reorder) {
DIGRAPHS_ASSERT(graph != NULL);
DIGRAPHS_ASSERT(CALL_1ARGS(IsDigraph, digraph_obj) ==
True);
DIGRAPHS_ASSERT(CALL_1ARGS(IsSymmetricDigraph, digraph_obj) ==
True);
UInt
const nr = DigraphNrVertices(digraph_obj);
Obj out = FuncOutNeighbours(0L, digraph_obj);
DIGRAPHS_ASSERT(nr < MAXVERTS);
DIGRAPHS_ASSERT(IS_PLIST(out));
clear_graph(graph, nr);
if (!reorder) {
for (uint16_t i = 1; i <= nr; i++) {
Obj nbs = ELM_PLIST(out, i);
DIGRAPHS_ASSERT(IS_LIST(nbs));
for (uint16_t j = 1; j <= LEN_LIST(nbs); j++) {
DIGRAPHS_ASSERT(IS_INTOBJ(ELM_LIST(nbs, j)));
add_edge_graph(graph, i - 1, INT_INTOBJ(ELM_LIST(nbs, j)) - 1);
}
}
}
else {
DIGRAPHS_ASSERT(ORDERED);
for (uint16_t i = 1; i <= nr; i++) {
// Nodes in the new graph
Obj nbs = ELM_PLIST(out, ORDER[i - 1] + 1);
DIGRAPHS_ASSERT(IS_LIST(nbs));
for (uint16_t j = 1; j <= LEN_LIST(nbs); j++) {
DIGRAPHS_ASSERT(IS_INTOBJ(ELM_LIST(nbs, j)));
add_edge_graph(
graph, i - 1, INVERSE_ORDER[INT_INTOBJ(ELM_LIST(nbs, j)) - 1]);
}
}
}
}
// Find orbit representatives of the group generated by STAB_GENS[rep_depth]
// and store them in REPS[rep_depth], only values in IMAGE_RESTRICT will be
// chosen as orbit representatives.
static bool compute_stabs_and_orbit_reps(uint16_t
const nr_nodes_1,
uint16_t
const nr_nodes_2,
uint16_t
const rep_depth,
uint16_t
const depth,
uint16_t
const pt,
bool const first_call) {
DIGRAPHS_ASSERT(rep_depth <= depth + 1);
if (depth == nr_nodes_1 - 1 && !first_call) {
// first_call is required in the case that nr_nodes_1 is 1, since without
// first_call this function returns false (in the next line) and REPS is
// not initialised.
return false;
// This doesn't really say anything about the stabiliser
}
else if (rep_depth > 0) {
point_stabilizer(
SCHREIER_SIMS, STAB_GENS[rep_depth - 1], STAB_GENS[rep_depth], pt);
if (STAB_GENS[rep_depth]->size == 0) {
// the stabiliser of pt in STAB_GENS[rep_depth - 1] is trivial
copy_bit_array(REPS[rep_depth], IMAGE_RESTRICT, nr_nodes_2);
complement_bit_arrays(REPS[rep_depth], VALS, nr_nodes_2);
// REPS[rep_depth] is a set of orbit representatives of the stabiliser of
// the existing values in MAP, which belong to VALS, and since the
// stabiliser is trivial, we take valid choice as the set of
// representatives.
return true;
// the stabiliser is trivial
}
}
init_bit_array(REPS[rep_depth],
false, nr_nodes_2);
copy_bit_array(ORB_LOOKUP, VALS, nr_nodes_2);
uint16_t fst = 0;
while (fst < PERM_DEGREE
&& (get_bit_array(ORB_LOOKUP, fst)
|| !get_bit_array(IMAGE_RESTRICT, fst))) {
fst++;
}
while (fst < PERM_DEGREE) {
ORB[0] = fst;
uint16_t n = 1;
// length of ORB
set_bit_array(REPS[rep_depth], fst,
true);
set_bit_array(ORB_LOOKUP, fst,
true);
for (uint16_t i = 0; i < n; ++i) {
for (uint16_t j = 0; j < STAB_GENS[rep_depth]->size; ++j) {
Perm gen = STAB_GENS[rep_depth]->perms[j];
uint16_t
const img = gen[ORB[i]];
if (!get_bit_array(ORB_LOOKUP, img)) {
ORB[n++] = img;
set_bit_array(ORB_LOOKUP, img,
true);
}
}
}
while (fst < PERM_DEGREE
&& (get_bit_array(ORB_LOOKUP, fst)
|| !get_bit_array(IMAGE_RESTRICT, fst))) {
fst++;
}
}
return false;
// the stabiliser is not trivial
}
// Rewrite the global variable MAP so that it contains a homomorphism of the
// original GAP level (di)graph, and not the possibly distinct (but isomorphic)
// copy in the homomorphism search. This should be called before any calls to
// the hook functions (i.e. after an entire homomorphism is found).
static void external_order_map_digraph(Digraph* digraph) {
if (!ORDERED) {
return;
}
for (uint16_t i = 0; i < digraph->nr_vertices; ++i) {
MAP_BUFFER[ORDER[i]] = MAP[i];
}
for (uint16_t i = 0; i < digraph->nr_vertices; ++i) {
MAP[i] = MAP_BUFFER[i];
}
}
static void external_order_map_graph(Graph* graph) {
if (!ORDERED) {
return;
}
for (uint16_t i = 0; i < graph->nr_vertices; ++i) {
MAP_BUFFER[ORDER[i]] = MAP[i];
}
for (uint16_t i = 0; i < graph->nr_vertices; ++i) {
MAP[i] = MAP_BUFFER[i];
}
}
// Rewrite the global variable MAP so that it contains a homomorphism of the
// internal (di)graph, and not the possibly distinct (but isomorphic) GAP level
// (di)graph. This should be called after any calls to the hook functions
// (i.e. after an entire homomorphism is found).
static void internal_order_map_digraph(Digraph
const*
const digraph) {
if (!ORDERED) {
return;
}
for (uint16_t i = 0; i < digraph->nr_vertices; ++i) {
MAP_BUFFER[INVERSE_ORDER[i]] = MAP[i];
}
for (uint16_t i = 0; i < digraph->nr_vertices; ++i) {
MAP[i] = MAP_BUFFER[i];
}
}
static void internal_order_map_graph(Graph
const*
const graph) {
if (!ORDERED) {
return;
}
for (uint16_t i = 0; i < graph->nr_vertices; ++i) {
MAP_BUFFER[INVERSE_ORDER[i]] = MAP[i];
}
for (uint16_t i = 0; i < graph->nr_vertices; ++i) {
MAP[i] = MAP_BUFFER[i];
}
}
static void set_automorphisms(Obj aut_grp_obj, PermColl* out) {
DIGRAPHS_ASSERT(out != NULL);
clear_perm_coll(out);
out->degree = PERM_DEGREE;
Obj gens = CALL_1ARGS(GeneratorsOfGroup, aut_grp_obj);
DIGRAPHS_ASSERT(IS_LIST(gens));
DIGRAPHS_ASSERT(LEN_LIST(gens) > 0);
for (
Int i = 1; i <= LEN_LIST(gens); ++i) {
Obj gen_obj = ELM_LIST(gens, i);
if (LargestMovedPointPerm(gen_obj) > 0) {
Perm
const p = new_perm_from_gap(gen_obj, PERM_DEGREE);
add_perm_coll(out, p);
free(p);
}
}
}
////////////////////////////////////////////////////////////////////////////////
// 6. The main recursive functions (and helpers)
////////////////////////////////////////////////////////////////////////////////
// Helper for the main recursive homomorphism function.
static ALWAYS_INLINE uint16_t
graph_homo_update_conditions(uint16_t
const depth,
uint16_t
const last_defined,
uint16_t
const vertex) {
push_conditions(
CONDITIONS, depth, vertex, GRAPH2->neighbours[MAP[last_defined]]);
store_size_conditions(CONDITIONS, vertex);
return size_conditions(CONDITIONS, vertex);
}
// The main recursive function for homomorphisms of graphs.
//
// The arguments are:
//
// 1. depth The current depth of the search (i.e. number of
// positions in MAP that are assigned).
// 2. pos The last position in MAP that was filled
// 3. rep_depth The depth of the stabilisers of points in MAP already.
// This is different than depth because we stop increasing
// the rep_depth when we first encounter a trivial
// stabiliser.
// 4. has_trivial_stab true if the stabiliser of the values already in MAP is
// trivial, false otherwise.
// 5. rank The current number of distinct values in MAP.
// 6. max_results The maximum number of results to find.
// 7. hint The desired number of distinct points in a (full)
// homomorphism.
// 8. count The number of homomorphisms found so far.
static void find_graph_homos(uint16_t depth,
uint16_t pos,
uint16_t rep_depth,
bool has_trivial_stab,
uint16_t rank,
uint64_t
const max_results,
uint64_t
const hint,
uint64_t*
const count) {
#ifdef DIGRAPHS_ENABLE_STATS
update_stats(STATS, depth);
#endif
if (depth == GRAPH1->nr_vertices) {
// Every position in MAP is assigned . . .
if (hint != UNDEFINED && rank != hint) {
#ifdef DIGRAPHS_ENABLE_STATS
STATS->nr_dead_branches++;
#endif
return;
}
external_order_map_graph(GRAPH1);
Obj ret =
HOOK(USER_PARAM, MAX(GRAPH1->nr_vertices, GRAPH2->nr_vertices), MAP);
internal_order_map_graph(GRAPH1);
(*count)++;
if (*count >= max_results || ret ==
True) {
longjmp(OUTOFHERE, 1);
}
return;
}
uint16_t next = 0;
// the next position to fill
uint16_t min = UNDEFINED;
// the minimum number of candidates for MAP[next]
uint16_t i;
BitArray* possible = BIT_ARRAY_BUFFER[depth];
if (depth > 0) {
// this is not the first call of the function
copy_bit_array(
MAP_UNDEFINED[depth], MAP_UNDEFINED[depth - 1], GRAPH1->nr_vertices);
copy_bit_array(possible, MAP_UNDEFINED[depth], GRAPH1->nr_vertices);
intersect_bit_arrays(
possible, GRAPH1->neighbours[pos], GRAPH1->nr_vertices);
FOR_SET_BITS(possible, GRAPH1->nr_vertices, i) {
size_t
const n = graph_homo_update_conditions(depth, pos, i);
if (n == 0) {
#ifdef DIGRAPHS_ENABLE_STATS
STATS->nr_dead_branches++;
#endif
pop_conditions(CONDITIONS, depth);
return;
}
STORE_MIN(min, next, n, i);
}
END_FOR_SET_BITS
if (min > 1) {
copy_bit_array(possible, MAP_UNDEFINED[depth], GRAPH1->nr_vertices);
complement_bit_arrays(
possible, GRAPH1->neighbours[pos], GRAPH1->nr_vertices);
FOR_SET_BITS(possible, GRAPH1->nr_vertices, i) {
size_t
const n = size_conditions(CONDITIONS, i);
STORE_MIN_BREAK(min, next, n, i);
}
END_FOR_SET_BITS
}
}
else {
for (i = 0; i < GRAPH1->nr_vertices; ++i) {
size_t
const n = size_conditions(CONDITIONS, i);
STORE_MIN_BREAK(min, next, n, i);
}
}
DIGRAPHS_ASSERT(get_bit_array(MAP_UNDEFINED[depth], next));
DIGRAPHS_ASSERT(next < GRAPH1->nr_vertices);
if (rank < hint) {
copy_bit_array(
possible, get_conditions(CONDITIONS, next), GRAPH2->nr_vertices);
complement_bit_arrays(possible, VALS, GRAPH2->nr_vertices);
intersect_bit_arrays(possible, REPS[rep_depth], GRAPH2->nr_vertices);
FOR_SET_BITS(possible, GRAPH2->nr_vertices, i) {
MAP[next] = i;
set_bit_array(VALS, i,
true);
set_bit_array(MAP_UNDEFINED[depth], next,
false);
if (!has_trivial_stab) {
find_graph_homos(depth + 1,
next,
rep_depth + 1,
compute_stabs_and_orbit_reps(GRAPH1->nr_vertices,
GRAPH2->nr_vertices,
rep_depth + 1,
depth,
i,
false),
rank + 1,
max_results,
hint,
count);
}
else {
find_graph_homos(depth + 1,
next,
rep_depth,
true,
rank + 1,
max_results,
hint,
count);
}
MAP[next] = UNDEFINED;
set_bit_array(VALS, i,
false);
set_bit_array(MAP_UNDEFINED[depth], next,
true);
}
END_FOR_SET_BITS
}
copy_bit_array(
possible, get_conditions(CONDITIONS, next), GRAPH2->nr_vertices);
intersect_bit_arrays(possible, VALS, GRAPH2->nr_vertices);
FOR_SET_BITS(possible, GRAPH2->nr_vertices, i) {
MAP[next] = i;
set_bit_array(MAP_UNDEFINED[depth], next,
false);
find_graph_homos(depth + 1,
next,
rep_depth,
has_trivial_stab,
rank,
max_results,
hint,
count);
MAP[next] = UNDEFINED;
set_bit_array(MAP_UNDEFINED[depth], next,
true);
}
END_FOR_SET_BITS
pop_conditions(CONDITIONS, depth);
}
// Helper for the main recursive monomorphism function.
static ALWAYS_INLINE uint16_t
graph_mono_update_conditions(uint16_t
const depth,
uint16_t
const last_defined,
uint16_t
const vertex) {
push_conditions(
CONDITIONS, depth, vertex, GRAPH2->neighbours[MAP[last_defined]]);
store_size_conditions(CONDITIONS, vertex);
return size_conditions(CONDITIONS, vertex);
}
// The main recursive function for monomorphisms of graphs.
//
// The arguments are:
//
// 1. depth The current depth of the search (i.e. number of
// positions in MAP that are assigned).
// 2. pos The last position in MAP that was filled
// 3. rep_depth The depth of the stabilisers of points in MAP already.
// This is different than depth because we stop increasing
// the rep_depth when we first encounter a trivial
// stabiliser.
// 4. has_trivial_stab true if the stabiliser of the values already in MAP is
// trivial, false otherwise.
// 5. max_results The maximum number of results to find.
// 6. count The number of embeddings found so far.
static void find_graph_monos(uint16_t depth,
uint16_t pos,
uint16_t rep_depth,
bool has_trivial_stab,
uint64_t
const max_results,
uint64_t*
const count) {
#ifdef DIGRAPHS_ENABLE_STATS
update_stats(STATS, depth);
#endif
if (depth == GRAPH1->nr_vertices) {
// we've assigned every position in <MAP>
external_order_map_graph(GRAPH1);
Obj ret =
HOOK(USER_PARAM, MAX(GRAPH1->nr_vertices, GRAPH2->nr_vertices), MAP);
internal_order_map_graph(GRAPH1);
(*count)++;
if (*count >= max_results || ret ==
True) {
longjmp(OUTOFHERE, 1);
}
return;
}
uint16_t next = 0;
// the next position to fill
uint16_t min = UNDEFINED;
// the minimum number of candidates for MAP[next]
uint16_t i;
BitArray* possible = BIT_ARRAY_BUFFER[depth];
if (depth > 0) {
// this is not the first call of the function
copy_bit_array(
MAP_UNDEFINED[depth], MAP_UNDEFINED[depth - 1], GRAPH1->nr_vertices);
copy_bit_array(possible, MAP_UNDEFINED[depth], GRAPH1->nr_vertices);
intersect_bit_arrays(
possible, GRAPH1->neighbours[pos], GRAPH1->nr_vertices);
FOR_SET_BITS(possible, GRAPH1->nr_vertices, i) {
size_t
const n = graph_mono_update_conditions(depth, pos, i);
if (n == 0) {
#ifdef DIGRAPHS_ENABLE_STATS
STATS->nr_dead_branches++;
#endif
pop_conditions(CONDITIONS, depth);
return;
}
STORE_MIN(min, next, n, i);
}
END_FOR_SET_BITS
if (min > 1) {
copy_bit_array(possible, MAP_UNDEFINED[depth], GRAPH1->nr_vertices);
complement_bit_arrays(
possible, GRAPH1->neighbours[pos], GRAPH1->nr_vertices);
FOR_SET_BITS(possible, GRAPH1->nr_vertices, i) {
size_t
const n = size_conditions(CONDITIONS, i);
STORE_MIN_BREAK(min, next, n, i);
}
END_FOR_SET_BITS
}
}
else {
for (i = 0; i < GRAPH1->nr_vertices; ++i) {
size_t
const n = size_conditions(CONDITIONS, i);
STORE_MIN_BREAK(min, next, n, i);
}
}
copy_bit_array(
possible, get_conditions(CONDITIONS, next), GRAPH2->nr_vertices);
intersect_bit_arrays(possible, REPS[rep_depth], GRAPH2->nr_vertices);
complement_bit_arrays(possible, VALS, GRAPH2->nr_vertices);
FOR_SET_BITS(possible, GRAPH2->nr_vertices, i) {
MAP[next] = i;
set_bit_array(VALS, i,
true);
set_bit_array(MAP_UNDEFINED[depth], next,
false);
if (!has_trivial_stab) {
find_graph_monos(depth + 1,
next,
rep_depth + 1,
compute_stabs_and_orbit_reps(GRAPH1->nr_vertices,
GRAPH2->nr_vertices,
rep_depth + 1,
depth,
i,
false),
max_results,
count);
}
else {
find_graph_monos(depth + 1, next, rep_depth,
true, max_results, count);
}
MAP[next] = UNDEFINED;
set_bit_array(VALS, i,
false);
set_bit_array(MAP_UNDEFINED[depth], next,
true);
}
END_FOR_SET_BITS
pop_conditions(CONDITIONS, depth);
}
// Helper for the main recursive embedding function.
static ALWAYS_INLINE uint16_t graph_embed_update_conditions(
uint16_t
const depth,
uint16_t
const last_defined,
uint16_t
const vertex,
void (*oper)(BitArray*
const, BitArray
const*
const, uint16_t
const)) {
push_conditions(CONDITIONS, depth, vertex, NULL);
oper(get_conditions(CONDITIONS, vertex),
GRAPH2->neighbours[MAP[last_defined]],
GRAPH2->nr_vertices);
store_size_conditions(CONDITIONS, vertex);
return size_conditions(CONDITIONS, vertex);
}
// The main recursive function for embeddings of graphs.
//
// The arguments are:
//
// 1. depth The current depth of the search (i.e. number of
// positions in MAP that are assigned).
// 2. pos The last position in MAP that was filled
// 3. rep_depth The depth of the stabilisers of points in MAP already.
// This is different than depth because we stop increasing
// the rep_depth when we first encounter a trivial
// stabiliser.
// 4. has_trivial_stab true if the stabiliser of the values already in MAP is
// trivial, false otherwise.
// 5. max_results The maximum number of results to find.
// 6. count The number of embeddings found so far.
static void find_graph_embeddings(uint16_t depth,
uint16_t pos,
uint16_t rep_depth,
bool has_trivial_stab,
uint64_t
const max_results,
uint64_t*
const count) {
#ifdef DIGRAPHS_ENABLE_STATS
update_stats(STATS, depth);
#endif
if (depth == GRAPH1->nr_vertices) {
// we've assigned every position in <MAP>
external_order_map_graph(GRAPH1);
Obj ret =
HOOK(USER_PARAM, MAX(GRAPH1->nr_vertices, GRAPH2->nr_vertices), MAP);
internal_order_map_graph(GRAPH1);
(*count)++;
if (*count >= max_results || ret ==
True) {
longjmp(OUTOFHERE, 1);
}
return;
}
uint16_t next = 0;
// the next position to fill
uint16_t min = UNDEFINED;
// the minimum number of candidates for MAP[next]
uint16_t i;
BitArray* possible = BIT_ARRAY_BUFFER[depth];
if (depth > 0) {
// this is not the first call of the function
copy_bit_array(
MAP_UNDEFINED[depth], MAP_UNDEFINED[depth - 1], GRAPH1->nr_vertices);
copy_bit_array(possible, MAP_UNDEFINED[depth], GRAPH1->nr_vertices);
intersect_bit_arrays(
possible, GRAPH1->neighbours[pos], GRAPH1->nr_vertices);
FOR_SET_BITS(possible, GRAPH1->nr_vertices, i) {
size_t
const n =
graph_embed_update_conditions(depth, pos, i, &intersect_bit_arrays);
if (n == 0) {
#ifdef DIGRAPHS_ENABLE_STATS
STATS->nr_dead_branches++;
#endif
pop_conditions(CONDITIONS, depth);
return;
}
STORE_MIN(min, next, n, i);
}
END_FOR_SET_BITS
copy_bit_array(possible, MAP_UNDEFINED[depth], GRAPH1->nr_vertices);
complement_bit_arrays(
possible, GRAPH1->neighbours[pos], GRAPH1->nr_vertices);
FOR_SET_BITS(possible, GRAPH1->nr_vertices, i) {
size_t
const n =
graph_embed_update_conditions(depth, pos, i, &complement_bit_arrays);
if (n == 0) {
#ifdef DIGRAPHS_ENABLE_STATS
STATS->nr_dead_branches++;
#endif
pop_conditions(CONDITIONS, depth);
return;
}
STORE_MIN(min, next, n, i);
}
END_FOR_SET_BITS
}
else {
for (i = 0; i < GRAPH1->nr_vertices; ++i) {
size_t
const n = size_conditions(CONDITIONS, i);
STORE_MIN_BREAK(min, next, n, i);
}
}
copy_bit_array(
possible, get_conditions(CONDITIONS, next), GRAPH2->nr_vertices);
intersect_bit_arrays(possible, REPS[rep_depth], GRAPH2->nr_vertices);
complement_bit_arrays(possible, VALS, GRAPH2->nr_vertices);
FOR_SET_BITS(possible, GRAPH2->nr_vertices, i) {
MAP[next] = i;
set_bit_array(VALS, i,
true);
set_bit_array(MAP_UNDEFINED[depth], next,
false);
if (!has_trivial_stab) {
find_graph_embeddings(depth + 1,
next,
rep_depth + 1,
compute_stabs_and_orbit_reps(GRAPH1->nr_vertices,
GRAPH2->nr_vertices,
rep_depth + 1,
depth,
MAP[next],
false),
max_results,
count);
}
else {
find_graph_embeddings(
depth + 1, next, rep_depth,
true, max_results, count);
}
MAP[next] = UNDEFINED;
set_bit_array(VALS, i,
false);
set_bit_array(MAP_UNDEFINED[depth], next,
true);
}
END_FOR_SET_BITS
pop_conditions(CONDITIONS, depth);
}
////////////////////////////////////////////////////////////////////////////////
// The next function should be called before find_graph_homos. This function
// simulates the first steps of the recursion using the information in
// partial_map_obj (if any) so that the values specified in partial_map_obj are
// installed in MAP first.
////////////////////////////////////////////////////////////////////////////////
static void init_partial_map_and_find_graph_homos(Obj partial_map_obj,
uint64_t
const max_results,
uint64_t
const hint,
uint64_t*
const count,
Obj injective_obj) {
uint16_t depth = 0;
uint16_t rep_depth = 0;
uint16_t last_defined = UNDEFINED;
bool last_stab_is_trivial = (STAB_GENS[0]->size == 0 ?
true :
false);
uint16_t rank = 0;
uint16_t next = UNDEFINED;
if (partial_map_obj != Fail) {
for (next = 0; next < LEN_LIST(partial_map_obj); ++next) {
if (ISB_LIST(partial_map_obj, next + 1)) {
if (depth > 0) {
DIGRAPHS_ASSERT(last_defined != UNDEFINED);
copy_bit_array(MAP_UNDEFINED[depth],
MAP_UNDEFINED[depth - 1],
GRAPH1->nr_vertices);
BitArray* possible = BIT_ARRAY_BUFFER[0];
copy_bit_array(possible, MAP_UNDEFINED[depth], GRAPH1->nr_vertices);
intersect_bit_arrays(
possible, GRAPH1->neighbours[last_defined], GRAPH1->nr_vertices);
if (INT_INTOBJ(injective_obj) == 0) {
uint16_t i;
// variable for the next for-loop
FOR_SET_BITS(possible, GRAPH1->nr_vertices, i) {
uint16_t n = graph_homo_update_conditions(depth, last_defined, i);
if (n == 0) {
return;
}
}
END_FOR_SET_BITS
}
else if (INT_INTOBJ(injective_obj) == 1) {
uint16_t i;
// variable for the next for-loop
FOR_SET_BITS(possible, GRAPH1->nr_vertices, i) {
uint16_t n = graph_mono_update_conditions(depth, last_defined, i);
if (n == 0) {
return;
}
}
END_FOR_SET_BITS
}
else {
DIGRAPHS_ASSERT(INT_INTOBJ(injective_obj) == 2);
uint16_t i;
// variable for the next for-loop
FOR_SET_BITS(possible, GRAPH1->nr_vertices, i) {
uint16_t n = graph_embed_update_conditions(
depth, last_defined, i, &intersect_bit_arrays);
if (n == 0) {
return;
}
END_FOR_SET_BITS
}
copy_bit_array(possible, MAP_UNDEFINED[depth], GRAPH1->nr_vertices);
complement_bit_arrays(possible,
GRAPH1->neighbours[last_defined],
GRAPH1->nr_vertices);
FOR_SET_BITS(possible, GRAPH1->nr_vertices, i) {
uint16_t n = graph_embed_update_conditions(
depth, last_defined, i, &complement_bit_arrays);
if (n == 0) {
return;
}
}
END_FOR_SET_BITS
}
}
uint16_t val = INT_INTOBJ(ELM_LIST(partial_map_obj, next + 1)) - 1;
next = (ORDERED ? INVERSE_ORDER[next] : next);
MAP[next] = val;
if (!get_bit_array(VALS, MAP[next])) {
rank++;
if (rank > hint) {
return;
}
}
set_bit_array(VALS, MAP[next],
true);
set_bit_array(MAP_UNDEFINED[depth], next,
false);
if (!last_stab_is_trivial) {
last_stab_is_trivial =
compute_stabs_and_orbit_reps(GRAPH1->nr_vertices,
GRAPH2->nr_vertices,
rep_depth + 1,
depth,
MAP[next],
false);
rep_depth++;
}
depth++;
last_defined = next;
next = (ORDERED ? ORDER[next] : next);
}
}
}
if (INT_INTOBJ(injective_obj) == 0) {
find_graph_homos(depth,
last_defined,
rep_depth,
last_stab_is_trivial,
rank,
max_results,
hint,
count);
}
else if (INT_INTOBJ(injective_obj) == 1) {
find_graph_monos(depth,
last_defined,
rep_depth,
last_stab_is_trivial,
max_results,
count);
}
else if (INT_INTOBJ(injective_obj) == 2) {
find_graph_embeddings(depth,
last_defined,
rep_depth,
last_stab_is_trivial,
max_results,
count);
}
}
// Helper for the main recursive homomorphism of digraphs function.
static ALWAYS_INLINE uint16_t
digraph_homo_update_conditions(uint16_t
const depth,
uint16_t
const last_defined,
uint16_t
const vertex) {
if (is_adjacent_digraph(DIGRAPH1, last_defined, vertex)) {
push_conditions(
CONDITIONS, depth, vertex, DIGRAPH2->out_neighbours[MAP[last_defined]]);
if (is_adjacent_digraph(DIGRAPH1, vertex, last_defined)) {
intersect_bit_arrays(get_conditions(CONDITIONS, vertex),
DIGRAPH2->in_neighbours[MAP[last_defined]],
DIGRAPH2->nr_vertices);
}
store_size_conditions(CONDITIONS, vertex);
}
else if (is_adjacent_digraph(DIGRAPH1, vertex, last_defined)) {
push_conditions(
CONDITIONS, depth, vertex, DIGRAPH2->in_neighbours[MAP[last_defined]]);
store_size_conditions(CONDITIONS, vertex);
}
return size_conditions(CONDITIONS, vertex);
}
// The main recursive function for homomorphisms of digraphs.
//
// The arguments are:
//
// 1. depth The current depth of the search (i.e. number of
// positions in MAP that are assigned).
// 2. pos The last position in MAP that was filled
// 3. rep_depth The depth of the stabilisers of points in MAP already.
// This is different than depth because we stop increasing
// the rep_depth when we first encounter a trivial
// stabiliser.
// 4. has_trivial_stab true if the stabiliser of the values already in MAP is
// trivial, false otherwise.
// 5. rank The current number of distinct values in MAP.
// 6. max_results The maximum number of results to find.
// 7. hint The desired number of distinct points in a (full)
// homomorphism.
// 8. count The number of homomorphisms found so far.
static void find_digraph_homos(uint16_t depth,
uint16_t pos,
uint16_t rep_depth,
bool has_trivial_stab,
uint16_t rank,
uint64_t
const max_results,
uint64_t
const hint,
uint64_t*
const count) {
#ifdef DIGRAPHS_ENABLE_STATS
update_stats(STATS, depth);
#endif
if (depth == DIGRAPH1->nr_vertices) {
// we've assigned every position in <MAP>
if (hint != UNDEFINED && rank != hint) {
#ifdef DIGRAPHS_ENABLE_STATS
STATS->nr_dead_branches++;
#endif
return;
}
external_order_map_digraph(DIGRAPH1);
Obj ret = HOOK(
USER_PARAM, MAX(DIGRAPH1->nr_vertices, DIGRAPH2->nr_vertices), MAP);
internal_order_map_digraph(DIGRAPH1);
(*count)++;
if (*count >= max_results || ret ==
True) {
longjmp(OUTOFHERE, 1);
}
return;
}
uint16_t next = 0;
// the next position to fill
uint16_t min = UNDEFINED;
// the minimum number of candidates for MAP[next]
uint16_t i;
if (depth > 0) {
// this is not the first call of the function
copy_bit_array(
MAP_UNDEFINED[depth], MAP_UNDEFINED[depth - 1], DIGRAPH1->nr_vertices);
FOR_SET_BITS(MAP_UNDEFINED[depth], DIGRAPH1->nr_vertices, i) {
uint16_t
const n = digraph_homo_update_conditions(depth, pos, i);
if (n == 0) {
#ifdef DIGRAPHS_ENABLE_STATS
STATS->nr_dead_branches++;
#endif
pop_conditions(CONDITIONS, depth);
return;
}
STORE_MIN(min, next, n, i);
}
END_FOR_SET_BITS
}
else {
// depth == 0
for (i = 0; i < DIGRAPH1->nr_vertices; ++i) {
size_t
const n = size_conditions(CONDITIONS, i);
STORE_MIN_BREAK(min, next, n, i);
}
}
BitArray* possible = BIT_ARRAY_BUFFER[depth];
if (rank < hint) {
copy_bit_array(
possible, get_conditions(CONDITIONS, next), DIGRAPH2->nr_vertices);
complement_bit_arrays(possible, VALS, DIGRAPH2->nr_vertices);
intersect_bit_arrays(possible, REPS[rep_depth], DIGRAPH2->nr_vertices);
FOR_SET_BITS(possible, DIGRAPH2->nr_vertices, i) {
MAP[next] = i;
set_bit_array(VALS, i,
true);
set_bit_array(MAP_UNDEFINED[depth], next,
false);
if (!has_trivial_stab) {
find_digraph_homos(depth + 1,
next,
rep_depth + 1,
compute_stabs_and_orbit_reps(DIGRAPH1->nr_vertices,
DIGRAPH2->nr_vertices,
rep_depth + 1,
depth,
i,
false),
rank + 1,
max_results,
hint,
count);
}
else {
find_digraph_homos(depth + 1,
next,
rep_depth,
true,
rank + 1,
max_results,
hint,
count);
}
MAP[next] = UNDEFINED;
set_bit_array(VALS, i,
false);
set_bit_array(MAP_UNDEFINED[depth], next,
true);
}
END_FOR_SET_BITS
}
copy_bit_array(
possible, get_conditions(CONDITIONS, next), DIGRAPH2->nr_vertices);
intersect_bit_arrays(possible, VALS, DIGRAPH2->nr_vertices);
FOR_SET_BITS(possible, DIGRAPH2->nr_vertices, i) {
MAP[next] = i;
set_bit_array(MAP_UNDEFINED[depth], next,
false);
find_digraph_homos(depth + 1,
next,
rep_depth,
has_trivial_stab,
rank,
max_results,
hint,
count);
MAP[next] = UNDEFINED;
set_bit_array(MAP_UNDEFINED[depth], next,
true);
}
END_FOR_SET_BITS
pop_conditions(CONDITIONS, depth);
}
// Helper for the main recursive monomorphism of digraphs function.
static ALWAYS_INLINE uint16_t
digraph_mono_update_conditions(uint16_t
const depth,
uint16_t
const last_defined,
uint16_t
const vertex) {
push_conditions(CONDITIONS, depth, vertex, NULL);
if (is_adjacent_digraph(DIGRAPH1, last_defined, vertex)) {
intersect_bit_arrays(get_conditions(CONDITIONS, vertex),
DIGRAPH2->out_neighbours[MAP[last_defined]],
DIGRAPH2->nr_vertices);
}
if (is_adjacent_digraph(DIGRAPH1, vertex, last_defined)) {
intersect_bit_arrays(get_conditions(CONDITIONS, vertex),
DIGRAPH2->in_neighbours[MAP[last_defined]],
DIGRAPH2->nr_vertices);
}
store_size_conditions(CONDITIONS, vertex);
return size_conditions(CONDITIONS, vertex);
}
// The main recursive function for monomorphisms of digraphs.
//
// The arguments are:
//
// 1. depth The current depth of the search (i.e. number of
// positions in MAP that are assigned).
// 2. pos The last position in MAP that was filled
// 3. rep_depth The depth of the stabilisers of points in MAP already.
// This is different than depth because we stop increasing
// the rep_depth when we first encounter a trivial
// stabiliser.
// 4. has_trivial_stab true if the stabiliser of the values already in MAP is
// trivial, false otherwise.
// 5. max_results The maximum number of results to find.
// 6. count The number of embeddings found so far.
static void find_digraph_monos(uint16_t depth,
uint16_t pos,
uint16_t rep_depth,
bool has_trivial_stab,
uint64_t
const max_results,
uint64_t*
const count) {
#ifdef DIGRAPHS_ENABLE_STATS
update_stats(STATS, depth);
#endif
if (depth == DIGRAPH1->nr_vertices) {
// we've assigned every position in <MAP>
external_order_map_digraph(DIGRAPH1);
Obj ret = HOOK(
USER_PARAM, MAX(DIGRAPH1->nr_vertices, DIGRAPH2->nr_vertices), MAP);
internal_order_map_digraph(DIGRAPH1);
(*count)++;
if (*count >= max_results || ret ==
True) {
longjmp(OUTOFHERE, 1);
}
return;
}
uint16_t next = 0;
// the next position to fill
uint16_t min = UNDEFINED;
// the minimum number of candidates for MAP[next]
uint16_t i;
if (depth > 0) {
// this is not the first call of the function
copy_bit_array(
MAP_UNDEFINED[depth], MAP_UNDEFINED[depth - 1], DIGRAPH1->nr_vertices);
FOR_SET_BITS(MAP_UNDEFINED[depth], DIGRAPH1->nr_vertices, i) {
size_t
const n = digraph_mono_update_conditions(depth, pos, i);
if (n == 0) {
#ifdef DIGRAPHS_ENABLE_STATS
STATS->nr_dead_branches++;
#endif
pop_conditions(CONDITIONS, depth);
return;
}
STORE_MIN(min, next, n, i);
}
END_FOR_SET_BITS
}
else {
for (i = 0; i < DIGRAPH1->nr_vertices; i++) {
size_t
const n = size_conditions(CONDITIONS, i);
STORE_MIN_BREAK(min, next, n, i);
}
}
BitArray* possible = BIT_ARRAY_BUFFER[depth];
copy_bit_array(
possible, get_conditions(CONDITIONS, next), DIGRAPH2->nr_vertices);
intersect_bit_arrays(possible, REPS[rep_depth], DIGRAPH2->nr_vertices);
complement_bit_arrays(possible, VALS, DIGRAPH2->nr_vertices);
FOR_SET_BITS(possible, DIGRAPH2->nr_vertices, i) {
MAP[next] = i;
set_bit_array(VALS, i,
true);
set_bit_array(MAP_UNDEFINED[depth], next,
false);
if (!has_trivial_stab) {
find_digraph_monos(depth + 1,
next,
rep_depth + 1,
compute_stabs_and_orbit_reps(DIGRAPH1->nr_vertices,
DIGRAPH2->nr_vertices,
rep_depth + 1,
depth,
i,
false),
max_results,
count);
}
else {
find_digraph_monos(depth + 1, next, rep_depth,
true, max_results, count);
}
MAP[next] = UNDEFINED;
set_bit_array(VALS, i,
false);
set_bit_array(MAP_UNDEFINED[depth], next,
true);
}
END_FOR_SET_BITS
pop_conditions(CONDITIONS, depth);
}
// Helper for the main recursive embedding digraphs function.
static ALWAYS_INLINE uint16_t
digraph_embed_update_conditions(uint16_t
const depth,
uint16_t
const last_def,
uint16_t
const vertex) {
push_conditions(CONDITIONS, depth, vertex, NULL);
if (is_adjacent_digraph(DIGRAPH1, last_def, vertex)) {
intersect_bit_arrays(get_conditions(CONDITIONS, vertex),
DIGRAPH2->out_neighbours[MAP[last_def]],
DIGRAPH2->nr_vertices);
}
else {
complement_bit_arrays(get_conditions(CONDITIONS, vertex),
DIGRAPH2->out_neighbours[MAP[last_def]],
DIGRAPH2->nr_vertices);
}
if (is_adjacent_digraph(DIGRAPH1, vertex, last_def)) {
intersect_bit_arrays(get_conditions(CONDITIONS, vertex),
DIGRAPH2->in_neighbours[MAP[last_def]],
DIGRAPH2->nr_vertices);
}
else {
complement_bit_arrays(get_conditions(CONDITIONS, vertex),
DIGRAPH2->in_neighbours[MAP[last_def]],
DIGRAPH2->nr_vertices);
}
store_size_conditions(CONDITIONS, vertex);
return size_conditions(CONDITIONS, vertex);
}
// The main recursive function for embeddings of digraphs.
//
// The arguments are:
//
// 1. depth The current depth of the search (i.e. number of
// positions in MAP that are assigned).
// 2. pos The last position in MAP that was filled
// 3. rep_depth The depth of the stabilisers of points in MAP already.
// This is different than depth because we stop increasing
// the rep_depth when we first encounter a trivial
// stabiliser.
// 4. has_trivial_stab true if the stabiliser of the values already in MAP is
// trivial, false otherwise.
// 5. max_results The maximum number of results to find.
// 6. count The number of embeddings found so far.
static void find_digraph_embeddings(uint16_t depth,
uint16_t pos,
uint16_t rep_depth,
bool has_trivial_stab,
uint64_t
const max_results,
uint64_t*
const count) {
#ifdef DIGRAPHS_ENABLE_STATS
update_stats(STATS, depth);
#endif
if (depth == DIGRAPH1->nr_vertices) {
// we've assigned every position in <MAP>
external_order_map_digraph(DIGRAPH1);
Obj ret = HOOK(
USER_PARAM, MAX(DIGRAPH1->nr_vertices, DIGRAPH2->nr_vertices), MAP);
internal_order_map_digraph(DIGRAPH1);
(*count)++;
if (*count >= max_results || ret ==
True) {
longjmp(OUTOFHERE, 1);
}
return;
}
uint16_t next = 0;
// the next position to fill
uint16_t min = UNDEFINED;
// the minimum number of candidates for MAP[next]
uint16_t i;
if (depth > 0) {
// this is not the first call of the function
copy_bit_array(
MAP_UNDEFINED[depth], MAP_UNDEFINED[depth - 1], DIGRAPH1->nr_vertices);
FOR_SET_BITS(MAP_UNDEFINED[depth], DIGRAPH1->nr_vertices, i) {
size_t
const n = digraph_embed_update_conditions(depth, pos, i);
if (n == 0) {
#ifdef DIGRAPHS_ENABLE_STATS
STATS->nr_dead_branches++;
#endif
pop_conditions(CONDITIONS, depth);
return;
}
STORE_MIN(min, next, n, i);
}
END_FOR_SET_BITS
}
else {
for (i = 0; i < DIGRAPH1->nr_vertices; i++) {
size_t
const n = size_conditions(CONDITIONS, i);
STORE_MIN_BREAK(min, next, n, i);
}
}
BitArray* possible = get_conditions(CONDITIONS, next);
copy_bit_array(
possible, get_conditions(CONDITIONS, next), DIGRAPH2->nr_vertices);
intersect_bit_arrays(possible, REPS[rep_depth], DIGRAPH2->nr_vertices);
complement_bit_arrays(possible, VALS, DIGRAPH2->nr_vertices);
FOR_SET_BITS(possible, DIGRAPH2->nr_vertices, i) {
MAP[next] = i;
set_bit_array(VALS, i,
true);
set_bit_array(MAP_UNDEFINED[depth], next,
false);
if (!has_trivial_stab) {
find_digraph_embeddings(
depth + 1,
next,
rep_depth + 1,
compute_stabs_and_orbit_reps(DIGRAPH1->nr_vertices,
DIGRAPH2->nr_vertices,
rep_depth + 1,
depth,
i,
false),
max_results,
count);
}
else {
find_digraph_embeddings(
depth + 1, next, rep_depth,
true, max_results, count);
}
MAP[next] = UNDEFINED;
set_bit_array(VALS, i,
false);
set_bit_array(MAP_UNDEFINED[depth], next,
true);
}
END_FOR_SET_BITS
pop_conditions(CONDITIONS, depth);
}
////////////////////////////////////////////////////////////////////////////////
// The next function should be called before find_digraph_homos. This function
// simulates the first steps of the recursion using the information in
// partial_map_obj (if any) so that the values specified in partial_map_obj are
// installed in MAP first.
////////////////////////////////////////////////////////////////////////////////
static void init_partial_map_and_find_digraph_homos(Obj partial_map_obj,
uint64_t
const max_results,
uint64_t
const hint,
uint64_t*
const count,
Obj injective_obj) {
uint16_t depth = 0;
uint16_t rep_depth = 0;
uint16_t last_defined = UNDEFINED;
bool last_stab_is_trivial = (STAB_GENS[0]->size == 0 ?
true :
false);
uint16_t rank = 0;
uint16_t next = UNDEFINED;
if (partial_map_obj != Fail) {
for (next = 0; next < LEN_LIST(partial_map_obj); ++next) {
if (ISB_LIST(partial_map_obj, next + 1)) {
if (depth > 0) {
DIGRAPHS_ASSERT(last_defined != UNDEFINED);
copy_bit_array(MAP_UNDEFINED[depth],
MAP_UNDEFINED[depth - 1],
DIGRAPH1->nr_vertices);
uint16_t i;
// variable for the next for-loop
FOR_SET_BITS(MAP_UNDEFINED[depth], DIGRAPH1->nr_vertices, i) {
uint16_t n;
if (INT_INTOBJ(injective_obj) == 0) {
n = digraph_homo_update_conditions(depth, last_defined, i);
}
else if (INT_INTOBJ(injective_obj) == 1) {
n = digraph_mono_update_conditions(depth, last_defined, i);
}
else {
DIGRAPHS_ASSERT(INT_INTOBJ(injective_obj) == 2);
n = digraph_embed_update_conditions(depth, last_defined, i);
}
if (n == 0) {
return;
}
}
END_FOR_SET_BITS
}
uint16_t val = INT_INTOBJ(ELM_LIST(partial_map_obj, next + 1)) - 1;
next = (ORDERED ? INVERSE_ORDER[next] : next);
MAP[next] = val;
if (!get_bit_array(VALS, MAP[next])) {
rank++;
if (rank > hint) {
return;
}
}
set_bit_array(VALS, MAP[next],
true);
set_bit_array(MAP_UNDEFINED[depth], next,
false);
if (!last_stab_is_trivial) {
last_stab_is_trivial =
compute_stabs_and_orbit_reps(DIGRAPH1->nr_vertices,
DIGRAPH2->nr_vertices,
rep_depth + 1,
depth,
MAP[next],
false);
rep_depth++;
}
depth++;
last_defined = next;
next = (ORDERED ? ORDER[next] : next);
}
}
}
if (INT_INTOBJ(injective_obj) == 0) {
find_digraph_homos(depth,
last_defined,
rep_depth,
last_stab_is_trivial,
rank,
max_results,
hint,
count);
}
else if (INT_INTOBJ(injective_obj) == 1) {
find_digraph_monos(depth,
last_defined,
rep_depth,
last_stab_is_trivial,
max_results,
count);
}
else {
DIGRAPHS_ASSERT(INT_INTOBJ(injective_obj) == 2);
find_digraph_embeddings(depth,
last_defined,
rep_depth,
last_stab_is_trivial,
max_results,
count);
}
return;
}
////////////////////////////////////////////////////////////////////////////////
// 7. The GAP-level function (and helpers)
////////////////////////////////////////////////////////////////////////////////
// Initialises the data structures required by the recursive functions for
// finding homomorphisms. If true is returned everything was initialised ok, if
// false is returned, then the arguments already imply that there can be no
// homomorphisms.
static bool init_data_from_args(Obj digraph1_obj,
Obj digraph2_obj,
Obj hook_obj,
Obj user_param_obj,
Obj max_results_obj,
Obj hint_obj,
Obj injective_obj,
Obj image_obj,
Obj partial_map_obj,
Obj colors1_obj,
Obj colors2_obj,
Obj order_obj,
Obj aut_grp_obj) {
uint16_t calculated_max_verts =
MAX(DigraphNrVertices(digraph1_obj), DigraphNrVertices(digraph2_obj));
if (!homos_data_initialized
|| (calculated_max_verts >= HOMOS_STRUCTURE_SIZE)) {
FuncDIGRAPHS_FREE_HOMOS_DATA(0L);
homos_data_initialized =
true;
// Rather arbitrary, but we multiply by 1.2 to avoid
// n = 1,2,3,4,5... causing constant reallocation
HOMOS_STRUCTURE_SIZE =
(calculated_max_verts + calculated_max_verts / 5) + 1;
// The previous line includes "+ 1" because below we do:
// "BLISS_GRAPH[3 * PERM_DEGREE]" but we only allocate bliss graphs in
// BLISS_GRAPH up to but not including 3 * HOMOS_STRUCTURE_SIZE. So if
// PERM_DEGREE = (# nodes in digraph2) = HOMOS_STRUCTURE_SIZE (the
// first equality always holds, the second if # nodes in digraph2 >
// # nodes in digraph1), then this is out of bounds.
#ifdef DIGRAPHS_ENABLE_STATS
STATS = safe_malloc(
sizeof(HomoStats));
#endif
DIGRAPH1 = new_digraph(HOMOS_STRUCTURE_SIZE);
DIGRAPH2 = new_digraph(HOMOS_STRUCTURE_SIZE);
GRAPH1 = new_graph(HOMOS_STRUCTURE_SIZE);
GRAPH2 = new_graph(HOMOS_STRUCTURE_SIZE);
IMAGE_RESTRICT = new_bit_array(HOMOS_STRUCTURE_SIZE);
ORB_LOOKUP = new_bit_array(HOMOS_STRUCTURE_SIZE);
REPS = safe_malloc(HOMOS_STRUCTURE_SIZE *
sizeof(BitArray*));
BIT_ARRAY_BUFFER =
(BitArray**) safe_calloc(HOMOS_STRUCTURE_SIZE,
sizeof(BitArray*));
MAP_UNDEFINED =
(BitArray**) safe_calloc(HOMOS_STRUCTURE_SIZE,
sizeof(BitArray*));
BLISS_GRAPH = (BlissGraph**) safe_calloc(3 * HOMOS_STRUCTURE_SIZE,
sizeof(BlissGraph*));
MAP = (uint16_t*) safe_calloc(HOMOS_STRUCTURE_SIZE,
sizeof(uint16_t));
COLORS2 = (uint16_t*) safe_calloc(HOMOS_STRUCTURE_SIZE,
sizeof(uint16_t));
INVERSE_ORDER =
(uint16_t*) safe_calloc(HOMOS_STRUCTURE_SIZE,
sizeof(uint16_t));
MAP_BUFFER =
(uint16_t*) safe_calloc(HOMOS_STRUCTURE_SIZE,
sizeof(uint16_t));
ORB = (uint16_t*) safe_calloc(HOMOS_STRUCTURE_SIZE,
sizeof(uint16_t));
ORDER = (uint16_t*) safe_calloc(HOMOS_STRUCTURE_SIZE,
sizeof(uint16_t));
STAB_GENS =
(PermColl**) safe_calloc(HOMOS_STRUCTURE_SIZE,
sizeof(PermColl*));
for (uint16_t i = 0; i < HOMOS_STRUCTURE_SIZE * 3; i++) {
BLISS_GRAPH[i] = bliss_digraphs_new(i);
}
for (uint16_t i = 0; i < HOMOS_STRUCTURE_SIZE; i++) {
REPS[i] = new_bit_array(HOMOS_STRUCTURE_SIZE);
BIT_ARRAY_BUFFER[i] = new_bit_array(HOMOS_STRUCTURE_SIZE);
MAP_UNDEFINED[i] = new_bit_array(HOMOS_STRUCTURE_SIZE);
STAB_GENS[i] = new_perm_coll(HOMOS_STRUCTURE_SIZE, HOMOS_STRUCTURE_SIZE);
}
VALS = new_bit_array(HOMOS_STRUCTURE_SIZE);
CONDITIONS = new_conditions(HOMOS_STRUCTURE_SIZE, HOMOS_STRUCTURE_SIZE);
SCHREIER_SIMS = new_schreier_sims();
}
#ifdef DIGRAPHS_ENABLE_STATS
clear_stats(STATS);
#endif
uint16_t nr1 = DigraphNrVertices(digraph1_obj);
uint16_t nr2 = DigraphNrVertices(digraph2_obj);
init_bit_array(MAP_UNDEFINED[0],
true, nr1);
init_bit_array(VALS,
false, nr2);
if (IS_LIST(order_obj)) {
ORDERED =
true;
for (uint16_t i = 0; i < nr1; i++) {
ORDER[i] = INT_INTOBJ(ELM_LIST(order_obj, i + 1)) - 1;
INVERSE_ORDER[ORDER[i]] = i;
}
}
else {
ORDERED =
false;
}
bool is_undirected;
if (CALL_1ARGS(IsSymmetricDigraph, digraph1_obj) ==
True
&& CALL_1ARGS(IsSymmetricDigraph, digraph2_obj) ==
True) {
init_graph_from_digraph_obj(GRAPH1, digraph1_obj, ORDERED);
init_graph_from_digraph_obj(GRAPH2, digraph2_obj,
false);
is_undirected =
true;
}
else {
init_digraph_from_digraph_obj(DIGRAPH1, digraph1_obj, ORDERED);
init_digraph_from_digraph_obj(DIGRAPH2, digraph2_obj,
false);
is_undirected =
false;
}
if (hook_obj != Fail) {
GAP_FUNC = hook_obj;
HOOK = homo_hook_gap;
}
else {
HOOK = homo_hook_collect;
}
USER_PARAM = user_param_obj;
clear_conditions(CONDITIONS, nr1, nr2);
// IMAGE_RESTRICT is a pointer to a BitArray of possible image values for the
// homomorphisms, it is also used by orbit_reps so that orbit reps are chosen
// from among the restricted values of the image . . .
set_bit_array_from_gap_list(IMAGE_RESTRICT, image_obj);
if (INT_INTOBJ(injective_obj) > 0
&& size_bit_array(IMAGE_RESTRICT, nr1) < nr1) {
// TODO shouldn't the first nr1 be nr2?
// homomorphisms should be injective (by injective_obj) but are not since
// the image is too restricted.
return false;
}
init_bit_array(BIT_ARRAY_BUFFER[0],
false, nr2);
if (partial_map_obj != Fail) {
for (uint16_t i = 1; i <= LEN_LIST(partial_map_obj); i++) {
if (ISB_LIST(partial_map_obj, i)) {
Obj o = ELM_LIST(partial_map_obj, i);
DIGRAPHS_ASSERT(IS_INTOBJ(o));
DIGRAPHS_ASSERT(INT_INTOBJ(o) > 0);
DIGRAPHS_ASSERT(INT_INTOBJ(o) <= nr2);
if (INT_INTOBJ(injective_obj) > 0) {
if (get_bit_array(BIT_ARRAY_BUFFER[0], INT_INTOBJ(o) - 1)) {
// partial_map_obj should be injective, but is not
return false;
}
set_bit_array(BIT_ARRAY_BUFFER[0], INT_INTOBJ(o) - 1,
true);
}
// The only value that vertex `i` can have is `o`!
if (ORDERED) {
init_bit_array(
get_conditions(CONDITIONS, INVERSE_ORDER[i - 1]),
false, nr2);
set_bit_array_from_gap_int(
get_conditions(CONDITIONS, INVERSE_ORDER[i - 1]), o);
}
else {
init_bit_array(get_conditions(CONDITIONS, i - 1),
false, nr2);
set_bit_array_from_gap_int(get_conditions(CONDITIONS, i - 1), o);
}
}
// Intersect everything in the first row of the conditions with <image>,
intersect_bit_arrays(
get_conditions(CONDITIONS, i - 1), IMAGE_RESTRICT, nr2);
}
}
init_bit_array(BIT_ARRAY_BUFFER[0],
false, nr2);
if (is_undirected) {
for (uint16_t i = 0; i < nr2; i++) {
if (is_adjacent_graph(GRAPH2, i, i)) {
set_bit_array(BIT_ARRAY_BUFFER[0], i,
true);
}
}
// Loops in digraph1 can only MAP to loops in digraph2
for (uint16_t i = 0; i < nr1; i++) {
if (is_adjacent_graph(GRAPH1, i, i)) {
intersect_bit_arrays(
get_conditions(CONDITIONS, i), BIT_ARRAY_BUFFER[0], nr2);
}
}
}
else {
for (uint16_t i = 0; i < nr2; i++) {
if (is_adjacent_digraph(DIGRAPH2, i, i)) {
set_bit_array(BIT_ARRAY_BUFFER[0], i,
true);
}
}
// Loops in digraph1 can only MAP to loops in digraph2
for (uint16_t i = 0; i < nr1; i++) {
if (is_adjacent_digraph(DIGRAPH1, i, i)) {
intersect_bit_arrays(
get_conditions(CONDITIONS, i), BIT_ARRAY_BUFFER[0], nr2);
}
}
}
// Process the vertex colours . . .
uint16_t* colors;
if (colors1_obj != Fail && colors2_obj != Fail) {
DIGRAPHS_ASSERT(IS_LIST(colors1_obj));
DIGRAPHS_ASSERT(IS_LIST(colors2_obj));
DIGRAPHS_ASSERT(LEN_LIST(colors1_obj) == nr1);
DIGRAPHS_ASSERT(LEN_LIST(colors2_obj) == nr2);
for (uint16_t i = 1; i <= LEN_LIST(colors2_obj); i++) {
DIGRAPHS_ASSERT(ISB_LIST(colors2_obj, i));
DIGRAPHS_ASSERT(IS_INTOBJ(ELM_LIST(colors2_obj, i)));
COLORS2[i - 1] = INT_INTOBJ(ELM_LIST(colors2_obj, i)) - 1;
}
for (uint16_t i = 1; i <= LEN_LIST(colors1_obj); i++) {
init_bit_array(BIT_ARRAY_BUFFER[0],
false, nr2);
DIGRAPHS_ASSERT(ISB_LIST(colors1_obj, i));
DIGRAPHS_ASSERT(IS_INTOBJ(ELM_LIST(colors1_obj, i)));
for (uint16_t j = 1; j <= LEN_LIST(colors2_obj); j++) {
if (INT_INTOBJ(ELM_LIST(colors1_obj, i))
== INT_INTOBJ(ELM_LIST(colors2_obj, j))) {
set_bit_array(BIT_ARRAY_BUFFER[0], j - 1,
true);
}
}
if (ORDERED) {
intersect_bit_arrays(get_conditions(CONDITIONS, INVERSE_ORDER[i - 1]),
BIT_ARRAY_BUFFER[0],
nr2);
}
else {
intersect_bit_arrays(
get_conditions(CONDITIONS, i - 1), BIT_ARRAY_BUFFER[0], nr2);
}
// can only map vertices of color i to vertices of color i
}
colors = COLORS2;
}
else {
colors = NULL;
}
// Ensure that the sizes of all conditions are known before we start and
// define the MAP
for (uint16_t i = 0; i < nr1; i++) {
store_size_conditions(CONDITIONS, i);
MAP[i] = UNDEFINED;
}
for (uint16_t i = nr1; i < MAX(nr1, nr2); i++) {
MAP[i] = i;
}
// Get generators of the automorphism group of the second (di)graph, and the
// orbit reps
PERM_DEGREE = nr2;
if (aut_grp_obj == Fail) {
if (colors == NULL) {
get_automorphism_group_from_gap(digraph2_obj, STAB_GENS[0]);
}
else if (is_undirected) {
#ifdef DIGRAPHS_WITH_INCLUDED_BLISS
automorphisms_graph(
GRAPH2, colors, STAB_GENS[0], BLISS_GRAPH[PERM_DEGREE]);
#else
automorphisms_graph(GRAPH2, colors, STAB_GENS[0]);
#endif
}
else {
#ifdef DIGRAPHS_WITH_INCLUDED_BLISS
automorphisms_digraph(
DIGRAPH2, colors, STAB_GENS[0], BLISS_GRAPH[3 * PERM_DEGREE]);
#else
automorphisms_digraph(DIGRAPH2, colors, STAB_GENS[0]);
#endif
}
}
else {
set_automorphisms(aut_grp_obj, STAB_GENS[0]);
}
compute_stabs_and_orbit_reps(nr1, nr2, 0, 0, UNDEFINED,
true);
return true;
}
// The next function is the main function for accessing the homomorphisms code.
//
// The arguments are:
//
// 1. digraph1_obj the source/domain of the homomorphisms sought
// 2. digraph2_obj the range/codomain of the homomorphisms sought
// 3. hook_obj apply this function to every homomorphism found, or Fail
// for no function.
// 4. user_param_obj GAP variable that can be used in the hook_obj, must be a
// plist if hook_obj is Fail.
// 5. max_results_obj the maximum number of homomorphisms to find
// 6. hint_obj the rank of any homomorphisms found
// 7. injective_obj should be 0 for non-injective, 1 for injective, 2 for
// embedding, or (for backwards compatibility, true for
// injective, or false for non-injective).
// 8. image_obj a list of vertices in digraph2_obj from which the images
// of any homomorphism will be taken.
// 9. partial_map_obj a partial map from digraph1_obj to digraph2_obj, only
// homomorphisms extending this partial map are found (if
// any). Can also be fail to indicate no partial mapping is
// defined.
// 10. colors1_obj a coloring of digraph1_obj
// 11. colors2_obj a coloring of digraph2_obj, only homomorphisms that
// respect these colorings are found.
// 12. order_obj an optional argument that can specify the order the
// vertices in digraph1_obj should be installed in the
// Graph or Digraph used in the recursive search. This does
// not directly specify which order vertices are visited in
// the search, but can have a large impact on the run time
// of the function. It seems in many cases to be a good
// idea for this to be the DigraphWelshPowellOrder, i.e.
// vertices ordered from highest to lowest degree.
// 13. aut_grp_obj an optional argument that can specify the
// automorphisms of the graph that will be used in the
// recursive search. If not given, the full automorphism
// group will be used.
Obj FuncHomomorphismDigraphsFinder(Obj self, Obj args) {
if (LEN_PLIST(args) < 11 || LEN_PLIST(args) > 13) {
ErrorQuit(
"there must be 11, 12, or 13 arguments, found %d,",
LEN_PLIST(args),
0L);
}
Obj digraph1_obj = ELM_PLIST(args, 1);
Obj digraph2_obj = ELM_PLIST(args, 2);
Obj hook_obj = ELM_PLIST(args, 3);
Obj user_param_obj = ELM_PLIST(args, 4);
Obj max_results_obj = ELM_PLIST(args, 5);
Obj hint_obj = ELM_PLIST(args, 6);
Obj injective_obj = ELM_PLIST(args, 7);
Obj image_obj = ELM_PLIST(args, 8);
Obj partial_map_obj = ELM_PLIST(args, 9);
Obj colors1_obj = ELM_PLIST(args, 10);
Obj colors2_obj = ELM_PLIST(args, 11);
Obj order_obj = Fail;
Obj aut_grp_obj = Fail;
if (LEN_PLIST(args) == 12) {
if (IS_LIST(ELM_PLIST(args, 12))) {
order_obj = ELM_PLIST(args, 12);
}
else {
aut_grp_obj = ELM_PLIST(args, 12);
}
}
if (LEN_PLIST(args) == 13) {
order_obj = ELM_PLIST(args, 12);
aut_grp_obj = ELM_PLIST(args, 13);
}
// Validate the arguments
if (CALL_1ARGS(IsDigraph, digraph1_obj) !=
True) {
ErrorQuit(
"the 1st argument <digraph1> must be a digraph, not %s,",
(
Int) TNAM_OBJ(digraph1_obj),
0L);
}
else if (DigraphNrVertices(digraph1_obj) > MAXVERTS) {
ErrorQuit(
"the 1st argument <digraph1> must have at most %d vertices, "
"found %d,",
MAXVERTS,
DigraphNrVertices(digraph1_obj));
}
if (CALL_1ARGS(IsDigraph, digraph2_obj) !=
True) {
ErrorQuit(
"the 2nd argument <digraph2> must be a digraph, not %s,",
(
Int) TNAM_OBJ(digraph2_obj),
0L);
}
else if (DigraphNrVertices(digraph2_obj) > MAXVERTS) {
ErrorQuit(
"the 2nd argument <digraph2> must have at most %d vertices, "
"found %d,",
MAXVERTS,
DigraphNrVertices(digraph2_obj));
}
if (hook_obj == Fail) {
if (!IS_LIST(user_param_obj) || !IS_MUTABLE_OBJ(user_param_obj)) {
ErrorQuit(
"the 3rd argument <hook> is fail and so the 4th argument must "
"be a mutable list, not %s,",
(
Int) TNAM_OBJ(user_param_obj),
0L);
}
}
else if (!IS_FUNC(hook_obj) || NARG_FUNC(hook_obj) != 2) {
ErrorQuit(
"the 3rd argument <hook> must be a function with 2 arguments,", 0L, 0L);
}
if (!IS_INTOBJ(max_results_obj) && max_results_obj != Infinity) {
ErrorQuit(
"the 5th argument <max_results> must be an integer "
"or infinity, not %s,",
(
Int) TNAM_OBJ(max_results_obj),
0L);
}
if (!IS_INTOBJ(hint_obj) && hint_obj != Fail) {
ErrorQuit(
"the 6th argument <hint> must be an integer "
"or fail, not %s,",
(
Int) TNAM_OBJ(hint_obj),
0L);
}
else if (IS_INTOBJ(hint_obj) && INT_INTOBJ(hint_obj) <= 0) {
ErrorQuit(
"the 6th argument <hint> must be a positive integer, "
"not %d,",
INT_INTOBJ(hint_obj),
0L);
}
if (!IS_INTOBJ(injective_obj) && injective_obj !=
True
&& injective_obj !=
False) {
ErrorQuit(
"the 7th argument <injective> must be an integer "
"or true or false, not %s,",
(
Int) TNAM_OBJ(injective_obj),
0L);
}
else if (IS_INTOBJ(injective_obj)) {
if (INT_INTOBJ(injective_obj) < 0 || INT_INTOBJ(injective_obj) > 2) {
ErrorQuit(
"the 7th argument <injective> must 0, 1, or 2, not %d,",
INT_INTOBJ(injective_obj),
0L);
}
}
else if (injective_obj ==
True) {
injective_obj = INTOBJ_INT(1);
}
else {
injective_obj = INTOBJ_INT(0);
}
if (!IS_LIST(image_obj) && image_obj != Fail) {
ErrorQuit(
"the 8th argument <image> must be a list or fail, not %s,",
(
Int) TNAM_OBJ(image_obj),
0L);
}
else if (IS_LIST(image_obj)) {
for (
Int i = 1; i <= LEN_LIST(image_obj); ++i) {
if (!ISB_LIST(image_obj, i)) {
ErrorQuit(
"the 8th argument <image> must be a dense list,", 0L, 0L);
}
else if (!IS_POS_INT(ELM_LIST(image_obj, i))) {
ErrorQuit(
"the 8th argument <image> must only contain positive "
"integers, but found %s in position %d,",
(
Int) TNAM_OBJ(ELM_LIST(image_obj, i)),
i);
}
else if (INT_INTOBJ(ELM_LIST(image_obj, i))
> DigraphNrVertices(digraph2_obj)) {
ErrorQuit(
"in the 8th argument <image> position %d is out of range, "
"must be in the range [1, %d],",
i,
DigraphNrVertices(digraph2_obj));
}
else if (INT_INTOBJ(
POS_LIST(image_obj, ELM_LIST(image_obj, i), INTOBJ_INT(0)))
< i) {
ErrorQuit(
"in the 8th argument <image> position %d is a duplicate,", i, 0L);
}
}
}
if (!IS_LIST(partial_map_obj) && partial_map_obj != Fail) {
ErrorQuit(
"the 9th argument <partial_map> must be a list or fail, not %s,",
(
Int) TNAM_OBJ(partial_map_obj),
0L);
}
else if (IS_LIST(partial_map_obj)) {
if (LEN_LIST(partial_map_obj) > DigraphNrVertices(digraph1_obj)) {
ErrorQuit(
"the 9th argument <partial_map> is too long, must be at most "
"%d, found %d,",
DigraphNrVertices(digraph1_obj),
LEN_LIST(partial_map_obj));
}
for (
Int i = 1; i <= LEN_LIST(partial_map_obj); ++i) {
if (ISB_LIST(partial_map_obj, i)
&& POS_LIST(image_obj, ELM_LIST(partial_map_obj, i), INTOBJ_INT(0))
== Fail) {
ErrorQuit(
"in the 9th argument <partial_map> the value %d in position "
"%d does not belong to the 7th argument <image>,",
INT_INTOBJ(ELM_LIST(partial_map_obj, i)),
i);
}
}
}
if (!IS_LIST(colors1_obj) && colors1_obj != Fail) {
ErrorQuit(
"the 10th argument <colors1> must be a list or fail, not %s,",
(
Int) TNAM_OBJ(colors1_obj),
0L);
}
else if (IS_LIST(colors1_obj)) {
colors1_obj = CALL_2ARGS(DIGRAPHS_ValidateVertexColouring,
INTOBJ_INT(DigraphNrVertices(digraph1_obj)),
colors1_obj);
}
if (!IS_LIST(colors2_obj) && colors2_obj != Fail) {
ErrorQuit(
"the 11th argument <colors2> must be a list or fail, not %s,",
(
Int) TNAM_OBJ(colors2_obj),
0L);
}
else if (IS_LIST(colors2_obj)) {
colors2_obj = CALL_2ARGS(DIGRAPHS_ValidateVertexColouring,
INTOBJ_INT(DigraphNrVertices(digraph2_obj)),
colors2_obj);
}
if ((IS_LIST(colors1_obj) && !IS_LIST(colors2_obj))
|| (colors1_obj == Fail && colors2_obj != Fail)) {
ErrorQuit(
"the 10th and 11th arguments <colors1> and <colors2> must both "
"be lists or both be fail,",
0L,
0L);
}
if (!IS_LIST(order_obj) && order_obj != Fail) {
ErrorQuit(
"the 12th argument <order> must be a list or fail, not %s,",
(
Int) TNAM_OBJ(order_obj),
0L);
}
else if (IS_LIST(order_obj)) {
if (LEN_LIST(order_obj) != DigraphNrVertices(digraph1_obj)) {
ErrorQuit(
"the 12th argument <order> must be a list of length %d, not %d,",
DigraphNrVertices(digraph1_obj),
LEN_LIST(order_obj));
}
for (
Int i = 1; i <= LEN_LIST(order_obj); ++i) {
if (!ISB_LIST(order_obj, i)) {
ErrorQuit(
"the 12th argument <order> must be a dense list, but "
"position %d is not bound,",
i,
0L);
}
else if (!IS_INTOBJ(ELM_LIST(order_obj, i))) {
ErrorQuit(
"the 12th argument <order> must consist of integers, but "
"found %s in position %d,",
(
Int) TNAM_OBJ(order_obj),
i);
}
else if (INT_INTOBJ(ELM_LIST(order_obj, i)) <= 0
|| INT_INTOBJ(ELM_LIST(order_obj, i))
> DigraphNrVertices(digraph1_obj)) {
ErrorQuit(
"the 12th argument <order> must consist of integers, in the "
"range [1, %d] but found %d,",
DigraphNrVertices(digraph1_obj),
INT_INTOBJ(ELM_LIST(order_obj, i)));
}
else if (INT_INTOBJ(
POS_LIST(order_obj, ELM_LIST(order_obj, i), INTOBJ_INT(0)))
< i) {
ErrorQuit(
"the 12th argument <order> must be duplicate-free, but "
"the value %d in position %d is a duplicate,",
INT_INTOBJ(ELM_LIST(order_obj, i)),
i);
}
}
}
if (aut_grp_obj != Fail) {
if (CALL_1ARGS(IsPermGroup, aut_grp_obj) !=
True) {
ErrorQuit(
"the 12th or 13th argument <aut_grp> must be a permutation group "
"or fail, not %s,",
(
Int) TNAM_OBJ(aut_grp_obj),
0L);
}
Obj gens = CALL_1ARGS(GeneratorsOfGroup, aut_grp_obj);
DIGRAPHS_ASSERT(IS_LIST(gens));
DIGRAPHS_ASSERT(LEN_LIST(gens) > 0);
Int lmp = INT_INTOBJ(CALL_1ARGS(LargestMovedPointPerms, gens));
if (lmp > 0 && LEN_LIST(gens) >= lmp) {
ErrorQuit(
"expected at most %d generators in the 12th or 13th argument "
"but got %d,",
lmp - 1,
LEN_LIST(gens));
}
for (
Int i = 1; i <= LEN_LIST(gens); ++i) {
if (colors2_obj != Fail) {
if (CALL_3ARGS(IsDigraphAutomorphism,
digraph2_obj,
ELM_LIST(gens, i),
colors2_obj)
!=
True) {
ErrorQuit(
"expected group of automorphisms, but found a "
"non-automorphism in position %d of the group generators,",
i,
0L);
}
}
else {
if (CALL_2ARGS(IsDigraphAutomorphism, digraph2_obj, ELM_LIST(gens, i))
!=
True) {
ErrorQuit(
"expected group of automorphisms, but found a "
"non-automorphism in position %d of the group generators,",
i,
0L);
}
}
}
}
if (image_obj != Fail
&& LEN_LIST(image_obj) != DigraphNrVertices(digraph2_obj)) {
bool warn =
false;
if (aut_grp_obj == Fail) {
warn =
true;
}
else {
Obj gens = CALL_1ARGS(GeneratorsOfGroup, aut_grp_obj);
Int lmp = INT_INTOBJ(CALL_1ARGS(LargestMovedPointPerms, gens));
warn = lmp > 0;
}
if (warn) {
Obj msg = MakeImmString(
"WARNING you are trying to find homomorphisms by specifying a "
"subset of the vertices of the target digraph. This might lead "
"to unexpected results! If this happens, try passing Group(()) as "
"the last argument. Please see the documentation of "
"HomomorphismDigraphsFinder for details.");
Obj info_args = NEW_PLIST(T_PLIST, 1);
SET_ELM_PLIST(info_args, 1, msg);
SET_LEN_PLIST(info_args, 1);
InfoDoPrint(InfoWarning, INTOBJ_INT(0), info_args);
}
}
// Some conditions that immediately rule out there being any homomorphisms.
if (((INT_INTOBJ(injective_obj) == 1 || INT_INTOBJ(injective_obj) == 2)
&& ((hint_obj != Fail
&& INT_INTOBJ(hint_obj) != DigraphNrVertices(digraph1_obj))
|| DigraphNrVertices(digraph1_obj)
> DigraphNrVertices(digraph2_obj)))
|| (hint_obj != Fail
&& (INT_INTOBJ(hint_obj) > DigraphNrVertices(digraph1_obj)
|| INT_INTOBJ(hint_obj) > DigraphNrVertices(digraph2_obj)))
|| LEN_LIST(image_obj) == 0
|| (hint_obj != Fail && INT_INTOBJ(hint_obj) > LEN_LIST(image_obj))) {
// Can't print stats here because they are not initialised, also why would
// we want to, nothing has actually happened yet!
return user_param_obj;
}
// Initialise all of the global variables that are used in the recursion.
// Returns false if the arguments somehow rule out there being any
// homomorphisms (i.e. if injective_obj indicates that the homomorphisms
// should be injective, and image_obj is too small).
if (!init_data_from_args(digraph1_obj,
digraph2_obj,
hook_obj,
user_param_obj,
max_results_obj,
hint_obj,
injective_obj,
image_obj,
partial_map_obj,
colors1_obj,
colors2_obj,
order_obj,
aut_grp_obj)) {
#ifdef DIGRAPHS_ENABLE_STATS
print_stats(STATS);
#endif
return user_param_obj;
}
uint64_t max_results =
(max_results_obj == Infinity ? SMALLINTLIMIT
: INT_INTOBJ(max_results_obj));
uint16_t hint = (IS_INTOBJ(hint_obj) ? INT_INTOBJ(hint_obj) : UNDEFINED);
uint64_t count = 0;
// go!
if (setjmp(OUTOFHERE) == 0) {
if (CALL_1ARGS(IsSymmetricDigraph, digraph1_obj) ==
True
&& CALL_1ARGS(IsSymmetricDigraph, digraph2_obj) ==
True) {
init_partial_map_and_find_graph_homos(
partial_map_obj, max_results, hint, &count, injective_obj);
}
else {
init_partial_map_and_find_digraph_homos(
partial_map_obj, max_results, hint, &count, injective_obj);
}
}
#ifdef DIGRAPHS_ENABLE_STATS
print_stats(STATS);
#endif
return user_param_obj;
}