Skip to content

Commit

Permalink
brain/*: remove forgetting by tuples
Browse files Browse the repository at this point in the history
It was complex to implement and not really the right thing.
Now that we record traces of generated messages, we don't need it.

Resolves #44.
  • Loading branch information
zephyrtronium committed Aug 20, 2024
1 parent cc976d0 commit 5f7609e
Show file tree
Hide file tree
Showing 8 changed files with 0 additions and 1,037 deletions.
33 changes: 0 additions & 33 deletions brain/braintest/braintest.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
// If a brain cannot be created without error, new should call t.Fatal.
func Test(ctx context.Context, t *testing.T, new func(context.Context) brain.Brain) {
t.Run("speak", testSpeak(ctx, new(ctx)))
t.Run("forget", testForget(ctx, new(ctx)))
t.Run("forgetMessage", testForgetMessage(ctx, new(ctx)))
t.Run("forgetDuring", testForgetDuring(ctx, new(ctx)))
t.Run("combinatoric", testCombinatoric(ctx, new(ctx)))
Expand Down Expand Up @@ -183,38 +182,6 @@ func testSpeak(ctx context.Context, br brain.Brain) func(t *testing.T) {
}
}

// testForget tests that a brain forgets what it forgets.
func testForget(ctx context.Context, br brain.Brain) func(t *testing.T) {
return func(t *testing.T) {
learn(ctx, t, br)
if err := brain.Forget(ctx, br, "kessoku", messages[0].Tokens()); err != nil {
t.Errorf("couldn't forget: %v", err)
}
for range 100 {
s, trace, err := brain.Speak(ctx, br, "kessoku", "")
if err != nil {
t.Errorf("couldn't speak: %v", err)
}
if strings.Contains(s, "bocchi") {
t.Errorf("remembered that which must be forgotten: %q", s)
}
if trace[len(trace)-1] == messages[0].ID {
t.Errorf("id %q should have been forgotten but was used in %q", messages[0].ID, trace)
}
}
for range 10000 {
s, _, err := brain.Speak(ctx, br, "sickhack", "")
if err != nil {
t.Errorf("couldn't speak: %v", err)
}
if strings.Contains(s, "bocchi") {
return
}
}
t.Error("didn't see bocchi in many attempts; deleted from wrong tag?")
}
}

// testForgetMessage tests that a brain can forget messages by ID.
func testForgetMessage(ctx context.Context, br brain.Brain) func(t *testing.T) {
return func(t *testing.T) {
Expand Down
57 changes: 0 additions & 57 deletions brain/kvbrain/forget.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,11 @@ package kvbrain
import (
"bytes"
"context"
"errors"
"fmt"
"slices"
"strings"
"sync"
"time"

"github.com/dgraph-io/badger/v4"

"github.com/zephyrtronium/robot/brain"
"github.com/zephyrtronium/robot/userhash"
)

Expand Down Expand Up @@ -94,58 +89,6 @@ func (p *past) findUser(user userhash.Hash) [][]byte {
return r
}

// Forget removes a set of recorded tuples. The tuples provided are as for
// Learn. If a tuple has been recorded multiple times, only the first
// should be deleted. If a tuple has not been recorded, it should be
// ignored.
func (br *Brain) Forget(ctx context.Context, tag string, tuples []brain.Tuple) error {
// Sort tuples so that we always seek forward.
slices.SortFunc(tuples, func(a, b brain.Tuple) int {
p := slices.Compare(a.Prefix, b.Prefix)
if p == 0 {
p = strings.Compare(a.Suffix, b.Suffix)
}
return p
})
err := br.knowledge.Update(func(txn *badger.Txn) error {
var errs error
opts := badger.DefaultIteratorOptions
it := txn.NewIterator(opts)
defer it.Close()
b := hashTag(nil, tag)
for _, t := range tuples {
b = append(appendPrefix(b[:tagHashLen], t.Prefix), '\xff') // terminate the prefix
it.Seek(b)
for it.ValidForPrefix(b) {
v := it.Item()
it.Next()
if v.IsDeletedOrExpired() {
continue
}
u, err := v.ValueCopy(nil)
if err != nil {
errs = errors.Join(errs, err)
continue
}
if string(u) != t.Suffix {
continue
}
if err := txn.Delete(v.KeyCopy(nil)); err != nil {
errs = errors.Join(errs, err)
continue
}
// Only delete a single instance of each tuple.
break
}
}
return errs
})
if err != nil {
return fmt.Errorf("couldn't forget: %w", err)
}
return nil
}

// ForgetMessage forgets everything learned from a single given message.
// If nothing has been learned from the message, it should be ignored.
func (br *Brain) ForgetMessage(ctx context.Context, tag, id string) error {
Expand Down
204 changes: 0 additions & 204 deletions brain/kvbrain/forget_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,210 +209,6 @@ func BenchmarkPastFindUser(b *testing.B) {
//go:noinline
func use(x [][]byte) {}

func TestForget(t *testing.T) {
type message struct {
id string
user userhash.Hash
tag string
time time.Time
tups []brain.Tuple
}
cases := []struct {
name string
msgs []message
forget []brain.Tuple
want map[string]string
}{
{
name: "none",
msgs: []message{
{
id: "1",
user: userhash.Hash{2},
tag: "kessoku",
time: time.Unix(0, 0),
tups: []brain.Tuple{
{
Prefix: []string{"ryou", "bocchi"},
Suffix: "kita",
},
},
},
},
forget: []brain.Tuple{
{
Prefix: []string{"eliza", "kikuri"},
Suffix: "shima",
},
},
want: map[string]string{
mkey("kessoku", "ryou\xffbocchi\xff\xff", "1"): "kita",
},
},
{
name: "suffix",
msgs: []message{
{
id: "1",
user: userhash.Hash{2},
tag: "kessoku",
time: time.Unix(0, 0),
tups: []brain.Tuple{
{
Prefix: []string{"ryou", "bocchi"},
Suffix: "kita",
},
},
},
},
forget: []brain.Tuple{
{
Prefix: []string{"eliza", "kikuri"},
Suffix: "kita",
},
},
want: map[string]string{
mkey("kessoku", "ryou\xffbocchi\xff\xff", "1"): "kita",
},
},
{
name: "prefix",
msgs: []message{
{
id: "1",
user: userhash.Hash{2},
tag: "kessoku",
time: time.Unix(0, 0),
tups: []brain.Tuple{
{
Prefix: []string{"ryou", "bocchi"},
Suffix: "kita",
},
},
},
},
forget: []brain.Tuple{
{
Prefix: []string{"ryou", "bocchi"},
Suffix: "shima",
},
},
want: map[string]string{
mkey("kessoku", "ryou\xffbocchi\xff\xff", "1"): "kita",
},
},
{
name: "tag",
msgs: []message{
{
id: "1",
user: userhash.Hash{2},
tag: "sickhack",
time: time.Unix(0, 0),
tups: []brain.Tuple{
{
Prefix: []string{"ryou", "bocchi"},
Suffix: "kita",
},
},
},
},
forget: []brain.Tuple{
{
Prefix: []string{"ryou", "bocchi"},
Suffix: "kita",
},
},
want: map[string]string{
mkey("sickhack", "ryou\xffbocchi\xff\xff", "1"): "kita",
},
},
{
name: "match",
msgs: []message{
{
id: "1",
user: userhash.Hash{2},
tag: "kessoku",
time: time.Unix(0, 0),
tups: []brain.Tuple{
{
Prefix: []string{"ryou", "bocchi"},
Suffix: "kita",
},
},
},
},
forget: []brain.Tuple{
{
Prefix: []string{"ryou", "bocchi"},
Suffix: "kita",
},
},
want: map[string]string{},
},
{
name: "single",
msgs: []message{
{
id: "1",
user: userhash.Hash{2},
tag: "kessoku",
time: time.Unix(0, 0),
tups: []brain.Tuple{
{
Prefix: []string{"ryou", "bocchi"},
Suffix: "kita",
},
},
},
{
id: "2",
user: userhash.Hash{2},
tag: "kessoku",
time: time.Unix(0, 0),
tups: []brain.Tuple{
{
Prefix: []string{"ryou", "bocchi"},
Suffix: "kita",
},
},
},
},
forget: []brain.Tuple{
{
Prefix: []string{"ryou", "bocchi"},
Suffix: "kita",
},
},
want: map[string]string{
mkey("kessoku", "ryou\xffbocchi\xff\xff", "2"): "kita",
},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
t.Parallel()
ctx := context.Background()
db, err := badger.Open(badger.DefaultOptions("").WithInMemory(true).WithLogger(nil))
if err != nil {
t.Fatal(err)
}
br := New(db)
for _, msg := range c.msgs {
err := br.Learn(ctx, msg.tag, msg.id, msg.user, msg.time, msg.tups)
if err != nil {
t.Errorf("failed to learn: %v", err)
}
}
if err := br.Forget(ctx, "kessoku", c.forget); err != nil {
t.Errorf("failed to forget: %v", err)
}
dbcheck(t, db, c.want)
})
}
}

func TestForgetMessage(t *testing.T) {
type message struct {
id string
Expand Down
18 changes: 0 additions & 18 deletions brain/learn.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,6 @@ type Learner interface {
// Each tuple's prefix has entropy reduction transformations applied.
// Tuples in the argument may share storage for prefixes.
Learn(ctx context.Context, tag, id string, user userhash.Hash, t time.Time, tuples []Tuple) error
// Forget removes a set of recorded tuples.
// The tuples provided are as for Learn.
// If a tuple has been recorded multiple times, only the first
// should be deleted.
// If a tuple has not been recorded, it should be ignored.
Forget(ctx context.Context, tag string, tuples []Tuple) error
// ForgetMessage forgets everything learned from a single given message.
// If nothing has been learned from the message, it should be ignored.
ForgetMessage(ctx context.Context, tag, id string) error
Expand All @@ -56,18 +50,6 @@ func Learn(ctx context.Context, l Learner, tag, id string, user userhash.Hash, t
return l.Learn(ctx, tag, id, user, t, tt)
}

// Forget removes tokens from a Learner.
func Forget(ctx context.Context, l Learner, tag string, toks []string) error {
if len(toks) == 0 {
return nil
}
tt := tuplesPool.Get()
defer func() { tuplesPool.Put(tt[:0]) }()
tt = slices.Grow(tt, len(toks)+1)
tt = tupleToks(tt, toks)
return l.Forget(ctx, tag, tt)
}

func tupleToks(tt []Tuple, toks []string) []Tuple {
slices.Reverse(toks)
pres := slices.Clone(toks)
Expand Down
Loading

0 comments on commit 5f7609e

Please sign in to comment.