Model Module
Directed Acyclic Graph (DAG) type and operations.
A DAG is a directed graph with no cycles. This module provides a type-safe wrapper around Graph that guarantees acyclicity through construction, enabling total functions for operations that would be partial on general graphs.
Core Concept
The DAG type uses the "smart constructor" pattern: - fromGraph validates acyclicity and wraps the graph - addEdge returns Result because it could create a cycle - addNode/removeNode/removeEdge are safe (cannot create cycles)
Once constructed, the DAG type guarantees no cycles exist, allowing algorithms like topological sort to be total functions.
When to Use
Use Case |
Example |
|---|---|
Task Scheduling |
Build systems (Make, Gradle), CI/CD pipelines |
Dependencies |
Package managers (npm, cargo), module imports |
Partial Orders |
Preference rankings, event causality |
Control Flow |
Compiler IRs, dataflow graphs |
Version Control |
Git commit history, merge bases |
Bayesian Networks |
Probabilistic graphical models |
Key Properties
- Guaranteed acyclic: Type system prevents cycles at construction
- Topological sort always succeeds: No need to handle cycle errors
- Longest path is well-defined: Can find critical paths in O(V+E)
- Shortest path is efficient: O(V+E) vs O((V+E) log V) for general graphs
Comparison with Graph
Aspect |
Dag |
Graph |
|---|---|---|
Cycles allowed |
❌ No |
✅ Yes |
addEdge result |
|
|
topologicalSort |
Always succeeds |
May fail |
longestPath |
O(V+E) |
NP-hard |
shortestPath |
O(V+E) |
O((V+E) log V) |
Example
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: |
|
References
<summary> Core graph data structures and basic operations for the yog library. This module defines the fundamental `Graph` type and provides all basic operations for creating and manipulating graphs. The graph uses an adjacency list representation with dual indexing (both outgoing and incoming edges) for efficient traversal in both directions. </summary>
<summary> Creates a new empty graph of the specified type. ## Example ```fsharp let graph = Model.empty Directed ``` </summary>
<summary> Adds a node to the graph with the given ID and data. If a node with this ID already exists, its data will be replaced. ## Example ```fsharp graph |> addNode 1 "Node A" |> addNode 2 "Node B" ``` </summary>
<summary> Adds an edge to the graph with the given weight. For directed graphs, adds a single edge from src to dst. For undirected graphs, adds edges in both directions. > **Note:** If `src` or `dst` have not been added via `addNode`, > the edge will still be created in the edge dictionaries, but the > nodes will be missing from `graph.Nodes`. This creates "ghost nodes" > that are traversable but invisible to functions that iterate over > nodes (e.g. `order`, `allNodes`). Use `addEdgeEnsured` to > auto-create missing endpoints with a default value. ## Example ```fsharp graph |> addEdge 1 2 10 ``` </summary>
<summary> Directed Acyclic Graph (DAG) type and operations. A DAG is a directed graph with no cycles. This module provides a type-safe wrapper around Graph that guarantees acyclicity through construction, enabling total functions for operations that would be partial on general graphs. ## Core Concept The DAG type uses the "smart constructor" pattern: - **fromGraph** validates acyclicity and wraps the graph - **addEdge** returns Result because it could create a cycle - **addNode/removeNode/removeEdge** are safe (cannot create cycles) Once constructed, the DAG type guarantees no cycles exist, allowing algorithms like topological sort to be total functions. ## When to Use | Use Case | Example | |----------|---------| | **Task Scheduling** | Build systems (Make, Gradle), CI/CD pipelines | | **Dependencies** | Package managers (npm, cargo), module imports | | **Partial Orders** | Preference rankings, event causality | | **Control Flow** | Compiler IRs, dataflow graphs | | **Version Control** | Git commit history, merge bases | | **Bayesian Networks** | Probabilistic graphical models | ## Key Properties - **Guaranteed acyclic**: Type system prevents cycles at construction - **Topological sort always succeeds**: No need to handle cycle errors - **Longest path is well-defined**: Can find critical paths in O(V+E) - **Shortest path is efficient**: O(V+E) vs O((V+E) log V) for general graphs ## Comparison with Graph | Aspect | Dag | Graph | |--------|-----|-------| | Cycles allowed | ❌ No | ✅ Yes | | addEdge result | `Result<Dag, error>` | `Graph` | | topologicalSort | Always succeeds | May fail | | longestPath | O(V+E) | NP-hard | | shortestPath | O(V+E) | O((V+E) log V) | ## Example ```fsharp open Yog.Dag // Build a task dependency DAG let dagResult = Yog.Model.empty Directed |> Yog.Model.addNode 0 "Compile" |> Yog.Model.addNode 1 "Link" |> Yog.Model.addNode 2 "Test" |> Yog.Model.addEdge 0 1 () |> Yog.Model.addEdge 1 2 () |> Model.fromGraph match dagResult with | Ok dag -> let order = Algorithms.topologicalSort dag printfn "Execution order: %A" order | Error CycleDetected -> printfn "Circular dependency detected!" ``` ## References - [Wikipedia: Directed Acyclic Graph](https://en.wikipedia.org/wiki/Directed_acyclic_graph) - [Topological Sorting](https://en.wikipedia.org/wiki/Topological_sorting) - [Smart Constructors](https://wiki.haskell.org/Smart_constructors) </summary>
<summary> The Guard: Uses isAcyclic to validate the graph. ## Parameters - `graph`: The graph to validate ## Returns - `Ok dag`: If the graph is acyclic - `Error CycleDetected`: If the graph contains a cycle ## Example ```fsharp let result = Model.fromGraph myGraph match result with | Ok dag -> printfn "Valid DAG" | Error CycleDetected -> printfn "Graph has a cycle" ``` </summary>
<summary> DAG-specific algorithms that leverage acyclicity guarantees. These algorithms are optimized for DAGs and would be incorrect or inefficient on general graphs with cycles. The DAG property enables linear-time solutions for problems that are NP-hard on general graphs. ## Algorithms Provided | Algorithm | Complexity | Use Case | |-----------|------------|----------| | `topologicalSort` | O(V+E) | Task scheduling, dependency ordering | | `longestPath` | O(V+E) | Critical path analysis (PERT/CPM) | | `shortestPath` | O(V+E) | Minimum cost path between nodes | | `transitiveClosure` | O(V²) | Reachability queries, indirect dependencies | | `transitiveReduction` | O(V×E) | Simplify graphs, remove implied edges | | `countReachability` | O(V+E) | Impact analysis, prerequisite counting | | `lowestCommonAncestors` | O(V×(V+E)) | Merge bases, shared dependencies | ## Why DAGs Are Special DAGs enable efficient algorithms for problems that are intractable on general graphs: - **Longest Path**: O(V+E) on DAGs vs NP-hard on general graphs - **Topological Sort**: Always succeeds on DAGs vs may fail with cycles - **Shortest Path**: O(V+E) on DAGs vs O((V+E) log V) with Dijkstra ## References - [Wikipedia: Directed Acyclic Graph](https://en.wikipedia.org/wiki/Directed_acyclic_graph) - [Longest Path Problem](https://en.wikipedia.org/wiki/Longest_path_problem) - [Transitive Closure](https://en.wikipedia.org/wiki/Transitive_closure) - [Transitive Reduction](https://en.wikipedia.org/wiki/Transitive_reduction) </summary>
<summary> Topological sort guaranteed to succeed for the Dag type. ## Complexity - **Time**: O(V + E) - **Space**: O(V) ## Returns List of node IDs in topological order (sources before targets). ## Note This function cannot fail because the Dag type guarantees acyclicity. If it somehow encounters a cycle, it raises an exception (indicating a bug). ## Example ```fsharp let order = Algorithms.topologicalSort dag // Process nodes in dependency order for node in order do executeTask node ``` </summary>
Functions and values
| Function or value |
Description
|
||
Adds an edge. Returns Result because it could create a cycle.
Parameters
Example
union case Result.Ok: ResultValue: 'T -> Result<'T,'TError>
val dag': obj
val printfn: format: Printf.TextWriterFormat<'T> -> 'T
union case Result.Error: ErrorValue: 'TError -> Result<'T,'TError>
val CycleDetected: obj
|
|||
|
|||
|
The Guard: Uses isAcyclic to validate the graph.
Parameters
Example
val result: Result<obj,obj>
union case Result.Ok: ResultValue: 'T -> Result<'T,'TError>
val dag: obj
val printfn: format: Printf.TextWriterFormat<'T> -> 'T
union case Result.Error: ErrorValue: 'TError -> Result<'T,'TError>
val CycleDetected: obj
|
||
|
|||
|
|||
|