Skip to content

Fixes #6 - perf: optimize python backend for task graph-analytics#35

Open
IronKommander wants to merge 3 commits into
iiitl:mainfrom
IronKommander:perf/graph_analytics
Open

Fixes #6 - perf: optimize python backend for task graph-analytics#35
IronKommander wants to merge 3 commits into
iiitl:mainfrom
IronKommander:perf/graph_analytics

Conversation

@IronKommander
Copy link
Copy Markdown

@IronKommander IronKommander commented Apr 11, 2026

Description

Fixes #6. This involved removing the 16 iterations on a python dict of dicts, and instead switiching over to numpy math.

Performance wins

graph_analytics on the python backend now runs in under a millisecond. Previously on the same backend, it ran in around 7 milliseconds.

Previous python backend v/s New python backend

> python -m chuck compare --old data/reports/snapshots/old.json --new data/reports/snapshots/check.json
A/B Comparison:
- old: old (a302e5f)
- new: check (cd19a00)
- same system: True
- backend preference: python -> python (same=True)
- old snapshot clean checkout: True
- new snapshot clean checkout: True
- average speedup: 1.6691x

Per capability:
- compute_core: 0.01765s -> 0.01746s, speedup 1.0109x, delta -1.08%, reliability 1.0 -> 1.0 (+0.000000)
- data_encoding: 0.058163s -> 0.059368s, speedup 0.9797x, delta 2.07%, reliability 1.0 -> 1.0 (+0.000000)
- graph_analytics: 0.007165s -> 0.000906s, speedup 7.9084x, delta -87.36%, reliability 1.0 -> 1.0 (+0.000000)
- io_pipeline: 0.087415s -> 0.091958s, speedup 0.9506x, delta 5.2%, reliability 1.0 -> 1.0 (+0.000000)
- memory_index: 0.077978s -> 0.076909s, speedup 1.0139x, delta -1.37%, reliability 0.997594 -> 0.997594 (+0.000000)
- memory_tier: 0.05148s -> 0.054776s, speedup 0.9398x, delta 6.4%, reliability 1.0 -> 1.0 (+0.000000)
- ordering_core: 0.193625s -> 0.193173s, speedup 1.0023x, delta -0.23%, reliability 1.0 -> 1.0 (+0.000000)
- prime_analytics: 0.523527s -> 0.533942s, speedup 0.9805x, delta 1.99%, reliability 0.996119 -> 0.996119 (+0.000000)
- relational_fusion: 0.017084s -> 0.017643s, speedup 0.9683x, delta 3.27%, reliability 1.0 -> 1.0 (+0.000000)
- retrieval_core: 0.001768s -> 0.001887s, speedup 0.9369x, delta 6.73%, reliability 0.875622 -> 0.875622 (+0.000000)

Current C++ backend v/s current python backend

The C++ backend for graph_analytics is quite slow at around 17ms, due to the implementation using the C++ STL map, which is tree-based and quite heavy. I will look to open an issue for this.

> python -m chuck compare --old data/reports/snapshots/cpp.json --new data/reports/snapshots/check.json
A/B Comparison:
- old: cpp (a302e5f)
- new: check (cd19a00)
- same system: True
- backend preference: cpp -> python (same=False)
- old snapshot clean checkout: True
- new snapshot clean checkout: True
- average speedup: 4.625x

Per capability:
- compute_core: 0.000337s -> 0.01746s, speedup 0.0193x, delta 5081.01%, reliability 0.995025 -> 1.0 (+0.004975)
- data_encoding: 0.061322s -> 0.059368s, speedup 1.0329x, delta -3.19%, reliability 0.995025 -> 1.0 (+0.004975)
- graph_analytics: 0.017675s -> 0.000906s, speedup 19.5088x, delta -94.87%, reliability 0.995025 -> 1.0 (+0.004975)
- io_pipeline: 0.11063s -> 0.091958s, speedup 1.203x, delta -16.88%, reliability 0.995025 -> 1.0 (+0.004975)
- memory_index: 0.004616s -> 0.076909s, speedup 0.06x, delta 1566.14%, reliability 0.99258 -> 0.997594 (+0.005014)
- memory_tier: 1.203638s -> 0.054776s, speedup 21.9738x, delta -95.45%, reliability 0.995025 -> 1.0 (+0.004975)
- ordering_core: 0.029801s -> 0.193173s, speedup 0.1543x, delta 548.21%, reliability 0.995025 -> 1.0 (+0.004975)
- prime_analytics: 0.058144s -> 0.533942s, speedup 0.1089x, delta 818.31%, reliability 0.991144 -> 0.996119 (+0.004975)
- relational_fusion: 0.01391s -> 0.017643s, speedup 0.7884x, delta 26.84%, reliability 0.995025 -> 1.0 (+0.004975)
- retrieval_core: 0.002643s -> 0.001887s, speedup 1.4006x, delta -28.6%, reliability 0.870647 -> 0.875622 (+0.004975)
  • Snapshot verification
