All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- Internal Priority Queue Implementation (
yog/internal/priority_queue,yog/internal/pairing_heap): Removed dependency ongleamy_structuresby implementing our own pairing heap and priority queue with the same contract. This reduces external dependencies while maintaining identical functionality.
- JSON Rendering Module (
yog/render/json): Removed in favor ofyog_iopackage for JSON serialization. - gleamy_structures dependency: No longer required, replaced by internal implementations.
- Deprecated DAG Modules (
yog/dag/algorithms,yog/dag/models): Removed deprecated plural module names. Use the singular versions (yog/dag/algorithm,yog/dag/model) instead. These were deprecated in v5.1.0.
-
Bidirectional Search (
yog/pathfinding/bidirectional): Meet-in-the-middle pathfinding algorithms for dramatic speedup:shortest_path_unweighted/3- Bidirectional BFS for unweighted graphs with O(b^(d/2)) complexity (up to 500× faster than standard BFS!)shortest_path/6- Bidirectional Dijkstra for weighted graphs (approximately 2× faster than standard Dijkstra)- Works with both directed and undirected graphs, leveraging yog's efficient
in_edgesstructure for backward search - Proper termination conditions ensuring optimality
- Comprehensive path reconstruction from meeting point
- Convenience wrappers:
shortest_path_int/3andshortest_path_float/3 - Performance Example: With branching factor 10 and depth 6: standard BFS explores 1,000,000 nodes vs bidirectional BFS explores only 2,000 nodes
-
Graph Operations Module (
yog/operation): New module implementing set-theoretic graph operations following NetworkX's "Graph as a Set" philosophy:- Set-Theoretic Operations:
union/2,intersection/2,difference/2,symmetric_difference/2for combining and comparing graphs - Composition & Joins:
disjoint_union/2(safe combination with auto re-indexing),cartesian_product/2(for grids and hypercubes),compose/2(merge overlapping graphs) - Graph Powers:
power/2creates the k-th power of a graph (connects nodes within distance k), useful for reachability analysis - Structural Comparison:
is_subgraph/2for subset validation,is_isomorphic/2for checking structural identity (with quick checks for node/edge counts and degree sequences) - All operations preserve graph structure and handle edge data appropriately
- See module documentation for algorithm complexities and use cases
- Set-Theoretic Operations:
-
Connected Components Algorithms (
yog/connectivity): New functions for finding connected components in undirected and weakly connected components in directed graphs:connected_components/1- Find connected components in undirected graphs using DFSweakly_connected_components/1- Find weakly connected components in directed graphs (treating edges as undirected)- Both algorithms run in O(V + E) time complexity
- See module documentation for comparison with existing SCC algorithms
-
Enhanced DOT Rendering (
yog/render/dot): Major improvements to Graphviz export functionality:-
Generic Data Types:
DotOptionsis now generic over node datanand edge datae, allowing it to work with any graph types without manual conversion- Use
default_dot_options()forStringedge data (backward compatible) - Use
default_dot_options_with_edge_formatter(fn(e) -> String)for custom edge types (e.g.,Int,Float, custom records) - Use
default_dot_options_with()for full control over both node and edge labeling
- Use
-
Per-Element Styling: New callback functions for fine-grained visual control:
node_attributes: fn(NodeId, n) -> List(#(String, String))- Set custom DOT attributes per node (e.g.,[#("fillcolor", "green"), #("shape", "diamond")])edge_attributes: fn(NodeId, NodeId, e) -> List(#(String, String))- Set custom DOT attributes per edge (e.g.,[#("color", "red"), #("penwidth", "2")])- Custom attributes override highlighting and default styles
-
Subgraphs and Clusters: New
Subgraphtype for visual node grouping:- Create visual clusters with
Subgraph(name: "cluster_0", label: Some("Group A"), node_ids: [1, 2, 3], ...) - Supports all Graphviz subgraph attributes:
style,fillcolor,color - Use
cluster_prefix in name for bounded rectangle visualization
- Create visual clusters with
-
Improved attribute formatting with consistent
key="value"syntax -
Example:
let options = DotOptions( ..dot.default_dot_options_with_edge_formatter(int.to_string), node_attributes: fn(id, _) { case id == start_node { True -> [#("fillcolor", "green")] False -> [] } }, subgraphs: Some([Subgraph(name: "cluster_a", label: Some("Module A"), node_ids: [1, 2])]), ) let dot_string = dot.to_dot(my_graph, options)
-
- Experimental Module Notices: Added experimental status warnings to
yog/multi/*(multigraphs) andyog/dag/*(DAG-specific operations) modules:- These modules are functional with minimal, working implementations
- May not be fully optimized for performance
- Additional features and performance enhancements planned
- API may be subject to change in future versions
- Notice added to all module files and documented in README under "
⚠️ Experimental Features" section
-
DAG Module Naming Convention: Renamed plural files to singular for consistency with Gleam conventions:
yog/dag/algorithms→yog/dag/algorithmyog/dag/models→yog/dag/model- Follows the same pluralization cleanup done in v5.0.0 for
property/andgenerator/modules
-
Consistent Parameter Labels: Added descriptive labels to all semiring and algorithm parameters across pathfinding, centrality, health, and community detection modules for improved API consistency and self-documentation:
- Pathfinding:
bellman_ford,floyd_warshall,a_star(including helper functions likerelaxation_passes,has_negative_cycle) - Centrality:
closeness,harmonic_centrality,betweennessand all convenience wrappers - Health:
diameter,radius,eccentricity,average_path_length - Community Detection:
girvan_newman(includingedge_betweennessand helper functions) - All functions now use consistent labels:
with_zero,with_add,with_compare,with_to_float,with_heuristic,with - Backward compatible: Both labeled and unlabeled calls are supported (e.g.,
dijkstra.shortest_path(graph, 1, 5, 0, int.add, int.compare)anddijkstra.shortest_path(in: graph, from: 1, to: 5, with_zero: 0, with_add: int.add, with_compare: int.compare)both work) - Follows the pattern established by Dijkstra's algorithm for a more uniform and intuitive API
- Pathfinding:
-
Clique Detection Empty Graph Bug (
yog/property/clique): Fixedall_maximal_cliquesto return an empty list[]for empty graphs instead of a list containing one empty set[set.new()]:- Problem:
all_maximal_cliqueson an empty graph would return[set.new()], inconsistent withmax_clique(returns empty set) andk_cliques(returns empty list) - Root Cause: The Bron-Kerbosch algorithm would report the empty set as a maximal clique when all candidate sets (R, P, X) were empty
- Solution: Added check to only report non-empty cliques as maximal cliques in
bron_kerbosch_all - Impact: Empty graphs now correctly return no maximal cliques, consistent with other clique functions
- Test Added:
all_maximal_cliques_empty_graph_testvalidates the fix
- Problem:
-
Eigenvector Centrality Oscillation Bug (
yog/centrality): Fixed critical bug where eigenvector centrality would oscillate and never converge for symmetric graphs:- Problem: Star graphs and other symmetric structures caused the power iteration algorithm to oscillate between two states indefinitely (e.g., [0.816, 0.408, 0.408] ↔ [0.577, 0.577, 0.577])
- Root Cause: Uniform initialization [1/√n, 1/√n, ...] contained equal components of eigenspaces with eigenvalues of equal magnitude but opposite signs (e.g., +√2 and -√2), causing 2-cycle oscillation
- Solution:
- Added small node-ID-based perturbation to initial vector to break symmetry:
1.0 + (id / 1000.0) - Implemented 2-cycle oscillation detection by tracking state from 2 iterations ago
- When oscillation is detected, returns the normalized average of the two oscillating states, which approximates the true principal eigenvector
- Added small node-ID-based perturbation to initial vector to break symmetry:
- Impact: 2-leaf star graphs now correctly return center ≈ 0.707, leaves ≈ 0.5 (ratio √2 ≈ 1.414) instead of all nodes ≈ 0.577
- Tests Added:
eigenvector_2leaf_star_exact_test- Validates exact eigenvector values with mathematical precisioneigenvector_triangle_exact_test- Tests complete triangle (K3) for equal centralityeigenvector_linear_chain_test- Validates 5-node chain with symmetry propertieseigenvector_barbell_test- Tests two triangles connected by bridge, validates bridge nodes have higher centrality
- All existing tests continue to pass with improved numerical accuracy
-
Module Naming Convention: Renamed plural directories to singular for consistency with Gleam conventions:
yog/properties/*→yog/property/*yog/generators/*→yog/generator/*
-
Module Rename:
yog/io/*→yog/render/*- The
iomodule name was misleading as it only contained rendering/output functionality - All rendering modules now live under
yog/render/:yog/io/dot→yog/render/dotyog/io/mermaid→yog/render/mermaidyog/io/json→yog/render/jsonyog/io/ascii→yog/render/ascii
- Update your imports:
import yog/io/dot→import yog/render/dot
- The
-
Typed Rendering Configuration:
yog/render/dot:DotOptionsnow uses robust Algebraic Data Types (ADTs) likeLayout,RankDir,NodeShape, andStyleinstead of strings. Numeric values now useFloatfor precision (e.g.,nodesep,penwidth).yog/render/mermaid:MermaidOptionsnow usesDirection,NodeShape, andCssLengthADTs, providing a type-safe way to configure diagram appearance.
-
Edge Addition API Changes:
add_edge()andadd_edge_with_combine()now returnResult(Graph, String)instead ofGraphto prevent "ghost nodes":add_edge(graph, from: 1, to: 2, with: 10)now returnsError("Node 1 does not exist")if nodes don't existadd_edge_with_combine(graph, from: 1, to: 2, with: 5, using: int.add)also returnsResult- Use
let assert Ok(graph) = add_edge(...)when nodes are guaranteed to exist - Use
result.try(add_edge(...))for chaining operations - For auto-creation of missing nodes, use the renamed functions:
add_edge_ensured()→add_edge_ensure()add_edge_ensured_with()→add_edge_with()
- Rationale: Previously, these functions could create "ghost nodes" that exist in edge dictionaries but not in the nodes map, causing unexpected behavior in algorithms like centrality calculations and topological sorts. Check this PR for more info.
-
Bulk Edge Addition Functions: New convenience functions for adding multiple edges in a single operation:
add_edges(graph, edges: List(#(NodeId, NodeId, e)))- Add multiple weighted edgesadd_simple_edges(graph, edges: List(#(NodeId, NodeId)))- Add multiple edges with weight 1add_unweighted_edges(graph, edges: List(#(NodeId, NodeId)))- Add multiple edges with weight Nil- These functions fail fast on the first missing node, reducing Result-handling boilerplate compared to chaining individual
add_edgecalls
-
Toroidal Grid Builder (
yog/builder/toroidal): Support for graphs with wrapping (torus) topology. Includes specialized toroidal distance heuristics:toroidal_manhattan_distance,toroidal_chebyshev_distance, andtoroidal_octile_distance. -
ASCII Art Rendering (
yog/render/ascii): New module for rendering grids and mazes as ASCII text, ideal for terminal output and debugging. -
Network Health Metrics (
yog/health): New module for measuring graph structural quality:diameter/5- Maximum distance (worst-case reachability)radius/5- Minimum eccentricity (best central point)eccentricity/6- Maximum distance from a specific nodeassortativity/1- Degree correlation (homophily vs heterophily)average_path_length/6- Typical separation between nodes
-
Grid Distance Heuristics: Added
chebyshev_distance(for 8-way movement) andoctile_distance(for realistic diagonal costs) toyog/builder/grid. -
F# Comparison: Added
GLEAM_FSHARP_COMPARISON.mddocumenting feature parity, API differences, and migration guidance between the Gleam and F# implementations of Yog. -
Community Detection Suite (
yog/community/*): Major new module implementing 10 community detection algorithms (~3,400 lines). Community detection identifies densely connected groups of nodes in graphs - essential for social network analysis, biological networks, recommendation systems, and infrastructure analysis.Core Module (
yog/community):Communitiestype: Maps nodes to community IDs with countDendrogramtype: Hierarchical community structure with merge history- Utilities:
communities_to_dict/1,largest_community/1,community_sizes/1,merge_communities/3
Algorithms (in
yog/community/):Algorithm Module Best For Complexity Louvain louvainLarge graphs, speed/quality balance O(E log V) Leiden leidenQuality guarantee, well-connected O(E log V) Label Propagation label_propagationVery large graphs, speed O(E × iters) Girvan-Newman girvan_newmanHierarchical structure O(E² × V) Walktrap walktrapRandom walk-based communities O(V² log V) Infomap infomapFlow-based, information-theoretic O(E × iters) Clique Percolation clique_percolationOverlapping communities O(3^(V/3)) Local Community local_communityMassive graphs, seed expansion O(S × E_S) Fluid Communities fluid_communitiesExact kpartitionsO(E × iters) Random Walk random_walkPrimitives for custom algorithms O(steps × k) Metrics Module (
yog/community/metrics):modularity/2- Newman's modularity Q for evaluating partition qualitycount_triangles/1,triangles_per_node/1- Triangle countingclustering_coefficient/2,average_clustering_coefficient/1- Clustering metricsdensity/1,community_density/2,average_community_density/2- Density metrics
Quick Start:
import yog/community/louvain let communities = louvain.detect(graph) io.debug(communities.num_communities) // => 4 // Hierarchical detection let dendrogram = louvain.detect_hierarchical(graph)
Algorithm Selection Guide:
- Speed Priority: Label Propagation > Fluid Communities > Louvain > Leiden
- Quality Priority: Leiden > Louvain > Infomap > Walktrap
- Specific size (k): Fluid Communities
- Massive/Infinite graphs: Local Community
- Hierarchical Structure: Girvan-Newman, Louvain, Leiden, Walktrap
- Overlapping Communities: Clique Percolation (nodes can belong to multiple)
- Flow-Based: Infomap (uses PageRank and Map Equation)
- Project Structure Reorganization:
- Moved
examples/→test/examples/for simpler execution without symlink hacks - Moved
bench/→test/bench/to consolidate test-related code
- Moved
- Undirected Edge Removal Symmetry: Calling
model.remove_edge(graph, src, dst)on anUndirectedgraph now automatically removes both thesrc -> dstand thedst -> srcreferences in a single call, rather than previously requiring two distinct calls.
-
Testing: Exhaustive property-based testing using
qcheckacross core algorithms (pathfinding, connectivity, MST) and properties. -
Documentation: Added comprehensive module-level docs to 22 modules including algorithm references with Wikipedia links, complexity tables, and usage examples. Modules:
connectivity,disjoint_set,model,mst,transform,traversal,io/*,flow/*,pathfinding/*,property/*,dag,centrality. -
F# Comparison: Added
GLEAM_FSHARP_COMPARISON.mddocumenting feature parity, API differences, and migration guidance between the Gleam and F# implementations of Yog.
- Project Structure Reorganization:
- Moved
examples/→test/examples/for simpler execution without symlink hacks - Moved
bench/→test/bench/to consolidate test-related code
- Moved
- Removed the
dagfacade module. Instead ofimport yog/dag, you should either importdag/modelordag/algorithms
-
Centrality Module (
yog/centrality): Graph analysis for identifying important nodesdegree/2: Local connectivity measure with In/Out/Total modescloseness/5&harmonic_centrality/5: Distance-based importance (latter handles disconnected graphs)betweenness/5: Bridge/gatekeeper detection using Brandes' algorithmpagerank/2: Link-quality importance with configurable dampingeigenvector/3: Influence based on neighbor centrality (power iteration)katz/5&alpha_centrality/5: Attenuated centrality for directed networks- Convenience wrappers:
*_int(),*_float()for common weight types
-
Multigraph Module (
yog/multi/*): Initial support for parallel edges (multiple edges between same node pair)yog/multi/model:MultiGraph(n, e)model with conversion helpersyog/multi/traversal: BFS/DFS with edge-aware visited trackingyog/multi/eulerian: Hierholzer's algorithm returningEdgeIdpaths for unambiguous parallel edge traversal
- Graph-First API:
graphparameter moved to first position inwalk,walk_until,fold_walk - Module Reorganization:
yog/components→yog/connectivityyog/min_cut→yog/flow/min_cutyog/max_flow→yog/flow/max_flowyog/topological_sort→yog/traversalyog/clique→yog/property/cliqueyog/bipartite→yog/property/bipartiteyog/eulerian→yog/property/eulerianyog/pathfinding→yog/pathfinding/dijkstra,yog/pathfinding/a_star,yog/pathfinding/bellman_ford,yog/pathfinding/floyd_warshall- Facade modules removed; import specific modules (e.g.,
yog/pathfinding/dijkstra)
- Rendering:
yog/rendersplit intoyog/io/*(mermaid, dot, ascii) - Traversal Control:
fold_walkandimplicit_foldnow useWalkControlenum (Continue,Stop,Halt)
- Convenience Wrappers:
*_int()and*_float()functions for common weight types:dijkstra:shortest_path_int,shortest_path_float,single_source_distances_int,single_source_distances_floata_star:a_star_int,a_star_floatbellman_ford:bellman_ford_int,bellman_ford_floatfloyd_warshall:floyd_warshall_int,floyd_warshall_floatmax_flow:edmonds_karp_int
- Live Builder (
yog/builder/live): Transaction-style builder for incremental graph construction withsync()for O(ΔE) updates - DAG Module (
yog/dag): StrictDag(n, e)type with O(V+E) algorithms:topological_sort,longest_path,transitive_closure,transitive_reductionlowest_common_ancestors,count_reachability
- Network Simplex (
yog/flow/network_simplex): Minimum cost flow solver is_acyclic/is_cyclicre-exported fromyog/property
- Top-level re-exports:
walk,walk_until,fold_walk,transpose,map_nodes, etc.
builder/gridpredicates now filter both source and destination cells
mst.kruskal(): Optimized disjoint set initialization
mst.prim(): Fixed neighbor expansion for undirected edgesmin_cut.global_min_cut(): Fixed cut weight calculation in Stoer-WagnertraversalDFS: Fixed stack order (now useslist.fold_right)
builder/grid.from_2d_list_with_topology(): Custom movement patterns- Chess-themed topologies:
rook(),bishop(),queen(),knight() - Movement predicates:
avoiding(),walkable(),always()
- Chess-themed topologies:
model.add_edge_ensured(): Auto-creates missing nodestransform.filter_edges(),transform.complement(),transform.to_directed(),transform.to_undirected()
clique: Precomputed adjacency sets + greedy pivotingmodel.neighbors(): O(N log N) deduplication using Setsfilter_nodes(): O(E log V) using Set membershipeulerian: O(E) Hierholzer's using Dict adjacency
- Implicit pathfinding:
implicit_dijkstra,implicit_dijkstra_by,implicit_a_star,implicit_a_star_by - Implicit Bellman-Ford:
implicit_bellman_ford,implicit_bellman_ford_by traversal.fold_walk: Stateful traversal withWalkControltraversal.implicit_fold,implicit_fold_by: BFS/DFS on implicit graphs- Maximum Clique:
max_clique,all_maximal_cliques,k_cliques(Bron-Kerbosch) pathfinding.distance_matrix: Auto-selects Floyd-Warshall vs multiple Dijkstramst.prim(): Prim's MST algorithmcomponents.kosaraju(): Kosaraju's SCC algorithm
floyd_warshall(): Return type changed fromDict(NodeId, Dict(NodeId, e))toDict(#(NodeId, NodeId), e)lexicographical_topological_sort(): Changedcompare_idstocompare_nodes(compares node data, not IDs)
min_cut.global_min_cut(): Fixed list reversal bug in MAStransform.contract(): Fixed weight doubling for undirected graphs
builder/grid: O(N²) → O(N) using dict lookupstraversalBFS: O(1) amortized queue operationsfloyd_warshall: Flat dictionary structuremin_cut: Heap-based MAS (O(V² log V) vs O(V³))
- Max Flow (
yog/max_flow): Edmonds-Karp algorithm - Graph Generators (
yog/generator): Complete, cycle, path, star, wheel, bipartite, random graphs (Erdős-Rényi, Barabási-Albert, Watts-Strogatz) - Stable Marriage (
yog/bipartite): Gale-Shapley algorithm
- Grid builder (
yog/builder/grid): 2D array to graph conversion - Single-source distances from Dijkstra
add_unweighted_edge,add_simple_edgeconveniences
- Labeled builder (
yog/builder/labeled): String/any type node labels yogergonomic API:directed(),undirected()- Visualization (
yog/render): Mermaid, DOT, ASCII
- Initial release
- Core graph structures (directed/undirected)
- Pathfinding: Dijkstra, A*, Bellman-Ford
- Traversal: BFS, DFS
- MST: Kruskal's algorithm
- Topological sort: Kahn's algorithm
- SCC: Tarjan's algorithm
- Transformations: transpose, map, filter, merge