Making more than one janus.consult() statements possible

I would like to generate my predicates with python.
Example with “pyswip” (AoC 2020 day 1)
(“input” is a file with a large number of integers)

#!/usr/bin/python

from pyswip import Prolog

prolog = Prolog()
for line in open("input", 'r').readlines():
  prolog.assertz("isExpense(%i)" % int(line))
prolog.assertz("""
sum2(X, Y, S, P) :-
  isExpense(X),
  isExpense(Y),
  X #< Y,
  X + Y #= S,
  X * Y #= P
""")

for result in prolog.query("sum2(X, Y, 2020, P), label([X, Y, P])"):
  print(f'{result["X"]} * {result["Y"]} = {result["P"]}')

Or is there another way to do this?
(Despite generating a large string in python.)

Thank you

I think it could/should look like this:

#!/usr/bin/python

import janus_swi as janus

nr = 0
for line in open("input", 'r').readlines():
  janus.consult("%i" % nr, "isExpense(%i)." % int(line))
  nr += 1
janus.consult("xxx", """
sum2(X, Y, S, P) :-
  isExpense(X),
  isExpense(Y),
  X #< Y,
  X + Y #= S,
  X * Y #= P.
""")

for result in janus.query("sum2(X, Y, 2020, P), label([X, Y, P])"):
  print(f'{result["X"]} * {result["Y"]} = {result["P"]}')

Although this probably works, it is extremely expensive as it adds a “file” to the admin for every line and does a lot of text to term conversion. Way more efficient is to call assertz/1, as in

for line in open("input", 'r').readlines():
  janus.query("assertz(isExpense(I))", {"I":  int(line)})

Generating Prolog source as a string and have Prolog handle that is slow and easily leads to injection attacks, i.e., it is unsafe. That holds for just about any language. The above precompiles the assertz(isExpense(I)) template, processing the lines safely and swiftly.

So you suggest the following:

#!/usr/bin/python

import janus_swi as janus

for line in open("input", 'r').readlines():
  janus.query("assertz(isExpense(I))", {"I":  int(line)})
janus.consult("xxx", """
sum2(X, Y, S, P) :-
  isExpense(X),
  isExpense(Y),
  X #< Y,
  X + Y #= S,
  X * Y #= P.
""")

for result in janus.query("sum2(X, Y, 2020, P), label([X, Y, P])"):
  print(f'{result["X"]} * {result["Y"]} = {result["P"]}')

But this gives:
[…]
janus_swi.janus.PrologError: sum2/4: Unknown procedure: isExpense/1

I use janus_swi version 1.4.0.

Sorry, should have been query_once(). This works for me:

#!/usr/bin/python

import janus_swi as janus

for line in open("input", 'r').readlines():
  janus.query_once("assertz(isExpense(I))", {"I":  int(line)})

janus.consult("xxx", """
sum2(X, Y, S, P) :-
  isExpense(X),
  isExpense(Y),
  X < Y,
  S is X + Y,
  P is X * Y.
""")

for result in janus.query("sum2(X, Y, 2020, P)"):
  print(f'{result["X"]} * {result["Y"]} = {result["P"]}')

Note that I replaced clpfd arithmetic with normal Prolog. It doesn’t make sense as X and Y are bound and you backtrack over all combinations of X and Y anyway. If you still want clpfd, include a :- use_module(library(clpfd)). line in the consult call. Compare the performance :slight_smile:

Then

python num.py
1000 * 1020 = 1020000
620 * 1400 = 868000

Thank you, this works for me and is faster and also more readable.

One more example which does not work (but with pyswip it does):

janus.query_once("factorial(0, 1).")

janus.consult("x", """
factorial(N, F) :-
  N #> 0,
  F #= F1 * N,
  N #= N1 + 1,
  factorial(N1, F1).
""")

In my larger example the fact is generated but the rules are not.

pyswip version:

#!/usr/bin/python

from pyswip import Prolog

prolog = Prolog()
prolog.assertz("factorial(0, 1)")
prolog.assertz("""
factorial(N, F) :-
  N #> 0,
  F #= F1 * N,
  N #= N1 + 1,
  factorial(N1, F1)
""")

