Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Changelog

All notable changes to GraphEm will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.0] - 2025-11-15

### Added
- **New graph generators** (`graphem/generators.py`):
- `generate_delaunay_triangulation()`: Generate planar graphs with triangular faces based on Delaunay triangulation
- `generate_complete_bipartite_graph()`: Generate complete bipartite graphs

### Changed
- **BREAKING: API aligned with CUDA version** - Major API changes for consistency:
- **All generators now return sparse adjacency matrices** (scipy.sparse.csr_matrix) instead of edge lists
- Renamed `erdos_renyi_graph()` → `generate_er()` for consistency with other generators
- `GraphEmbedder` now accepts `adjacency` (sparse matrix) instead of `edges` + `n_vertices`
- `GraphEmbedder` parameter renamed: `my_logger` → `logger_instance`
- `generate_bipartite_graph()` now accepts `p` and `seed` parameters for better control
- `compute_vertex_degrees()` now accepts adjacency matrix instead of edge list

- **GraphEmbedder improvements**:
- Added `seed` parameter for reproducibility
- Added `get_positions()` method that returns numpy array
- Made `positions` a property (internally uses `_positions`)
- Automatically infers number of vertices from adjacency matrix shape
- Improved adjacency matrix validation

### Fixed
- **Critical bug fix in influence maximization** (`graphem/influence.py`): Fixed `ndlib_estimated_influence()` function to correctly initialize seed nodes using NDlib's proper API. Previously, the function was using an incorrect configuration method that resulted in seeds not being properly set, leading to inaccurate influence estimations. The fix ensures:
- Seeds are now correctly initialized using `config.add_model_initial_configuration("Infected", seeds)` instead of the incorrect `config.add_node_configuration("status", seed, 1)`
- Influenced node counts are now correctly retrieved from `iterations[-1]['node_count'].get(2, 0)` instead of manually iterating through status values
- All influence maximization benchmarks and comparisons now produce accurate results
- High-degree seeds now correctly show higher influence than low-degree seeds

This bug affected all influence maximization functionality including `graphem_seed_selection()`, `greedy_seed_selection()`, and benchmark comparisons. Users should re-run any influence maximization experiments performed with previous versions.

### Migration Guide
To upgrade from 0.1.x to 0.2.0:

**Generators:**
```python
# OLD (0.1.x)
edges = ge.erdos_renyi_graph(n=100, p=0.1) # Returns edge list
n = 100

# NEW (0.2.0)
adj = ge.generate_er(n=100, p=0.1) # Returns sparse adjacency matrix
n = adj.shape[0] # Infer from matrix
```

**GraphEmbedder:**
```python
# OLD (0.1.x)
embedder = ge.GraphEmbedder(edges=edges, n_vertices=n, n_components=2, my_logger=logger)

# NEW (0.2.0)
embedder = ge.GraphEmbedder(adjacency=adj, n_components=2, logger_instance=logger)
```

**compute_vertex_degrees:**
```python
# OLD (0.1.x)
degrees = ge.compute_vertex_degrees(n, edges)

# NEW (0.2.0)
degrees = ge.compute_vertex_degrees(adj)
```

## [Previous Releases]

