add comments to public methods

This commit is contained in:
filifa 2025-05-04 12:56:34 -04:00
parent cfc6b24814
commit c4e90a1170
1 changed files with 27 additions and 1 deletions

View File

@ -29,14 +29,18 @@ import (
"gonum.org/v1/gonum/mat" "gonum.org/v1/gonum/mat"
) )
// AbsorbingMarkovChain is a graph representing an absorbing Markov chain.
type AbsorbingMarkovChain struct { type AbsorbingMarkovChain struct {
*multi.WeightedDirectedGraph *multi.WeightedDirectedGraph
} }
// NewAbsorbingMarkovChain returns an absorbing Markov chain with no nodes or
// edges.
func NewAbsorbingMarkovChain() *AbsorbingMarkovChain { func NewAbsorbingMarkovChain() *AbsorbingMarkovChain {
return &AbsorbingMarkovChain{WeightedDirectedGraph: multi.NewWeightedDirectedGraph()} return &AbsorbingMarkovChain{WeightedDirectedGraph: multi.NewWeightedDirectedGraph()}
} }
// IsValid returns true if the graph is a valid Markov chain.
func (g *AbsorbingMarkovChain) IsValid() bool { func (g *AbsorbingMarkovChain) IsValid() bool {
for nodes := g.Nodes(); nodes.Next(); { for nodes := g.Nodes(); nodes.Next(); {
u := nodes.Node() u := nodes.Node()
@ -61,6 +65,8 @@ func (g *AbsorbingMarkovChain) outWeightSum(u graph.Node) float64 {
return sum return sum
} }
// AbsorbingNodes returns all the nodes in the Markov chain identified as
// absorbing nodes.
func (g *AbsorbingMarkovChain) AbsorbingNodes() []graph.Node { func (g *AbsorbingMarkovChain) AbsorbingNodes() []graph.Node {
absorbingNodes := make([]graph.Node, 0) absorbingNodes := make([]graph.Node, 0)
for nodes := g.Nodes(); nodes.Next(); { for nodes := g.Nodes(); nodes.Next(); {
@ -80,6 +86,9 @@ func (g *AbsorbingMarkovChain) AbsorbingNodes() []graph.Node {
return absorbingNodes return absorbingNodes
} }
// IsAbsorbing returns true if the Markov chain is an absorbing chain. This
// means at least one node is an absorbing node and that every node can reach
// an absorbing node.
func (g *AbsorbingMarkovChain) IsAbsorbing() bool { func (g *AbsorbingMarkovChain) IsAbsorbing() bool {
absorbingNodes := g.AbsorbingNodes() absorbingNodes := g.AbsorbingNodes()
if len(absorbingNodes) == 0 { if len(absorbingNodes) == 0 {
@ -106,6 +115,7 @@ func (g *AbsorbingMarkovChain) canBeAbsorbed(u graph.Node, absorbingNodes []grap
return false return false
} }
// AdjacencyMatrix returns the graph's adjacency matrix.
func (g *AbsorbingMarkovChain) AdjacencyMatrix() *mat.Dense { func (g *AbsorbingMarkovChain) AdjacencyMatrix() *mat.Dense {
adj := simple.NewDirectedMatrix(g.Nodes().Len(), 0, 0, 0) adj := simple.NewDirectedMatrix(g.Nodes().Len(), 0, 0, 0)
for edges := g.WeightedEdges(); edges.Next(); { for edges := g.WeightedEdges(); edges.Next(); {
@ -133,28 +143,40 @@ func (g *AbsorbingMarkovChain) AdjacencyMatrix() *mat.Dense {
return a return a
} }
// NewEdge returns a weightedEdge that can be added to the Markov chain.
func (g *AbsorbingMarkovChain) NewEdge(from, to graph.Node) graph.Edge { func (g *AbsorbingMarkovChain) NewEdge(from, to graph.Node) graph.Edge {
e := g.WeightedDirectedGraph.NewWeightedLine(from, to, math.NaN()).(multi.WeightedLine) e := g.WeightedDirectedGraph.NewWeightedLine(from, to, math.NaN()).(multi.WeightedLine)
return &weightedEdge{WeightedLine: e} return &weightedEdge{WeightedLine: e}
} }
// NewNode returns a Node that can be added to the Markov chain.
func (g *AbsorbingMarkovChain) NewNode() graph.Node { func (g *AbsorbingMarkovChain) NewNode() graph.Node {
return &Node{Node: g.WeightedDirectedGraph.NewNode()} return &Node{Node: g.WeightedDirectedGraph.NewNode()}
} }
// SetEdge adds a weighted edge to the Markov chain.
func (g *AbsorbingMarkovChain) SetEdge(e graph.Edge) { func (g *AbsorbingMarkovChain) SetEdge(e graph.Edge) {
g.WeightedDirectedGraph.SetWeightedLine(e.(*weightedEdge)) g.WeightedDirectedGraph.SetWeightedLine(e.(*weightedEdge))
} }
// weightedEdge is a DOT-aware multi.WeightedLine. By being a
// multi.WeightedLine, it allows for self-loops, which are important for
// absorbing Markov chains.
// TODO: this is a little confusing, maybe just have checks in the code that
// there's only one line in each edge?
type weightedEdge struct { type weightedEdge struct {
multi.WeightedLine multi.WeightedLine
} }
// ReversedEdge returns a new weightedEdge with the same weight, but the
// direction reversed. It exists mainly to satisfy the graph.Edge interface.
func (e *weightedEdge) ReversedEdge() graph.Edge { func (e *weightedEdge) ReversedEdge() graph.Edge {
revLine := multi.WeightedLine{F: e.T, T: e.F, W: e.W} revLine := multi.WeightedLine{F: e.T, T: e.F, W: e.W}
return &weightedEdge{WeightedLine: revLine} return &weightedEdge{WeightedLine: revLine}
} }
// SetAttribute enables storing the weight read from a DOT file. It errors if
// an attribute is read that can't be stored in a weightedEdge.
func (e *weightedEdge) SetAttribute(attr encoding.Attribute) error { func (e *weightedEdge) SetAttribute(attr encoding.Attribute) error {
var err error var err error
@ -162,21 +184,25 @@ func (e *weightedEdge) SetAttribute(attr encoding.Attribute) error {
case "weight": case "weight":
e.W, err = strconv.ParseFloat(attr.Value, 64) e.W, err = strconv.ParseFloat(attr.Value, 64)
default: default:
err = errors.New("unknown key" + attr.Key) err = errors.New("unknown key:" + attr.Key)
} }
return err return err
} }
// Node is a DOT-aware graph.Node.
type Node struct { type Node struct {
graph.Node graph.Node
dotID string dotID string
} }
// SetDOTID sets the node's DOT ID. It enables storing the node name read from
// a DOT file.
func (n *Node) SetDOTID(id string) { func (n *Node) SetDOTID(id string) {
n.dotID = id n.dotID = id
} }
// DOTID returns the node's DOT ID.
func (n *Node) DOTID() string { func (n *Node) DOTID() string {
return n.dotID return n.dotID
} }