implement checking if graph is an absorbing markov chain

This commit is contained in:
filifa
2025-05-01 21:00:45 -04:00
parent d898b53f89
commit 5228ebccbe
4 changed files with 135 additions and 4 deletions

View File

@@ -0,0 +1,107 @@
/*
Copyright © 2025 filifa
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package markov
import (
"errors"
"math"
"strconv"
"gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/encoding"
"gonum.org/v1/gonum/graph/simple"
)
type AbsorbingMarkovChain struct {
*simple.WeightedDirectedGraph
}
func NewAbsorbingMarkovChain() *AbsorbingMarkovChain {
return &AbsorbingMarkovChain{WeightedDirectedGraph: simple.NewWeightedDirectedGraph(math.NaN(), math.NaN())}
}
func (g *AbsorbingMarkovChain) IsValid() bool {
for nodes := g.Nodes(); nodes.Next(); {
u := nodes.Node().(*node)
if g.From(u.ID()).Len() == 0 {
continue
}
if g.outWeightSum(u) != 1 {
return false
}
}
return true
}
func (g *AbsorbingMarkovChain) outWeightSum(u *node) float64 {
sum := 0.0
for nodes := g.From(u.ID()); nodes.Next(); {
v := nodes.Node()
e := g.WeightedEdge(u.ID(), v.ID())
if e != nil {
sum += e.(*weightedEdge).W
}
}
return sum
}
func (g *AbsorbingMarkovChain) NewEdge(from, to graph.Node) graph.Edge {
e := g.WeightedDirectedGraph.NewWeightedEdge(from, to, math.NaN()).(simple.WeightedEdge)
return &weightedEdge{WeightedEdge: e}
}
func (g *AbsorbingMarkovChain) NewNode() graph.Node {
return &node{Node: g.WeightedDirectedGraph.NewNode()}
}
func (g *AbsorbingMarkovChain) SetEdge(e graph.Edge) {
g.WeightedDirectedGraph.SetWeightedEdge(e.(*weightedEdge))
}
type weightedEdge struct {
simple.WeightedEdge
}
func (e *weightedEdge) SetAttribute(attr encoding.Attribute) error {
var err error
switch attr.Key {
case "weight":
e.W, err = strconv.ParseFloat(attr.Value, 64)
default:
err = errors.New("unknown key" + attr.Key)
}
return err
}
type node struct {
graph.Node
dotID string
}
func (n *node) SetDOTID(id string) {
n.dotID = id
}
func (n *node) DOTID() string {
return n.dotID
}

View File

@@ -19,10 +19,13 @@ package cmd
import (
"os"
"scm.dairydemon.net/filifa/dptdist/cmd/internal/markov"
"github.com/spf13/cobra"
"gonum.org/v1/gonum/graph/encoding/dot"
)
var file string
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
@@ -36,7 +39,24 @@ This application is a tool to generate the needed files
to quickly create a Cobra application.`,
// Uncomment the following line if your bare application
// has an action associated with it:
// Run: func(cmd *cobra.Command, args []string) { },
Run: parse,
}
func parse(cmd *cobra.Command, args []string) {
data, err := os.ReadFile(file)
if err != nil {
panic(err)
}
graph := markov.NewAbsorbingMarkovChain()
err = dot.Unmarshal(data, graph)
if err != nil {
panic(err)
}
if !graph.IsValid() {
panic("not an absorbing Markov chain!")
}
}
// Execute adds all child commands to the root command and sets flags appropriately.
@@ -58,6 +78,7 @@ func init() {
// Cobra also supports local flags, which will only run
// when this action is called directly.
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
rootCmd.Flags().StringVarP(&file, "file", "f", "", "dot file with absorbing Markov chain")
rootCmd.MarkFlagRequired("file")
}