123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591 |
- package main
- import (
- "fmt"
- "image"
- "image/color"
- "log"
- "math"
- "math/rand"
- "os"
- "strings"
- "time"
- "gioui.org/app"
- "gioui.org/f32"
- "gioui.org/font/gofont"
- "gioui.org/io/key"
- "gioui.org/layout"
- "gioui.org/op"
- "gioui.org/op/clip"
- "gioui.org/op/paint"
- "gioui.org/text"
- "gioui.org/unit"
- "gioui.org/widget/material"
- )
- const MAX_LOOP = 100_000_000
- type C = layout.Context
- type D = layout.Dimensions
- var darkPalette = material.Palette{
- Bg: color.NRGBA{R: 0x28, G: 0x28, B: 0x28, A: 0xff},
- Fg: color.NRGBA{R: 0xeb, G: 0xdb, B: 0xb2, A: 0xff},
- ContrastBg: color.NRGBA{R: 0x48, G: 0x85, B: 0x88, A: 0xff},
- ContrastFg: color.NRGBA{R: 0xeb, G: 0xdb, B: 0xb2, A: 0xff},
- }
- 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)
- }
- os.Exit(0)
- }()
- app.Main()
- }
- func run(window *app.Window) error {
- theme := material.NewTheme()
- theme.Palette = darkPalette
- theme.Shaper =
- text.NewShaper(text.WithCollection(gofont.Collection()))
- seed := int64(0)
- g := Graph{}
- 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]
- g.adjacency = g._adjacency[:0]
- 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, stepCnt := 0, 0
- fps, sps := 0, 0
- simFinishedTime := time.Now()
- simCnt := int64(0)
- var (
- stepOutput strings.Builder
- statusText strings.Builder
- ops op.Ops
- screensaverMode bool = true
- finished,
- resetRequested,
- bumpRequested bool
- )
- for {
- switch e := window.Event().(type) {
- case app.DestroyEvent:
- return e.Err
- case app.FrameEvent:
- gtx := app.NewContext(&ops, e)
- // Handle input
- for {
- event, ok := e.Source.Event(
- key.Filter{Name: "B"},
- key.Filter{Name: "R"},
- key.Filter{Name: "S", Required: key.ModShift},
- key.Filter{Name: "<", Required: key.ModShift},
- key.Filter{Name: ">", Required: key.ModShift},
- )
- if !ok {
- break
- }
- switch evt := event.(type) {
- case key.Event:
- switch evt.Name {
- case "B":
- if evt.State == key.Press {
- bumpRequested = true
- }
- case "R":
- if evt.State == key.Press {
- resetRequested = true
- }
- case "S":
- if evt.State == key.Press {
- screensaverMode = !screensaverMode
- }
- case "<":
- if evt.State == key.Press {
- simPeriod += 50 * time.Millisecond
- simPeriod = min(simPeriod, 10*time.Second)
- }
- case ">":
- if evt.State == key.Press {
- simPeriod -= 50 * time.Millisecond
- simPeriod = max(simPeriod, 0)
- }
- }
- }
- }
- if resetRequested ||
- (screensaverMode && finished &&
- time.Since(simFinishedTime) >= 5*time.Second) {
- (&g).Reset()
- resetRequested = false
- finished = false
- simCnt++
- }
- if time.Since(lastStep) >= simPeriod {
- if int(g.order) == cap(g.nodes) && bumpRequested {
- bump(&g, bumpShift)
- //bumpShift++
- bumpRequested = false
- }
- stepOutput.Reset()
- stepOutput.WriteString((&g).Step())
- //statusText.WriteString((&g).Semilattice())
- lastStep = time.Now()
- addNodeAtRandom(&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(stepOutput.String())
- title.Text = statusText.String()
- if int(g.order) == cap(g.nodes) && !finished {
- finished = true
- simFinishedTime = time.Now()
- }
- e.Source.Execute(op.InvalidateCmd{})
- e.Frame(gtx.Ops)
- frameCnt++
- }
- }
- }
- func bump(g *Graph, shift int) int {
- root := -1
- for n := 0; n < int(g.order); n++ {
- if g.nodes[n] == 1 {
- root = n
- break
- }
- }
- if root < 0 {
- return root
- }
- g.nodes[root] <<= shift
- return root
- nodes := make([]NodeId, 1, g.order)
- nextNodes := make(map[NodeId]struct{}, g.order)
- nodes[0] = NodeId(root)
- for len(nodes) > 0 {
- for _, n := range nodes {
- for _, e := range g.edges[g.offsets[n]:g.offsets[n+1]] {
- nextNodes[e] = struct{}{}
- }
- }
- nodes = nodes[:len(nextNodes)]
- i := 0
- for n := range nextNodes {
- delete(nextNodes, n)
- nodes[i] = n
- i++
- if log2(g.nodes[n])+uint32(shift) >= 30 {
- log.Printf("Out-of-address-space condition for node %d", n)
- continue
- }
- g.nodes[n] <<= shift
- }
- }
- return root
- }
- func addNodeAtRandom(g *Graph) (order uint32) {
- if g.order == uint32(cap(g.nodes)) ||
- g.size == uint32(cap(g.edges)) {
- return g.order
- }
- /*threshold := 0.05 * float64(g.order)
- if g.random.ExpFloat64() <= threshold {
- return g.order
- }*/
- g.edges = g.edges[:cap(g.edges)]
- g.nodes = g.nodes[:cap(g.nodes)]
- i := int(g.order)
- g.nodes[i] = 1
- g.offsets[i] = EdgeId(g.size)
- for j := i + 1; j < len(g.nodes); j++ {
- g.edges[g.size] = NodeId(j)
- distance := float64(j - i)
- edgeProbability := 1 / (1 + math.Exp(0.25*distance))
- if g.random.Float64() > edgeProbability {
- continue
- }
- g.degrees[i]++
- g.size++
- }
- g.order++
- g.offsets[g.order] = EdgeId(g.size)
- g.nodes = g.nodes[:g.order]
- g.edges = g.edges[:g.size]
- return g.order
- }
- type NodeId uint32
- type EdgeId uint32
- const GraphOrder int = 128
- type Graph struct {
- _adjacency [GraphOrder * (GraphOrder - 1)]NodeId
- _edges [(GraphOrder * (GraphOrder - 1)) >> 1]NodeId
- _nodes [GraphOrder]uint32
- _allocs [GraphOrder]uint32
- _offsets [GraphOrder + 1]EdgeId
- _degrees [GraphOrder]byte
- random *rand.Rand
- adjacency []NodeId // Bidirectional adjacent node ids
- edges []NodeId // Unidirectional adjacent node ids
- nodes []uint32
- allocs []uint32 // Address allocation state, per node
- offsets []EdgeId
- 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 i := range g.offsets {
- g.offsets[i] = 0
- }
- for i := range g.nodes {
- g.nodes[i] = 0
- }
- for i := range g.allocs {
- g.allocs[i] = 0
- }
- for i := range g.edges {
- g.edges[i] = 0
- }
- g.nodes = g.nodes[:0]
- g.edges = g.edges[:0]
- g.order, g.size = 0, 0
- }
- func (g *Graph) Semilattice() string {
- var b strings.Builder
- for n := 0; n < len(g.nodes); n++ {
- b.WriteString(fmt.Sprintf("nodeAddr=%d neighbors=", g.nodes[n]))
- for _, e := range g.edges[g.offsets[n]:g.offsets[n+1]] {
- if uint32(e) >= g.order {
- continue
- }
- b.WriteString(fmt.Sprintf("%d, ", g.nodes[e]))
- }
- b.WriteString("\n")
- }
- return b.String()
- }
- func (g *Graph) Step() string {
- for n := range g.nodes {
- if g.allocs[n] == 0 {
- g.allocs[n] = 1 // Seed allocator
- }
- 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]]
- shiftStart := 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
- for i := 0; i < min(shiftStart, len(edges)); i++ {
- e := edges[i]
- if uint32(e) < g.order && g.nodes[n] >= g.nodes[e] {
- g.nodes[e] = g.nodes[n] << (i + 1)
- }
- }
- }
- // Assign addresses to neighbors
- for i := shiftStart; i < len(edges); i++ {
- nextAlloc := (g.allocs[n] << shift) | g.nodes[n]
- e := edges[i]
- if uint32(e) >= g.order ||
- (g.nodes[e] != 1 && nextAlloc >= g.nodes[e]) {
- continue
- }
- g.nodes[e] = nextAlloc
- g.allocs[n]++
- }
- }
- return fmt.Sprintf("nodes=%v\nedges=%v", g.nodes, g.edges)
- }
- func (g *Graph) Layout(
- gtx layout.Context,
- th *material.Theme,
- ) D {
- squareMax := min(
- gtx.Constraints.Max.X,
- gtx.Constraints.Max.Y,
- )
- 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++ {
- 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)
- path.Begin(gtx.Ops)
- path.MoveTo(f32.Pt(float32(x1), float32(y1)))
- path.LineTo(f32.Pt(float32(x2), float32(y2)))
- paint.FillShape(gtx.Ops, color,
- clip.Stroke{
- Path: path.End(),
- Width: float32(unit.Dp(1)),
- }.Op(),
- )
- }
- // 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
- halfW, halfH := scaled.Max.X>>1, scaled.Max.Y>>1
- pos := image.Pt(gtx.Dp(x1)-halfW, gtx.Dp(y1)-halfH)
- macro := op.Record(gtx.Ops)
- stack := op.Offset(pos).Push(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)}
- }
- func (g *Graph) LayoutDist(
- gtx layout.Context,
- th *material.Theme,
- ) D {
- dims := f32.Pt(400, 100)
- width := gtx.Dp(unit.Dp(dims.X))
- height := gtx.Dp(unit.Dp(dims.Y))
- defer op.Affine( // Flip y-axis and scale
- f32.Affine2D{}.
- Scale(f32.Pt(dims.X/2, dims.Y/2), f32.Pt(1.0, -1.0)),
- ).Push(gtx.Ops).Pop()
- barWidth := 0
- if g.order > 0 {
- pxPerBucket := float32(width) / float32(g.order)
- barWidth = gtx.Dp(unit.Dp(pxPerBucket))
- }
- color := color.NRGBA{R: 0xaa, G: 0, B: 0xaa, A: 0xff}
- maxOutDegree := 0
- for n := range g.nodes {
- d := g.offsets[n+1] - g.offsets[n]
- maxOutDegree = max(maxOutDegree, int(d))
- }
- convFactor := float64(height) / float64(maxOutDegree)
- for n := range g.nodes {
- cnt := g.offsets[n+1] - g.offsets[n]
- y := gtx.Dp(unit.Dp(float64(cnt) * convFactor))
- stack := clip.Rect{
- Min: image.Pt(n*barWidth, 0),
- Max: image.Pt((n+1)*barWidth, y),
- }.Push(gtx.Ops)
- paint.ColorOp{Color: color}.Add(gtx.Ops)
- paint.PaintOp{}.Add(gtx.Ops)
- stack.Pop()
- }
- return layout.Dimensions{Size: image.Pt(width, height)}
- }
- func distance(a, b uint32) uint32 {
- var s, e uint32 = 1, 2
- for s < 32 && a&(e-1) == b&(e-1) {
- s++
- e <<= 1
- }
- s -= 1
- return ones(a>>s) + ones(b>>s)
- }
- func ones(x uint32) uint32 {
- var c uint32
- for c = 0; x != 0; c++ {
- x &= x - 1 // clear the least significant bit set
- }
- return c
- }
- func idToPt(id uint32, convFactor float32) (x, y unit.Dp) {
- x0, y0 := demorton(reverse32(id))
- x, y = unit.Dp(float32(x0)*convFactor),
- unit.Dp(float32(y0)*convFactor)
- return
- }
- func log2(x uint32) uint32 {
- b := []uint32{0x2, 0xC, 0xF0, 0xFF00, 0xFFFF0000}
- 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]
- }
- }
- return r
- }
- func reverse32(x uint32) uint32 {
- var rev uint32
- var mask uint32 = 0xff
- for i := 0; i < 4; i++ {
- rshift := 8 * i
- lshift := 8 * (3 - i)
- rev |= uint32(reverse((x&mask)>>rshift)) << lshift
- mask <<= 8
- }
- return rev
- }
- func reverse(b uint32) byte {
- b = (b * 0x0802 & 0x22110) | (b * 0x8020 & 0x88440)
- b *= 0x10101
- b >>= 16
- return byte(b)
- }
- func demorton(z uint32) (x, y uint16) {
- res := (uint64(z) | (uint64(z) << 31)) & 0x5555555555555555
- res = (res | (res >> 1)) & 0x3333333333333333
- res = (res | (res >> 2)) & 0x0f0f0f0f0f0f0f0f
- res = (res | (res >> 4)) & 0x00ff00ff00ff00ff
- res = res | (res >> 8)
- x = uint16(res)
- y = uint16(res >> 32)
- return
- }
- func morton(x, y uint16) uint32 {
- var res uint64
- res = uint64(x) | (uint64(y) << 32)
- res = (res | (res << 8)) & 0x00ff00ff00ff00ff
- res = (res | (res << 4)) & 0x0f0f0f0f0f0f0f0f
- res = (res | (res << 2)) & 0x3333333333333333
- res = (res | (res << 1)) & 0x5555555555555555
- return uint32(res | (res >> 31))
- }
|