For release history before this changelog was established, see the [GitHub Releases](https://github.com/igorrivin/graphem/releases) page.
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,12 @@ pip install git+https://github.com/igorrivin/graphem.git
```python
import graphem as ge

# Generate graph
edges = ge.erdos_renyi_graph(n=500, p=0.01)
# Generate graph (returns sparse adjacency matrix)
adjacency = ge.generate_er(n=500, p=0.01)

# Create embedder
embedder = ge.GraphEmbedder(
edges=edges,
n_vertices=500,
adjacency=adjacency,
n_components=3
)

Expand All @@ -90,7 +89,7 @@ seeds = ge.graphem_seed_selection(embedder, k=10)

# Estimate influence spread
import networkx as nx
G = nx.from_edgelist(edges)
G = nx.from_scipy_sparse_array(adjacency)
influence, _ = ge.ndlib_estimated_influence(G, seeds, p=0.1)
print(f"Influence: {influence} nodes ({influence/500:.1%})")
```
Expand All @@ -102,7 +101,7 @@ from graphem.benchmark import benchmark_correlations

# Compare embedding radii with centrality measures
results = benchmark_correlations(
ge.erdos_renyi_graph,
ge.generate_er,
graph_params={'n': 200, 'p': 0.05},
n_components=3,
num_iterations=40
Expand Down
27 changes: 14 additions & 13 deletions docs/api_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,16 @@ Graph Generators

**graphem.generators** - Generate various graph types for testing and experimentation.

Provides NetworkX-based generators for standard graph models including random graphs, scale-free networks, small-world graphs, and more.
Provides NetworkX-based generators for standard graph models including random graphs, scale-free networks, small-world graphs, and more. All generators return sparse adjacency matrices (scipy.sparse.csr_matrix).

Key functions:
- ``erdos_renyi_graph(n, p)`` - Random graph with edge probability p
- ``generate_sbm(n_per_block, num_blocks, p_in, p_out)`` - Stochastic block model
- ``generate_ba(n, m)`` - Barabási-Albert preferential attachment
- ``generate_ws(n, k, p)`` - Watts-Strogatz small-world
- ``generate_scale_free(n, ...)`` - Scale-free network
- ``generate_geometric(n, radius)`` - Random geometric graph
- ``generate_er(n, p, seed)`` - Erdős–Rényi random graph with edge probability p
- ``generate_sbm(n_per_block, num_blocks, p_in, p_out, seed)`` - Stochastic block model
- ``generate_ba(n, m, seed)`` - Barabási-Albert preferential attachment
- ``generate_ws(n, k, p, seed)`` - Watts-Strogatz small-world
- ``generate_scale_free(n, seed)`` - Scale-free network
- ``generate_geometric(n, radius, seed)`` - Random geometric graph
- ``compute_vertex_degrees(adjacency)`` - Compute vertex degrees from adjacency matrix

.. automodule:: graphem.generators
:members:
Expand All @@ -63,14 +64,14 @@ Key functions:
Influence Maximization
----------------------

**graphem.influence** - Seed selection algorithms for influence maximization in networks.

Implements GraphEm-based seed selection using radial distances from embedding origin, plus traditional greedy methods with NDlib simulation.
**graphem.influence** - Seed selection and influence estimation for networks.

Key functions:
- ``graphem_seed_selection(embedder, k)`` - Select seeds based on radial distances
- ``greedy_seed_selection(G, k, p)`` - Traditional greedy algorithm
- ``ndlib_estimated_influence(G, seeds, p)`` - Evaluate influence using Independent Cascades
- ``graphem_seed_selection(embedder, k, num_iterations)`` - Fast seed selection using embedding radial distances
- ``greedy_seed_selection(G, k, p, iterations_count)`` - Greedy algorithm maximizing marginal influence gain
- ``ndlib_estimated_influence(G, seeds, p, iterations_count)`` - Estimate influence spread via Independent Cascades simulation

Returns seed node lists and influence spread estimates (node counts).

.. automodule:: graphem.influence
:members:
Expand Down
47 changes: 23 additions & 24 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,25 +79,24 @@ Quick Start Example
import graphem as ge
import networkx as nx

# Generate a scale-free network
edges = ge.generate_ba(n=1000, m=3)
# Generate a scale-free network (returns sparse adjacency matrix)
adjacency = ge.generate_ba(n=1000, m=3, seed=42)

# Create and run embedding
embedder = ge.GraphEmbedder(
edges=edges,
n_vertices=1000,
adjacency=adjacency,
n_components=3
)
embedder.run_layout(num_iterations=50)

# Find influential nodes
seeds = ge.graphem_seed_selection(embedder, k=20)

# Estimate influence spread
G = nx.Graph(edges)
influence, _ = ge.ndlib_estimated_influence(G, seeds, p=0.1)
G = nx.from_scipy_sparse_array(adjacency)
influence, _ = ge.ndlib_estimated_influence(G, seeds, p=0.1, iterations_count=100)
print(f"Influence spread: {influence} nodes ({influence/1000:.1%})")

# Visualize results
embedder.display_layout()

Expand Down Expand Up @@ -136,11 +135,11 @@ High performance index for efficient k-nearest neighbor search in high-dimension
Influence Maximization
~~~~~~~~~~~~~~~~~~~~~~

Advanced algorithms for identifying influential nodes in networks:
Algorithms for identifying influential nodes:

* **GraphEm Method**: Uses embedding radial distances to select diverse, influential seeds
* **Greedy Baseline**: Traditional greedy algorithm for comparison
* **Spread Simulation**: NDlib integration for accurate influence estimation
* **GraphEm Method**: Fast selection using embedding radial distances
* **Greedy Algorithm**: Iterative marginal gain maximization
* **Influence Estimation**: Independent Cascades simulation via NDlib

Graph Generators
~~~~~~~~~~~~~~~~
Expand All @@ -149,18 +148,18 @@ Comprehensive collection of standard and custom graph models:

.. code-block:: python

# Classic models
edges = ge.erdos_renyi_graph(n=500, p=0.02)
edges = ge.generate_ba(n=500, m=3) # Scale-free
edges = ge.generate_ws(n=500, k=6, p=0.1) # Small-world
# Classic models (all return sparse adjacency matrices)
adjacency = ge.generate_er(n=500, p=0.02, seed=42)
adjacency = ge.generate_ba(n=500, m=3, seed=42) # Scale-free
adjacency = ge.generate_ws(n=500, k=6, p=0.1, seed=42) # Small-world

# Community structures
edges = ge.generate_sbm(sizes=[100, 150, 100], p_in=0.1, p_out=0.01)
edges = ge.generate_caveman(clique_size=10, num_cliques=5)
adjacency = ge.generate_sbm(n_per_block=100, num_blocks=3, p_in=0.1, p_out=0.01, seed=42)
adjacency = ge.generate_caveman(l=5, k=10)

# Specialized networks
edges = ge.generate_geometric(n=300, radius=0.2)
edges = ge.generate_road_network(grid_size=20, connection_prob=0.8)
adjacency = ge.generate_geometric(n=300, radius=0.2, seed=42)
adjacency = ge.generate_road_network(width=20, height=20)

Performance Characteristics
---------------------------
Expand Down
10 changes: 5 additions & 5 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,12 @@ Test your installation:
.. code-block:: python

import graphem as ge
# Generate a small test graph
edges = ge.erdos_renyi_graph(n=100, p=0.1)
embedder = ge.GraphEmbedder(edges, n_vertices=100)

# Generate a small test graph (returns sparse adjacency matrix)
adjacency = ge.generate_er(n=100, p=0.1, seed=42)
embedder = ge.GraphEmbedder(adjacency=adjacency, n_components=2)
embedder.run_layout(num_iterations=10)

print("GraphEm installation successful!")

Troubleshooting
Expand Down
Loading