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 (
|
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
1
go.mod
|
@ -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
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/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=
|
||||||
|
|
Loading…
Reference in New Issue