Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Evaluating program against many different sets of facts #161

Open
riccardopinosio opened this issue Mar 14, 2022 · 4 comments
Open

Evaluating program against many different sets of facts #161

riccardopinosio opened this issue Mar 14, 2022 · 4 comments

Comments

@riccardopinosio
Copy link

Hello,

I have a situation where I have a base program P with a set of clauses. This base program needs to be evaluated many times for different sets of facts A, B, C, ...
The evaluation of the base program P against each set of facts happens concurrently in different goroutines.
What would be a sensible way to achieve this? Ideally program P would be loaded from file just once, but then evaluated together with sets of facts A, B, C, ...
The sets of facts are defined from go code by string formatting currently.

@triska
Copy link

triska commented Mar 14, 2022

You can represent all facts for example in an association list, such as an AVL tree (see library(assoc), a public domain library written by Richard O'Keefe, available in many Prolog systems). You can pass around such a tree as an argument to the clauses, and the clauses would "look up" facts using the explicitly passed assocation list, instead of the implicit Prolog database.

In this way, the evaluation is completely thread-safe: Each thread can get passed its own assocation list that it uses to look up such facts. Lookup in AVL trees is O(log(N)) in the number N of facts, and can be made a bit more efficient maybe by using hash-tables if needed.

@triska
Copy link

triska commented Mar 14, 2022

One additional comment about how to obtain such trees from strings: For this, it would help to provide the predicate read_from_chars/2 as in SICStus and Scryer Prolog, allowing us to read a Prolog term from a list of characters.

Sample use:

?- read_from_chars("f(a,b).", T).
   T = f(a,b).

Once you have these facts as Prolog terms, you can store them in an AVL tree as mentioned, and look them up there.

@ichiban
Copy link
Owner

ichiban commented Mar 16, 2022

How about creating instances for each A, B, C, ...?
prolog.Interpreter is not goroutine safe so we should keep them inside of their own goroutines and feed them facts via channels.

https://go.dev/play/p/zWzUth7nWu_L

multiple instances with shared/different facts
package main

import (
	"fmt"

	"github.com/ichiban/prolog"
)

func main() {
	names := []string{"a", "b", "c"}

	facts := make([]chan string, len(names))
	queries := make([]chan string, len(names))
	results := make(chan string, 1)
	for i, n := range names {
		facts[i] = make(chan string, 1)
		queries[i] = make(chan string, 1)

		go func(name string, fact, query <-chan string) {
			p := prolog.New(nil, nil)
			if err := p.Exec(`:- set_prolog_flag(unknown, fail).`); err != nil {
				panic(err)
			}

			for {
				select {
				case f := <-fact:
					if err := p.Exec(f); err != nil {
						panic(err)
					}
				case q := <-query:
					sol := p.QuerySolution(q)
					switch err := sol.Err(); err {
					case nil:
						results <- name
					case prolog.ErrNoSolutions:
						break
					default:
						panic(err)
					}
				}
			}
		}(n, facts[i], queries[i])
	}

	// Shared facts/rules.
	for i := range names {
		facts[i] <- `
rice.
wasabi.
sushi(X) :- X, rice, wasabi.
`
	}

	// Individual facts.
	facts[0] <- `tuna.`
	facts[1] <- `salmon.`
	facts[2] <- `egg.`

	go func() {
		for _, q := range queries {
			q <- `sushi(salmon).`
		}
	}()

	fmt.Printf("%s\n", <-results)
}

@ichiban
Copy link
Owner

ichiban commented Dec 16, 2022

As of v0.15.0, you can implement read_from_chars/2 like this:

package main

import (
	"bytes"
	"errors"
	"fmt"
	"unicode/utf8"

	"github.com/ichiban/prolog"
	"github.com/ichiban/prolog/engine"
)

func main() {
	p := prolog.New(nil, nil)
	p.Register2(engine.NewAtom("read_from_chars"), readFromChars)

	var s struct {
		T prolog.TermString
	}
	if err := p.QuerySolution(`read_from_chars("f(a,b).", T).`).Scan(&s); err != nil {
		panic(err)
	}

	fmt.Printf("T = %s\n", s.T)
}

func readFromChars(vm *engine.VM, chars, term engine.Term, k engine.Cont, env *engine.Env) *engine.Promise {
	var b bytes.Buffer
	iter := engine.ListIterator{List: chars, Env: env}
	for iter.Next() {
		switch r := iter.Current().(type) {
		case engine.Atom:
			// Atom is either a rune (r <= utf8.MaxRune) or an ID for the interned string (r > utf8.MaxRune).
			if r > utf8.MaxRune {
				return engine.Error(errors.New("not a character"))
			}
			_, _ = b.WriteRune(rune(r))
		default:
			return engine.Error(errors.New("not a character"))
		}
	}
	if err := iter.Err(); err != nil {
		return engine.Error(err)
	}

	s := engine.NewInputTextStream(&b)
	return engine.ReadTerm(vm, s, term, engine.List(), k, env)
}
T = f(a,b)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants