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 ( import (
"os" "os"
"scm.dairydemon.net/filifa/dptdist/cmd/internal/markov"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gonum.org/v1/gonum/graph/encoding/dot"
) )
var file string
// rootCmd represents the base command when called without any subcommands // rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
@ -36,7 +39,24 @@ This application is a tool to generate the needed files
to quickly create a Cobra application.`, to quickly create a Cobra application.`,
// Uncomment the following line if your bare application // Uncomment the following line if your bare application
// has an action associated with it: // 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. // 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 // Cobra also supports local flags, which will only run
// when this action is called directly. // when this action is called directly.
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 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")
} }

1
go.mod
View File

@ -6,4 +6,5 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/cobra v1.9.1 // indirect github.com/spf13/cobra v1.9.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect github.com/spf13/pflag v1.0.6 // indirect
gonum.org/v1/gonum v0.15.0 // indirect
) )

2
go.sum
View File

@ -6,5 +6,7 @@ github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
gonum.org/v1/gonum v0.15.0 h1:2lYxjRbTYyxkJxlhC+LvJIx3SsANPdRybu1tGj9/OrQ=
gonum.org/v1/gonum v0.15.0/go.mod h1:xzZVBJBtS+Mz4q0Yl2LJTk+OxOg4jiXZ7qBoM0uISGo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=