implement checking if graph is an absorbing markov chain
This commit is contained in:
parent
d898b53f89
commit
5228ebccbe
|
@ -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
|
||||
}
|
29
cmd/root.go
29
cmd/root.go
|
@ -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")
|
||||
}
|
||||
|
||||
|
||||
|
|
1
go.mod
1
go.mod
|
@ -6,4 +6,5 @@ require (
|
|||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/spf13/cobra v1.9.1 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
gonum.org/v1/gonum v0.15.0 // indirect
|
||||
)
|
||||
|
|
2
go.sum
2
go.sum
|
@ -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/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
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/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
Loading…
Reference in New Issue