Skip to content

Commit

Permalink
Merge pull request #513 from graphistry/dev/more-graph
Browse files Browse the repository at this point in the history
Dev/more graph
  • Loading branch information
lmeyerov authored Nov 2, 2023
2 parents 613b932 + 61f497d commit c17eb9e
Show file tree
Hide file tree
Showing 8 changed files with 282 additions and 11 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,24 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Development]

### Added

* igraph: support `compute_igraph('community_optimal_modularity')`
* igraph: `compute_igraph('articulation_points')` labels nodes that are articulation points

### Fixed

* Type error in arrow uploader exception handler
* igraph: default coerce Graph-type node labels to strings, enabling plotting of g.compute_igraph('k_core')
* igraph: fix coercions when using numeric IDs that were confused by igraph swizzling

### Infra

* dask: Fixed parsing error in hypergraph dask tests
* igraph: Ensure in compute_igraph tests that default mode results coerce to arrow tables
* igraph: Test chaining
* tests: mount source folders to enable dev iterations without rebuilding

## [0.29.6 - 2023-10-23]

### Docs
Expand Down
1 change: 1 addition & 0 deletions docker/test-cpu-local.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ docker run \
-e WITH_TYPECHECK=$WITH_TYPECHECK \
-e WITH_BUILD=$WITH_BUILD \
-e WITH_TEST=$WITH_TEST \
-v "`pwd`/../graphistry:/opt/pygraphistry/graphistry:ro" \
--rm \
${NETWORK} \
graphistry/test-cpu:${TEST_CPU_VERSION} \
Expand Down
1 change: 1 addition & 0 deletions docker/test-gpu-local.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ docker run \
-e WITH_TYPECHECK=$WITH_TYPECHECK \
-e WITH_TEST=$WITH_TEST \
-e WITH_BUILD=$WITH_BUILD \
-v "`pwd`/../graphistry:/opt/pygraphistry/graphistry:ro" \
--security-opt seccomp=unconfined \
--rm \
${NETWORK} \
Expand Down
4 changes: 2 additions & 2 deletions graphistry/PlotterBase.py
Original file line number Diff line number Diff line change
Expand Up @@ -1462,9 +1462,9 @@ def to_igraph(self,


def compute_igraph(self,
alg: str, out_col: Optional[str] = None, directed: Optional[bool] = None, use_vids: bool = False, params: dict = {}
alg: str, out_col: Optional[str] = None, directed: Optional[bool] = None, use_vids: bool = False, params: dict = {}, stringify_rich_types: bool = True
):
return compute_igraph_base(self, alg, out_col, directed, use_vids, params)
return compute_igraph_base(self, alg, out_col, directed, use_vids, params, stringify_rich_types)
compute_igraph.__doc__ = compute_igraph_base.__doc__


Expand Down
2 changes: 1 addition & 1 deletion graphistry/arrow_uploader.py
Original file line number Diff line number Diff line change
Expand Up @@ -660,7 +660,7 @@ def post_arrow(self, arr: pa.Table, graph_type: str, opts: str = ''):
except requests.exceptions.HTTPError as e:
logger.error('Failed to post arrow to %s (%s)', sub_path, "{}/{}{}".format(self.server_base_path, sub_path, f"?{opts}" if len(opts) > 0 else ""), exc_info=True)
logger.error('%s', e)
logger.error('%s', e.response.text)
logger.error('%s', e.response.text if e.response else None)
raise e
except Exception as e:
logger.error('Failed to post arrow to %s', sub_path, exc_info=True)
Expand Down
46 changes: 41 additions & 5 deletions graphistry/plugins/igraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ def from_igraph(self,
nodes_df = nodes_df[ node_attributes ]

if g._nodes is not None and merge_if_existing:
if g._node is None:
raise ValueError('Non-None g._nodes and merge_if_existing=True, yet no g._node is defined')
if len(g._nodes) != len(nodes_df):
logger.warning('node tables do not match in length; switch merge_if_existing to False or load_nodes to False or add missing nodes')

Expand All @@ -126,8 +128,21 @@ def from_igraph(self,
node_id_col = None
elif node_col in ig_vs_df:
node_id_col = node_col
#User to_igraph() with numeric IDs may swizzle id mappings (ex: sparse numeric) so try to un-swizzle
#FIXME: how to handle dense edge case's swizzling?
elif g._node is not None and g._nodes[g._node].dtype.name == ig_vs_df.reset_index()['vertex ID'].dtype.name:
node_id_col = None
found = False
#FIXME: This seems quite error prone... what if any fields already exist?
for c in ['name', 'id', 'idx', NODE]:
if c in ig_vs_df.columns:
if g._nodes[g._node].min() == ig_vs_df[c].min() and g._nodes[g._node].max() == ig_vs_df[c].max():
if g._nodes[g._node].sort_values().equals(ig_vs_df[c].sort_values()):
node_id_col = c
found = True
break
if not found:
logger.debug('lacks matching sortable dimension, likely passed integers-as-vids, continue without remapping')
node_id_col = None
elif 'name' in ig_vs_df:
node_id_col = 'name'
else:
Expand Down Expand Up @@ -250,6 +265,7 @@ def to_igraph(


compute_algs = [
'articulation_points',
'authority_score',
'betweenness',
'bibcoupling',
Expand All @@ -267,6 +283,7 @@ def to_igraph(
'community_leading_eigenvector',
'community_leiden',
'community_multilevel',
'community_optimal_modularity',
'community_spinglass',
'community_walktrap',
'constraint',
Expand All @@ -288,7 +305,8 @@ def compute_igraph(
out_col: Optional[str] = None,
directed: Optional[bool] = None,
use_vids=False,
params: dict = {}
params: dict = {},
stringify_rich_types=True
) -> Plottable:
"""Enrich or replace graph using igraph methods
Expand All @@ -307,6 +325,9 @@ def compute_igraph(
:param params: Any named parameters to pass to the underlying igraph method
:type params: dict
:param stringify_rich_types: When rich types like igraph.Graph are returned, which may be problematic for downstream rendering, coerce them to strings
:type stringify_rich_types: bool
:returns: Plotter
:rtype: Plotter
Expand Down Expand Up @@ -374,10 +395,25 @@ def compute_igraph(
return from_igraph(self, out)
elif isinstance(out, list) and self._nodes is None:
raise ValueError("No g._nodes table found; use .bind(), .nodes(), .materialize_nodes()")
elif len(out) == len(self._nodes):
clustering = out
elif alg == 'articulation_points':
assert isinstance(out, list) # List[int]
membership = [0] * len(ig.vs)
for i in out:
membership[i] = 1
clustering = membership
elif isinstance(out, list) and len(out) == len(self._nodes):
if stringify_rich_types and len(out) > 0 and all((isinstance(c, igraph.Graph) for c in out)):
#ex: k_core
clustering = [str(c) for c in out]
else:
clustering = out
else:
raise RuntimeError(f'Unexpected output type "{type(out)}"; should be VertexClustering, VertexDendrogram, Graph, or list_<|V|>')
if isinstance(out, list) and len(out) > 0:
xtra = f" (element 0 type: {type(out[0])})"
else:
xtra = ""

raise RuntimeError(f'Unexpected output type "{type(out)}"{xtra}; should be VertexClustering, VertexDendrogram, Graph, or list_<|V|>')

ig.vs[out_col] = clustering

Expand Down
Loading

0 comments on commit c17eb9e

Please sign in to comment.