/* Copyright (C) 2024 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 . */ 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" ) // projectNetwork is a directed graph of activity nodes type projectNetwork struct { *simple.DirectedGraph } // newProjectNetwork is a constructor for a projectNetwork func newProjectNetwork() *projectNetwork { return &projectNetwork{DirectedGraph: simple.NewDirectedGraph()} } // NewNode returns an activity node func (g *projectNetwork) NewNode() graph.Node { return &activity{Node: g.DirectedGraph.NewNode()} } // calculateTimes computes the times for each activity in g 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 } // forwardPass computes the early times for each activity func (g *projectNetwork) forwardPass(toposort []*activity) { for _, a := range toposort { g.setEarlyTimes(a) } } // backwardPass computes the late times and slack for each activity using the // early times func (g *projectNetwork) backwardPass(toposort []*activity) { for i := len(toposort) - 1; i >= 0; i-- { a := toposort[i] g.setLateTimes(a) } } // setEarlyTimes sets the early times for an activity func (g *projectNetwork) setEarlyTimes(a *activity) { predecessors := g.To(a.ID()) for predecessors.Next() { p := predecessors.Node().(*activity) if a.earlyStart < p.earlyFinish { a.earlyStart = p.earlyFinish } } a.earlyFinish = a.earlyStart + a.duration } // setLateTimes sets the late times for an activity func (g *projectNetwork) setLateTimes(a *activity) { a.lateFinish = math.Inf(1) successors := g.From(a.ID()) for successors.Next() { s := successors.Node().(*activity) if a.lateFinish > s.lateStart { a.lateFinish = s.lateStart } } if a.lateFinish == math.Inf(1) { a.lateFinish = a.earlyFinish } a.lateStart = a.lateFinish - a.duration a.slack = a.lateFinish - a.earlyFinish } // an activity represents a task in the project network type activity struct { graph.Node dotID string duration float64 earlyStart float64 earlyFinish float64 lateStart float64 lateFinish float64 slack float64 } // Attributes returns all the activity attributes to include in the DOT file func (a *activity) Attributes() []encoding.Attribute { return []encoding.Attribute{ {"DUR", strconv.FormatFloat(a.duration, 'f', 2, 64)}, {"ES", strconv.FormatFloat(a.earlyStart, 'f', 2, 64)}, {"EF", strconv.FormatFloat(a.earlyFinish, 'f', 2, 64)}, {"LS", strconv.FormatFloat(a.lateStart, 'f', 2, 64)}, {"LF", strconv.FormatFloat(a.lateFinish, 'f', 2, 64)}, {"SLACK", strconv.FormatFloat(a.slack, 'f', 2, 64)}, } } // SetAttribute permits reading the duration from the DOT file 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 } // DOTID returns the DOTID of the activity func (a *activity) DOTID() string { return a.dotID } // SetDOTID sets the DOT ID of the activity func (a *activity) SetDOTID(id string) { a.dotID = id }