Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add pert calculation #6

Merged
merged 2 commits into from
Jun 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion attr.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
4 changes: 4 additions & 0 deletions edge.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
7 changes: 6 additions & 1 deletion graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
49 changes: 47 additions & 2 deletions pert.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package graphman

import (
"fmt"
"math"
"strconv"
"strings"
"time"
Expand All @@ -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
}

Expand All @@ -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()),
)
Expand All @@ -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
}
112 changes: 93 additions & 19 deletions pert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
2 changes: 2 additions & 0 deletions vertex.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down