igraph_delete_edges(...) doesn't delete attributes for the deleted edges

NB: The server I am working off of is ancient, and is limited to using cmake v.3.16, so I am unable to upgrade past igraph v.0.9.10. If this has been fixed in later releases, please let me know, and I’ll figure out some other workaround.

In short: if I delete a set of edges from my graph using igraph_delete_edges, the deleted edges’ attributes are still visible if I pull the whole vector of edge attributes. I am unable to provide a full reprex, but let me know if there’s anything additional I can supply…

Thanks for your help.

cout << igraph_ecount(graph) << endl;
/* Prints  593 edges */

igraph_delete_edges(graph, igraph_ess_vector(&edges_to_delete));
cout << igraph_ecount(graph) << endl;
/* Prints  475  Edges*/
  
  
/* Check to make sure the edge attributes actually got deleted */
igraph_vector_t temp_v;
igraph_vector_init(&temp_v, 0);
EANV(graph, "type", &temp_v);
  
for (int i = 0; i < igraph_vector_size(&temp_v); i++) {
    cout << i << ": " << VECTOR(temp_v)[i] << ", ";
}

/* Prints out attributes of 593 edges */ 

#include <igraph.h>

int main(void) {
    igraph_t g;
    igraph_vector_t temp_v;
    igraph_vector_t edges_to_delete;

    igraph_set_attribute_table(&igraph_cattribute_table);

    igraph_full(&g, 100, IGRAPH_DIRECTED, 0) ;

    for (int i = 0; i < 600; i++) {
        SETEAN(&g, "type", i, 1);
    }
    igraph_vector_init(&edges_to_delete, 60);
    for (int i = 0; i < 60; i++) {
        VECTOR(edges_to_delete)[i] = i * 10;
    }

    igraph_vector_init(&temp_v, 0);
    EANV(&g, "type", &temp_v);
    printf("%ld\n", igraph_vector_size(&temp_v));
    igraph_delete_edges(&g, igraph_ess_vector(&edges_to_delete));
    EANV(&g, "type", &temp_v);
    printf("%ld\n", igraph_vector_size(&temp_v));
    igraph_vector_destroy(&temp_v);
    igraph_vector_destroy(&edges_to_delete);

    igraph_destroy(&g);
}

I’ve tried this (with 9.10), and this prints out

9900
9840

Like you would expect. Any idea what you’re doing differently?

The only thing I see different is that my graph is undirected. I ran your code changing to undirected, and got the correct output:

4950
4890

I will continue to investigate further. Thank you

I won’t have access to a computer for a few more days so all I want to say for the moment is that you can easily compile a recent CMake yourself and install it privately in ~/.local or ~/local. Please don’t use old igraph versions. Compiling your own CMake is no more difficult than compiling igraph.

I wasn’t able to successfully debug this before the weekend. @GroteGnoom’s code works fine, but when I tried to do the same procedure to my graph object, kept having this weird behavior. Today I am upgrading igraph to v0.10.4, and will write back after.

When you install your own CMake, you can pass -DCMAKE_INSTALL_PREFIX=$HOME/local or similar at the configuration step to indicate where CMake should be installed. Then add $HOME/local/bin to your PATH using something like export PATH=$HOME/local/bin:$PATH.

That said, I do not recall any relevant bug fixes to the C attribute handler, so upgrading might not change anything.

It is possible that this is not a bug in igraph, but some subtle mistake in your code that somehow corrupts the graph object. A reproducible example would be necessary to know for sure. If you can give a sketch of how you create the graph object, we might notice something relevant.

Thank you— yes, I imagine that this is an error upstream in the process. I will create a reprex for you today. It’s quite possible that I will catch the bug in the process of creating the reprex itself. Thank you for you quick reply times in this forum!

OK, Here is a reproducible example. I have identified the issue as lying in how I have structured my CMakeLists. Previously, I was compiling my project files as a dylib and then linking to the executable main fn. I was doing this because I also have a python API for my program (passing various graph attributes and simulation parameters from python, running simulation in C++, then returning the results to python). Linking the main executable to the compiled library produced the strange behavior, where deleting edges did not also delete the corresponding attributes. If I change the CMakeLists.txt to directly link the project files to the executable, things behave as expected. However, then I’m not sure why the library is compiling strangely.

I am developing currently on MacOSX 12.4. Our server cluster, that previously was limiting me from upgrading past igraph 0.9.10, runs Ubuntu 18. I have added a local install of CMake on the cluster to match the version on my mac. Everything in both environments is now running CMake 3.20 and igraph 10.4. This issue persisted on both Mac and Ubuntu after upgrading Cmake and igraph.

src/main.cpp:

#include<igraph.h>
#include<iostream>
#include "BICS_ABM.h"

using namespace std;

#define MAXCHAR 1000



int main(int argc, char **argv) {

    igraph_t graph;
    igraph_set_attribute_table(&igraph_cattribute_table);
    igraph_full(&graph, 100, false, false);

    test_routine(&graph);
    cout << "Ran routine" << endl;

    DELALL(&graph);
    igraph_destroy(&graph);

    return 0;

}

