mindsicle_test.go raw

   1  package mindsicle
   2  
   3  import (
   4  	"bytes"
   5  	"testing"
   6  
   7  	"git.mleku.dev/mleku/dendrite/pkg/axiom"
   8  	"git.mleku.dev/mleku/dendrite/pkg/lattice"
   9  	"git.mleku.dev/mleku/dendrite/pkg/spore"
  10  )
  11  
  12  type testConstraint struct{ tag string }
  13  
  14  func (c testConstraint) Tag() string                { return c.tag }
  15  func (c testConstraint) Admits(e axiom.Element) bool { return e.Type() == c.tag }
  16  
  17  type testElement struct {
  18  	typ string
  19  	val string
  20  }
  21  
  22  func (e testElement) Type() string { return e.typ }
  23  func (e testElement) Value() any   { return e.val }
  24  
  25  func testConstraintFactory(tag string) axiom.Constraint {
  26  	return testConstraint{tag}
  27  }
  28  
  29  func TestFreezeThawRoundTrip(t *testing.T) {
  30  	// Build a small lattice: 3 nodes, 2 edges, 2 occupied.
  31  	l := lattice.New()
  32  	n0 := l.AddNode([]axiom.Constraint{testConstraint{"word"}})
  33  	n1 := l.AddNode([]axiom.Constraint{testConstraint{"word"}})
  34  	n2 := l.AddNode([]axiom.Constraint{testConstraint{"number"}})
  35  	l.Connect(n0, n1)
  36  	l.Connect(n1, n2)
  37  
  38  	n0.Bond(testElement{"word", "hello"})
  39  	n1.Bond(testElement{"word", "world"})
  40  	n0.SetPermutation(2)
  41  	n0.SetProjection(3, 5, 42)
  42  	n0.SetEnergy(true)
  43  
  44  	// Increment age on n0 twice.
  45  	n0.IncrementAge()
  46  	n0.IncrementAge()
  47  
  48  	// Create spore.
  49  	sp := spore.Extract(l)
  50  
  51  	// Freeze.
  52  	m := Freeze(l, sp)
  53  	if len(m.Nodes) != 3 {
  54  		t.Fatalf("expected 3 nodes, got %d", len(m.Nodes))
  55  	}
  56  	if m.Version != Version {
  57  		t.Fatalf("expected version %d, got %d", Version, m.Version)
  58  	}
  59  
  60  	// Serialize → deserialize.
  61  	var buf bytes.Buffer
  62  	if _, err := m.WriteTo(&buf); err != nil {
  63  		t.Fatalf("WriteTo: %v", err)
  64  	}
  65  	m2, err := ReadMindsicle(&buf)
  66  	if err != nil {
  67  		t.Fatalf("ReadMindsicle: %v", err)
  68  	}
  69  	if len(m2.Nodes) != 3 {
  70  		t.Fatalf("deserialized: expected 3 nodes, got %d", len(m2.Nodes))
  71  	}
  72  
  73  	// Thaw.
  74  	l2 := m2.Thaw(testConstraintFactory)
  75  	if l2.Size() != 3 {
  76  		t.Fatalf("thawed lattice size: expected 3, got %d", l2.Size())
  77  	}
  78  
  79  	// Verify occupants.
  80  	tn0 := l2.Node(0)
  81  	if !tn0.Occupied() {
  82  		t.Fatal("node 0 should be occupied")
  83  	}
  84  	if tn0.Occupant().Type() != "word" {
  85  		t.Fatalf("node 0 type: expected 'word', got %q", tn0.Occupant().Type())
  86  	}
  87  	if tn0.Occupant().Value().(string) != "hello" {
  88  		t.Fatalf("node 0 value: expected 'hello', got %v", tn0.Occupant().Value())
  89  	}
  90  
  91  	// Verify vacancy.
  92  	tn2 := l2.Node(2)
  93  	if tn2.Occupied() {
  94  		t.Fatal("node 2 should be vacant")
  95  	}
  96  
  97  	// Verify connectivity: node 1 should have 2 neighbors.
  98  	tn1 := l2.Node(1)
  99  	if len(tn1.Neighbors()) != 2 {
 100  		t.Fatalf("node 1 neighbors: expected 2, got %d", len(tn1.Neighbors()))
 101  	}
 102  
 103  	// Verify age survives round-trip.
 104  	if tn0.Age() != 2 {
 105  		t.Fatalf("node 0 age: expected 2, got %d", tn0.Age())
 106  	}
 107  
 108  	// Verify permutation.
 109  	if tn0.Permutation() != 2 {
 110  		t.Fatalf("node 0 perm: expected 2, got %d", tn0.Permutation())
 111  	}
 112  
 113  	// Verify projection.
 114  	if tn0.ProjectionVertex() != 3 {
 115  		t.Fatalf("node 0 proj vertex: expected 3, got %d", tn0.ProjectionVertex())
 116  	}
 117  	if tn0.ProjectionKey() != 5 {
 118  		t.Fatalf("node 0 proj key: expected 5, got %d", tn0.ProjectionKey())
 119  	}
 120  	if tn0.ProjectionPath() != 42 {
 121  		t.Fatalf("node 0 proj path: expected 42, got %d", tn0.ProjectionPath())
 122  	}
 123  
 124  	// Verify bond count.
 125  	if tn0.BondCount() != 1 {
 126  		t.Fatalf("node 0 bond count: expected 1, got %d", tn0.BondCount())
 127  	}
 128  }
 129  
 130  func TestEmptyLattice(t *testing.T) {
 131  	l := lattice.New()
 132  	m := Freeze(l, nil)
 133  	if len(m.Nodes) != 0 {
 134  		t.Fatalf("expected 0 nodes, got %d", len(m.Nodes))
 135  	}
 136  
 137  	var buf bytes.Buffer
 138  	m.WriteTo(&buf)
 139  	m2, err := ReadMindsicle(&buf)
 140  	if err != nil {
 141  		t.Fatalf("ReadMindsicle: %v", err)
 142  	}
 143  
 144  	l2 := m2.Thaw(testConstraintFactory)
 145  	if l2.Size() != 0 {
 146  		t.Fatalf("thawed empty lattice size: expected 0, got %d", l2.Size())
 147  	}
 148  }
 149  
 150  func TestVacantNodes(t *testing.T) {
 151  	l := lattice.New()
 152  	l.AddNode([]axiom.Constraint{testConstraint{"word"}})
 153  	l.AddNode([]axiom.Constraint{testConstraint{"number"}})
 154  
 155  	m := Freeze(l, nil)
 156  
 157  	var buf bytes.Buffer
 158  	m.WriteTo(&buf)
 159  	m2, _ := ReadMindsicle(&buf)
 160  	l2 := m2.Thaw(testConstraintFactory)
 161  
 162  	if l2.Size() != 2 {
 163  		t.Fatalf("expected 2 nodes, got %d", l2.Size())
 164  	}
 165  	// Both vacant.
 166  	for i := range 2 {
 167  		if l2.Node(lattice.NodeID(i)).Occupied() {
 168  			t.Fatalf("node %d should be vacant", i)
 169  		}
 170  	}
 171  	// Constraints survived.
 172  	cs := l2.Node(0).Constraints()
 173  	if len(cs) != 1 || cs[0].Tag() != "word" {
 174  		t.Fatalf("node 0 constraints: expected [word], got %v", cs)
 175  	}
 176  }
 177  
 178  func TestCandidates(t *testing.T) {
 179  	l := lattice.New()
 180  	n := l.AddNode([]axiom.Constraint{testConstraint{"word"}})
 181  	n.AddCandidate(testElement{"word", "alpha"})
 182  	n.AddCandidate(testElement{"word", "beta"})
 183  
 184  	m := Freeze(l, nil)
 185  
 186  	var buf bytes.Buffer
 187  	m.WriteTo(&buf)
 188  	m2, _ := ReadMindsicle(&buf)
 189  	l2 := m2.Thaw(testConstraintFactory)
 190  
 191  	tn := l2.Node(0)
 192  	// Candidates are added via AddCandidate which checks constraints.
 193  	// frozenElement has type "word" which satisfies testConstraint{"word"}.
 194  	cands := tn.Candidates()
 195  	if len(cands) != 2 {
 196  		t.Fatalf("expected 2 candidates, got %d", len(cands))
 197  	}
 198  	types := map[string]bool{}
 199  	for _, c := range cands {
 200  		types[c.Value().(string)] = true
 201  	}
 202  	if !types["alpha"] || !types["beta"] {
 203  		t.Fatalf("candidates missing: got %v", types)
 204  	}
 205  }
 206