|
@@ -5,6 +5,7 @@ import (
|
|
|
"image"
|
|
|
"image/color"
|
|
|
"log"
|
|
|
+ "math"
|
|
|
"math/rand"
|
|
|
"os"
|
|
|
"strings"
|
|
@@ -23,6 +24,8 @@ import (
|
|
|
"gioui.org/widget/material"
|
|
|
)
|
|
|
|
|
|
+const MAX_LOOP = 100_000_000
|
|
|
+
|
|
|
type C = layout.Context
|
|
|
type D = layout.Dimensions
|
|
|
|
|
@@ -36,6 +39,8 @@ var darkPalette = material.Palette{
|
|
|
func main() {
|
|
|
go func() {
|
|
|
window := new(app.Window)
|
|
|
+ window.Option(app.Title("Not-a-graph-layout-algorithm graph layout algorithm"))
|
|
|
+
|
|
|
err := run(window)
|
|
|
if err != nil {
|
|
|
log.Fatal(err)
|
|
@@ -52,9 +57,11 @@ func run(window *app.Window) error {
|
|
|
theme.Shaper =
|
|
|
text.NewShaper(text.WithCollection(gofont.Collection()))
|
|
|
|
|
|
+ seed := int64(0)
|
|
|
g := Graph{}
|
|
|
- g.random = rand.New(rand.NewSource(0))
|
|
|
+ g.random = rand.New(rand.NewSource(seed))
|
|
|
g.offsets = g._offsets[:]
|
|
|
+ g.degrees = g._degrees[:]
|
|
|
g.nodes = g._nodes[:0]
|
|
|
g.allocs = g._allocs[:]
|
|
|
g.edges = g._edges[:0]
|
|
@@ -64,20 +71,21 @@ func run(window *app.Window) error {
|
|
|
title := material.Body1(theme, "")
|
|
|
title.Color = theme.Fg
|
|
|
title.Alignment = text.Start
|
|
|
+ title.WrapPolicy = text.WrapWords
|
|
|
|
|
|
simPeriod := 50 * time.Millisecond
|
|
|
lastStep := time.Now()
|
|
|
bumpShift := 1
|
|
|
lastSecond := time.Now()
|
|
|
- frameCnt := 0
|
|
|
- stepCnt := 0
|
|
|
+ frameCnt, stepCnt := 0, 0
|
|
|
fps, sps := 0, 0
|
|
|
- lastFinish := time.Now()
|
|
|
+ simFinishedTime := time.Now()
|
|
|
+ simCnt := int64(0)
|
|
|
var (
|
|
|
stepOut strings.Builder
|
|
|
statusText strings.Builder
|
|
|
ops op.Ops
|
|
|
- screensaverMode bool
|
|
|
+ screensaverMode bool = true
|
|
|
finished bool
|
|
|
)
|
|
|
for {
|
|
@@ -117,7 +125,7 @@ func run(window *app.Window) error {
|
|
|
}
|
|
|
case "S":
|
|
|
if evt.State == key.Press {
|
|
|
- screensaverMode = true
|
|
|
+ screensaverMode = !screensaverMode
|
|
|
}
|
|
|
case "<":
|
|
|
if evt.State == key.Press {
|
|
@@ -132,46 +140,21 @@ func run(window *app.Window) error {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
if resetRequested ||
|
|
|
- (screensaverMode &&
|
|
|
- time.Since(lastFinish) >= 10*time.Second) {
|
|
|
+ (screensaverMode && finished &&
|
|
|
+ time.Since(simFinishedTime) >= 5*time.Second) {
|
|
|
(&g).Reset()
|
|
|
- lastFinish = time.Now()
|
|
|
+ resetRequested = false
|
|
|
finished = false
|
|
|
- }
|
|
|
- paint.Fill(&ops, theme.Bg)
|
|
|
-
|
|
|
- //margin := layout.UniformInset(unit.Dp(8))
|
|
|
- layout.Flex{
|
|
|
- Axis: layout.Vertical,
|
|
|
- Alignment: layout.Start,
|
|
|
- Spacing: layout.SpaceEnd,
|
|
|
- }.Layout(gtx,
|
|
|
- layout.Rigid(func(gtx C) D {
|
|
|
- return (&g).Layout(gtx, theme)
|
|
|
- }),
|
|
|
- layout.Rigid(func(gtx C) D {
|
|
|
- return title.Layout(gtx)
|
|
|
- }),
|
|
|
- layout.Rigid(func(gtx C) D {
|
|
|
- return (&g).LayoutDist(gtx, theme)
|
|
|
- }),
|
|
|
- )
|
|
|
-
|
|
|
- if time.Since(lastSecond) >= time.Second {
|
|
|
- fps, sps = frameCnt, stepCnt
|
|
|
- frameCnt, stepCnt = 0, 0
|
|
|
- lastSecond = time.Now()
|
|
|
+ simCnt++
|
|
|
}
|
|
|
|
|
|
- statusText.Reset()
|
|
|
- statusText.WriteString(fmt.Sprintf("simulation rate: %s per step\n", simPeriod))
|
|
|
- statusText.WriteString(fmt.Sprintf("fps=%d; sps=%d\n", fps, sps))
|
|
|
-
|
|
|
if time.Since(lastStep) >= simPeriod {
|
|
|
if int(g.order) == cap(g.nodes) && bumpRequested {
|
|
|
bump(&g, bumpShift)
|
|
|
bumpShift++
|
|
|
+ bumpRequested = false
|
|
|
}
|
|
|
stepOut.Reset()
|
|
|
stepOut.WriteString((&g).Step())
|
|
@@ -181,11 +164,54 @@ func run(window *app.Window) error {
|
|
|
addRandomNode(&g)
|
|
|
stepCnt++
|
|
|
}
|
|
|
+
|
|
|
+ paint.Fill(&ops, theme.Bg)
|
|
|
+
|
|
|
+ layout.UniformInset(
|
|
|
+ unit.Dp(10),
|
|
|
+ ).Layout(gtx, func(gtx C) D {
|
|
|
+ return layout.Flex{
|
|
|
+ Axis: layout.Horizontal,
|
|
|
+ Alignment: layout.Start,
|
|
|
+ Spacing: layout.SpaceEnd,
|
|
|
+ }.Layout(gtx,
|
|
|
+ layout.Rigid(func(gtx C) D {
|
|
|
+ return (&g).Layout(gtx, theme)
|
|
|
+ }),
|
|
|
+ layout.Rigid(func(gtx C) D {
|
|
|
+ return layout.Flex{
|
|
|
+ Axis: layout.Vertical,
|
|
|
+ Alignment: layout.Start,
|
|
|
+ Spacing: layout.SpaceEnd,
|
|
|
+ }.Layout(gtx,
|
|
|
+ layout.Rigid(func(gtx C) D {
|
|
|
+ return (&g).LayoutDist(gtx, theme)
|
|
|
+ }),
|
|
|
+ layout.Flexed(1.0, func(gtx C) D {
|
|
|
+ return title.Layout(gtx)
|
|
|
+ }),
|
|
|
+ )
|
|
|
+ }),
|
|
|
+ )
|
|
|
+ })
|
|
|
+
|
|
|
+ if time.Since(lastSecond) >= time.Second {
|
|
|
+ fps, sps = frameCnt, stepCnt
|
|
|
+ frameCnt, stepCnt = 0, 0
|
|
|
+ lastSecond = time.Now()
|
|
|
+ }
|
|
|
+
|
|
|
+ statusText.Reset()
|
|
|
+ statusText.WriteString(fmt.Sprintf("seed %d, simulation %d\n", seed, simCnt+1))
|
|
|
+ statusText.WriteString(fmt.Sprintf("simulation rate: %s per step\n", simPeriod))
|
|
|
+ statusText.WriteString(fmt.Sprintf("fps=%d; sps=%d\n", fps, sps))
|
|
|
+
|
|
|
statusText.WriteString(stepOut.String())
|
|
|
title.Text = statusText.String()
|
|
|
|
|
|
if int(g.order) == cap(g.nodes) && !finished {
|
|
|
finished = true
|
|
|
+ simFinishedTime = time.Now()
|
|
|
}
|
|
|
e.Source.Execute(op.InvalidateCmd{})
|
|
|
|
|
@@ -224,8 +250,8 @@ func bump(g *Graph, shift int) int {
|
|
|
delete(nextNodes, n)
|
|
|
nodes[i] = n
|
|
|
i++
|
|
|
- if log2(g.nodes[n])+uint32(shift) >= 31 {
|
|
|
- // Out of address space
|
|
|
+ if log2(g.nodes[n])+uint32(shift) >= 30 {
|
|
|
+ log.Printf("Out-of-address-space condition for node %d", n)
|
|
|
continue
|
|
|
}
|
|
|
g.nodes[n] <<= shift
|
|
@@ -235,15 +261,9 @@ func bump(g *Graph, shift int) int {
|
|
|
}
|
|
|
|
|
|
func addRandomNode(g *Graph) (order uint32) {
|
|
|
- order = g.order
|
|
|
if g.order == uint32(cap(g.nodes)) ||
|
|
|
g.size == uint32(cap(g.edges)) {
|
|
|
- return
|
|
|
- }
|
|
|
- threshold := 0.02 * float64(g.order)
|
|
|
-
|
|
|
- if g.random.ExpFloat64() <= threshold {
|
|
|
- return
|
|
|
+ return g.order
|
|
|
}
|
|
|
g.edges = g.edges[:cap(g.edges)]
|
|
|
g.nodes = g.nodes[:cap(g.nodes)]
|
|
@@ -252,23 +272,24 @@ func addRandomNode(g *Graph) (order uint32) {
|
|
|
g.nodes[i] = 1
|
|
|
g.offsets[i] = EdgeId(g.size)
|
|
|
|
|
|
- threshold = float64(2.0)
|
|
|
+ for j := i + 1; j < len(g.nodes); j++ {
|
|
|
+ g.edges[g.size] = NodeId(j)
|
|
|
|
|
|
- for j := 1; i+j < len(g.nodes); j++ {
|
|
|
- g.edges[g.size] = NodeId(i + j)
|
|
|
+ distance := float64(j - i)
|
|
|
+ edgeProbability := 1 / (1 + math.Exp(0.25*distance))
|
|
|
|
|
|
- if g.random.ExpFloat64() <= threshold {
|
|
|
+ if g.random.Float64() > edgeProbability {
|
|
|
continue
|
|
|
}
|
|
|
+ g.degrees[i]++
|
|
|
g.size++
|
|
|
}
|
|
|
g.order++
|
|
|
- order = g.order
|
|
|
g.offsets[g.order] = EdgeId(g.size)
|
|
|
g.nodes = g.nodes[:g.order]
|
|
|
g.edges = g.edges[:g.size]
|
|
|
|
|
|
- return
|
|
|
+ return g.order
|
|
|
}
|
|
|
|
|
|
func generateRandomGraph(g *Graph) {
|
|
@@ -299,34 +320,38 @@ func generateRandomGraph(g *Graph) {
|
|
|
type NodeId uint32
|
|
|
type EdgeId uint32
|
|
|
|
|
|
-const GraphOrder = 64
|
|
|
+const GraphOrder int = 64
|
|
|
|
|
|
type Graph struct {
|
|
|
_offsets [GraphOrder + 1]EdgeId
|
|
|
_nodes [GraphOrder]uint32
|
|
|
_allocs [GraphOrder]uint32
|
|
|
- _edges [(GraphOrder * (GraphOrder - 1)) >> 2]NodeId
|
|
|
+ _edges [(GraphOrder * (GraphOrder - 1)) >> 1]NodeId
|
|
|
+ _degrees [GraphOrder]byte
|
|
|
random *rand.Rand
|
|
|
offsets []EdgeId
|
|
|
nodes []uint32
|
|
|
allocs []uint32 // Address allocation state, per node
|
|
|
- edges []NodeId
|
|
|
- size uint32 // Number of edges added to graph
|
|
|
- order uint32 // Number of nodes added to graph
|
|
|
+ edges []NodeId // Adjacent node ids
|
|
|
+ degrees []byte // Node degrees
|
|
|
+ size uint32 // Number of edges added to graph
|
|
|
+ order uint32 // Number of nodes added to graph
|
|
|
+ source NodeId // A selected source node
|
|
|
+ target NodeId // A selected target node
|
|
|
}
|
|
|
|
|
|
func (g *Graph) Reset() {
|
|
|
- for o := range g.offsets {
|
|
|
- g.offsets[o] = 0
|
|
|
+ for i := range g.offsets {
|
|
|
+ g.offsets[i] = 0
|
|
|
}
|
|
|
- for n := range g.nodes {
|
|
|
- g.nodes[n] = 0
|
|
|
+ for i := range g.nodes {
|
|
|
+ g.nodes[i] = 0
|
|
|
}
|
|
|
- for a := range g.allocs {
|
|
|
- g.allocs[a] = 0
|
|
|
+ for i := range g.allocs {
|
|
|
+ g.allocs[i] = 0
|
|
|
}
|
|
|
- for e := range g.edges {
|
|
|
- g.edges[e] = 0
|
|
|
+ for i := range g.edges {
|
|
|
+ g.edges[i] = 0
|
|
|
}
|
|
|
g.nodes = g.nodes[:0]
|
|
|
g.edges = g.edges[:0]
|
|
@@ -356,22 +381,35 @@ func (g *Graph) Step() string {
|
|
|
}
|
|
|
shift := log2(g.nodes[n]) + 1
|
|
|
|
|
|
+ if log2(g.allocs[n])+shift >= 30 {
|
|
|
+ log.Printf("Out-of-address-space condition for node %d", n)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ edges := g.edges[g.offsets[n]:g.offsets[n+1]]
|
|
|
+
|
|
|
+ // Shift a node containing exactly one 1
|
|
|
+ if (n == 0 || g.nodes[n] > 1) && // root or assigned
|
|
|
+ g.nodes[n]&(g.nodes[n]-1) == 0 { // ...contains one 1
|
|
|
+ if len(edges) > 0 {
|
|
|
+ e := edges[0]
|
|
|
+ if uint32(e) < g.order && g.nodes[n] >= g.nodes[e] {
|
|
|
+ g.nodes[e] = g.nodes[n] << 1
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
// Assign addresses to neighbors
|
|
|
- for _, e := range g.edges[g.offsets[n]:g.offsets[n+1]] {
|
|
|
+ for i := 1; i < len(edges); i++ {
|
|
|
+ nextAlloc := (g.allocs[n] << shift) | g.nodes[n]
|
|
|
+ e := edges[i]
|
|
|
if uint32(e) >= g.order ||
|
|
|
- g.nodes[n] < g.nodes[e] {
|
|
|
- continue
|
|
|
- }
|
|
|
- if log2(g.allocs[n])+shift >= 30 {
|
|
|
- // Out of address space
|
|
|
+ (g.nodes[e] != 1 && nextAlloc >= g.nodes[e]) {
|
|
|
continue
|
|
|
}
|
|
|
- nextAddr := (g.allocs[n] << shift) | g.nodes[n]
|
|
|
- g.nodes[e] = nextAddr
|
|
|
+ g.nodes[e] = nextAlloc
|
|
|
g.allocs[n]++
|
|
|
}
|
|
|
}
|
|
|
- return fmt.Sprintf("nodes=%v\n", g.nodes)
|
|
|
+ return fmt.Sprintf("nodes=%v\nedges=%v", g.nodes, g.edges)
|
|
|
}
|
|
|
|
|
|
func (g *Graph) Layout(
|
|
@@ -385,22 +423,24 @@ func (g *Graph) Layout(
|
|
|
convFactor := float32(squareMax) / 65535.0
|
|
|
circle := clip.Ellipse{Max: image.Pt(16, 16)}
|
|
|
|
|
|
+ // Outer margin
|
|
|
defer op.Offset(image.Pt(10, 10)).Push(gtx.Ops).Pop()
|
|
|
|
|
|
var path clip.Path
|
|
|
for n := 0; n < len(g.nodes); n++ {
|
|
|
- if g.nodes[n] == 1 { // Ignore root and orphaned nodes
|
|
|
- continue
|
|
|
- }
|
|
|
x1, y1 := idToPt(g.nodes[n], convFactor)
|
|
|
|
|
|
for e := range g.edges[g.offsets[n]:g.offsets[n+1]] {
|
|
|
if uint32(e) >= g.order {
|
|
|
continue
|
|
|
}
|
|
|
+ if g.nodes[n] == 1 { // Skip orphans and roots
|
|
|
+ continue
|
|
|
+ }
|
|
|
color := th.Fg
|
|
|
if distance(g.nodes[n], g.nodes[e]) > 4 {
|
|
|
color = th.ContrastBg
|
|
|
+ color.A = 0xd0
|
|
|
}
|
|
|
x2, y2 := idToPt(g.nodes[e], convFactor)
|
|
|
|
|
@@ -414,7 +454,9 @@ func (g *Graph) Layout(
|
|
|
}.Op(),
|
|
|
)
|
|
|
}
|
|
|
- shift := int(log2(g.nodes[n]) >> 3)
|
|
|
+ // Circle size is proportional to address length.
|
|
|
+ length := log2(g.nodes[n])
|
|
|
+ shift := int(length >> 3)
|
|
|
scaled := circle
|
|
|
scaled.Max.X = scaled.Max.X >> shift
|
|
|
scaled.Max.Y = scaled.Max.Y >> shift
|
|
@@ -422,9 +464,16 @@ func (g *Graph) Layout(
|
|
|
|
|
|
pos := image.Pt(gtx.Dp(x1)-halfW, gtx.Dp(y1)-halfH)
|
|
|
|
|
|
+ macro := op.Record(gtx.Ops)
|
|
|
stack := op.Offset(pos).Push(gtx.Ops)
|
|
|
- paint.FillShape(gtx.Ops, th.Fg, scaled.Op(gtx.Ops))
|
|
|
+ nodeColor := th.Fg
|
|
|
+ if length >= 24 {
|
|
|
+ nodeColor = color.NRGBA{R: 0xaa, G: 0, B: 0xaa, A: 0xff}
|
|
|
+ }
|
|
|
+ paint.FillShape(gtx.Ops, nodeColor, scaled.Op(gtx.Ops))
|
|
|
stack.Pop()
|
|
|
+ c := macro.Stop()
|
|
|
+ op.Defer(gtx.Ops, c)
|
|
|
}
|
|
|
return layout.Dimensions{Size: image.Pt(squareMax, squareMax)}
|
|
|
}
|
|
@@ -500,14 +549,14 @@ func idToPt(id uint32, convFactor float32) (x, y unit.Dp) {
|
|
|
|
|
|
func log2(x uint32) uint32 {
|
|
|
b := []uint32{0x2, 0xC, 0xF0, 0xFF00, 0xFFFF0000}
|
|
|
- S := []uint32{1, 2, 4, 8, 16}
|
|
|
+ s := []uint32{1, 2, 4, 8, 16}
|
|
|
|
|
|
var r uint32 = 0
|
|
|
|
|
|
for i := 4; i >= 0; i-- {
|
|
|
if x&b[i] != 0 {
|
|
|
- x >>= S[i]
|
|
|
- r |= S[i]
|
|
|
+ x >>= s[i]
|
|
|
+ r |= s[i]
|
|
|
}
|
|
|
}
|
|
|
return r
|