for result in prolog.query("factorial(X, Y)"):
  if result['X'] == 10:
    break
  print(result)

Why would it work? You first query factorial/2 before defining it and next you provide half an implementation. This works:

#!/usr/bin/python

import janus_swi as janus

janus.consult("x", """
:- use_module(library(clpfd)).
factorial(0, 1).
factorial(N, F) :-
  N #> 0,
  F #= F1 * N,
  N #= N1 + 1,
  factorial(N1, F1).
""")

for result in janus.query("factorial(X, Y)"):
  if result['X'] == 10:
    break
  print(result)

Now:

> python3 fac.py
{'truth': True, 'X': 0, 'Y': 1}
{'truth': True, 'X': 1, 'Y': 1}
{'truth': True, 'X': 2, 'Y': 2}
{'truth': True, 'X': 3, 'Y': 6}
{'truth': True, 'X': 4, 'Y': 24}
{'truth': True, 'X': 5, 'Y': 120}
{'truth': True, 'X': 6, 'Y': 720}
{'truth': True, 'X': 7, 'Y': 5040}
{'truth': True, 'X': 8, 'Y': 40320}
{'truth': True, 'X': 9, 'Y': 362880}

Sorry, I missed the “assertz”.
Should be like this:

janus.query_once("assertz(factorial(0, 1))")

janus.consult("x", """
factorial(N, F) :-
  N #> 0,
  F #= F1 * N,
  N #= N1 + 1,
  factorial(N1, F1).
""")

And I want to have it in two python statements, so I can make a loop over “janus.query”.
The difference now is one is a fact and one a rule with the same name.

Just to show you where I need this:
This solves SEND+MORE=MONEY and any other puzzle of this kind.
It is the Prolog way how I learned addition in elementary school.

#!/usr/bin/python

import janus_swi as janus

def print_prolog(query_input):
  print("Query: %s" % query_input)
  query = janus.query(query_input)
  anzahl = 0
  while result := query.next():
    print(result)
    anzahl += 1
  print("Number:", anzahl) 
  query.close()

for x in range(10):
  for y in range(10): # range(x, 10)
    digit = (x + y) % 10
    over = (x + y) // 10
#    print("charsum('0', '%i', '%i', '%i', '%i')" % (x, y, digit, over))
    janus.query_once("assertz(charsum('0', '%i', '%i', '%i', '%i'))" % (x, y, digit, over))

janus.consult("x", """
charsum('1', X, Y, Z, Cout) :-
  charsum('0', X, Y, Z1, C1),
  charsum('0', Z1, '1', Z, C2),
  charsum('0', C1, C2, Cout, '0').
sum_intern('0', [], [], [], '0').
sum_intern('1', [], [], ['1'], '0').
sum_intern(Cin, [], [Y | Yrest], [Z | Zrest], Cout) :-
  charsum(Cin, '0', Y, Z, Carry),
  sum_intern(Carry, [], Yrest, Zrest, Cout).
sum_intern(Cin, [X | Xrest], [], [Z | Zrest], Cout) :-
  charsum(Cin, X, '0', Z, Carry),
  sum_intern(Carry, Xrest, [], Zrest, Cout).
sum_intern(Cin, [X | Xrest], [Y | Yrest], [Z | Zrest], Cout) :-
  charsum(Cin, X, Y, Z, Carry),
  sum_intern(Carry, Xrest, Yrest, Zrest, Cout).
sum_rev(X, Y, Z) :-
  sum_intern('0', X, Y, Z, '0').
different([]).
different([X | Rs]) :-
  not(member(X, Rs)),
  different(Rs).
""")

print()
print_prolog("""
  sum_rev([D, N, E, S], [E, R, O, M], [Y, E, N, O, M]),
  different([S, E, N, D, M, O, R, Y])
""")

You can mix asserted code and code you load with janus.consult(). You need to add a declaration

 :- dynamic charsum/5.

at the start of the consulted part though, telling the compiler that clauses can be added/removed dynamically.

I leave it to others to comment on this way to defined SEND+MORE=MONEY. Surely, there are way more Prolog friendly ways to do this :slight_smile:

Thank with this it works.