diff --git a/cmd/cyan/main.go b/cmd/cyan/main.go index 574d885..08e7d74 100644 --- a/cmd/cyan/main.go +++ b/cmd/cyan/main.go @@ -20,6 +20,7 @@ import ( "github.com/rwcarlsen/cyan/nuc" "github.com/rwcarlsen/cyan/post" "github.com/rwcarlsen/cyan/query" + "github.com/rwcarlsen/cyan/taint" _ "github.com/rwcarlsen/go-sqlite3" ) @@ -857,7 +858,13 @@ func doFlowGraph(cmd string, args []string) { fmt.Println(" nodesep=1.0;") fmt.Println(" edge [fontsize=9];") for _, arc := range arcs { - fmt.Printf(" \"%v\" -> \"%v\" [label=\"%v\\n(%.3g kg)\"];\n", arc.Src, arc.Dst, arc.Commod, arc.Quantity) + srcname := arc.SrcProto + dstname := arc.DstProto + if !*proto { + srcname = fmt.Sprintf("%v %v", arc.SrcProto, arc.SrcId) + dstname = fmt.Sprintf("%v %v", arc.DstProto, arc.DstId) + } + fmt.Printf(" \"%v\" -> \"%v\" [label=\"%v\\n(%.3g kg)\"];\n", srcname, dstname, arc.Commod, arc.Quantity) } fmt.Println("}") } @@ -990,63 +997,52 @@ func doTaint(cmd string, args []string) { log.Printf("%v\n", cmds.Help(cmd)) fs.PrintDefaults() } - t := fs.Int("time", -1, "time step for which to print taint") + t := fs.Int("t", -1, "time step for which to print taint") res := fs.Int("res", -1, "resource ID of object to track") fs.Parse(args) initdb() if *t == -1 || *res == -1 { - log.Fatalf("'-t' and '-res' flags are both required") + log.Fatalf("'-t' and '-res' flags are both required and cannot be negative") } - s := ` -SELECT t.time AS Time,t.SenderId AS SenderId,send.Prototype AS SenderProto,t.ReceiverId AS ReceiverId,recv.Prototype AS ReceiverProto,t.Commodity AS Commodity,SUM(r.Quantity*c.MassFrac) AS Quantity -FROM transactions AS t -JOIN resources AS r ON t.resourceid=r.resourceid AND r.simid=t.simid -JOIN agents AS send ON t.senderid=send.agentid AND send.simid=t.simid -JOIN agents AS recv ON t.receiverid=recv.agentid AND recv.simid=t.simid -JOIN compositions AS c ON c.qualid=r.qualid AND c.simid=t.simid -WHERE t.simid=? {{index . 0}} {{index . 1}} {{index . 2}} {{index . 3}} -GROUP BY t.transactionid -` - - filters := make([]string, 4) - iargs := []interface{}{simid} - if *from != "" { - if *byagent { - filters[0] = "AND t.senderid=?" - fromid, err := strconv.Atoi(*from) - if err != nil { - log.Fatalf("invalid agent ID (-from=%v)", *from) - } - iargs = append(iargs, fromid) - } else { - filters[0] = "AND send.prototype=?" - iargs = append(iargs, *from) - } - } - if *to != "" { - if *byagent { - filters[1] = "AND t.receiverid=?" - toid, err := strconv.Atoi(*to) - if err != nil { - log.Fatalf("invalid agent ID (-to=%v)", *to) - } - iargs = append(iargs, toid) - } else { - filters[1] = "AND recv.prototype=?" - iargs = append(iargs, *to) + roots := taint.TreeFromDb(db, simid) + var base *taint.Node + for _, root := range roots { + if base = root.Locate(*res); base != nil { + break } } - if *commod != "" { - filters[2] = "AND t.commodity=?" - iargs = append(iargs, *commod) + if base == nil { + log.Fatalf("couldn't find resource id %v in graph", *res) } - filters[3] = nuclidefilter(*nucs) - tmpl := template.Must(template.New("sql").Parse(s)) - var buf bytes.Buffer - tmpl.Execute(&buf, filters) - customSql[cmd] = buf.String() - doCustom(os.Stdout, cmd, iargs...) + taints := base.Taint() + + // print graph dot file + byproto := false + t1, t2 := 0, -1 + arcs, err := query.FlowGraph(db, simid, t1, t2, byproto) + fatalif(err) + + fmt.Println("digraph ResourceFlows {") + fmt.Println(" overlap = false;") + fmt.Println(" nodesep=1.0;") + fmt.Println(" edge [fontsize=9];") + for _, arc := range arcs { + var srctaint taint.TaintVal + if ts := taints[arc.SrcId]; *t < len(ts) { + srctaint = ts[*t] + } + var dsttaint taint.TaintVal + if ts := taints[arc.DstId]; *t < len(ts) { + dsttaint = ts[*t] + } + //srccolor := (srctaint.Taint * srctaint.Quantity) / base.Quantity + //dstcolor := (dsttaint.Taint * dsttaint.Quantity) / base.Quantity + srcname := fmt.Sprintf("%v %v\\n(%.5g kg of %.5g taint)", arc.SrcProto, arc.SrcId, srctaint.Quantity, srctaint.Taint) + dstname := fmt.Sprintf("%v %v\\n(%.5g kg of %.5g taint)", arc.DstProto, arc.DstId, dsttaint.Quantity, dsttaint.Taint) + fmt.Printf(" \"%v\" -> \"%v\" [label=\"%v\"];\n", srcname, dstname, arc.Commod) + } + fmt.Println("}") } diff --git a/query/query.go b/query/query.go index 335f5a8..f7e59b1 100644 --- a/query/query.go +++ b/query/query.go @@ -245,8 +245,11 @@ func InvMassAt(db *sql.DB, simid []byte, t int, agents ...int) (mass float64, er } type FlowArc struct { - Src string - Dst string + SrcId int + DstId int + SrcProto string + DstProto string + AgentId int Commod string Quantity float64 } @@ -262,7 +265,7 @@ func FlowGraph(db *sql.DB, simid []byte, t0, t1 int, groupByProto bool) (arcs [] var sql string if !groupByProto { - sql = `SELECT snd.Prototype || " " || tr.SenderId,rcv.Prototype || " " || tr.ReceiverId,tr.Commodity,SUM(res.Quantity) FROM ( + sql = `SELECT snd.AgentId,rcv.AgentId,snd.Prototype,rcv.Prototype,tr.Commodity,SUM(res.Quantity) FROM ( Resources AS res INNER JOIN Transactions AS tr ON tr.ResourceId = res.ResourceId INNER JOIN Agents AS snd ON snd.AgentId = tr.SenderId @@ -272,7 +275,7 @@ func FlowGraph(db *sql.DB, simid []byte, t0, t1 int, groupByProto bool) (arcs [] AND tr.Time >= ? AND tr.Time < ? ) GROUP BY tr.SenderId,tr.ReceiverId,tr.Commodity;` } else { - sql = `SELECT snd.Prototype,rcv.Prototype,tr.Commodity,SUM(res.Quantity) FROM ( + sql = `SELECT snd.AgentId,rcv.AgentId,snd.Prototype,rcv.Prototype,tr.Commodity,SUM(res.Quantity) FROM ( Resources AS res INNER JOIN Transactions AS tr ON tr.ResourceId = res.ResourceId INNER JOIN Agents AS snd ON snd.AgentId = tr.SenderId @@ -289,7 +292,7 @@ func FlowGraph(db *sql.DB, simid []byte, t0, t1 int, groupByProto bool) (arcs [] } for rows.Next() { arc := FlowArc{} - if err := rows.Scan(&arc.Src, &arc.Dst, &arc.Commod, &arc.Quantity); err != nil { + if err := rows.Scan(&arc.SrcId, &arc.DstId, &arc.SrcProto, &arc.DstProto, &arc.Commod, &arc.Quantity); err != nil { return nil, err } arcs = append(arcs, arc) diff --git a/taint/taint.go b/taint/taint.go index edee599..524f7b7 100644 --- a/taint/taint.go +++ b/taint/taint.go @@ -4,6 +4,7 @@ import ( "bytes" "database/sql" "fmt" + "sort" "strings" ) @@ -23,6 +24,21 @@ type Node struct { par2mark bool } +type bytime []*NodeData + +func (ns bytime) Len() int { return len(ns) } +func (ns bytime) Swap(i, j int) { ns[i], ns[j] = ns[j], ns[i] } +func (ns bytime) Less(i, j int) bool { + if ns[i].Time != ns[j].Time { + return ns[i].Time < ns[j].Time + } else if ns[i].ResId != ns[j].ResId { + return ns[i].ResId < ns[j].ResId + } else if ns[i].AgentId != -1 && ns[j].AgentId != -1 { + return ns[i].AgentId < ns[j].AgentId + } + return false +} + func (n *Node) String() string { var buf bytes.Buffer n.str(&buf, 0) @@ -65,6 +81,7 @@ type NodeData struct { type nodeMap map[int][]*Node func Tree(nodes []*NodeData) (roots []*Node) { + sort.Sort(bytime(nodes)) nodemap := nodeMap{} nextid := 0 @@ -111,9 +128,34 @@ func Tree(nodes []*NodeData) (roots []*Node) { roots = append(roots, node) } } + + for _, root := range roots { + root.fixagentid() + } return roots } +// fixagentid assigns missing AgentId's to nodes. some nodes if generated from a cyclus database query don't have AgentId's +// associated with them and had them marked as -1. These are ommitted from +// the db because they don't affect inventories (i.e. intra-time-step, +// intra-agent modifications). So we can fill these in by assigning the same +// agentid as the parent node(s). +func (n *Node) fixagentid() { + if n == nil { + return + } + + if n.AgentId == -1 { + if n.Parent1 != nil { + n.AgentId = n.Parent1.AgentId + } else { + // give up? + } + } + n.Child1.fixagentid() + n.Child2.fixagentid() +} + type TaintVal struct { Taint float64 Quantity float64 @@ -154,11 +196,18 @@ func (n *Node) Taint() map[int][]TaintVal { n.ResetTaint() n.taintfrac = 1.0 + + // mark dirty edges n.Child1.mark() n.Child2.mark() + + // calculate taintfracs n.Child1.taint() n.Child2.taint() + + // aggregate by agent id and time n.taintnodes(all) + return all } @@ -203,7 +252,6 @@ func (n *Node) taint() { n.Parent2.taintfrac*n.Parent2.Quantity) / n.Quantity } - fmt.Printf("agent %v, t %v: taint=%v\n", n.AgentId, n.Time, n.taintfrac) n.Child1.taint() n.Child2.taint() } @@ -227,6 +275,23 @@ func (n *Node) taintnodes(all map[int][]TaintVal) { Taint: taintqty / qty, Quantity: qty, } + + // fill in blank times between this node and its next child + if n.Child1 != nil { + for len(all[n.AgentId]) < n.Child1.Time+1 { + all[n.AgentId] = append(all[n.AgentId], TaintVal{}) + } + for t := n.Time + 1; t < n.Child1.Time; t++ { + prev := all[n.AgentId][t] + qty := prev.Quantity + n.Quantity + taintqty := prev.Taint*prev.Quantity + n.taintfrac*n.Quantity + all[n.AgentId][t] = TaintVal{ + Taint: taintqty / qty, + Quantity: qty, + } + + } + } } if n.Child1 != nil { @@ -240,9 +305,9 @@ func (n *Node) taintnodes(all map[int][]TaintVal) { func TreeFromDb(db *sql.DB, simid []byte) (roots []*Node) { s := `SELECT r.ResourceId,r.TimeCreated,inv.StartTime,r.Quantity,r.QualId,r.Parent1,r.Parent2,inv.AgentId FROM resources AS r - INNER JOIN Inventories AS inv ON inv.SimId = r.SimId AND inv.ResourceId = r.ResourceId + LEFT JOIN Inventories AS inv ON inv.SimId = r.SimId AND inv.ResourceId = r.ResourceId WHERE r.SimId = ? - ORDER BY r.TimeCreated,r.ResourceId,inv.StartTime` + ORDER BY r.ResourceId,r.TimeCreated,inv.StartTime` rows, err := db.Query(s, simid) if err != nil { panic(err.Error()) @@ -250,12 +315,13 @@ func TreeFromDb(db *sql.DB, simid []byte) (roots []*Node) { defer rows.Close() nodes := []*NodeData{} - var time2 int + var time2 sql.NullInt64 + var agentid sql.NullInt64 for rows.Next() { n := &NodeData{} - err := rows.Scan(&n.ResId, &n.Time, &time2, &n.Quantity, &n.QualId, &n.Parent1, &n.Parent2, &n.AgentId) + err := rows.Scan(&n.ResId, &n.Time, &time2, &n.Quantity, &n.QualId, &n.Parent1, &n.Parent2, &agentid) if err != nil { panic(err.Error()) } @@ -264,8 +330,13 @@ func TreeFromDb(db *sql.DB, simid []byte) (roots []*Node) { // its current state, then the node must be associated with the time // when the resource moved into that agent rather than when it was // created. - if time2 > n.Time { - n.Time = time2 + if time2.Valid && int(time2.Int64) > n.Time { + n.Time = int(time2.Int64) + } + + n.AgentId = -1 + if agentid.Valid { + n.AgentId = int(agentid.Int64) } nodes = append(nodes, n)