image

Working

This approach is optimized for graphs having low number of edges, but with high number of nodes, avoiding making a full adjacency matrix. np.bincount() has been used to implement this.

  • cols is a numpy array holding the nodes to which directed edges have been directed. rows is a numpy array holding the nodes from where the directed edges start. Combining them, a adjacency matrix can be created, where adj[rows][cols] == 1.
  • Other vectors, such as out_degrees holding degree of each node, rank vector holding rank, initialized with 1/N, N being number of nodes, trans_wt holding the weight a source node will direct to its children nodes, are initialized.
  • In 16 iterations, msgs that are transmitted from source nodes are calculated and msgs received are also calculated, and added with the base to a final rank.
  • Then, we return our final metadata along with checksum

Implementation details

The original backend codebase for graph_analytics, written in pure Python, was still very efficient at its task. I originally tried creating a adjacency matrix to create a transition matrix and using fast numpy matrix multiplication to achieve the task, as can be seen in this commit, but as it turns out, creating and initializing a 1000x1000 matrix is more expensive computationally than iterating over small number of edges(2-4 per node, so 2000-4000 iterations), even when using numpy to avoid writing for loops.

At first, I was also researching on how to improve the generate function in python. This was the orignal snippet i wrote that would return an adjacency matrix:

def generate(node_count: int, seed: int) -> np.ndarray: 
    rng = np.random.default_rng(seed) 
    adj = np.zeros((node_count, node_count), dtype=np.float128)
    nodes = np.arange(node_count)

    degrees = rng.integers(2, 5, size=node_count)
    weights = rng.random((node_count, node_count))
    frwd_conn = (nodes+1) % node_count
    weights[nodes, frwd_conn] = -1.0

    targets = np.argsort(weights, axis = 1)
    mask = nodes < degrees[:, np.newaxis]
    selected_targets = targets[mask]

    rows = np.repeat(nodes, degrees)
    adj[rows, selected_targets] = True
    return adj

This approach seemed faster to me due to reducing reliance on pure Python. However, I soon realized that this would break on the regression tests, and also I would have to change the C++ backend for it to have a chance to work.

