Skip to content

Commit

Permalink
feat: allow NULL for cut_prob parameter of motif finding functions
Browse files Browse the repository at this point in the history
closes #2555
  • Loading branch information
szhorvat committed Aug 22, 2024
1 parent e2795c2 commit 03760a0
Show file tree
Hide file tree
Showing 11 changed files with 69 additions and 77 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
### Changed

- `igraph_feedback_arc_set()` uses a much faster method for solving the exact minimum feedback arc set problem. The new method (`IGRAPH_FAS_EXACT_IP_CG`) is used by default (i.e. with `IGRAPH_FAS_EXACT_IP`), but the previous method is also kept available (`IGRAPH_FAS_EXACT_IP_TI`).
- `igraph_motifs_randesu()`, `igraph_motifs_randesu_callback()`, `igraph_motifs_randesu_estimate()` and `igraph_motifs_randesu_no()` now accept `NULL` for their `cut_prob` parameter, signifying that a complete search should be performed.

### Fixed

Expand Down
6 changes: 2 additions & 4 deletions examples/simple/igraph_motifs_randesu.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,13 @@ int main(void) {

igraph_t graph;
igraph_vector_t hist;
igraph_real_t zeros[] = { 0.0, 0.0, 0.0, 0.0, 0.0 };
igraph_vector_t cut_prob;

/* Compute the 4-motif distritbuion in Zachary's karate club network. */

igraph_famous(&graph, "Zachary");
igraph_vector_init(&hist, 0);

igraph_motifs_randesu(&graph, &hist, 4, igraph_vector_view(&cut_prob, zeros, 4));
igraph_motifs_randesu(&graph, &hist, 4, NULL);

/* Compute the total number of motifs (connected 4-vertex subgraphs)
* so that we can print the normalized distribution. */
Expand All @@ -47,7 +45,7 @@ int main(void) {
/* Identify the vertices of each three-motif in a small Kautz graph. */

igraph_kautz(&graph, 2, 1);
igraph_motifs_randesu_callback(&graph, 3, igraph_vector_view(&cut_prob, zeros, 3), &print_motif, NULL);
igraph_motifs_randesu_callback(&graph, 3, NULL, &print_motif, NULL);
igraph_destroy(&graph);

return 0;
Expand Down
4 changes: 1 addition & 3 deletions fuzzing/misc_algos.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {

igraph_minimum_cycle_basis(&graph, &ivl1, -1, true, true, NULL);

igraph_vector_resize(&v2, 3);
igraph_vector_null(&v2);
igraph_motifs_randesu(&graph, &v1, 3, &v2);
igraph_motifs_randesu(&graph, &v1, 3, NULL);

igraph_list_triangles(&graph, &iv1);

Expand Down
6 changes: 3 additions & 3 deletions interfaces/functions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1953,15 +1953,15 @@ igraph_write_graph_dot:
#######################################

igraph_motifs_randesu:
PARAMS: GRAPH graph, OUT VECTOR hist, INTEGER size=3, VECTOR cut_prob
PARAMS: GRAPH graph, OUT VECTOR hist, INTEGER size=3, OPTIONAL VECTOR cut_prob

igraph_motifs_randesu_estimate:
PARAMS: |-
GRAPH graph, OUT INTEGER est, INTEGER size=3, VECTOR cut_prob,
GRAPH graph, OUT INTEGER est, INTEGER size=3, OPTIONAL VECTOR cut_prob,
INTEGER sample_size, OPTIONAL VECTOR_INT sample
igraph_motifs_randesu_no:
PARAMS: GRAPH graph, OUT INTEGER no, INTEGER size=3, VECTOR cut_prob
PARAMS: GRAPH graph, OUT INTEGER no, INTEGER size=3, OPTIONAL VECTOR cut_prob

igraph_dyad_census:
PARAMS: GRAPH graph, OUT REAL mut, OUT REAL asym, OUT REAL null
Expand Down
60 changes: 35 additions & 25 deletions src/misc/motifs.c
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,9 @@ static igraph_error_t igraph_i_motifs_randesu_update_hist(
* isomorphism code.
* \param cut_prob Vector of probabilities for cutting the search tree
* at a given level. The first element is the first level, etc.
* Supply all zeros here (of length \p size) to find all motifs
* in a graph.
* To perform a complete search and find all motifs, supply
* either an all-zero vector of length \p size, or (since
* igraph 0.10.14) a \c NULL pointer.
* \return Error code.
*
* \sa \ref igraph_motifs_randesu_estimate() for estimating the number
Expand Down Expand Up @@ -150,7 +151,7 @@ igraph_error_t igraph_motifs_randesu(const igraph_t *graph, igraph_vector_t *his
}
}

if (igraph_vector_size(cut_prob) != size) {
if (cut_prob != NULL && igraph_vector_size(cut_prob) != size) {
IGRAPH_ERRORF("Cut probability vector size (%" IGRAPH_PRId ") must agree with motif size (%" IGRAPH_PRId ").",
IGRAPH_EINVAL, igraph_vector_size(cut_prob), size);
}
Expand Down Expand Up @@ -223,8 +224,9 @@ igraph_error_t igraph_motifs_randesu(const igraph_t *graph, igraph_vector_t *his
* motif finding code, but the graph isomorphism code.
* \param cut_prob Vector of probabilities for cutting the search tree
* at a given level. The first element is the first level, etc.
* Supply all zeros here (of length \c size) to find all motifs
* in a graph.
* To perform a complete search and find all motifs, supply
* either an all-zero vector of length \p size, or (since
* igraph 0.10.14) a \c NULL pointer.
* \param callback A pointer to a function of type \ref igraph_motifs_handler_t.
* This function will be called whenever a new motif is found.
* \param extra Extra argument to pass to the callback function.
Expand Down Expand Up @@ -303,7 +305,7 @@ igraph_error_t igraph_motifs_randesu_callback(const igraph_t *graph, igraph_inte
}
}

if (igraph_vector_size(cut_prob) != size) {
if (cut_prob != NULL && igraph_vector_size(cut_prob) != size) {
IGRAPH_ERRORF("Cut probability vector size (%" IGRAPH_PRId ") must agree with motif size (%" IGRAPH_PRId ").",
IGRAPH_EINVAL, igraph_vector_size(cut_prob), size);
}
Expand Down Expand Up @@ -333,8 +335,10 @@ igraph_error_t igraph_motifs_randesu_callback(const igraph_t *graph, igraph_inte

IGRAPH_ALLOW_INTERRUPTION();

if (VECTOR(*cut_prob)[0] == 1 || RNG_UNIF01() < VECTOR(*cut_prob)[0]) {
continue;
if (cut_prob) {
if (VECTOR(*cut_prob)[0] == 1 || RNG_UNIF01() < VECTOR(*cut_prob)[0]) {
continue;
}
}

/* init G */
Expand All @@ -359,7 +363,7 @@ igraph_error_t igraph_motifs_randesu_callback(const igraph_t *graph, igraph_inte
igraph_stack_int_clear(&stack);

while (level > 1 || !igraph_vector_int_empty(&adjverts)) {
igraph_real_t cp = VECTOR(*cut_prob)[level];
igraph_real_t cp = cut_prob ? VECTOR(*cut_prob)[level] : 0;

if (level == size - 1) {
s = igraph_vector_int_size(&adjverts) / 2;
Expand Down Expand Up @@ -520,10 +524,11 @@ igraph_error_t igraph_motifs_randesu_callback(const igraph_t *graph, igraph_inte
* \param graph The graph object to study.
* \param est Pointer to an integer, the result will be stored here.
* \param size The size of the subgraphs to look for.
* \param cut_prob Vector giving the probabilities to cut a branch of
* the search tree and omit counting the motifs in that branch.
* It contains a probability for each level. Supply \p size
* zeros here to count all the motifs in the sample.
* \param cut_prob Vector of probabilities for cutting the search tree
* at a given level. The first element is the first level, etc.
* To perform a complete search and find all motifs, supply
* either an all-zero vector of length \p size, or (since
* igraph 0.10.14) a \c NULL pointer.
* \param sample_size The number of vertices to use as the
* sample. This parameter is only used if the \p parsample
* argument is a null pointer.
Expand Down Expand Up @@ -560,7 +565,7 @@ igraph_error_t igraph_motifs_randesu_estimate(const igraph_t *graph, igraph_inte
IGRAPH_EINVAL, size);
}

if (igraph_vector_size(cut_prob) != size) {
if (cut_prob != NULL && igraph_vector_size(cut_prob) != size) {
IGRAPH_ERRORF("Cut probability vector size (%" IGRAPH_PRId ") must agree with motif size (%" IGRAPH_PRId ").",
IGRAPH_EINVAL, igraph_vector_size(cut_prob), size);
}
Expand Down Expand Up @@ -605,9 +610,10 @@ igraph_error_t igraph_motifs_randesu_estimate(const igraph_t *graph, igraph_inte

IGRAPH_ALLOW_INTERRUPTION();

if (VECTOR(*cut_prob)[0] == 1 ||
RNG_UNIF01() < VECTOR(*cut_prob)[0]) {
continue;
if (cut_prob) {
if (VECTOR(*cut_prob)[0] == 1 || RNG_UNIF01() < VECTOR(*cut_prob)[0]) {
continue;
}
}

/* init G */
Expand All @@ -632,7 +638,7 @@ igraph_error_t igraph_motifs_randesu_estimate(const igraph_t *graph, igraph_inte
igraph_stack_int_clear(&stack);

while (level > 1 || !igraph_vector_int_empty(&adjverts)) {
igraph_real_t cp = VECTOR(*cut_prob)[level];
igraph_real_t cp = cut_prob ? VECTOR(*cut_prob)[level] : 0.0;

if (level == size - 1) {
s = igraph_vector_int_size(&adjverts) / 2;
Expand Down Expand Up @@ -739,8 +745,11 @@ igraph_error_t igraph_motifs_randesu_estimate(const igraph_t *graph, igraph_inte
* \param no Pointer to an integer type, the result will be stored
* here.
* \param size The size of the motifs to count.
* \param cut_prob Vector giving the probabilities that a branch of
* the search tree will be cut at a given level.
* \param cut_prob Vector of probabilities for cutting the search tree
* at a given level. The first element is the first level, etc.
* To perform a complete search and find all connected subgraphs,
* supply either an all-zero vector of length \p size, or (since
* igraph 0.10.14) a \c NULL pointer.
* \return Error code.
* \sa \ref igraph_motifs_randesu(), \ref
* igraph_motifs_randesu_estimate().
Expand All @@ -765,7 +774,7 @@ igraph_error_t igraph_motifs_randesu_no(const igraph_t *graph, igraph_integer_t
IGRAPH_EINVAL, size);
}

if (igraph_vector_size(cut_prob) != size) {
if (cut_prob != NULL && igraph_vector_size(cut_prob) != size) {
IGRAPH_ERRORF("Cut probability vector size (%" IGRAPH_PRId ") must agree with motif size (%" IGRAPH_PRId ").",
IGRAPH_EINVAL, igraph_vector_size(cut_prob), size);
}
Expand All @@ -788,9 +797,10 @@ igraph_error_t igraph_motifs_randesu_no(const igraph_t *graph, igraph_integer_t

IGRAPH_ALLOW_INTERRUPTION();

if (VECTOR(*cut_prob)[0] == 1 ||
RNG_UNIF01() < VECTOR(*cut_prob)[0]) {
continue;
if (cut_prob) {
if (VECTOR(*cut_prob)[0] == 1 || RNG_UNIF01() < VECTOR(*cut_prob)[0]) {
continue;
}
}

/* init G */
Expand All @@ -815,7 +825,7 @@ igraph_error_t igraph_motifs_randesu_no(const igraph_t *graph, igraph_integer_t
igraph_stack_int_clear(&stack);

while (level > 1 || !igraph_vector_int_empty(&adjverts)) {
igraph_real_t cp = VECTOR(*cut_prob)[level];
igraph_real_t cp = cut_prob ? VECTOR(*cut_prob)[level] : 0.0;

if (level == size - 1) {
s = igraph_vector_int_size(&adjverts) / 2;
Expand Down
14 changes: 7 additions & 7 deletions tests/unit/igraph_motifs_randesu.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

igraph_error_t print_motif(const igraph_t *graph, igraph_vector_int_t *vids,
igraph_integer_t isoclass, void* extra) {
IGRAPH_UNUSED(graph);
IGRAPH_UNUSED(extra);
printf("Class %" IGRAPH_PRId ": ", isoclass);
print_vector_int(vids);
return IGRAPH_SUCCESS;
Expand All @@ -36,30 +38,28 @@ int main(void) {

igraph_t g;
igraph_vector_t hist;
igraph_real_t zeros[] = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
igraph_vector_t cut_prob;
igraph_integer_t size;

igraph_ring(&g, 1000, IGRAPH_DIRECTED, 1, 1);
igraph_vector_init(&hist, 0);
igraph_motifs_randesu(&g, &hist, 3, igraph_vector_view(&cut_prob, zeros, 3));
igraph_motifs_randesu(&g, &hist, 3, NULL);
print_vector(&hist);
igraph_destroy(&g);
igraph_vector_destroy(&hist);

igraph_famous(&g, "Octahedral");
size = 3;
printf("Motif size: %" IGRAPH_PRId "\n", size);
igraph_motifs_randesu_callback(&g, size, igraph_vector_view(&cut_prob, zeros, size), &print_motif, NULL);
igraph_motifs_randesu_callback(&g, size, NULL, &print_motif, NULL);
size = 4;
printf("Motif size: %" IGRAPH_PRId "\n", size);
igraph_motifs_randesu_callback(&g, size, igraph_vector_view(&cut_prob, zeros, size), &print_motif, NULL);
igraph_motifs_randesu_callback(&g, size, NULL, &print_motif, NULL);
size = 5;
printf("Motif size: %" IGRAPH_PRId "\n", size);
igraph_motifs_randesu_callback(&g, size, igraph_vector_view(&cut_prob, zeros, size), &print_motif, NULL);
igraph_motifs_randesu_callback(&g, size, NULL, &print_motif, NULL);
size = 6;
printf("Motif size: %" IGRAPH_PRId "\n", size);
igraph_motifs_randesu_callback(&g, size, igraph_vector_view(&cut_prob, zeros, size), &print_motif, NULL);
igraph_motifs_randesu_callback(&g, size, NULL, &print_motif, NULL);

igraph_destroy(&g);

Expand Down
26 changes: 10 additions & 16 deletions tests/unit/igraph_motifs_randesu_estimate.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,10 @@ void call_and_print(igraph_t *graph, int size, igraph_vector_t *cut_prob,

int main(void) {
igraph_t g_0, g_1, g_50_full, g_4_3_1;
igraph_vector_t cut_prob_0_3;
igraph_vector_t cut_prob_0_4;
igraph_vector_t cut_prob_01;
igraph_vector_int_t parsample;
igraph_integer_t estimate;

igraph_vector_init_real(&cut_prob_0_3, 3, 0.0, 0.0, 0.0);
igraph_vector_init_real(&cut_prob_0_4, 4, 0.0, 0.0, 0.0, 0.0);
igraph_vector_init_real(&cut_prob_01, 3, 0.1, 0.1, 0.1);
igraph_vector_int_init_range(&parsample, 0, 41);

Expand All @@ -48,47 +44,45 @@ int main(void) {
igraph_small(&g_4_3_1, 4, 0, 0,1, 1,2, 2,0, -1);

printf("No vertices:\n");
call_and_print(&g_0, /*size*/ 3, &cut_prob_0_3, /*sample_size*/ 1, /*parsample*/ NULL);
call_and_print(&g_0, /*size*/ 3, NULL, /*sample_size*/ 1, /*parsample*/ NULL);

printf("One vertex:\n");
call_and_print(&g_1, /*size*/ 3, &cut_prob_0_3, /*sample_size*/ 1, /*parsample*/ NULL);
call_and_print(&g_1, /*size*/ 3, NULL, /*sample_size*/ 1, /*parsample*/ NULL);

printf("Full graph of 50 vertices, motif size 3, sample all, (50 choose 3 = 19600):\n");
call_and_print(&g_50_full, /*size*/ 3, &cut_prob_0_3, /*sample_size*/ 50, /*parsample*/ NULL);
call_and_print(&g_50_full, /*size*/ 3, NULL, /*sample_size*/ 50, /*parsample*/ NULL);

printf("Full graph of 50 vertices, motif size 3, sample all, cut_prob 0.1 at each level:\n");
call_and_print(&g_50_full, /*size*/ 3, &cut_prob_01, /*sample_size*/ 50, /*parsample*/ NULL);

printf("Full graph of 50 vertices, motif size 3, sample 20:\n");
call_and_print(&g_50_full, /*size*/ 3, &cut_prob_0_3, /*sample_size*/ 20, /*parsample*/ NULL);
call_and_print(&g_50_full, /*size*/ 3, NULL, /*sample_size*/ 20, /*parsample*/ NULL);

printf("Full graph of 50 vertices, motif size 3, sample first 40:\n");
call_and_print(&g_50_full, /*size*/ 3, &cut_prob_0_3, /*sample_size*/ 0, &parsample);
call_and_print(&g_50_full, /*size*/ 3, NULL, /*sample_size*/ 0, &parsample);

printf("Full graph of 50 vertices, motif size 4, sample 20 (50 choose 4 = 230300):\n");
call_and_print(&g_50_full, /*size*/ 4, &cut_prob_0_4, /*sample_size*/ 20, /*parsample*/ NULL);
call_and_print(&g_50_full, /*size*/ 4, NULL, /*sample_size*/ 20, /*parsample*/ NULL);

printf("Triangle and a vertex, motif size 4, sample all:\n");
call_and_print(&g_4_3_1, /*size*/ 4, &cut_prob_0_4, /*sample_size*/ 4, /*parsample*/ NULL);
call_and_print(&g_4_3_1, /*size*/ 4, NULL, /*sample_size*/ 4, /*parsample*/ NULL);

VERIFY_FINALLY_STACK();
igraph_set_error_handler(igraph_error_handler_ignore);

printf("Cut prob too short.\n");
IGRAPH_ASSERT(igraph_motifs_randesu_estimate(&g_4_3_1, &estimate, /*size*/ 14, &cut_prob_0_3, /*sample_size*/ 4, /*parsample*/ NULL) == IGRAPH_EINVAL);
IGRAPH_ASSERT(igraph_motifs_randesu_estimate(&g_4_3_1, &estimate, /*size*/ 14, &cut_prob_01, /*sample_size*/ 4, /*parsample*/ NULL) == IGRAPH_EINVAL);

printf("Too many samples.\n");
IGRAPH_ASSERT(igraph_motifs_randesu_estimate(&g_4_3_1, &estimate, /*size*/ 4, &cut_prob_0_4, /*sample_size*/ 40, /*parsample*/ NULL) == IGRAPH_EINVAL);
IGRAPH_ASSERT(igraph_motifs_randesu_estimate(&g_4_3_1, &estimate, /*size*/ 4, NULL, /*sample_size*/ 40, /*parsample*/ NULL) == IGRAPH_EINVAL);

printf("Too many parsamples.\n");
IGRAPH_ASSERT(igraph_motifs_randesu_estimate(&g_4_3_1, &estimate, /*size*/ 4, &cut_prob_0_4, /*sample_size*/ 4, /*parsample*/ &parsample) == IGRAPH_EINVVID);
IGRAPH_ASSERT(igraph_motifs_randesu_estimate(&g_4_3_1, &estimate, /*size*/ 4, NULL, /*sample_size*/ 4, /*parsample*/ &parsample) == IGRAPH_EINVVID);

igraph_destroy(&g_0);
igraph_destroy(&g_1);
igraph_destroy(&g_50_full);
igraph_destroy(&g_4_3_1);
igraph_vector_destroy(&cut_prob_0_3);
igraph_vector_destroy(&cut_prob_0_4);
igraph_vector_destroy(&cut_prob_01);
igraph_vector_int_destroy(&parsample);

Expand Down
4 changes: 2 additions & 2 deletions tests/unit/igraph_motifs_randesu_estimate.out
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Full graph of 50 vertices, motif size 3, sample all, (50 choose 3 = 19600):
Estimate: 19600

Full graph of 50 vertices, motif size 3, sample all, cut_prob 0.1 at each level:
Estimate: 15990
Estimate: 16034

Full graph of 50 vertices, motif size 3, sample 20:
Estimate: 16040
Expand All @@ -17,7 +17,7 @@ Full graph of 50 vertices, motif size 3, sample first 40:
Estimate: 23800

Full graph of 50 vertices, motif size 4, sample 20 (50 choose 4 = 230300):
Estimate: 153070
Estimate: 241430

Triangle and a vertex, motif size 4, sample all:
Estimate: 0
Expand Down
Loading

0 comments on commit 03760a0

Please sign in to comment.