///|
/// Graph and probabilistic dependency utilities for structured models.
pub struct GraphEdge {
from : Int
to : Int
weight : Double
label : String
} derive(Debug, Eq, ToJson, FromJson)
///|
pub fn GraphEdge::new(from : Int, to : Int, weight : Double) -> GraphEdge {
{ from, to, weight, label: "" }
}
///|
pub struct WeightedGraph {
nodes : Int
directed : Bool
edges : Array[GraphEdge]
} derive(Debug, Eq, ToJson, FromJson)
///|
pub fn WeightedGraph::new(nodes : Int, directed : Bool) -> WeightedGraph {
if nodes < 0 {
abort("WeightedGraph: nodes must be non-negative")
}
{ nodes, directed, edges: [] }
}
///|
pub fn WeightedGraph::add_edge(
self : WeightedGraph,
edge : GraphEdge,
) -> WeightedGraph {
if edge.from < 0 ||
edge.from >= self.nodes ||
edge.to < 0 ||
edge.to >= self.nodes {
abort("WeightedGraph: edge endpoint out of range")
}
let edges = self.edges.copy()
edges.push(edge)
if !self.directed && edge.from != edge.to {
edges.push({
from: edge.to,
to: edge.from,
weight: edge.weight,
label: edge.label,
})
}
{ ..self, edges, }
}
///|
pub fn WeightedGraph::neighbors(
self : WeightedGraph,
node : Int,
) -> Array[(Int, Double)] {
if node < 0 || node >= self.nodes {
abort("WeightedGraph: node out of range")
}
let result : Array[(Int, Double)] = []
for edge in self.edges.iter() {
if edge.from == node {
result.push((edge.to, edge.weight))
}
}
result
}
///|
pub fn WeightedGraph::degree(self : WeightedGraph, node : Int) -> Int {
self.neighbors(node).length()
}
///|
pub fn WeightedGraph::edge_count(self : WeightedGraph) -> Int {
self.edges.length()
}
///|
pub fn WeightedGraph::has_edge(
self : WeightedGraph,
from : Int,
to : Int,
) -> Bool {
self.edges.any(fn(edge) { edge.from == from && edge.to == to })
}
///|
pub fn WeightedGraph::total_weight(self : WeightedGraph) -> Double {
self.edges.fold(init=0.0, (total, edge) => total + edge.weight)
}
///|
pub fn WeightedGraph::adjacency_matrix(
self : WeightedGraph,
) -> Array[Array[Double]] {
let matrix : Array[Array[Double]] = []
for _ in 0.. Array[(Int, Double)] {
let result : Array[(Int, Double)] = []
for edge in self.edges.iter() {
if edge.to == node {
result.push((edge.from, edge.weight))
}
}
result
}
///|
pub fn WeightedGraph::outgoing_weight(
self : WeightedGraph,
node : Int,
) -> Double {
self.neighbors(node).fold(init=0.0, (total, item) => total + item.1)
}
///|
pub fn graph_bfs(graph : WeightedGraph, start : Int) -> Array[Int] {
let visited : Array[Bool] = []
for _ in 0.. Array[Int] {
let visited : Array[Bool] = []
for _ in 0.. 0 {
let node = stack[stack.length() - 1]
let _ = stack.pop()
if !visited[node] {
visited[node] = true
result.push(node)
for item in graph.neighbors(node) {
if !visited[item.0] {
stack.push(item.0)
}
}
}
}
result
}
///|
pub struct ShortestPath {
distance : Double
nodes : Array[Int]
reachable : Bool
} derive(Debug, Eq, ToJson, FromJson)
///|
pub fn dijkstra(
graph : WeightedGraph,
start : Int,
target : Int,
) -> ShortestPath {
if start < 0 || target < 0 || start >= graph.nodes || target >= graph.nodes {
abort("dijkstra: node out of range")
}
let distances : Array[Double] = []
for _ in 0..= 0 {
path.push(cursor)
if cursor == start {
break
}
cursor = previous[cursor]
}
for left in 0.. left && path[right] > -1 {
let temp = path[left]
path[left] = path[right]
path[right] = temp
}
}
}
{ distance: distances[target], nodes: path, reachable: true }
}
///|
pub fn pagerank(
graph : WeightedGraph,
damping : Double,
iterations : Int,
) -> Array[Double] {
if damping < 0.0 || damping > 1.0 || iterations < 0 {
abort("pagerank: invalid inputs")
}
if graph.nodes == 0 {
return []
}
let rank : Array[Double] = []
for _ in 0.. 0.0 {
next[edge.to] = next[edge.to] +
damping * rank[edge.from] * edge.weight / weight
}
}
for index in 0.. Double {
let neighbors = graph.neighbors(node).map(fn(item) { item.0 })
if neighbors.length() < 2 {
return 0.0
}
let mut links = 0
for left in 0.. MarkovChain {
let states = transition.length()
if states == 0 || initial.length() != states {
abort("MarkovChain: dimension mismatch")
}
for row in transition.iter() {
if row.length() != states {
abort("MarkovChain: transition must be square")
}
}
{ states, transition, initial }
}
///|
pub fn MarkovChain::step(
self : MarkovChain,
distribution : Array[Double],
) -> Array[Double] {
if distribution.length() != self.states {
abort("MarkovChain: distribution dimension mismatch")
}
let result : Array[Double] = []
for target in 0.. Array[Double] {
if steps < 0 {
abort("MarkovChain: steps must be non-negative")
}
let mut result = self.initial.copy()
for _ in 0.. Array[Double] {
let mut result = self.initial.copy()
for _ in 0.. Double {
let distribution = self.distribution(steps)
if values.length() != self.states {
abort("MarkovChain: state values dimension mismatch")
}
let mut result = 0.0
for index in 0.. TransitionEstimate {
if sequence.length() < 2 || states <= 0 || smoothing < 0.0 {
abort("estimate_transitions: invalid inputs")
}
let counts : Array[Array[Int]] = []
for _ in 0.. sum_value + value)
.to_double() +
smoothing * states.to_double()
for target in 0.. Double {
if sequence.length() < 2 {
return 0.0
}
let mut result = 0.0
for index in 1..