From 35f82feb3ecb0b22f2e16100b9144b3bcb6e27af Mon Sep 17 00:00:00 2001 From: filifa Date: Sat, 25 May 2024 17:28:47 -0500 Subject: [PATCH] initial commit --- .gitignore | 2 + go.mod | 5 ++ go.sum | 2 + main.go | 42 ++++++++++++++++ projectnetwork.go | 124 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 175 insertions(+) create mode 100644 .gitignore create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 projectnetwork.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ad4c48e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +slacktime +*.gv diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..358f7b0 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module scm.dairydemon.net/filifa/slacktime + +go 1.19 + +require gonum.org/v1/gonum v0.15.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..2ea68bd --- /dev/null +++ b/go.sum @@ -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= diff --git a/main.go b/main.go new file mode 100644 index 0000000..43a11b5 --- /dev/null +++ b/main.go @@ -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)) +} diff --git a/projectnetwork.go b/projectnetwork.go new file mode 100644 index 0000000..eb1fbd1 --- /dev/null +++ b/projectnetwork.go @@ -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 +}