Skip to content

Conversation

@iosonofabio
Copy link
Member

@iosonofabio iosonofabio commented Jan 19, 2021

This tries to implement #357, i.e. generating a Graph object from a sparse scipy matrix, which is the de facto standard in Pythonlands for such cases.

The key is to bypass GraphBase.Adjacency, which dispatches to a C function expecting a dense matrix. Instead, I reproduce the logic of the various C-level functions depending on the mode (e.g. igraph_i_adjacency_lower, all in the igraph project, src/constructors.c).

A few notes:

  • graceful try to import scipy to check if the matrix is sparse
  • added a case for (dense) numpy 2D ndarrays (which include numpy.matrix object) with a similar graceful import
  • outsourced the sparse case to a private _ function in a new sparse_matrix module to avoid cluttering the main module. Happy to discuss namespacing or alternatives
  • the details of the logic use a more Pythonic syntax than their C counterparts, while trying to be somewhat efficient. No efforts were undertaken to actually optimize these operations, and I wouldn't mind getting rid of some black magic if you don't like it (sums over conditional lazy comprehensions).

Funnily enough, the concrete case that prompted me to make this PR is a weighted adjacency sparse matrix... so I would like to implement the weighted case as well, any suggestions how to call the class method in that case? Perhaps just graph.AdjacencyWeighted?

No tests written yet.

edit: Oh I just saw Weighted_Adjacency.

@iosonofabio iosonofabio requested a review from ntamas January 19, 2021 23:30
@iosonofabio
Copy link
Member Author

Huh, CI fails in an interesting way and I wouldn't mind some advice. It has to do with inheritance (alas!).

This PR currently has Adjacency as a classmethod of Graph, consistent with the current declaration. If the input is not a sparse matrix, I would like to just use the old call. But it's tricky.

@classmethod def Adjacency(klass, ...): return GraphBase.Adjacency(...) # or return super().Adjacency(...)

does not work, because GraphBase.Adjacency is ALSO a class method, so it will construct whatever object you give it as first argument, which via the dot notation means whatever object comes before the dot. So if we call it like this it will construct a GraphBase, not a Graph. On the other hand, return Graph.Adjacency(...) becomes an infinite recursion.

I could make another copy of GraphBase.Adjacency as GraphBase._Adjacency and then exploit the fact that when subclassing, Python will automatically create Graph._Adjacency, but is that alright or do you guys know of a better method?

@iosonofabio
Copy link
Member Author

I pushed the _Adjacency hack so you guys can see it in action, but am not super happy about it, of course. Any suggestions would be welcome @ntamas @szhorvat @vtraag

@ntamas
Copy link
Member

ntamas commented Jan 28, 2021

I think that this is a bug in GraphBase.Adjacency(), but let me check.


Edit: it seems like I have already started adding test cases for inheritance in test_basic.py (see InheritanceTests), but it does not cover class methods yet. I'll update the test cases to see whether there is indeed a bug there or not.

@ntamas
Copy link
Member

ntamas commented Jan 28, 2021

@iosonofabio It looks like the correct syntax for calling a class method of the superclass is:

class InheritedGraph(Graph): @classmethod def Adjacency(cls, *args, **kwds): result = super(InheritedGraph, cls).Adjacency(*args, **kwds) return result 

So I would try something like super(Graph, cls).Adjacency() from the Graph implementation to call Adjacency in GraphBase. Let me know if it doesn't work.

Copy link
Member

@ntamas ntamas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few minor comments here and there, otherwise I think it's good to go!

@iosonofabio
Copy link
Member Author

I fixed those, but let me add the weighted case too since I'm here

@iosonofabio iosonofabio marked this pull request as ready for review January 31, 2021 06:12
@iosonofabio
Copy link
Member Author

rebased on top of the GitHub actions PR, let's see if this works

@ntamas
Copy link
Member

ntamas commented Feb 8, 2021

@iosonofabio Can you rebase this once again on top of master to see if the CI checks pass? I can merge it after that.

@jvail
Copy link

jvail commented Mar 3, 2021

Sorry for spoiling your PR discussion: It is just a pity this could not make it into the latest release. Would be a very useful feature!

@ntamas
Copy link
Member

ntamas commented Mar 3, 2021

There will be a 0.9.1 soon(ish) as the API documentation build is currently broken, so I'm hoping to squeeze this into that one.

@ntamas ntamas added this to the 0.9.1 milestone Mar 9, 2021
@ntamas
Copy link
Member

ntamas commented Mar 10, 2021

Okay, I'm gonna merge this into master as it seems to be okay. If the tests don't pass in master, I'll just fix those there.

@ntamas ntamas merged commit cc8d5aa into igraph:master Mar 10, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants