Ann: SWI-Prolog 8.3.21

Dear SWI-Prolog user,

This release brings more enhancements and fixes to (lazy) monotonic
tabling as well as several enhacements and extensions. Highlights:

  • Extended clp(fd) propagation by Mohammed Malik.

  • Fixed position mananament for ansi_format/3 by Robert Sedlacek.

  • Several fixes and enhancements to => rules:

    • Handling them in .qlf/saved state files
    • Make goal_expansion/2 work on their body and guard.
    • Compile Head => true as a fact with single sided unification.
    • Make instance/2 handle => rules with a guard.
  • Compiler enhancement. Compile p(X) :- X = f(_), g(X). such
    that the X = f(_) unification actually happens as part of the
    head unification (which is faster) and clause indexing works
    on such clauses.

  • Experimental efficient determinism checking. This currently
    consists of

    • det/1, a directive that declares predicates to be deterministic.
      For example, :- det((p/1, q/3)).
    • A goal $/0 that acts as a cut, declaring that the remainder
      of the body shall terminate with deterministic success.
      Neither the naming nor the exact errors are set in stone. There
      will also be a $(Goal) with the obvious meaning that Goal shall
      succeed deterministically. Note that the last call in a
      deterministic predicate is subject to last-call optimization.
      If this last goal does not succeed deterministically the error
      is raised against this goal rather than the goal declared to
      be deterministic. Debug mode may be used to examine the stack
      and find the predicate declared to be det.

    Comments are welcome.

MacOS users for the binaries must upgrade to XQuartz 2.8.0 for the IDE
tools to work. Note that the X11 IDE tools sometimes do not work from
the App after updates of MacOS. Sometimes it helps to reinstall XQuartz.
What also helps is running setenv('DISPLAY', ':0'). You can add that
to your ~/.config/swi-prolog/init.pl as follows:

:- initialization setenv('DISPLAY', ':0').

Enjoy — Jan

SWI-Prolog Changelog since version 8.3.20

  • FIXED: Positioning fixes and tests for ansi_format/3. Changes
    ansi_format/3 to format prepended and appended escape sequences
    separately while keeping line position information on the stream
    intact.

    Also adds tests for position information correctness and single nonlist
    arguments.

  • FIXED: Use safer shift_for_copy/1 to erroneous interaction with
    choicepoints in tabling.

  • ADDED: shift_for_copy/1 to deal with the rstrictions for shift that
    we need if we want to restart the continuation in a different context.

  • FIXED: clause_info/5 to avoid creating a cyclic term. due to wrongly
    recognised X → call(X) translation.

  • ENHANCED: Move unification to plain head arguments at the start of
    the body to unification in the head. Possible reuse of these
    arguments in the body reuse the head argument. This allows for
    clauses like below to exploit clause indexing while avoiding to
    copy the f(_) term. p(X) :- X = f(_), q(X).

  • ADDED: $/0: Cut that claims determinism for the remainder of the
    predicate.

  • ADDED: det/1 declaration to declare a predicate deterministic and
    check this at runtime at the VM level.

  • REFACTOR: Move P_INCREMENTAL flag to tabling properties.

  • ENHANCED: Compilation of head => true to avoid calling true/0.

  • ENHANCED: Remove true guard for SSU unification.

  • FIXED: instance/2 to handle single sided unification rules.
    Abramo Bagnara.

  • FIXED: goal_expansion/2 on the guard for an SSU rule head, guard => body. Abramo Bagnara.

  • FIXED: Updated QLF version number as fix for => breaks loading old
    qlf files.

  • FIXED: Handle => rules in saved states and .qlf files. Abramo Bagnara.

  • FIXED: When updating a monotonic answer subsumptive tabled node we
    must remove old answers from the queues before removing these
    answers from the trie.

  • FIXED: GUI tracer could reset live variables in a stack trace,
    leading to different behaviour in trace mode.

  • FIXED: Recursive calls from C should reset the FR_INRESET flag
    as delimited continuations do not pass callbacks.

  • FIXED: goal_expansion/2 for => rules. Abramo Bagnara.

  • FIXED: Uninitilized tr_erased_no for clauses loaded from a QLF file
    or state.

  • FIXED: Transaction rollback from lazy monotonic tables did not properly
    delete new answers, leaving the trie in an inconsistent state.

  • DEBUG: Wrong assert() check when compiled for debugging.

  • FIXED: Provide new internal API for dealing with UTF-8 decoding
    that cannot peek after the given buffer when decoding UTF-8
    from a length-delimited string. The old code could read up to
    5 characters after the buffer on invalid UTF-8 input. Decoder
    provided by Abramo Bagnara.

  • FIXED: Processing lazy monotonic queued answers may queue new answers
    for this table, so can only mark it valid if all dependents are
    valid and no new answers are queued.

  • ENHANCED: update of falsecount after partial reevaluation of a
    lazy monotonic predicate can be accurate, possibly avoiding
    recomputation.

  • ADDED: CLP(FD): implement propagation for bitshifts This is a squash
    of 3 commits. Their commits messages are shown below

  • ENHANCED: clp/clpfd.pl propagate bitshifts

    Fixes issue #568

  • MODIFIED: clp/clpfd.pl pshift/4 propagation

    Shortened the nonvar(Y) case a bit

  • MODIFIED: CLP(FD): Fix pshift propagation bug

    Occurs when X =:= 0, in which case Y cannot be restricted, and when X
    =:= -1, in which case Y =< 0, but this is now handled in the last
    condition. These errors were caught in the test case mentioned in the
    previous commit message, but with Op = (X << Y #= Z)

  • ADDED: CLP(FD): implement div propagation Squash of 4 commits,
    their commit messages are shown below

  • MODIFIED: clp/clpfd.pl cis_div_/3 fix typo?

    s=//=div=

  • ENHANCED: clp/clpfd.pl propagation of pdiv/3

    Improve propagation basing code on ptzdiv/3’s propagation

  • MODIFIED: CLP(FD): Fix mistake in cis_div

    Effectively reverting the previous s=//=div=. Introduces cis_fdiv for
    floor division to compensate for this, although note that there div
    has slightly more failures (a few thousand) than // according to
    Triska’s test in commit 435209799b58ae01537c82baea6754f8e27a5c70. Also
    the inf,sup checks in cis_fdiv need to be checked.

  • MODIFIED: CLP(FD): Fix mistakes in div propagation

    by modifying domain_expand_more and domain_contract_less to consider
    the
    differences between div and //. I tried to use =.. to generate
    the
    expressions in domain_contract_less, but couldn’t, this should be a
    TODO. Additionally, if I noted correctly, the previous uses of
    cis_slash
    could have been replaced with the previous cis_div, and so I merged
    them
    to rename cis_fdiv to cis_div, to then be able to use cis_div for floor
    division and cis_slash for round-to-zero division, with the same
    sup,inf
    handling.

    Below is a test I used to check for downright mistakes in bounds, as
    resulting from div propagation errors before this commit, adapted
    from
    one of Triska’s tests. So this test should result in 0 errors.

    :- use_module(library(clpfd)).
    
    ineqs(L, U, V, Cs) :- phrase(ineqs(L,U,V), Cs).
    
    ineqs(L0, U, V) --> ineq_lower(L0, U, V, L), ineq_upper(L, U, V).
    
    ineq_lower(L, _, V, L) --> [V #>= L].
    ineq_lower(L0, U, V, L) --> { L0 #< U, L1 #= L0 + 1 },
    

ineq_lower(L1, U, V, L).

   ineq_upper(_, U, V) --> [V #=< U].
   ineq_upper(L, U0, V) --> { L #< U0, U #= U0 - 1 }, ineq_upper(L,

U, V).

   run :- Vs = [X,Y,Z], maplist(ineqs(-4,4), Vs, Cs),
    maplist(maplist(call), Cs), maplist(fd_dom, Vs, Doms),
    Op=(X div Y #= Z), findall(Vs, (label(Vs),call(Op)), Sols),
    call(Op), maplist(fd_inf, Vs, Infs), maplist(fd_sup, Vs,
    Sups), %findall(Vs, label(Vs), Sols), Tuple = [_,_,_],
    tuples_in([Tuple], Sols), maplist(fd_inf, Tuple, TInfs),
    maplist(fd_sup, Tuple, TSups), TInfs = [TXI,TYI,TZI], Infs
    = [XI,YI,ZI], TSups = [TXS,TYS,TZS], Sups = [XS,YS,ZS],
    ( XI=<TXI, YI=<TYI, ZI=<TZI -> true
	    %TZI == ZI -> true
	    %TInfs == Infs -> true
	    ; portray_clause(failed_lower_bounds(Doms,(maplist(in,
    Vs,Doms),Op),Infs,TInfs,Sols))
	    %;
    ((nonvar(X))->portray_clause(failed_lower_bounds(Doms,(maplist(in,
    Vs,Doms),X*Y#=Z),Infs,TInfs,Sols)) ;true)
	    %;
    ((nonvar(X);nonvar(Y);nonvar(Z))->portray_clause(failed_lower_bounds(Doms,(maplist(in,
    Vs,Doms),X*Y#=Z),Infs,TInfs,Sols)) ;true) ), ( %TSups ==
    Sups -> true
	     XS >= TXS, YS >= TYS, ZS >= TZS -> true %ZS
	    == TZS -> true %XS == TXS, YS == TYS -> true ;
	    portray_clause(failed_upper_bounds(Doms,(maplist(in,
	    Vs,Doms),Op),Sups,TSups,Sols)) %; ((nonvar(X))->
	    portray_clause(failed_upper_bounds(Doms,(maplist(in,
	    Vs,Doms),X*Y#=Z),Sups,TSups,Sols));true)
		    %; ((nonvar(Y);nonvar(X);nonvar(Z))->
    portray_clause(failed_upper_bounds(Doms,(maplist(in,
    Vs,Doms),X*Y#=Z),Sups,TSups,Sols));true) ), false.

Changing the body of run/0 to the following,

   run :- Vs = [X,Y,Z], maplist(ineqs(-4,4), Vs, Cs),
    maplist(maplist(call), Cs), maplist(fd_dom, Vs, Doms),
    Op=(X div Y #= Z), %findall(Vs, (label(Vs),call(Op)),
    Sols), call(Op), maplist(fd_inf, Vs, Infs), maplist(fd_sup,
    Vs, Sups), findall(Vs, label(Vs), Sols), Tuple = [_,_,_],
    tuples_in([Tuple], Sols), maplist(fd_inf, Tuple, TInfs),
    maplist(fd_sup, Tuple, TSups), TInfs = [TXI,TYI,TZI], Infs =
    [XI,YI,ZI], TSups = [TXS,TYS,TZS], Sups = [XS,YS,ZS], ( %
    XI=<TXI, YI=<TYI, ZI=<TZI -> true
	    %TZI == ZI -> true
	    TInfs == Infs -> true
	    ; portray_clause(failed_lower_bounds(Doms,(maplist(in,
    Vs,Doms),Op),Infs,TInfs,Sols))
	    %;
    ((nonvar(X))->portray_clause(failed_lower_bounds(Doms,(maplist(in,
    Vs,Doms),X*Y#=Z),Infs,TInfs,Sols)) ;true)
	    %;
    ((nonvar(X);nonvar(Y);nonvar(Z))->portray_clause(failed_lower_bounds(Doms,(maplist(in,
    Vs,Doms),X*Y#=Z),Infs,TInfs,Sols)) ;true) ), ( TSups ==
    Sups -> true
	    % XS >= TXS, YS >= TYS, ZS >= TZS -> true
	    %ZS == TZS -> true
	    %XS == TXS, YS == TYS -> true
	    ; portray_clause(failed_upper_bounds(Doms,(maplist(in,
    Vs,Doms),Op),Sups,TSups,Sols))
	    %; ((nonvar(X))->
		    portray_clause(failed_upper_bounds(Doms,(maplist(in,
		    Vs,Doms),X*Y#=Z),Sups,TSups,Sols));true)
		    %; ((nonvar(Y);nonvar(X);nonvar(Z))->
		    portray_clause(failed_upper_bounds(Doms,(maplist(in,
		    Vs,Doms),X*Y#=Z),Sups,TSups,Sols));true)
    ),
    false.

and running this for both div and // gives the following count of
bound errors respectively, 42939 and 42765.

  • FIXED: Re-evaluation of lazy monotonic tables with cyclic dependencies
    could result in non-termination after
    e13b73087dcd64e252430bb19f36e37681f7e8a1

  • ENHANCED: Use efficient list_to_set/2 internally.

  • ENHANCE: prune valid nodes from the re-evaluation graph.

  • FIXED: transaction rollback of a new lazy monotonic answer must
    remove the deleted answer node from the
    affected queues.

  • FIXED: Lazy monotonic reevaluation with not equal length falsepaths.

  • FIXED: tabled re-evaluation path minimization (mainly avoids some
    redundant memory usage and computations).

Package xpce

  • FIXED: GUI tracer interface to library(portray_text).
3 Likes

Only for static predicates, right? Are there more differences between
dynamic and static now? What about thread local?

Edit 25.03.2021:
It seems there is a new manual section for the thingy:

2.18.3 Indexing for body code
This transformation is only performed on static code.
https://www.swi-prolog.org/pldoc/man?section=indexbody

With body indexing you can simplify DCG translation. Currently I get in SWI-Prolog 8.3.21, the head movement is done by the DCG translation:

?- [user].
a --> [b], c.
a --> []

?- listing(a/2).
a([b|A], B) :-
    c(A, B).
a(A, A).

?- expand_term((a --> [b], c), X).
X =  (a([b|_1826], _1848):-c(_1826, _1848)).

?- expand_term((a --> []), X).
X =  (a(_3086, _3086):-true).

But with body indexing the head movement is redundant, you could also more simply translate:

?- listing(a/2).
a(A, B) :-
   A = [b|C],
   c(C, B).
a(A, B) :-
   A = B.

But I dont know whether the compiler can do the corresponding movement. The compiler also preferably recognizes that the first arguments ‘A’ are unused in the body.

One more question about body indexing. Does body indexing affect the order of attribute variable unify hooks? Like for example are unify hooks later executed than without the optimization. I don’t find a mention of delayed goals in body indexing documentation.

On the same line a further question, can SWI-Prolog have some effects that delayed goals are executed after the cut? ECLiPSe Prolog has such effects since they agressively optimize body indexing including cuts. There are examples in the ECLiPSe documentation.

Yes. The order of unifications changes. Attvar triggered by head unification are executed in the neck (:-, =>) before the remainder of the body. Further, they are executed after successful completion of a built-in and the inlined VM body unification instructions. No sane constraint program should rely on the unification order though.

No. I guess that should be consider buggy behavior. Well, combining curs and → with constraints
is a very bad idea.

Can the new body indexing also index this:

p(A, X0, X) :- nonvar(A), A = [], !, X0 = X.

Works in my system, since I do not move anything into the head. Only scanning the body, including nonvar/1. But I am also planning some head movement, but it takes some time to implement it and I need to carefully benchmark it. Some stuff was there but didn’t go into release 1.5.0.

So it works, but it is nevertheless work in progress.

One more question about this new feature:

Will deep indexing also work?