-
Notifications
You must be signed in to change notification settings - Fork 1
core: Graph.modularityScore — 11th graduation (Newman modularity) #322
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -170,3 +170,104 @@ module Graph = | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if s = n then acc <- acc + entry.Weight | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if t = n then acc <- acc + entry.Weight | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| acc | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// **Modularity score (Q) for a node partition.** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Newman's modularity measures how well a partition of | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// nodes into groups captures community structure: high | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// values (> 0.3-0.4) indicate dense within-group edges and | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// sparse across-group edges, i.e. a strong community | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// structure; values near 0 indicate random-looking edge | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// distribution. Negative values indicate within-group | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// sparsity BELOW the random baseline (rare). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Formula: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Q = (1 / 2m) * sum over i,j of | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// [ A[i,j] - (k_i * k_j) / (2m) ] * delta(c_i, c_j) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// where: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// - `A[i,j]` is the symmetrized edge weight | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// - `k_i = sum_j A[i,j]` (weighted degree of node i) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// - `m = (1/2) * sum_{i,j} A[i,j]` (total edge weight; /2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// because each undirected edge counts twice in the sum) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// - `c_i` is the community label of node i | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// - `delta(c_i, c_j) = 1` iff `c_i = c_j` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Returns `Some Q` when modularity is defined; `None` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// when the graph is empty or every node is unassigned. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Nodes missing from `partition` are treated as singleton | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// groups (each in a unique trivial community). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// **Cartel-detection use:** after injecting a cartel | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// clique into a baseline, running a community detector | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// (e.g. Louvain — future graduation) on the attacked | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// graph produces a partition; the resulting modularity | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// jumps relative to the baseline's partition. This | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// primitive computes Q GIVEN a partition; the detector | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// produces the partition. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// **MVP note:** this function computes Q for a CALLER- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// supplied partition. A full-fidelity detection pipeline | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// needs (Louvain | Girvan-Newman | spectral-clustering) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// to produce the partition, plus a null-baseline to | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// calibrate the modularity threshold. Those are separate | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// graduations. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Provenance: 11th ferry §2 (community modularity) + 13th | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// ferry metrics + 14th ferry alert row "Modularity Q jump | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// > 0.1 or Q > 0.4". Implementation Otto (11th graduation). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// > 0.1 or Q > 0.4". Implementation Otto (11th graduation). | |
| /// > 0.1 or Q > 0.4". Implementation tracked under the 11th | |
| /// graduation artifacts. |
Copilot
AI
Apr 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
twoM is used as the "2m" normalization constant. Because Graph supports signed weights (retractions), it’s possible for twoM to be negative; the current guard only checks twoM = 0.0, which can yield a negative normalization and surprising Q values. Consider defining modularity only for nonnegative total weight (e.g., return None when twoM <= 0.0 and/or when any symmetrized edge weight is negative), or document the intended signed-weight semantics explicitly.
| if twoM = 0.0 then None | |
| if twoM <= 0.0 then None |
Copilot
AI
Apr 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
community i does a Map.tryFind for every (i,j) pair inside the O(n²) loop. Even for moderate n, this adds a lot of repeated work (and allocations/lookup overhead). Precompute an int[]/array of community labels for all nodes once before the nested loop, then compare array entries inside the loop.
| let community i = | |
| let node = nodeList.[i] | |
| match Map.tryFind node partition with | |
| | Some c -> c | |
| | None -> -(i + 1) // unique negative = singleton | |
| let mutable q = 0.0 | |
| for i in 0 .. n - 1 do | |
| for j in 0 .. n - 1 do | |
| if community i = community j then | |
| let communities = | |
| Array.init n (fun i -> | |
| let node = nodeList.[i] | |
| match Map.tryFind node partition with | |
| | Some c -> c | |
| | None -> -(i + 1)) // unique negative = singleton | |
| let mutable q = 0.0 | |
| for i in 0 .. n - 1 do | |
| for j in 0 .. n - 1 do | |
| if communities.[i] = communities.[j] then |
Copilot
AI
Apr 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation builds dense n×n adjacency/symmetry matrices and then performs nested i/j loops, making runtime and memory O(n²) regardless of edge count. Since Graph is ZSet-backed and likely sparse, consider a sparse computation (iterate only existing edges and use per-community degree/weight aggregates) to keep this usable for larger graphs.
| let idx = | |
| nodeList | |
| |> List.mapi (fun i node -> node, i) | |
| |> Map.ofList | |
| // Symmetrized adjacency A_sym[i,j] = (A[i,j] + A[j,i]) / 2 | |
| let adj = Array2D.create n n 0.0 | |
| let span = g.Edges.AsSpan() | |
| for k in 0 .. span.Length - 1 do | |
| let entry = span.[k] | |
| let (s, t) = entry.Key | |
| let i = idx.[s] | |
| let j = idx.[t] | |
| adj.[i, j] <- adj.[i, j] + double entry.Weight | |
| let sym = Array2D.create n n 0.0 | |
| for i in 0 .. n - 1 do | |
| for j in 0 .. n - 1 do | |
| sym.[i, j] <- (adj.[i, j] + adj.[j, i]) / 2.0 | |
| // Weighted degree k_i = sum_j A_sym[i, j] | |
| let k = Array.create n 0.0 | |
| for i in 0 .. n - 1 do | |
| let mutable acc = 0.0 | |
| for j in 0 .. n - 1 do | |
| acc <- acc + sym.[i, j] | |
| k.[i] <- acc | |
| // 2m = sum of all degrees (undirected) | |
| let twoM = | |
| let mutable acc = 0.0 | |
| for i in 0 .. n - 1 do | |
| acc <- acc + k.[i] | |
| acc | |
| if twoM = 0.0 then None | |
| else | |
| // Community label per node: partition lookup, or | |
| // node-index-based-singleton when missing | |
| let community i = | |
| let node = nodeList.[i] | |
| match Map.tryFind node partition with | |
| | Some c -> c | |
| | None -> -(i + 1) // unique negative = singleton | |
| let mutable q = 0.0 | |
| for i in 0 .. n - 1 do | |
| for j in 0 .. n - 1 do | |
| if community i = community j then | |
| let expected = (k.[i] * k.[j]) / twoM | |
| q <- q + (sym.[i, j] - expected) | |
| Some (q / twoM) | |
| let communityByNode = | |
| nodeList | |
| |> List.mapi (fun i node -> | |
| let community = | |
| match Map.tryFind node partition with | |
| | Some c -> c | |
| | None -> -(i + 1) // unique negative = singleton | |
| node, community) | |
| |> Map.ofList | |
| let addToDictionary | |
| (dict: System.Collections.Generic.Dictionary<'K, double>) | |
| key | |
| delta = | |
| match dict.TryGetValue key with | |
| | true, value -> dict.[key] <- value + delta | |
| | false, _ -> dict.[key] <- delta | |
| let degreeByNode = | |
| System.Collections.Generic.Dictionary<'N, double>() | |
| let internalWeightByCommunity = | |
| System.Collections.Generic.Dictionary<int, double>() | |
| let degreeByCommunity = | |
| System.Collections.Generic.Dictionary<int, double>() | |
| let span = g.Edges.AsSpan() | |
| for k in 0 .. span.Length - 1 do | |
| let entry = span.[k] | |
| let (s, t) = entry.Key | |
| let w = double entry.Weight | |
| // Weighted degree k_i = sum_j A_sym[i, j]. | |
| // Under A_sym = (A + A^T) / 2, each directed edge | |
| // contributes w/2 to its source degree and w/2 to | |
| // its target degree. Self-loops still contribute w. | |
| addToDictionary degreeByNode s (w / 2.0) | |
| addToDictionary degreeByNode t (w / 2.0) | |
| let sCommunity = communityByNode.[s] | |
| let tCommunity = communityByNode.[t] | |
| // Sum of A_sym[i, j] over node pairs within a | |
| // community equals the total directed edge weight | |
| // whose endpoints both lie in that community. | |
| if sCommunity = tCommunity then | |
| addToDictionary internalWeightByCommunity sCommunity w | |
| // 2m = sum of all weighted degrees in the symmetrized | |
| // undirected view. | |
| let twoM = | |
| let mutable acc = 0.0 | |
| for kvp in degreeByNode do | |
| acc <- acc + kvp.Value | |
| acc | |
| if twoM = 0.0 then None | |
| else | |
| for kvp in degreeByNode do | |
| let community = communityByNode.[kvp.Key] | |
| addToDictionary degreeByCommunity community kvp.Value | |
| let mutable q = 0.0 | |
| for kvp in degreeByCommunity do | |
| let community = kvp.Key | |
| let communityDegree = kvp.Value | |
| let internalWeight = | |
| match internalWeightByCommunity.TryGetValue community with | |
| | true, value -> value | |
| | false, _ -> 0.0 | |
| let degreeFraction = communityDegree / twoM | |
| q <- q + (internalWeight / twoM) - (degreeFraction * degreeFraction) | |
| Some q |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Docstring says the function returns
Nonewhen "every node is unassigned", but the implementation treats missing nodes as singleton communities and will still returnSomefor any graph with nonzero total weight (even ifpartitionis empty). Please either update the doc comment to match the implemented behavior, or change the behavior to actually returnNonewhenpartitionassigns no nodes (and document that choice).