src/BICS_ABM.cpp (This normally contains the main routine run in the program):

#include<igraph.h>
#include<vector>
#include<string>
#include<iostream>
#include "BICS_ABM.h"

void test_routine(igraph_t* graph) {

    igraph_vector_t temp_v;
    igraph_vector_init(&temp_v, igraph_ecount(graph));
    SETEANV(graph, "type", &temp_v);
    SETEANV(graph, "duration", &temp_v);

    igraph_vector_int_t edges_to_delete;
    igraph_vector_int_init(&edges_to_delete, 10);
    for (int i = 0; i < 10; i++) {
        VECTOR(edges_to_delete)[i] = i * 10;
    }
    igraph_delete_edges(graph, igraph_ess_vector(&edges_to_delete));
    igraph_vector_int_destroy(&edges_to_delete);

    /* Pull edge attribute vector */
    igraph_vector_resize(&temp_v, 0);
    EANV(graph, "type", &temp_v);
    cout << "Number of edges in graph: " << igraph_ecount(graph) << "; Number of items in attribute vector: " << igraph_vector_size(&temp_v) << endl;
    igraph_vector_destroy(&temp_v);

}


src/BICS_ABM.h:

#include <igraph.h>
using namespace std;
void test_routine(igraph_t* graph) ;

Ran the following shell commands to add igraph library:
% git submodule add https://github.com/igraph/igraph.git
% cd igraph
% git checkout 0.10.4

Previously, I had the following CMakelists.txt file. This yielded the incorrect behavior by printing
Number of edges in graph: 4940; Number of items in attribute vector: 4950

cmake_minimum_required(VERSION 3.16)
set (CMAKE_CXX_STANDARD 17)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
project(BICS_ABM)
add_subdirectory(igraph)

add_library(BICS_ABM_lib SHARED 
    src/BICS_ABM.h 
    src/BICS_ABM.cpp 
    )

add_executable(BICS_ABM 
    src/main.cpp 
    )


target_link_libraries(BICS_ABM_lib PUBLIC igraph)
target_link_libraries(BICS_ABM PUBLIC BICS_ABM_lib igraph)

Using the following CMakeLists.txt we get the correct result:
Number of edges in graph: 4940; Number of items in attribute vector: 4940

cmake_minimum_required(VERSION 3.16)
set (CMAKE_CXX_STANDARD 17)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
project(BICS_ABM)
add_subdirectory(igraph)

add_library(BICS_ABM_lib SHARED 
    src/BICS_ABM.h 
    src/BICS_ABM.cpp 
    )

add_executable(BICS_ABM 
    src/BICS_ABM.h 
    src/BICS_ABM.cpp 
    src/main.cpp 
    )


target_link_libraries(BICS_ABM_lib PUBLIC igraph)
target_link_libraries(BICS_ABM PUBLIC igraph)



Thanks for the detailed explanation! I’m not sure what’s going on, but my best quick guess is that it’s maybe related to igraph_cattribute_table being a global?

Another observation is that if I take the content of the routine function and place it inside of the main function, the error goes away. Only happens when the routine is in a separate function— so that points to something related to variable scoping.

It also seems to work correctly when I put the

    igraph_set_attribute_table(&igraph_cattribute_table);

in the BICS_ABM.cpp, but the documentation tells us not to do that.

@tamas, do you know what’s going on?

ah, if I do

    printf("%p\n", &igraph_cattribute_table);

in main.cpp and BICS_ABM.cpp, I get different addresses. So there’s two of them.
I expected the linker to give an error, but apparently the one in the library is just resolved locally, and then one in the executable is added later. Very confusing!

Indeed it’s very confusing, and honestly I don’t know what’s going on here. igraph_cattribute_table is declared as an exported extern const igraph_attribute_table_t in igraph_attributes.h so the linker should be aware that there’s such a symbol in the library and any references to it from other files should refer to the same symbol.

Can you guys post a small, self-contained, reproducible example in a Github repo? Ideally one that we can compile with CMake using the standard mkdir build && cd build && cmake .. && make dance, assuming that igraph is installed properly on the system with the standard facilities so CMake knows about it and how to find it. Then we could take it from there.

1 Like

If I try the same thing with an installed igraph, then it works correctly.
I just installed igraph via my package manager and removed the

add_subdirectory(igraph)

So I think your proposed way of reproducing it will not reproduce it.

I apologize, I had to step away from this project for a few weeks. You can find a repo here:

As requested, can be run using mkdir -p build && cd build && cmake .. && make && cd .. && ./build/BICS_ABM

Hopefully this helps debugging. I would prefer to install igraph this way so that I can easily work off of multiple machines and make sure the installation is the same across all. For now, since I am nearing my dissertation submission deadline, I will just install using normal package manager instead of adding as a submodule :slight_smile:

1 Like

This example seems to confirm @GroteGnoom’s conclusion that there are two copies of symbols.

I am doing the following experiments on macOS 10.14 on Intel:

