ridgeworks / clpBNR

CLP(BNR) module for SWI-Prolog
MIT License
38 stars 5 forks source link

The behavior of numbervars/4, binding interval solutions #3

Closed JeanChristopheRohner closed 3 years ago

JeanChristopheRohner commented 3 years ago

Hi!

Not sure if this a problem or not, but should the code below return the interval (1.21, 1.2100000000000006)? The predicate below instead fails.

test:-
    {X == 1.1 * 1.1},
    numbervars(X, 0, _, [attvar(bind)]),
    writeln(X).
ridgeworks commented 3 years ago

I would expect this to fail. numbervars unifies the first argument with a term of the form $VAR(N). When X is an interval, it can only be unified with a numeric value or another interval. So when the option is attvar(bind) this causes a unification failure. attvar(skip) should work.

I assume you're trying to assign a more meaningful/simpler name to internal intervals. This has crossed my mind, but couldn't think of any way where the costs outweigh the benefits. Let me know if you have any ideas.

JeanChristopheRohner commented 3 years ago

I understand. My goal was to "pretty print" variable names, without the underscore format.

With this:

test1:- 
    {X * X == 4}, 
    numbervars(X, 0, _, [attvar(skip)]),
    Y = someFunctor(this, X, that, _),
    writeln(Y).

I get the output: someFunctor(this,_17060{real(-2,2)},that,_17628)

What i ended up doing to solve my problem was to strip the variable name for any attributed variables only keeping the interval:

test2:-
    {X * X == 4},
    Y = someFunctor(this, X, that, _),
    Y =.. L,
    maplist(parse_arg, L, L2),
    Y2 =.. L2,
    numbervars(Y2, 0, _),
    writeln(Y2).

parse_arg(I, O):- attvar(I), I::O.
parse_arg(I, O):- \+attvar(I), I = O.

Which yields: someFunctor(this,real(-2,2),that,A)

Kind regards, JC

ridgeworks commented 3 years ago

OK. One thing worth considering: by stripping the var you lose the ability to distinguish between two intervals with same domain, e.g., [X,Y]::real(1,10) would result in X and Y both being output as real(1,10) (which may or may not matter to you).

JeanChristopheRohner commented 3 years ago

Yes, i would prefer to be able to have both the variable and interval. I just couldn't figure out how to keep the variable and pretty print its name (i.e. A instead of _58926395).

JeanChristopheRohner commented 3 years ago

A hack that seems to work to get the variable is to convert to and from a string with term_string/2. The code in test below produces uninstantiated named variables (A, B, and so on) when the interval is too large, and a midpoint solution when the interval is small. The code in test2 produces both the interval and var name.

MIDPOINT

:-use_module(library(clpBNR)).

test:-
    {X * X == 4}, %here the resulting interval is to large (-2, 2) for a midpoint to say something meaningful
    {Y == 1.1 * 1.1}, %the interval is small enough to make a midpoint meaningful
    F = someFunctor(this, X, that, _, Y, X, nested(Y, X)), %a compound, where X and Y appear multiple times (to check if numbervars gives them the same name)
    F =.. L, %convert compound to list
    maplist(parse_arg, L, L1), %parse arg, see below
    F2 =.. L1, %convert parsed list back to compound
    term_string(F2, F3), %convert compound to string
    term_string(F4, F3), %convert string back to compound
    numbervars(F4, 0, _), %do numbervars on this compound
    writeln(F4).

parse_arg(I, O):- attvar(I), delta(I, D), {D < 0.0001}, midpoint(I, O). % replace with midpoint if the interval is reasonably small
parse_arg(I, O):- attvar(I), delta(I, D), {D >= 0.0001}, O = I. % continue using attvar if the interval is too large
parse_arg(I, O):- \+attvar(I), \+compound(I), I = O. %not attvar and not compound so just use the old argument
parse_arg(I, O):- compound(I), I =.. L, maplist(parse_arg, L, L1), O =.. L1. %term is a compound so apply parse_arg to each argument

Yields: someFunctor(this,A,that,B,1.2100000000000004,A,nested(1.2100000000000004,A))

BOTH VARNAME and INTEVAL

test2:-
    {X * X == 4}, 
    {Y == 1.1 * 1.1}, 
    F = someFunctor(this, X, that, _, Y, X, nested(Y, X)), %a compound, where X and Y appear multiple times (to check if numbervars gives them the same name)
    F =.. L, %convert compound to list
    maplist(parse_arg2, L, L1), %parse arg
    F2 =.. L1, %convert parsed list back to compound
    term_string(F2, F3), %convert compound to string
    term_string(F4, F3), %convert string back to compound
    numbervars(F4, 0, _), %do numbervars on this compound
    writeln(F4).
parse_arg2(I, O):- attvar(I), I::DOM, O = I:DOM.
parse_arg2(I, O):- \+attvar(I), \+compound(I), I = O.
parse_arg2(I, O):- compound(I), I =.. L, maplist(parse_arg2, L, L1), O =.. L1.

Yields: someFunctor(this,A:real(-2,2),that,B,C:real(1.21,1.2100000000000006),A:real(-2,2),nested(C:real(1.21,1.2100000000000006),A:real(-2,2)))