initial commit
This commit is contained in:
commit
35f82feb3e
|
@ -0,0 +1,2 @@
|
|||
slacktime
|
||||
*.gv
|
|
@ -0,0 +1,5 @@
|
|||
module scm.dairydemon.net/filifa/slacktime
|
||||
|
||||
go 1.19
|
||||
|
||||
require gonum.org/v1/gonum v0.15.0 // indirect
|
|
@ -0,0 +1,2 @@
|
|||
gonum.org/v1/gonum v0.15.0 h1:2lYxjRbTYyxkJxlhC+LvJIx3SsANPdRybu1tGj9/OrQ=
|
||||
gonum.org/v1/gonum v0.15.0/go.mod h1:xzZVBJBtS+Mz4q0Yl2LJTk+OxOg4jiXZ7qBoM0uISGo=
|
|
@ -0,0 +1,42 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"gonum.org/v1/gonum/graph/encoding/dot"
|
||||
)
|
||||
|
||||
func main() {
|
||||
f := flag.String("f", "", "graphviz file")
|
||||
flag.Parse()
|
||||
|
||||
if *f == "" {
|
||||
log.Fatal("-f is required")
|
||||
}
|
||||
|
||||
contents, err := os.ReadFile(*f)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
g := newProjectNetwork()
|
||||
err = dot.Unmarshal(contents, g)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = g.calculateTimes()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
data, err := dot.Marshal(g, "", "", "\t")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println(string(data))
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"gonum.org/v1/gonum/graph"
|
||||
"gonum.org/v1/gonum/graph/encoding"
|
||||
"gonum.org/v1/gonum/graph/simple"
|
||||
"gonum.org/v1/gonum/graph/topo"
|
||||
)
|
||||
|
||||
type projectNetwork struct {
|
||||
*simple.DirectedGraph
|
||||
}
|
||||
|
||||
func newProjectNetwork() *projectNetwork {
|
||||
return &projectNetwork{DirectedGraph: simple.NewDirectedGraph()}
|
||||
}
|
||||
|
||||
func (g *projectNetwork) NewNode() graph.Node {
|
||||
return &activity{Node: g.DirectedGraph.NewNode()}
|
||||
}
|
||||
|
||||
func (g *projectNetwork) calculateTimes() error {
|
||||
sorted, err := topo.Sort(g)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
toposort := make([]*activity, len(sorted))
|
||||
for i := range sorted {
|
||||
toposort[i] = sorted[i].(*activity)
|
||||
}
|
||||
|
||||
g.forwardPass(toposort)
|
||||
g.backwardPass(toposort)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *projectNetwork) forwardPass(toposort []*activity) {
|
||||
for _, a := range toposort {
|
||||
g.setEarlyTimes(a)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *projectNetwork) backwardPass(toposort []*activity) {
|
||||
n := len(toposort)
|
||||
|
||||
end := toposort[n-1]
|
||||
end.lateFinish = end.earlyFinish
|
||||
end.lateStart = end.lateFinish - end.duration
|
||||
|
||||
for i := n - 2; i >= 0; i-- {
|
||||
a := toposort[i]
|
||||
g.setLateTimes(a)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *projectNetwork) setEarlyTimes(a *activity) {
|
||||
es := 0.0
|
||||
predecessors := g.To(a.ID())
|
||||
for predecessors.Next() {
|
||||
p := predecessors.Node().(*activity)
|
||||
es = math.Max(es, p.earlyFinish)
|
||||
}
|
||||
a.earlyStart = es
|
||||
a.earlyFinish = a.earlyStart + a.duration
|
||||
}
|
||||
|
||||
func (g *projectNetwork) setLateTimes(a *activity) {
|
||||
lf := math.Inf(1)
|
||||
successors := g.From(a.ID())
|
||||
for successors.Next() {
|
||||
s := successors.Node().(*activity)
|
||||
lf = math.Min(lf, s.lateStart)
|
||||
}
|
||||
a.lateFinish = lf
|
||||
a.lateStart = a.lateFinish - a.duration
|
||||
a.slack = a.lateFinish - a.earlyFinish
|
||||
}
|
||||
|
||||
type activity struct {
|
||||
graph.Node
|
||||
dotID string
|
||||
duration float64
|
||||
earlyStart float64
|
||||
earlyFinish float64
|
||||
lateStart float64
|
||||
lateFinish float64
|
||||
slack float64
|
||||
}
|
||||
|
||||
func (a *activity) Attributes() []encoding.Attribute {
|
||||
return []encoding.Attribute{
|
||||
{"DUR", strconv.FormatFloat(a.duration, 'g', -1, 64)},
|
||||
{"ES", strconv.FormatFloat(a.earlyStart, 'g', -1, 64)},
|
||||
{"EF", strconv.FormatFloat(a.earlyStart, 'g', -1, 64)},
|
||||
{"LS", strconv.FormatFloat(a.lateStart, 'g', -1, 64)},
|
||||
{"LF", strconv.FormatFloat(a.lateFinish, 'g', -1, 64)},
|
||||
{"SLACK", strconv.FormatFloat(a.slack, 'g', -1, 64)},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *activity) SetAttribute(attr encoding.Attribute) error {
|
||||
var err error
|
||||
switch attr.Key {
|
||||
case "DUR":
|
||||
a.duration, err = strconv.ParseFloat(attr.Value, 64)
|
||||
default:
|
||||
err = errors.New("can't set attribute " + attr.Key)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *activity) DOTID() string {
|
||||
return a.dotID
|
||||
}
|
||||
|
||||
func (a *activity) SetDOTID(id string) {
|
||||
a.dotID = id
|
||||
}
|
Loading…
Reference in New Issue