2

I am working on a project that is mainly based on logic programming. I have predifined related rules and facts that the program uses to calculate probabilities then these probabilities are appended to the data and fed into a further machine learning model. The program to calculate the probabilities can be easily defined in prolog as follows for example:

has_lot_work(daniel, 8). %number of lets say urgent tasks
has_lot_work(david, 3).
stress(X, P) :- has_lot_work(X, P2), P is P2 / 100.
to_smoke(X, Prob) :- stress(X, P1), friends(Y, X), influences(Y, X, P2), smokes(Y), Prob is P1 + P2.
to_have_asthma(X, 0.3) :- smokes(X). %30 percent of current smokers get asthma
to_have_asthma(X, Prob) :- to_smoke(X, P2), Prob is P2 * 0.25. %25 percent of smokers-to-be will get asthma
friends(X, Y) :- friend(X, Y).
friends(X, Y) :- friend(Y, X).
influences(X, Y, 0.4) :- friends(X, Y). %friends influence each other by 40 percent
friend(peter, david).
friend(peter, rebecca).
friend(daniel, rebecca).
smokes(peter).
smokes(rebecca).

In the example, I am interested in calculation the probability of someone to smoke (to_smoke(Who, Prob)) and to get asthma (to_have_asthma(Who, Prob)). I use python to get and clean the data and for the ML model afterwards, so I wanted to apply this logic in python as well. But couldn't find a way to do this logic calculations and I couldn't find a proper way to connect python with prolog without errors and problems.

mnoorfawi
  • 111
  • 1
  • 2
  • 10

5 Answers5

5

I tried to develop something that uses prolog's syntax, approach and backtracking in python to solve the issue. I wrote pytholog library. I am sharing the answers to get comments and if there are better ways to do it.

In pytholog I initiate a knowledge base and I query it.

import pytholog as pl
friends_kb = pl.KnowledgeBase("friends")
friends_kb([
    "has_lot_work(daniel, 8)",
    "has_lot_work(david, 3)",
    "stress(X, P) :- has_lot_work(X, P2), P is P2 / 100",
    "to_smoke(X, Prob) :- stress(X, P1), friends(Y, X), influences(Y, X, P2), smokes(Y), Prob is P1 + P2",
    "to_have_asthma(X, 0.3) :- smokes(X)",
    "to_have_asthma(X, Prob) :- to_smoke(X, P2), Prob is P2 * 0.25",
    "friends(X, Y) :- friend(X, Y)",
    "friends(X, Y) :- friend(Y, X)",
    "influences(X, Y, 0.4) :- friends(X, Y)",
    "friend(peter, david)",
    "friend(peter, rebecca)",
    "friend(daniel, rebecca)",
    "smokes(peter)",
    "smokes(rebecca)"
])
print(friends_kb.query(pl.Expr("to_smoke(Who, P)")))
# [{'Who': 'daniel', 'P': 0.48000000000000004}, {'Who': 'david', 'P': 0.43000000000000005}]
print(friends_kb.query(pl.Expr("to_have_asthma(Who, P)")))
# [{'Who': 'peter', 'P': '0.3'}, {'Who': 'rebecca', 'P': '0.3'}, {'Who': 'daniel', 'P': 0.12000000000000001}, {'Who': 'david', 'P': 0.10750000000000001}]
mnoorfawi
  • 111
  • 1
  • 2
  • 10
4

You have a few choices but as AFAIK there is no built-in methods or predicates for Python so you need to access it as a library as such.

  1. One popular way is to use miniKanren. I have not used this, but just looking at its syntax is enough to make me run away in horror.

  2. Another approach is to find an interface between Python and Prolog and one popular one is between Python and SWI-Prolog, e.g pyswip. There are many of these so you will have to hunt and check to find which one works best for your needs. Again I have not used these.

If you do go with this choice to use a library to connect to SWI-Prolog, I do know that those questions pop up from time to time at the SWI-Prolog forum.

This is one I would chose as you get access to a full feature Prolog with cut, backtracking, tabling, etc.

  1. A much harder approach but one that will give the most insight is to implement the logic in Python yourself. See: Implementing the Prolog Unification algorithm in Python? Backtracking
