implement basic constraint engine in gim, which will be used to determine positioning of vertices
This commit is contained in:
parent
286c2fbb35
commit
79a171323d
1
gg/gg.go
1
gg/gg.go
@ -455,6 +455,7 @@ func Equal(g1, g2 *Graph) bool {
|
|||||||
// passed to the callback and used as the starting point of the traversal. If
|
// passed to the callback and used as the starting point of the traversal. If
|
||||||
// the callback returns false the traversal is stopped.
|
// the callback returns false the traversal is stopped.
|
||||||
func (g *Graph) Walk(startWith *Vertex, callback func(*Vertex) bool) {
|
func (g *Graph) Walk(startWith *Vertex, callback func(*Vertex) bool) {
|
||||||
|
// TODO figure out how to make Walk deterministic
|
||||||
g.makeView()
|
g.makeView()
|
||||||
if len(g.view) == 0 {
|
if len(g.view) == 0 {
|
||||||
return
|
return
|
||||||
|
109
gim/constraint/constraint.go
Normal file
109
gim/constraint/constraint.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
// Package constraint implements an extremely simple constraint engine.
|
||||||
|
// Elements, and constraints on those elements, are given to the engine, which
|
||||||
|
// uses those constraints to generate an output. Elements are defined as a
|
||||||
|
// string
|
||||||
|
package constraint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mediocregopher/ginger/gg"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Constraint describes a constraint on an element. The Elem field must be
|
||||||
|
// filled in, as well as exactly one other field
|
||||||
|
type Constraint struct {
|
||||||
|
Elem string
|
||||||
|
|
||||||
|
// LT says that Elem is less than this element
|
||||||
|
LT string
|
||||||
|
}
|
||||||
|
|
||||||
|
const ltEdge = gg.Str("lt")
|
||||||
|
|
||||||
|
// Engine processes sets of constraints to generate an output
|
||||||
|
type Engine struct {
|
||||||
|
g *gg.Graph
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEngine initializes and returns an empty Engine
|
||||||
|
func NewEngine() *Engine {
|
||||||
|
return &Engine{g: gg.Null}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddConstraint adds the given constraint to the engine's set, returns false if
|
||||||
|
// the constraint couldn't be added due to a conflict with a previous constraint
|
||||||
|
func (e *Engine) AddConstraint(c Constraint) bool {
|
||||||
|
elem := gg.Str(c.Elem)
|
||||||
|
g := e.g.AddValueIn(gg.ValueOut(elem, ltEdge), gg.Str(c.LT))
|
||||||
|
|
||||||
|
// Check for loops in g starting at c.Elem, bail if there are any
|
||||||
|
{
|
||||||
|
seen := map[*gg.Vertex]bool{}
|
||||||
|
start := g.Value(elem)
|
||||||
|
var hasLoop func(v *gg.Vertex) bool
|
||||||
|
hasLoop = func(v *gg.Vertex) bool {
|
||||||
|
if seen[v] {
|
||||||
|
return v == start
|
||||||
|
}
|
||||||
|
seen[v] = true
|
||||||
|
for _, out := range v.Out {
|
||||||
|
if hasLoop(out.To) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if hasLoop(start) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.g = g
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Solve uses the constraints which have been added to the engine to give a
|
||||||
|
// possible solution. The given element is one which has been added to the
|
||||||
|
// engine and whose value is known to be zero.
|
||||||
|
func (e *Engine) Solve() map[string]int {
|
||||||
|
m := map[string]int{}
|
||||||
|
if len(e.g.Values()) == 0 {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
vElem := func(v *gg.Vertex) string {
|
||||||
|
return string(v.Value.(gg.Str))
|
||||||
|
}
|
||||||
|
|
||||||
|
// first the roots are determined to be the elements with no In edges, which
|
||||||
|
// _must_ exist since the graph presumably has no loops
|
||||||
|
var roots []*gg.Vertex
|
||||||
|
e.g.Walk(nil, func(v *gg.Vertex) bool {
|
||||||
|
if len(v.In) == 0 {
|
||||||
|
roots = append(roots, v)
|
||||||
|
m[vElem(v)] = 0
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
// sanity check
|
||||||
|
if len(roots) == 0 {
|
||||||
|
panic("no roots found in graph somehow")
|
||||||
|
}
|
||||||
|
|
||||||
|
// a vertex's value is then the length of the longest path from it to one of
|
||||||
|
// the roots
|
||||||
|
var walk func(*gg.Vertex, int)
|
||||||
|
walk = func(v *gg.Vertex, val int) {
|
||||||
|
if elem := vElem(v); val > m[elem] {
|
||||||
|
m[elem] = val
|
||||||
|
}
|
||||||
|
for _, out := range v.Out {
|
||||||
|
walk(out.To, val+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, root := range roots {
|
||||||
|
walk(root, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
94
gim/constraint/constraint_test.go
Normal file
94
gim/constraint/constraint_test.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package constraint
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEngineAddConstraint(t *T) {
|
||||||
|
{
|
||||||
|
e := NewEngine()
|
||||||
|
assert.True(t, e.AddConstraint(Constraint{Elem: "0", LT: "1"}))
|
||||||
|
assert.True(t, e.AddConstraint(Constraint{Elem: "1", LT: "2"}))
|
||||||
|
assert.True(t, e.AddConstraint(Constraint{Elem: "-1", LT: "0"}))
|
||||||
|
assert.False(t, e.AddConstraint(Constraint{Elem: "1", LT: "0"}))
|
||||||
|
assert.False(t, e.AddConstraint(Constraint{Elem: "2", LT: "0"}))
|
||||||
|
assert.False(t, e.AddConstraint(Constraint{Elem: "2", LT: "-1"}))
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
e := NewEngine()
|
||||||
|
assert.True(t, e.AddConstraint(Constraint{Elem: "0", LT: "1"}))
|
||||||
|
assert.True(t, e.AddConstraint(Constraint{Elem: "0", LT: "2"}))
|
||||||
|
assert.True(t, e.AddConstraint(Constraint{Elem: "1", LT: "2"}))
|
||||||
|
assert.True(t, e.AddConstraint(Constraint{Elem: "2", LT: "3"}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEngineSolve(t *T) {
|
||||||
|
assertSolve := func(exp map[string]int, cc ...Constraint) {
|
||||||
|
e := NewEngine()
|
||||||
|
for _, c := range cc {
|
||||||
|
assert.True(t, e.AddConstraint(c), "c:%#v", c)
|
||||||
|
}
|
||||||
|
assert.Equal(t, exp, e.Solve())
|
||||||
|
}
|
||||||
|
|
||||||
|
// basic
|
||||||
|
assertSolve(
|
||||||
|
map[string]int{"a": 0, "b": 1, "c": 2},
|
||||||
|
Constraint{Elem: "a", LT: "b"},
|
||||||
|
Constraint{Elem: "b", LT: "c"},
|
||||||
|
)
|
||||||
|
|
||||||
|
// "triangle" graph
|
||||||
|
assertSolve(
|
||||||
|
map[string]int{"a": 0, "b": 1, "c": 2},
|
||||||
|
Constraint{Elem: "a", LT: "b"},
|
||||||
|
Constraint{Elem: "a", LT: "c"},
|
||||||
|
Constraint{Elem: "b", LT: "c"},
|
||||||
|
)
|
||||||
|
|
||||||
|
// "hexagon" graph
|
||||||
|
assertSolve(
|
||||||
|
map[string]int{"a": 0, "b": 1, "c": 1, "d": 2, "e": 2, "f": 3},
|
||||||
|
Constraint{Elem: "a", LT: "b"},
|
||||||
|
Constraint{Elem: "a", LT: "c"},
|
||||||
|
Constraint{Elem: "b", LT: "d"},
|
||||||
|
Constraint{Elem: "c", LT: "e"},
|
||||||
|
Constraint{Elem: "d", LT: "f"},
|
||||||
|
Constraint{Elem: "e", LT: "f"},
|
||||||
|
)
|
||||||
|
|
||||||
|
// "hexagon" with centerpoint graph
|
||||||
|
assertSolve(
|
||||||
|
map[string]int{"a": 0, "b": 1, "c": 1, "center": 2, "d": 3, "e": 3, "f": 4},
|
||||||
|
Constraint{Elem: "a", LT: "b"},
|
||||||
|
Constraint{Elem: "a", LT: "c"},
|
||||||
|
Constraint{Elem: "b", LT: "d"},
|
||||||
|
Constraint{Elem: "c", LT: "e"},
|
||||||
|
Constraint{Elem: "d", LT: "f"},
|
||||||
|
Constraint{Elem: "e", LT: "f"},
|
||||||
|
|
||||||
|
Constraint{Elem: "c", LT: "center"},
|
||||||
|
Constraint{Elem: "b", LT: "center"},
|
||||||
|
Constraint{Elem: "center", LT: "e"},
|
||||||
|
Constraint{Elem: "center", LT: "d"},
|
||||||
|
)
|
||||||
|
|
||||||
|
// multi-root, using two triangles which end up connecting
|
||||||
|
assertSolve(
|
||||||
|
map[string]int{"a": 0, "b": 1, "c": 2, "d": 0, "e": 1, "f": 2, "g": 3},
|
||||||
|
Constraint{Elem: "a", LT: "b"},
|
||||||
|
Constraint{Elem: "a", LT: "c"},
|
||||||
|
Constraint{Elem: "b", LT: "c"},
|
||||||
|
|
||||||
|
Constraint{Elem: "d", LT: "e"},
|
||||||
|
Constraint{Elem: "d", LT: "f"},
|
||||||
|
Constraint{Elem: "e", LT: "f"},
|
||||||
|
|
||||||
|
Constraint{Elem: "f", LT: "g"},
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user