implement checking if graph is an absorbing markov chain
This commit is contained in:
107
cmd/internal/markov/absorbing.go
Normal file
107
cmd/internal/markov/absorbing.go
Normal 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
|
||||
}
|
||||
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")
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user