Guy Coder
  • 24,501
  • 8
  • 71
  • 136
  • You don't like parentheses, Guy? >:-) Have you checked out [The Reasoned Schemer](https://mitpress.mit.edu/books/reasoned-schemer)? – David Tonhofer Apr 08 '20 at 11:39
3

Take a look at z3 theorem prover, it has python bindings as well. Here is the guide

You can formulate your problem in z3 like this:

import z3


def main():
    person = z3.Datatype("Person")
    for p in ["daniel", "david", "peter", "rebecca"]:
        person.declare(p)

    person = person.create()
    p1, p2 = z3.Const("p1", person), z3.Const("p2", person)
    s = z3.Solver()

    has_lot_work = z3.Function("has_lot_work", person, z3.RealSort())
    s.add(has_lot_work(person.daniel) == 8)
    s.add(has_lot_work(person.david) == 3)

    friends = z3.Function("friends", person, person, z3.BoolSort())
    friend = z3.Function("friend", person, person, z3.BoolSort())
    s.add(
        z3.ForAll(
            [p1, p2], z3.Implies(z3.Or(friend(p1, p2), friend(p2, p1)), friends(p1, p2))
        )
    )
    s.add(friend(person.peter, person.david))
    s.add(friend(person.peter, person.rebecca))
    s.add(friend(person.daniel, person.rebecca))

    smokes = z3.Function("smokes", person, z3.BoolSort())
    s.add(smokes(person.peter))
    s.add(smokes(person.rebecca))
    s.add(z3.Not(smokes(person.david)))
    s.add(z3.Not(smokes(person.daniel)))

    stress = z3.Function("stress", person, z3.RealSort())
    s.add(z3.ForAll(p1, stress(p1) == has_lot_work(p1) / 100.0))

    influences = z3.Function("influences", person, person, z3.RealSort())
    s.add(z3.ForAll([p1, p2], influences(p1, p2) == z3.If(friends(p1, p2), 0.4, 0.0)))

    to_smoke = z3.Function("to_smoke", person, z3.RealSort())
    s.add(
        z3.ForAll(
            [p1, p2],
            z3.Or(
                p1 == p2,
                z3.Not(smokes(p2)),
                z3.Not(friends(p2, p1)),
                to_smoke(p1) == stress(p1) + influences(p2, p1),
            ),
        )
    )

    to_have_asthma = z3.Function("to_have_asthma", person, z3.RealSort())
    s.add(
        z3.ForAll(
            p1,
            to_have_asthma(p1)
            == z3.If(
                smokes(p1),
                0.3,
                to_smoke(p1) * 0.25,
            ),
        )
    )

    s.check()
    model = s.model()
    result = {
        p: float(model.eval(to_have_asthma(p)).as_fraction())
        for p in [person.peter, person.daniel, person.david, person.rebecca]
    }
    print(result)


if __name__ == "__main__":
    main()

Output:

{peter: 0.3, daniel: 0.12, david: 0.1075, rebecca: 0.3}
grihabor
  • 71
  • 1
  • 5
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes – Muhammad Dyas Yaskur Feb 15 '21 at 09:45
1

PyLog seems to be what you are looking for. https://pypi.org/project/pylog/

pip install pylog
Gabriel Petersson
  • 8,434
  • 4
  • 32
  • 41
-1

There is a Python version of the Dogelog runtime. It can be bottled into a single Python file, no server roundtrip, just ISO core Prolog in one Python file, requires Python 3.10.

The Python version has also a foreign function interface. But easiest to try your knowledge base is put it into a Prolog text file, consult it and query it:

>python.exe toplevel.py
Dogelog Runtime, Prolog to the Moon, 0.9.3
(c) 1985-2021, XLOG Technologies AG, Switzerland
?- ['prob.pl'].
true.
?- to_have_asthma(X, Y).
X = peter, Y = 0.3;
X = rebecca, Y = 0.3;
X = daniel, Y = 0.12000000000000001;
X = david, Y = 0.10750000000000001;
fail.

Dogelog runtime is open source and can be found on GitHub. The attended query answerer for the Python version is one way to use it and part of the console tutorial.

Doglog Runtime: Python Console Example
https://github.com/jburse/dogelog-moon/tree/main/samples/console

The Example as a Prolog Text as is
https://gist.github.com/jburse/00d14dee1582a131a52bdddf1ffb9dcb#file-prob-pl