$ ls -lh *BICS*
-rwxr-xr-x  1 szhorvat  staff   452K May 25 14:24 BICS_ABM*
-rwxr-xr-x  1 szhorvat  staff   443K May 25 14:24 libBICS_ABM_lib.dylib*

Notice the similar size of the library and executable, and in particular the unusually large size of the executable.

Let’s list symbols in both with nm:

~/R/i/build (main|✔) $ nm -CU libBICS_ABM_lib.dylib | head
000000000005bb0c s GCC_except_table36
000000000005bac0 s GCC_except_table4
000000000005bb1c s GCC_except_table41
000000000005baf4 s GCC_except_table7
0000000000005830 t _IGRAPH_FINALLY_CLEAN
00000000000058d0 t _IGRAPH_FINALLY_ENTER
00000000000059c0 t _IGRAPH_FINALLY_EXIT
00000000000055a0 t _IGRAPH_FINALLY_FREE
0000000000005700 t _IGRAPH_FINALLY_REAL
00000000000058b0 t _IGRAPH_FINALLY_STACK_SIZE
~/R/i/build (main|✔) $ nm -CU BICS_ABM | head
000000010005d9ac s GCC_except_table36
000000010005d960 s GCC_except_table4
000000010005d9bc s GCC_except_table41
000000010005d994 s GCC_except_table7
00000001000052e0 t _IGRAPH_FINALLY_CLEAN
0000000100005380 t _IGRAPH_FINALLY_ENTER
0000000100005470 t _IGRAPH_FINALLY_EXIT
0000000100005050 t _IGRAPH_FINALLY_FREE
00000001000051b0 t _IGRAPH_FINALLY_REAL
0000000100005360 t _IGRAPH_FINALLY_STACK_SIZE

I’m a bit out of my comfort zone here, but I believe t indicates an internal symbol that is included directly in the binary (not linked from elsewhere). Symbols linked from elsewhere would be a U. This is confirmed by building igraph as a shared library, with tests, and examining the test executables with nm.

With nm, the -C option is for demangling, the -U options is for skipping U (undefined) symbols.


For completeness:

~/R/i/build (main|✔) $ nm -CU BICS_ABM | grep igraph_cattribute_table
0000000100061288 s _igraph_cattribute_table
~/R/i/build (main|✔) $ nm -CU libBICS_ABM_lib.dylib | grep igraph_cattribute_table
000000000005f288 s _igraph_cattribute_table

Just to add that when I remove the igraph subdirectory and instead link to igraph installed via homebrew, I get the following executable sizes:

(base) eroubenoff@MacBook-Air-3 build % ls -lh *BICS*
-rwxr-xr-x  1 eroubenoff  staff    55K May 25 08:58 BICS_ABM
-rwxr-xr-x  1 eroubenoff  staff    40K May 25 08:58 libBICS_ABM_lib.dylib

Which are an order of magnitude smaller than what is listed above.

Listing the symbols with this version:

(base) eroubenoff@MacBook-Air-3 build % nm -C libBICS_ABM_lib.dylib | head
0000000000003eec t GCC_except_table38
0000000000003ea0 t GCC_except_table4
0000000000003efc t GCC_except_table43
0000000000003ed4 t GCC_except_table7
                 U _Unwind_Resume
0000000000003090 T test_routine(igraph_s*)
0000000000003b00 t std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::__is_long() const
0000000000003510 t std::__1::basic_ostream<char, std::__1::char_traits<char> >::sentry::operator bool() const
0000000000003b80 t std::__1::__compressed_pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::__rep, std::__1::allocator<char> >::first() const
0000000000003810 t std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> >::failed() const

(base) eroubenoff@MacBook-Air-3 build % nm -C BICS_ABM | head
0000000100003f30 t GCC_except_table38
0000000100003ee4 t GCC_except_table4
0000000100003f40 t GCC_except_table43
0000000100003f18 t GCC_except_table7
                 U _Unwind_Resume
                 U test_routine(igraph_s*)
0000000100003a90 t std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::__is_long() const
00000001000034a0 t std::__1::basic_ostream<char, std::__1::char_traits<char> >::sentry::operator bool() const
0000000100003b10 t std::__1::__compressed_pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::__rep, std::__1::allocator<char> >::first() const
00000001000037a0 t std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> >::failed() const

And finally, note how grep igraph_cattribute_table on the dylib matches no results:

(base) eroubenoff@MacBook-Air-3 build % nm -C BICS_ABM | grep igraph_cattribute_table
                 U igraph_cattribute_table
(base) eroubenoff@MacBook-Air-3 build % nm -C libBICS_ABM_lib.dylib | grep igraph_cattribute_table
(base) eroubenoff@MacBook-Air-3 build %

Unfortunately I don’t have a good enough understanding of linking procedures, and how CMake works, to understand (1) why this happens (2) is it at all avoidable with add_subdirectory().

If I use the following in your CMakeLists.txt, there are still duplicate symbols:

target_link_libraries(BICS_ABM_lib PUBLIC igraph)
target_link_libraries(BICS_ABM PRIVATE BICS_ABM_lib)