Testing hypothetical implications in Prolog

I am trying to model sets of constraints where one set of constraints may or may not imply another set of constraints.

Some examples of individual constraints implying other constraints:

  • IsInstance(Mapping) → IsInstance(Iterable)
  • GreaterThan(0) → GreaterThan(-1)
  • KeysAre([“A”, “B”, 1]) → HasKey(“A”)
  • KeysAre([“A”, “B”, 1]) → MissingKey(“D”)
  • KeysAre([“A”, “B”, 1]) → IsInstance(Mapping)

I don’t need to worry about evaluating the constraints on specific values, but I do want to consider many different combinations of constraints.

The way I was expecting to use Prolog was to:

  1. Define lots of rules for when constraints imply other constraints
  2. Query whether implications hold (Does KeysAre([“A”, “B”, 1]) imply IsInstance(Iterable)?)

Based on my limited experience with Prolog, it seems like one supported approach to model does constraint set A imply constraint set B:

  1. Define facts that encode the premise of my implication (all constraints in A)
  2. Define rules that can generate implied constraints from more specific constraints
  3. Query whether the conclusion of the implication can be inferred (all constraints in B)

One thing about the above that is weird is that it seems like the rules need to be defined after the facts, because many of the rules are recursive, and the facts are the base cases (and need to be defined first afaik)
This has some awkward implications (as far as I can tell)

  1. Since the rules are being defined after the facts, I am worrying about namespaces. I don’t think I can create a module for each group of rules, and then use those names in my dynamically generated fact files.
  2. Also, if I tried to just merge everything into one module/file, I would need to worry about collisions and wouldn’t be able to use the module organization for managing the namespaces.
  3. And if I didn’t merge everything into one module/file, I would need to worry about cases where some kind of constraint doesn’t appear in constraint set A, and so the modules trying to use that proposition would not be able to find it in the fact module(s).

Some paths forwards which I can see:

  1. Generate all of my facts/rules in the order into one giant file, and then manually worry about namespace collisions.
  2. Make all of my rules dynamic, and then use asserta & retract to add my facts afterwards - I haven’t really tested that yet, so I’m not sure how well that would work.

For additional context, I am currently using Janus to work with both Python an Prolog. I am currently building all of the files from strings in Python and then calling Prolog from Python.

Any advice on how to approach this would be appreciated!

EDIT:

Another thing which is awkward with my current approach is that my facts (which need to be the first thing defined) often need to include references to python objects (parameters of constraints). Janus makes it easy to pass python objects as variables when asking queries, but currently my approach for referencing them in “facts” is painful. I create a simple rule like
isInstance(Mapping) :- py_call(is_instance:load_class('collections.abc.Mapping'), Mapping)., but I’m not sure how my full system will fit together yet, and this seems like another thing which may be tricky to work around. Maybe the asserta approach would address this?

Without some snippets it is a bit hard to judge exactly what you are after.

That depends. Snippets would help here. This seems like a case where tabling (see table/1) can do miracles dealing with guaranteed termination and better semantics for negation (well founded semantics).

As for the facts and rules I think think you could go this way:

  • Put all facts into a module facts
  • Put each ruleset in a module. Instead of calling a fact, call facts:myfact(X,Y,...)

Dynamic solutions are possible as well. SWI-Prolog is a bit exceptional in the sense that dynamic code is as fast as static code.

It is probably faster to call Prolog to assert the facts directly rather than writing them to a file and reading them back. Note that it may also be ok to leave the data in Python and merely define a predicate in Prolog that makes the Python data accessible from Prolog.

You can safely assert rules that contain Python object references.

Oh, or import the facts database of course. That is, unless facts and rules use the same predicate names, but that may not be a good idea anyway. Sorry for the self-reply.