| 
									
										
										
										
											2025-05-02 01:00:45 +00:00
										 |  |  | /* | 
					
						
							|  |  |  | 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 ( | 
					
						
							|  |  |  | 	"gonum.org/v1/gonum/graph" | 
					
						
							| 
									
										
										
										
											2025-05-02 06:04:10 +00:00
										 |  |  | 	"gonum.org/v1/gonum/graph/multi" | 
					
						
							| 
									
										
										
										
											2025-05-02 01:00:45 +00:00
										 |  |  | 	"gonum.org/v1/gonum/graph/simple" | 
					
						
							| 
									
										
										
										
											2025-05-03 01:02:05 +00:00
										 |  |  | 	"gonum.org/v1/gonum/graph/topo" | 
					
						
							| 
									
										
										
										
											2025-05-02 05:04:00 +00:00
										 |  |  | 	"gonum.org/v1/gonum/mat" | 
					
						
							| 
									
										
										
										
											2025-05-02 01:00:45 +00:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-04 16:56:34 +00:00
										 |  |  | // AbsorbingMarkovChain is a graph representing an absorbing Markov chain.
 | 
					
						
							| 
									
										
										
										
											2025-05-02 01:00:45 +00:00
										 |  |  | type AbsorbingMarkovChain struct { | 
					
						
							| 
									
										
										
										
											2025-05-02 06:04:10 +00:00
										 |  |  | 	*multi.WeightedDirectedGraph | 
					
						
							| 
									
										
										
										
											2025-05-02 01:00:45 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-04 16:56:34 +00:00
										 |  |  | // NewAbsorbingMarkovChain returns an absorbing Markov chain with no nodes or
 | 
					
						
							|  |  |  | // edges.
 | 
					
						
							| 
									
										
										
										
											2025-05-02 01:00:45 +00:00
										 |  |  | func NewAbsorbingMarkovChain() *AbsorbingMarkovChain { | 
					
						
							| 
									
										
										
										
											2025-05-02 06:04:10 +00:00
										 |  |  | 	return &AbsorbingMarkovChain{WeightedDirectedGraph: multi.NewWeightedDirectedGraph()} | 
					
						
							| 
									
										
										
										
											2025-05-02 01:00:45 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-04 16:56:34 +00:00
										 |  |  | // IsValid returns true if the graph is a valid Markov chain.
 | 
					
						
							| 
									
										
										
										
											2025-05-02 01:00:45 +00:00
										 |  |  | func (g *AbsorbingMarkovChain) IsValid() bool { | 
					
						
							|  |  |  | 	for nodes := g.Nodes(); nodes.Next(); { | 
					
						
							| 
									
										
										
										
											2025-05-03 00:27:12 +00:00
										 |  |  | 		u := nodes.Node() | 
					
						
							| 
									
										
										
										
											2025-05-02 06:04:10 +00:00
										 |  |  | 		if g.outWeightSum(u) != 1 { | 
					
						
							| 
									
										
										
										
											2025-05-02 01:00:45 +00:00
										 |  |  | 			return false | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-02 05:09:19 +00:00
										 |  |  | func (g *AbsorbingMarkovChain) outWeightSum(u graph.Node) float64 { | 
					
						
							| 
									
										
										
										
											2025-05-02 01:00:45 +00:00
										 |  |  | 	sum := 0.0 | 
					
						
							|  |  |  | 	for nodes := g.From(u.ID()); nodes.Next(); { | 
					
						
							|  |  |  | 		v := nodes.Node() | 
					
						
							|  |  |  | 		e := g.WeightedEdge(u.ID(), v.ID()) | 
					
						
							|  |  |  | 		if e != nil { | 
					
						
							| 
									
										
										
										
											2025-05-02 06:04:10 +00:00
										 |  |  | 			sum += e.Weight() | 
					
						
							| 
									
										
										
										
											2025-05-02 01:00:45 +00:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return sum | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-04 16:56:34 +00:00
										 |  |  | // AbsorbingNodes returns all the nodes in the Markov chain identified as
 | 
					
						
							|  |  |  | // absorbing nodes.
 | 
					
						
							| 
									
										
										
										
											2025-05-03 00:24:35 +00:00
										 |  |  | func (g *AbsorbingMarkovChain) AbsorbingNodes() []graph.Node { | 
					
						
							|  |  |  | 	absorbingNodes := make([]graph.Node, 0) | 
					
						
							|  |  |  | 	for nodes := g.Nodes(); nodes.Next(); { | 
					
						
							|  |  |  | 		u := nodes.Node() | 
					
						
							|  |  |  | 		successors := g.From(u.ID()) | 
					
						
							|  |  |  | 		if successors.Len() != 1 { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		successors.Next() | 
					
						
							|  |  |  | 		v := successors.Node() | 
					
						
							|  |  |  | 		if u == v { | 
					
						
							|  |  |  | 			absorbingNodes = append(absorbingNodes, u) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return absorbingNodes | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-04 16:56:34 +00:00
										 |  |  | // IsAbsorbing returns true if the Markov chain is an absorbing chain. This
 | 
					
						
							|  |  |  | // means at least one node is an absorbing node and that every node can reach
 | 
					
						
							|  |  |  | // an absorbing node.
 | 
					
						
							| 
									
										
										
										
											2025-05-03 01:02:05 +00:00
										 |  |  | func (g *AbsorbingMarkovChain) IsAbsorbing() bool { | 
					
						
							|  |  |  | 	absorbingNodes := g.AbsorbingNodes() | 
					
						
							|  |  |  | 	if len(absorbingNodes) == 0 { | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for nodes := g.Nodes(); nodes.Next(); { | 
					
						
							|  |  |  | 		u := nodes.Node() | 
					
						
							|  |  |  | 		if !g.canBeAbsorbed(u, absorbingNodes) { | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (g *AbsorbingMarkovChain) canBeAbsorbed(u graph.Node, absorbingNodes []graph.Node) bool { | 
					
						
							|  |  |  | 	for _, v := range absorbingNodes { | 
					
						
							|  |  |  | 		if topo.PathExistsIn(g, u, v) { | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return false | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-04 16:56:34 +00:00
										 |  |  | // AdjacencyMatrix returns the graph's adjacency matrix.
 | 
					
						
							| 
									
										
										
										
											2025-05-04 03:21:15 +00:00
										 |  |  | func (g *AbsorbingMarkovChain) AdjacencyMatrix() *mat.Dense { | 
					
						
							| 
									
										
										
										
											2025-05-02 06:04:10 +00:00
										 |  |  | 	adj := simple.NewDirectedMatrix(g.Nodes().Len(), 0, 0, 0) | 
					
						
							| 
									
										
										
										
											2025-05-02 05:04:00 +00:00
										 |  |  | 	for edges := g.WeightedEdges(); edges.Next(); { | 
					
						
							| 
									
										
										
										
											2025-05-02 06:04:10 +00:00
										 |  |  | 		e := edges.WeightedEdge() | 
					
						
							|  |  |  | 		if e.From() == e.To() { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		adj.SetWeightedEdge(e) | 
					
						
							| 
									
										
										
										
											2025-05-02 05:04:00 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	a := mat.DenseCopyOf(adj.Matrix()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	nodes := adj.Nodes() | 
					
						
							|  |  |  | 	for i := 0; nodes.Next(); i++ { | 
					
						
							|  |  |  | 		id := nodes.Node().ID() | 
					
						
							| 
									
										
										
										
											2025-05-03 00:27:12 +00:00
										 |  |  | 		u := g.Node(id) | 
					
						
							| 
									
										
										
										
											2025-05-02 06:04:10 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		e := g.WeightedEdge(u.ID(), u.ID()) | 
					
						
							|  |  |  | 		if e != nil { | 
					
						
							|  |  |  | 			a.Set(i, i, e.Weight()) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2025-05-02 05:04:00 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return a | 
					
						
							|  |  |  | } |