Skip to content
Open
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
45 changes: 27 additions & 18 deletions chuck/tasks/graph_analytics/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import Any

from ...common import TaskSpec, round6
import numpy as np


def generate(node_count: int, seed: int) -> dict[str, list[str]]:
Expand All @@ -19,28 +20,36 @@ def generate(node_count: int, seed: int) -> dict[str, list[str]]:
return graph


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.

if not graph:
return {"node_count": 0, "top_node": "", "top_score": 0.0, "checksum": 0.0}

rank = {node: 1.0 / len(nodes) for node in nodes}
outgoing = {node: graph[node] if graph[node] else nodes for node in nodes}
base = (1.0 - damping) / len(nodes)
nodes = sorted(graph)
N = len(nodes)
idx_map = {node: i for i, node in enumerate(nodes)}

rows = np.array([idx_map[src] for src, targets in graph.items() for _ in targets], dtype=np.int64)
cols = np.array([idx_map[tgt] for _, targets in graph.items() for tgt in targets], dtype=np.int64)
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
Comment on lines +33 to +37
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.


for _ in range(iterations):
new_rank = {node: base for node in nodes}
for node in nodes:
share = rank[node] / len(outgoing[node])
for target in outgoing[node]:
new_rank[target] += damping * share
rank = new_rank
top_node = max(nodes, key=lambda node: (rank[node], node))
checksum = sum((index + 1) * rank[node] for index, node in enumerate(nodes))
msgs = (rank * trans_wt)[rows]
recieved = np.bincount(cols, weights=msgs, minlength=N)
rank = recieved + base
Comment on lines +41 to +42
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.


top_node = int(np.argmax(rank))
mult = np.arange(1, N+1)
checksum = float(np.dot(mult, rank))

return {
"node_count": len(nodes),
"top_node": top_node,
"top_score": round6(rank[top_node]),
"checksum": round6(checksum),
"node_count": N,
"top_node": f"n{top_node:04d}",
"top_score": round6(float(rank[top_node])),
"checksum" : round6(checksum),
}


Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ version = "0.1.0"
description = "Solver toolkit for 10 computational tasks with optional native C++ backends"
requires-python = ">=3.10"
readme = "README.md"
dependencies = [
"numpy"
]

[tool.setuptools]

Expand Down
Loading