Files
obitools4/pkg/obigraph/graph.go
Eric Coissac 017030bcce Add obiminion first version
Former-commit-id: aa5ace7bd4d2266333715fca7094d1c3cbbb5e6d
2024-05-14 08:16:12 +02:00

328 lines
8.0 KiB
Go

package obigraph
import (
"bytes"
"fmt"
"io"
"math"
"os"
"text/template"
log "github.com/sirupsen/logrus"
)
type Edge[T any] struct {
From int
To int
Data *T
}
type Edges[T any] map[int]map[int]*T
type Graph[V any, T any] struct {
Name string
Vertices *[]V
Edges *Edges[T]
ReverseEdges *Edges[T]
VertexWeight func(int) float64
VertexId func(int) string
EdgeWeight func(int, int) float64
}
func NewEdges[T any]() *Edges[T] {
e := make(map[int]map[int]*T)
return (*Edges[T])(&e)
}
// AddEdge adds an edge to the graph between two vertices.
//
// Parameters:
// - from: the index of the starting vertex.
// - to: the index of the ending vertex.
// - data: a pointer to the data associated with the edge.
func (e *Edges[T]) AddEdge(from, to int, data *T) {
fnode, ok := (*e)[from]
if !ok {
fnode = make(map[int]*T)
(*e)[from] = fnode
}
fnode[to] = data
}
// NewGraph creates a new graph with the specified name and vertices.
//
// Parameters:
// - name: a string representing the name of the graph.
// - vertices: a slice of vertices of type V.
//
// Returns:
// - Graph[V, T]: the newly created graph.
func NewGraph[V, T any](name string, vertices *[]V) *Graph[V, T] {
return &Graph[V, T]{
Name: name,
Vertices: vertices,
Edges: NewEdges[T](),
ReverseEdges: NewEdges[T](),
VertexWeight: func(i int) float64 {
return 1.0
},
EdgeWeight: func(i, j int) float64 {
return 1.0
},
VertexId: func(i int) string {
return fmt.Sprintf("V%d", i)
},
}
}
// AddEdge adds an edge between two vertices in the graph.
//
// Parameters:
// - from: the index of the starting vertex.
// - to: the index of the ending vertex.
// - data: a pointer to the data associated with the edge.
func (g *Graph[V, T]) AddEdge(from, to int, data *T) {
lv := len(*g.Vertices)
if from >= lv || to >= lv {
log.Errorf("out of bounds vertex index: %d or %d (max: %d)", from, to, lv-1)
}
g.Edges.AddEdge(from, to, data)
g.Edges.AddEdge(to, from, data)
g.ReverseEdges.AddEdge(to, from, data)
g.ReverseEdges.AddEdge(from, to, data)
}
// AddDirectedEdge adds a directed edge from one vertex to another in the graph.
//
// Parameters:
// - from: an integer representing the index of the starting vertex.
// - to: an integer representing the index of the ending vertex.
// - data: a pointer to the data associated with the edge.
func (g *Graph[V, T]) AddDirectedEdge(from, to int, data *T) {
lv := len(*g.Vertices)
if from >= lv || to >= lv {
log.Errorf("out of bounds vertex index: %d or %d (max: %d)", from, to, lv-1)
}
g.Edges.AddEdge(from, to, data)
g.ReverseEdges.AddEdge(to, from, data)
}
// SetAsDirectedEdge sets the edge from one vertex to another as directed in the graph.
//
// Parameters:
// - from: an integer representing the index of the starting vertex.
// - to: an integer representing the index of the ending vertex.
func (g *Graph[V, T]) SetAsDirectedEdge(from, to int) {
lv := len(*g.Vertices)
if from >= lv || to >= lv {
log.Errorf("out of bounds vertex index: %d or %d (max: %d)", from, to, lv-1)
}
if _, ok := (*g.Edges)[from][to]; ok {
if _, ok := (*g.Edges)[to][from]; ok {
delete((*g.Edges)[to], from)
delete((*g.Edges)[from], to)
}
return
}
log.Error("no edge from ", from, " to ", to)
}
// Neighbors generates a list of neighbor vertices for a given vertex index in the graph.
//
// Parameters:
// - v: an integer representing the index of the vertex.
// Returns:
// - []int: a list of neighbor vertices.
func (g *Graph[V, T]) Neighbors(v int) []int {
if neighbors, ok := (*g.Edges)[v]; ok {
rep := make([]int, 0, len(neighbors))
for k := range neighbors {
rep = append(rep, k)
}
return rep
}
return nil
}
// Degree calculates the degree of a vertex in a graph.
//
// Parameters:
// - v: an integer representing the index of the vertex.
//
// Returns:
// - an integer representing the degree of the vertex.
func (g *Graph[V, T]) Degree(v int) int {
if neighbors, ok := (*g.Edges)[v]; ok {
return len(neighbors)
}
return 0
}
// Parents returns a list of parent vertices for a given vertex index in the graph.
//
// Parameters:
// - v: an integer representing the index of the vertex.
//
// Returns:
// - []int: a list of parent vertices.
func (g *Graph[V, T]) Parents(v int) []int {
if parents, ok := (*g.ReverseEdges)[v]; ok {
rep := make([]int, 0, len(parents))
for k := range parents {
rep = append(rep, k)
}
return rep
}
return nil
}
// ParentDegree calculates the degree of a vertex in a graph by counting the number of its parent vertices.
//
// Parameters:
// - v: an integer representing the index of the vertex.
//
// Returns:
// - an integer representing the degree of the vertex.
func (g *Graph[V, T]) ParentDegree(v int) int {
if parents, ok := (*g.ReverseEdges)[v]; ok {
return len(parents)
}
return 0
}
type gml_graph[V any, T any] struct {
Graph *Graph[V, T]
As_directed bool
Min_degree int
Threshold float64
Scale int
}
// Gml generates a GML representation of the graph.
//
// as_directed: whether the graph should be treated as directed or undirected.
// threshold: the threshold value.
// scale: the scaling factor.
// string: the GML representation of the graph.
func (g *Graph[V, T]) Gml(as_directed bool, min_degree int, threshold float64, scale int) string {
// (*seqs)[1].Count
var gml bytes.Buffer
data := gml_graph[V, T]{
Graph: g,
As_directed: as_directed,
Min_degree: min_degree,
Threshold: threshold,
Scale: scale,
}
digraphTpl := template.New("gml_digraph")
digraph := ` {{$context := .}}
graph [
comment "{{ if $context.As_directed }}Directed graph{{ else }}Undirected graph{{ end }} {{ Name }}"
directed {{ if $context.As_directed }}1{{ else }}0{{ end }}
{{range $index, $data:= $context.Graph.Vertices}}
{{ if (ge (Degree $index) $context.Min_degree)}}
node [
id {{$index}}
graphics [
type "{{ Shape $index }}"
h {{ Sqrt (VertexWeight $index) }}
w {{ Sqrt (VertexWeight $index) }}
]
]
{{ end }}
{{ end }}
{{range $source, $data:= $context.Graph.Edges}}
{{range $target, $edge:= $data}}
{{ if and (ge $source $context.Min_degree) (ge $target $context.Min_degree) (or $context.As_directed (lt $source $target))}}
edge [ source {{$source}}
target {{$target}}
color "#00FF00"
]
{{ end }}
{{ end }}
{{ end }}
]
`
tmpl, err := digraphTpl.Funcs(template.FuncMap{
"Sqrt": func(i float64) int { return scale * int(math.Floor(math.Sqrt(i))) },
"Name": func() string { return g.Name },
"VertexId": func(i int) string { return g.VertexId(i) },
"Degree": func(i int) int { return g.Degree(i) },
"VertexWeight": func(i int) float64 { return g.VertexWeight(i) },
"Shape": func(i int) string {
if g.VertexWeight(i) >= threshold {
return "circle"
} else {
return "rectangle"
}
},
}).Parse(digraph)
if err != nil {
panic(err)
}
err = tmpl.Execute(&gml, data)
if err != nil {
panic(err)
}
return gml.String()
}
// WriteGml writes the GML representation of the graph to an io.Writer.
//
// w: the io.Writer to write the GML representation to.
// as_directed: whether the graph should be treated as directed or undirected.
// threshold: the threshold value.
// scale: the scaling factor.
func (g *Graph[V, T]) WriteGml(w io.Writer, as_directed bool, min_degree int, threshold float64, scale int) {
_, err := w.Write([]byte(g.Gml(as_directed, min_degree, threshold, scale)))
if err != nil {
panic(err)
}
}
// WriteGmlFile writes the graph in GML format to the specified file.
//
// filename: the name of the file to write the GML representation to.
// as_directed: whether the graph should be treated as directed or undirected.
// threshold: the threshold value.
// scale: the scaling factor.
func (g *Graph[V, T]) WriteGmlFile(filename string, as_directed bool, min_degree int, threshold float64, scale int) {
f, err := os.Create(filename)
if err != nil {
panic(err)
}
defer f.Close()
g.WriteGml(f, as_directed, min_degree, threshold, scale)
}