diff --git a/attr.go b/attr.go index 445d821..55a22d4 100644 --- a/attr.go +++ b/attr.go @@ -40,10 +40,18 @@ func (a Attrs) SetTitle(title string) Attrs { } func (a Attrs) SetPert(opt, real, pess float64) Attrs { - a["pert"] = PertAttrs{ + a["pert"] = &PertAttrs{ Optimistic: opt, Realistic: real, Pessimistic: pess, } return a } + +func (a Attrs) GetPert() *PertAttrs { + pa, found := a["pert"] + if !found { + return nil + } + return pa.(*PertAttrs) +} diff --git a/edge.go b/edge.go index f0646c0..5436658 100644 --- a/edge.go +++ b/edge.go @@ -11,10 +11,14 @@ type Edge struct { Attrs } +type EdgeCostFN func(e *Edge) int64 + func newEdge(src, dst *Vertex, attrs ...Attrs) *Edge { var a Attrs if len(attrs) > 0 { a = attrs[0] + } else { + a = make(map[string]interface{}) } return &Edge{ src: src, diff --git a/graph.go b/graph.go index 75b8741..9ace9b5 100644 --- a/graph.go +++ b/graph.go @@ -115,6 +115,11 @@ func (g Graph) findAllPathsRec(current, target *Vertex, prefix Path) Paths { } func (g Graph) FindShortestPath(srcID, dstID string) (Path, int64) { + costFN := func(e *Edge) int64 { return 1 } + return g.FindShortestPathFN(srcID, dstID, costFN) +} + +func (g Graph) FindShortestPathFN(srcID, dstID string, fn EdgeCostFN) (Path, int64) { src := g.GetVertex(srcID) dst := g.GetVertex(dstID) if src == nil || dst == nil { @@ -147,7 +152,7 @@ func (g Graph) FindShortestPath(srcID, dstID string) (Path, int64) { if n.dijkstra.visited { continue } - dist := u.dijkstra.dist + 1 // 1 could be replace by a value + dist := u.dijkstra.dist + fn(e) if dist < n.dijkstra.dist { n.dijkstra.dist = dist n.dijkstra.prev = e diff --git a/pert.go b/pert.go index 56d05bd..74ba945 100644 --- a/pert.go +++ b/pert.go @@ -2,6 +2,7 @@ package graphman import ( "fmt" + "math" "strconv" "strings" "time" @@ -16,7 +17,7 @@ type PertAttrs struct { Pessimistic, Realistic, Optimistic float64 } -func (pa PertAttrs) Estimate() float64 { +func (pa PertAttrs) WeightedEstimate() float64 { return (pa.Pessimistic + pa.Optimistic + 4*pa.Realistic) / 6 } @@ -29,13 +30,17 @@ func (pa PertAttrs) Variance() float64 { return sd * sd } +func (pa PertAttrs) AverageEstimate() float64 { + return (pa.Pessimistic + pa.Optimistic + pa.Realistic) / 3 +} + func (pa PertAttrs) String() string { return fmt.Sprintf( "To=%s,Tm=%s,Tp=%s,Te=%s,σe=%s,Ve=%s", prettyFloat(pa.Optimistic), prettyFloat(pa.Realistic), prettyFloat(pa.Pessimistic), - prettyFloat(pa.Estimate()), + prettyFloat(pa.WeightedEstimate()), prettyFloat(pa.StandardDeviation()), prettyFloat(pa.Variance()), ) @@ -47,3 +52,43 @@ func prettyFloat(f float64) string { out = strings.TrimRight(out, ".") return out } + +type PertResult struct { + CriticalPathVariance float64 + CriticalPathStandardDeviation float64 + CriticalPath Path +} + +func (pr PertResult) String() string { + return fmt.Sprintf( + "Tσe=%s,TVe=%s", + prettyFloat(pr.CriticalPathStandardDeviation), + prettyFloat(pr.CriticalPathVariance), + ) +} + +func ComputePert(g *Graph) PertResult { + result := PertResult{} + + // apply pert defaults before computing + for _, edge := range g.edges { + pa := edge.GetPert() + if pa == nil { + if edge.Attrs == nil { + edge.Attrs = make(map[string]interface{}) + } + edge.SetPert(1, 1, 1) + pa = edge.GetPert() + } + if pa.Realistic == 0 { + pa.Realistic = 1 + } + if pa.Pessimistic == 0 && pa.Optimistic == 0 { + pa.Pessimistic = pa.Realistic + pa.Optimistic = pa.Realistic + } + result.CriticalPathVariance += pa.Variance() + } + result.CriticalPathStandardDeviation = math.Sqrt(result.CriticalPathVariance) + return result +} diff --git a/pert_test.go b/pert_test.go index 1468530..3037075 100644 --- a/pert_test.go +++ b/pert_test.go @@ -4,28 +4,102 @@ import ( "fmt" ) -func Example_pert() { +func ExamplePertResult_withValue() { graph := New() - graph.AddEdge("1", "2", Attrs{}.SetTitle("a").SetPert(3, 6, 15)) - graph.AddEdge("1", "3", Attrs{}.SetTitle("b").SetPert(2, 5, 14)) - graph.AddEdge("1", "4", Attrs{}.SetTitle("c").SetPert(6, 12, 30)) - graph.AddEdge("2", "5", Attrs{}.SetTitle("d").SetPert(2, 5, 8)) - graph.AddEdge("2", "6", Attrs{}.SetTitle("e").SetPert(5, 11, 17)) - graph.AddEdge("3", "6", Attrs{}.SetTitle("f").SetPert(3, 6, 15)) - graph.AddEdge("4", "7", Attrs{}.SetTitle("g").SetPert(3, 9, 27)) - graph.AddEdge("5", "7", Attrs{}.SetTitle("h").SetPert(1, 4, 7)) - graph.AddEdge("6", "7", Attrs{}.SetTitle("i").SetPert(4, 19, 28)) + graph.AddEdge("1", "2", Attrs{}.SetPert(3, 6, 15)) + graph.AddEdge("1", "3", Attrs{}.SetPert(2, 5, 14)) + graph.AddEdge("1", "4", Attrs{}.SetPert(6, 12, 30)) + graph.AddEdge("2", "5", Attrs{}.SetPert(2, 5, 8)) + graph.AddEdge("2", "6", Attrs{}.SetPert(5, 11, 17)) + graph.AddEdge("3", "6", Attrs{}.SetPert(3, 6, 15)) + graph.AddEdge("4", "7", Attrs{}.SetPert(3, 9, 27)) + graph.AddEdge("5", "7", Attrs{}.SetPert(1, 4, 7)) + graph.AddEdge("6", "7", Attrs{}.SetPert(4, 19, 28)) + + fmt.Println("graph before computing:") + for _, e := range graph.Edges() { + fmt.Println("*", e) + } + fmt.Println() + + result := ComputePert(graph) + fmt.Println("graph after computing:") for _, e := range graph.Edges() { fmt.Println("*", e) } + fmt.Println("result:", result) + + // Output: + // graph before computing: + // * (1,2)[[pert:To=3,Tm=6,Tp=15,Te=7,σe=2,Ve=4]] + // * (1,3)[[pert:To=2,Tm=5,Tp=14,Te=6,σe=2,Ve=4]] + // * (1,4)[[pert:To=6,Tm=12,Tp=30,Te=14,σe=4,Ve=16]] + // * (2,5)[[pert:To=2,Tm=5,Tp=8,Te=5,σe=1,Ve=1]] + // * (2,6)[[pert:To=5,Tm=11,Tp=17,Te=11,σe=2,Ve=4]] + // * (3,6)[[pert:To=3,Tm=6,Tp=15,Te=7,σe=2,Ve=4]] + // * (4,7)[[pert:To=3,Tm=9,Tp=27,Te=11,σe=4,Ve=16]] + // * (5,7)[[pert:To=1,Tm=4,Tp=7,Te=4,σe=1,Ve=1]] + // * (6,7)[[pert:To=4,Tm=19,Tp=28,Te=18,σe=4,Ve=16]] + // + // graph after computing: + // * (1,2)[[pert:To=3,Tm=6,Tp=15,Te=7,σe=2,Ve=4]] + // * (1,3)[[pert:To=2,Tm=5,Tp=14,Te=6,σe=2,Ve=4]] + // * (1,4)[[pert:To=6,Tm=12,Tp=30,Te=14,σe=4,Ve=16]] + // * (2,5)[[pert:To=2,Tm=5,Tp=8,Te=5,σe=1,Ve=1]] + // * (2,6)[[pert:To=5,Tm=11,Tp=17,Te=11,σe=2,Ve=4]] + // * (3,6)[[pert:To=3,Tm=6,Tp=15,Te=7,σe=2,Ve=4]] + // * (4,7)[[pert:To=3,Tm=9,Tp=27,Te=11,σe=4,Ve=16]] + // * (5,7)[[pert:To=1,Tm=4,Tp=7,Te=4,σe=1,Ve=1]] + // * (6,7)[[pert:To=4,Tm=19,Tp=28,Te=18,σe=4,Ve=16]] + // result: Tσe=8.12,TVe=66 +} + +func ExamplePertResult_withoutValue() { + graph := New() + graph.AddEdge("1", "2") + graph.AddEdge("1", "3") + graph.AddEdge("1", "4") + graph.AddEdge("2", "5") + graph.AddEdge("2", "6") + graph.AddEdge("3", "6") + graph.AddEdge("4", "7") + graph.AddEdge("5", "7") + graph.AddEdge("6", "7") + + fmt.Println("graph before computing:") + for _, e := range graph.Edges() { + fmt.Println("*", e) + } + fmt.Println() + + result := ComputePert(graph) + fmt.Println("graph after computing:") + for _, e := range graph.Edges() { + fmt.Println("*", e) + } + fmt.Println("result:", result) + // Output: - // * (1,2)[[pert:To=3,Tm=6,Tp=15,Te=7,σe=2,Ve=4,title:a]] - // * (1,3)[[pert:To=2,Tm=5,Tp=14,Te=6,σe=2,Ve=4,title:b]] - // * (1,4)[[pert:To=6,Tm=12,Tp=30,Te=14,σe=4,Ve=16,title:c]] - // * (2,5)[[pert:To=2,Tm=5,Tp=8,Te=5,σe=1,Ve=1,title:d]] - // * (2,6)[[pert:To=5,Tm=11,Tp=17,Te=11,σe=2,Ve=4,title:e]] - // * (3,6)[[pert:To=3,Tm=6,Tp=15,Te=7,σe=2,Ve=4,title:f]] - // * (4,7)[[pert:To=3,Tm=9,Tp=27,Te=11,σe=4,Ve=16,title:g]] - // * (5,7)[[pert:To=1,Tm=4,Tp=7,Te=4,σe=1,Ve=1,title:h]] - // * (6,7)[[pert:To=4,Tm=19,Tp=28,Te=18,σe=4,Ve=16,title:i]] + // graph before computing: + // * (1,2) + // * (1,3) + // * (1,4) + // * (2,5) + // * (2,6) + // * (3,6) + // * (4,7) + // * (5,7) + // * (6,7) + // + // graph after computing: + // * (1,2)[[pert:To=1,Tm=1,Tp=1,Te=1,σe=0,Ve=0]] + // * (1,3)[[pert:To=1,Tm=1,Tp=1,Te=1,σe=0,Ve=0]] + // * (1,4)[[pert:To=1,Tm=1,Tp=1,Te=1,σe=0,Ve=0]] + // * (2,5)[[pert:To=1,Tm=1,Tp=1,Te=1,σe=0,Ve=0]] + // * (2,6)[[pert:To=1,Tm=1,Tp=1,Te=1,σe=0,Ve=0]] + // * (3,6)[[pert:To=1,Tm=1,Tp=1,Te=1,σe=0,Ve=0]] + // * (4,7)[[pert:To=1,Tm=1,Tp=1,Te=1,σe=0,Ve=0]] + // * (5,7)[[pert:To=1,Tm=1,Tp=1,Te=1,σe=0,Ve=0]] + // * (6,7)[[pert:To=1,Tm=1,Tp=1,Te=1,σe=0,Ve=0]] + // result: Tσe=0,TVe=0 } diff --git a/vertex.go b/vertex.go index 587452b..8f1a6ee 100644 --- a/vertex.go +++ b/vertex.go @@ -26,6 +26,8 @@ func newVertex(id string, attrs ...Attrs) *Vertex { var a Attrs if len(attrs) > 0 { a = attrs[0] + } else { + a = make(map[string]interface{}) } return &Vertex{ id: id,