Pull request checklist

  • updated or added relevant benchmark impact notes
  • regenerated baselines if output changed
  • passed regression tests
  • updated docs (README.md, docs/capabilities/* as needed)

This PR is now ready for review @Aaryan-Dadu

Summary by CodeRabbit

  • Refactor

    • PageRank algorithm now uses vectorized computation
    • Node identifiers in output now formatted as standardized strings (e.g., "n0001")
    • Numeric results explicitly formatted as Python floats
  • Chores

    • Added NumPy as a required dependency

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 11, 2026

Walkthrough

The pull request replaces the dictionary and loop-based PageRank algorithm implementation with a NumPy-vectorized approach. Key changes include using dense arrays for node/rank initialization, np.bincount for accumulation instead of per-node loops, and adjusting output formatting. NumPy is added as a project dependency.

Changes

Cohort / File(s) Summary
PageRank Algorithm Vectorization
chuck/tasks/graph_analytics/task.py
Replaced dictionary/loop-based PageRank with NumPy vectorization: dense array initialization, indexed multiplication, and np.bincount accumulation. Early empty-graph check changed from if not nodes to if not graph. Output formatting updated: top_node returns formatted string "n%04d", and checksum/top_score explicitly cast NumPy floats to Python floats before rounding. Function signature whitespace adjusted in type annotation.
Dependency Management
pyproject.toml
Added numpy as a project dependency under [project].dependencies.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

speedup

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fixes #6 - perf: optimize python backend for task graph-analytics' clearly and specifically describes the main change: a performance optimization of the graph-analytics task's Python backend. It directly aligns with the core changeset which replaces dictionary-based PageRank with NumPy vectorization.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@IronKommander IronKommander changed the title Perf/graph analytics perf: optimize python backend for task graph-analytics Apr 11, 2026
@IronKommander IronKommander marked this pull request as ready for review April 11, 2026 18:47
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
pyproject.toml (1)

11-13: Consider pinning a minimum NumPy version for reproducibility.

Leaving the dependency unpinned may lead to inconsistent behavior across environments if NumPy introduces breaking API changes in future major releases. A minimum version constraint (e.g., numpy>=1.20) would document the tested baseline while still allowing upgrades.

💡 Suggested improvement
 dependencies = [
-    "numpy"
+    "numpy>=1.20"
 ]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pyproject.toml` around lines 11 - 13, Update the dependencies entry for
"numpy" in the pyproject.toml dependencies list to specify a minimum tested
version (e.g., change the "numpy" entry to a version-constrained spec like
"numpy>=1.20") so that the dependencies array documents the tested baseline and
prevents accidental installs of incompatible major releases; update the
"dependencies" list item that currently contains the string "numpy" to include
the version constraint.
chuck/tasks/graph_analytics/task.py (1)

48-53: Inconsistent spacing before colon on line 52.

"checksum" : has an extra space before the colon, unlike the other keys in the dict. This doesn't affect functionality but is inconsistent with the surrounding code style.

✏️ Proposed fix
         "top_score": round6(float(rank[top_node])),
-        "checksum" : round6(checksum),
+        "checksum": round6(checksum),
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@chuck/tasks/graph_analytics/task.py` around lines 48 - 53, The return
dictionary in the function that builds the graph analytics result has
inconsistent spacing for the "checksum" key; change `"checksum" :
round6(checksum),` to `"checksum": round6(checksum),` so the colon spacing
matches the other entries (node_count, top_node, top_score) — locate the return
block that references N, top_node, rank, round6 and update the key spacing
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@chuck/tasks/graph_analytics/task.py`:
- Line 23: The function signature for solve has an extra space before the comma
in the type annotation; update the signature in the solve function declaration
(def solve) to remove the space so the parameter type reads dict[str,
list[str]], iterations: int = 16, damping: float = 0.85) — i.e., change
"dict[str, list[str]] ," to "dict[str, list[str]]," to correct the typing
syntax.
- Around line 41-42: There's a typo: the variable is assigned as "recieved" but
should be "received"; update the assignment where np.bincount is called
(currently "recieved = np.bincount(cols, weights=msgs, minlength=N)") to
"received = np.bincount(cols, weights=msgs, minlength=N)" and update any
subsequent references to use "received" (e.g., the next line "rank = recieved +
base" should become "rank = received + base") so all usages of recieved are
replaced consistently.
- Around line 33-37: out_degree may contain zeros for dangling nodes causing
trans_wt = damping / out_degree to produce inf and drop their rank; change the
logic to (1) compute trans_wt safely by initializing trans_wt =
np.zeros_like(out_degree, dtype=np.float64) and only assigning
trans_wt[out_degree>0] = damping / out_degree[out_degree>0], and (2) account for
dangling nodes each iteration by computing dangling_sum = damping *
rank[out_degree==0].sum() and adding dangling_sum / N to every node's next-rank
(or include it in base) so dangling rank is redistributed uniformly; update uses
of trans_wt, rank, base, damping accordingly.

---

Nitpick comments:
In `@chuck/tasks/graph_analytics/task.py`:
- Around line 48-53: The return dictionary in the function that builds the graph
analytics result has inconsistent spacing for the "checksum" key; change
`"checksum" : round6(checksum),` to `"checksum": round6(checksum),` so the colon
spacing matches the other entries (node_count, top_node, top_score) — locate the
return block that references N, top_node, rank, round6 and update the key
spacing accordingly.

In `@pyproject.toml`:
- Around line 11-13: Update the dependencies entry for "numpy" in the
pyproject.toml dependencies list to specify a minimum tested version (e.g.,
change the "numpy" entry to a version-constrained spec like "numpy>=1.20") so
that the dependencies array documents the tested baseline and prevents
accidental installs of incompatible major releases; update the "dependencies"
list item that currently contains the string "numpy" to include the version
constraint.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ee38d1d3-91cc-417c-b4fa-edd9a29be88b

📥 Commits

Reviewing files that changed from the base of the PR and between a302e5f and 1041f78.

📒 Files selected for processing (2)
  • chuck/tasks/graph_analytics/task.py
  • pyproject.toml

def solve(graph: dict[str, list[str]], iterations: int = 16, damping: float = 0.85) -> dict[str, Any]:
nodes = sorted(graph)
if not nodes:
def solve(graph: dict[str, list[str]] , iterations: int = 16, damping: float = 0.85) -> dict[str, Any]:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove extraneous space before comma.

There's an extra space in the function signature: dict[str, list[str]] , should be dict[str, list[str]],.

✏️ Proposed fix
-def solve(graph: dict[str, list[str]] , iterations: int = 16, damping: float = 0.85) -> dict[str, Any]:
+def solve(graph: dict[str, list[str]], iterations: int = 16, damping: float = 0.85) -> dict[str, Any]:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def solve(graph: dict[str, list[str]] , iterations: int = 16, damping: float = 0.85) -> dict[str, Any]:
def solve(graph: dict[str, list[str]], iterations: int = 16, damping: float = 0.85) -> dict[str, Any]:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@chuck/tasks/graph_analytics/task.py` at line 23, The function signature for
solve has an extra space before the comma in the type annotation; update the
signature in the solve function declaration (def solve) to remove the space so
the parameter type reads dict[str, list[str]], iterations: int = 16, damping:
float = 0.85) — i.e., change "dict[str, list[str]] ," to "dict[str, list[str]],"
to correct the typing syntax.

Comment on lines +33 to +37
out_degree = np.bincount(rows, minlength=N).astype(np.float64)

rank = np.full((N,), 1.0/N, dtype=np.float64)
base = (1.0 - damping) / N
trans_wt = damping / out_degree
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential division by zero for graphs with sink nodes (dangling nodes).

If a node has no outgoing edges, out_degree[idx] will be 0, causing trans_wt to contain inf at that index. While this won't crash (since dangling nodes don't appear in rows), the node's rank is silently lost rather than being redistributed—standard PageRank typically handles dangling nodes by redistributing their rank to all nodes.

This won't affect the current generator (which always creates 2–4 edges per node), but arbitrary input graphs could yield incorrect results without any warning.

🛡️ Possible defensive fix
     out_degree = np.bincount(rows, minlength=N).astype(np.float64)
+    # Handle dangling nodes: set out_degree to 1 to avoid inf; their rank contribution is zero anyway
+    out_degree[out_degree == 0] = 1.0

     rank = np.full((N,), 1.0/N, dtype=np.float64)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
out_degree = np.bincount(rows, minlength=N).astype(np.float64)
rank = np.full((N,), 1.0/N, dtype=np.float64)
base = (1.0 - damping) / N
trans_wt = damping / out_degree
out_degree = np.bincount(rows, minlength=N).astype(np.float64)
# Handle dangling nodes: set out_degree to 1 to avoid inf; their rank contribution is zero anyway
out_degree[out_degree == 0] = 1.0
rank = np.full((N,), 1.0/N, dtype=np.float64)
base = (1.0 - damping) / N
trans_wt = damping / out_degree
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@chuck/tasks/graph_analytics/task.py` around lines 33 - 37, out_degree may
contain zeros for dangling nodes causing trans_wt = damping / out_degree to
produce inf and drop their rank; change the logic to (1) compute trans_wt safely
by initializing trans_wt = np.zeros_like(out_degree, dtype=np.float64) and only
assigning trans_wt[out_degree>0] = damping / out_degree[out_degree>0], and (2)
account for dangling nodes each iteration by computing dangling_sum = damping *
rank[out_degree==0].sum() and adding dangling_sum / N to every node's next-rank
(or include it in base) so dangling rank is redistributed uniformly; update uses
of trans_wt, rank, base, damping accordingly.

Comment on lines +41 to +42
recieved = np.bincount(cols, weights=msgs, minlength=N)
rank = recieved + base
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Typo: recievedreceived.

✏️ Proposed fix
-        recieved = np.bincount(cols, weights=msgs, minlength=N)
-        rank = recieved + base
+        received = np.bincount(cols, weights=msgs, minlength=N)
+        rank = received + base
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
recieved = np.bincount(cols, weights=msgs, minlength=N)
rank = recieved + base
received = np.bincount(cols, weights=msgs, minlength=N)
rank = received + base
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@chuck/tasks/graph_analytics/task.py` around lines 41 - 42, There's a typo:
the variable is assigned as "recieved" but should be "received"; update the
assignment where np.bincount is called (currently "recieved = np.bincount(cols,
weights=msgs, minlength=N)") to "received = np.bincount(cols, weights=msgs,
minlength=N)" and update any subsequent references to use "received" (e.g., the
next line "rank = recieved + base" should become "rank = received + base") so
all usages of recieved are replaced consistently.

@Aaryan-Dadu
Copy link
Copy Markdown
Member

The PR is great! And a speedup of 7x is really good, but can we make it great!

Could you look into some other approaches that could outperform these implementation too?
I kept the title of the issue (Replace PageRank dict-of-dicts iteration in graph_analytics) more general for this exact reason. We want to replace it with overall much better implementation of graph_analytics.

@Aaryan-Dadu
Copy link
Copy Markdown
Member

You can refer to https://github.com/iiitl/chuck#:~:text=References%20for%20probabilistic%20direction as a starting point. I know you wouldn't be able to read them in a while but just take a gist of it.
Your originality would be praised more so please don't rely on AI for ideation too.

@IronKommander
Copy link
Copy Markdown
Author

Thanks for the documentation, I will certainly look into it. can you please clarify a doubt of mine:

  1. Right now, graph_analytics runs in under 1ms. To make time deltas visible, is increasing the number of nodes and edges for testing a good idea? If so, optimization would likely occur for two types of graphs: graphs with low amount of nodes and high amount of edges, or vice versa?

@Aaryan-Dadu
Copy link
Copy Markdown
Member

Thanks for the documentation, I will certainly look into it. can you please clarify a doubt of mine:

  1. Right now, graph_analytics runs in under 1ms. To make time deltas visible, is increasing the number of nodes and edges for testing a good idea? If so, optimization would likely occur for two types of graphs: graphs with low amount of nodes and high amount of edges, or vice versa?

Yes, but please don't commit them just tweak them for testing locally, and yeah I would also favor having different approach for lower number of node and higher, similar in spirit to how introsort works.

@IronKommander
Copy link
Copy Markdown
Author

@Aaryan-Dadu, so I went through all the documentation you provided me with, key things that I noted down that maybe could have been of help to me in this task were bloom filters(for checking whether two elements were connected to transmit rank between them?) or the Monte Carlo algo to check whether the product of two matrices was equal to the third one. (this one was a far reaching thought)

Initially, I was implementing a standard power iteration on a Markov matrix for calculating pagerank. However, if #41 gets merged, the number of nodes will increase drastically and it would not make sense to create such a dense adjacency matrix, with many zeroes. As such, I have been reading on the probabilistic implementation(Monte carlo), of simply letting surfers walk randomly through the graph, and deriving rank from this. This would save on memory and save time initializing the dense matrix as well. What are your thoughts on this?

@Aaryan-Dadu
Copy link
Copy Markdown
Member

@Aaryan-Dadu, so I went through all the documentation you provided me with, key things that I noted down that maybe could have been of help to me in this task were bloom filters(for checking whether two elements were connected to transmit rank between them?) or the Monte Carlo algo to check whether the product of two matrices was equal to the third one. (this one was a far reaching thought)

Initially, I was implementing a standard power iteration on a Markov matrix for calculating pagerank. However, if #41 gets merged, the number of nodes will increase drastically and it would not make sense to create such a dense adjacency matrix, with many zeroes. As such, I have been reading on the probabilistic implementation(Monte carlo), of simply letting surfers walk randomly through the graph, and deriving rank from this. This would save on memory and save time initializing the dense matrix as well. What are your thoughts on this?

The issue is marked as hard because it requires exploration and measurements, personally I think that the monte carlo implementation should outperform but we would have to measure and try before finalising the way. And I am unaware about standard power iteration on a Markov matrix so would have to look about it first.
And please don't consider #41, I merge all the PRs in dev branch because I will be giving a detailed look at them later so consider your changes to be completely independent from those.

@Aaryan-Dadu
Copy link
Copy Markdown
Member

Please add **Fixes #6 ** in your PR description to link the issue to the PR.

@IronKommander IronKommander changed the title perf: optimize python backend for task graph-analytics **Fixes #6** - perf: optimize python backend for task graph-analytics Apr 12, 2026
@mini-walkerx
Copy link
Copy Markdown

mini-walkerx Bot commented Apr 12, 2026

👋 @IronKommander, issue #6 is currently assigned to @Phantom0299. If their work doesn't close the issue, you'll get a chance.

@IronKommander IronKommander changed the title **Fixes #6** - perf: optimize python backend for task graph-analytics Fixes #6 - perf: optimize python backend for task graph-analytics Apr 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace PageRank dict-of-dicts iteration in graph_analytics

2 participants