Eyob-Sol's picture
Upload 24 files
dbc8c36 verified
Raw
History Blame Contribute Delete
3.08 kB
"""
Metrics and basic statistics for Collatz graphs.
"""
from __future__ import annotations
from typing import Dict, Any, Iterable
import pandas as pd
def _to_int_nodes(nodes: Iterable[Any]) -> list[int]:
"""
Safely convert an iterable of node labels to a list of integers.
Non-convertible labels are skipped.
"""
int_nodes: list[int] = []
for n in nodes:
try:
int_nodes.append(int(n))
except Exception:
continue
return int_nodes
def compute_basic_graph_stats(df_edges: pd.DataFrame) -> Dict[str, Any]:
"""
Compute basic statistics for a Collatz graph represented
as a DataFrame of edges with columns ["Source", "Target"].
Returns a dictionary with:
- num_nodes: total number of distinct nodes
- num_edges: total number of edges
- num_odd: number of odd-valued nodes
- num_even: number of even-valued nodes
- min_node: minimum node value (if any)
- max_node: maximum node value (if any)
- num_cycles: number of cycles (here always 1: the trivial 1–2–4–1 cycle)
"""
if not {"Source", "Target"}.issubset(df_edges.columns):
raise ValueError("df_edges must contain 'Source' and 'Target' columns.")
raw_nodes = set(df_edges["Source"]).union(set(df_edges["Target"]))
nodes = _to_int_nodes(raw_nodes)
num_nodes = len(nodes)
num_edges = len(df_edges)
num_odd = sum(1 for n in nodes if n % 2 == 1)
num_even = sum(1 for n in nodes if n % 2 == 0)
min_node = min(nodes) if nodes else None
max_node = max(nodes) if nodes else None
# By construction, your graphs contain only the trivial 1–2–4–1 cycle.
num_cycles = 1 if num_nodes > 0 else 0
return {
"num_nodes": num_nodes,
"num_edges": num_edges,
"num_odd": num_odd,
"num_even": num_even,
"min_node": min_node,
"max_node": max_node,
"num_cycles": num_cycles,
}
def format_stats_markdown(stats: Dict[str, Any]) -> str:
"""
Format the statistics dictionary returned by compute_basic_graph_stats
into a human-readable Markdown string for display in the UI.
"""
if not stats:
return "_No statistics available._"
num_nodes = stats.get("num_nodes", 0)
num_edges = stats.get("num_edges", 0)
num_odd = stats.get("num_odd", 0)
num_even = stats.get("num_even", 0)
min_node = stats.get("min_node", None)
max_node = stats.get("max_node", None)
num_cycles = stats.get("num_cycles", 0)
lines = [
"### Graph Statistics",
"",
f"- **Nodes:** {num_nodes}",
f"- **Edges:** {num_edges}",
f"- **Odd nodes:** {num_odd}",
f"- **Even nodes:** {num_even}",
]
if min_node is not None and max_node is not None:
lines.append(f"- **Node value range:** {min_node} to {max_node}")
# Trivial cycle information
if num_cycles:
lines.append(f"- **Cycles:** {num_cycles} (trivial cycle 1 β†’ 2 β†’ 4 β†’ 1)")
return "\n".join(lines)