Boolean expressions

86 views
Skip to first unread message

Emmanuel Charpentier

unread,
May 5, 2021, 5:45:50 PM5/5/21
to sage-devel

At least Sympy, Maxima and Mathematica can treat boolean expressions as other expressions. Maxima : :

(%i1) display2d:false;

(%o1) false
(%i2) foo: a and b;

(%o2) a and b
(%i3) bar: subst([a=(%pi>3), b=(%pi<4)], foo);

(%o3) %pi > 3 and %pi < 4
(%i4) is(bar);

(%o4) unknown
(%i5) bar, ev;

(%o5) true

Sympy:

>>> a, b, x=symbols("a, b, x")
>>> foo.subs({a:pi>3, b:x<4})
x < 4
>>> from sympy import *
>>> a, b, x=symbols("a, b, x")
>>> foo=And(a, b) ; foo
a & b
>>> foo.subs({a:pi>3, b:x<4})
x < 4
>>> foo.subs({a:pi>3, b:pi<4})
True

Thanks to Sage’s and and or “lazy” operators and the interpretation on anything not zero as True, Sage’s expressions containing logical operators gives surprising results :

sage: var("a, b")
(a, b)
sage: foo = a and b ; foo
b
sage: (x>3) and (x<4)
x > 3
sage: (pi>3) and (x<4)
x < 4
sage: (pi>3) and (pi<4)
pi < 4

bool((pi>3) and (pi<4))
True

As a consequence, we have no way to translate in sage the boolean expressions sometimes returned by Sympy or Maxima (e. g. Piecewise functions returned by Sympy for some integrations. It ois easy to slap _sage_ methods to Sympy’s logical functions in terms of Python expressions, but :

- their interpretation won't be what us expected, and

- these expressions won't translate to Maxima in a useful way :
sage: "print(%s)"%(a and b)
'print(b)'
sage: maxima("print(%s)"%(a and b))
b

Another (better) solution is to wrap, for example, Sympy’s logical functions in symbolic function, and define suitanle conversion functions. This is easy for Sage<-> sympy translations ; Sage -> maxima translation is also manageable.

Where I’m stuck is the Maxima->Sage translation (necessary given the importance of Maxima for a lot of our symbolics) : the current interface isn’”t useable :

sage: maxima("a and b")
aandb
sage: maxima("a and b").sage()
aandb
sage: maxima("a and b").sage().parent()
Symbolic Ring
sage: maxima("a and b").sage().variables()
(aandb,)
sage: maxima("a and b").sage().operator() is None
True
sage: maxima("a and b").sage().operands()
[]

or worse :

sage: maxima("subst([a=(%pi>3), b=(%pi<4)], a and b)")
%pi>3and%pi<4
sage: maxima("subst([a=(%pi>3), b=(%pi<4)], a and b)").sage()
---------------------------------------------------------------------------
SyntaxError                               Traceback (most recent call last)

[ Snip... ]

TypeError: unable to make sense of Maxima expression 'pi>3andpi<4' in Sage

Maxima’s logical operators are not recognized as such and are “pasted” along their arguments.

What should be done would be to get them parsed, and translated a Sages logical function calls. An alternative would be to create “symbolic logical operators” at least for the classes relevant asossible Maxima returns, but, as far as I know, this is not possible in Python.

In both cases, this requires recognizing and treating specially Maxima logical operators. The code of the current Maxima interfaces stymied me : I have been unable to identify the relevant parts. Traceing test expressions failed, since the cricial parts probably written in Cython, aren’t accessible to pdb.

Any hint or criticism well received.

Nils Bruin

unread,
May 6, 2021, 2:06:18 AM5/6/21
to sage-devel
"and" and "or" in python are not logical operators -- they are flow control for expressions; much like the " ... if ... else ..." expression is. Their behaviour cannot be overloaded. The bitwise operators "&" and "|" would be candidates for overloading.

Concerning the translation from maxima to sage, the string parser may be difficult that way. I'm not so sure it can do smart things with infix. The binary-based interface would be very easy to adapt, though:

sage: M=maxima_calculus
sage: c=M('a and b')
sage: c.ecl()
<ECL: ((MAND SIMP) $A $B)>

Adapting the s-expr based translator would be very straightforward, since (as you can see) the and appears in exactly the same way other functions would. Example:

sage: c=M('sin(a) + b')
sage: c.ecl()
<ECL: ((MPLUS SIMP) ((%SIN SIMP) $A) $B)>

The challenge would be that sage calculus never fully completed the transition to the s-expr translation. It's much more efficient and robust, but the strings-based translation was "good enough" for anyone to bother completing the transition. A few operations (integrals and limits if I'm not mistaken) do use this layer, but other don't (I think simplifications mostly don't). Perhaps this is enough motivation to complete the transition, or at least to the degree needed for boolean expressions?

The relevant conversion functions are

sage.interfaces.maxima_lib.sr_to_max
sage.interfaces.maxima_lib.max_to_sr

They use some dictionaries that would need to be preseeded with appropriate translation values for MAND and MOR. You'd furthermore have to search for occurrences of them in the library to see where they are already used.

Emmanuel Charpentier

unread,
May 6, 2021, 6:22:32 AM5/6/21
to sage-devel
Dear Nils,

Thank you for this hindsight full response. A couple remarks below :

Le jeudi 6 mai 2021 à 08:06:18 UTC+2, Nils Bruin a écrit :
"and" and "or" in python are not logical operators -- they are flow control for expressions; much like the " ... if ... else ..." expression is. Their behaviour cannot be overloaded. The bitwise operators "&" and "|" would be candidates for overloading.

Concerning the translation from maxima to sage, the string parser may be difficult that way.  I'm not so sure it can do smart things with infix.

it may do ; it already does for the arithmetic operators :

sage: ME=maxima
sage: c=ME("a^b")
sage: c.parent()
Maxima
sage: c.sage()
a^b
sage: [maxima.part(c,u) for u in (0..len(c))]
["^", a, b]
 So maybe we can use whatever `maxima` does for "^" as a cookie-cutter for logical functions ?

 
The binary-based interface would be very easy to adapt, though:

sage: M=maxima_calculus
sage: c=M('a and b')
sage: c.ecl()
<ECL: ((MAND SIMP) $A $B)>

Adapting the s-expr based translator would be very straightforward, since (as you can see) the and appears in exactly the same way other functions would. Example:

sage: c=M('sin(a) + b')
sage: c.ecl()
<ECL: ((MPLUS SIMP) ((%SIN SIMP) $A) $B)>

Agreed so far.
 
The challenge would be that sage calculus never fully completed the transition to the s-expr translation.

That is a different problem. If I understand you correctly, you would like to complete the transition to the library interface and essentially kill the pexpect one, thus dispensing with updating it for logical operators ?

This would leave in the dark all code previously written using  pexpect-specific features of the Maxima interface. Not so nice... 
 
It's much more efficient and robust, but the strings-based translation was "good enough" for anyone to bother completing the transition. A few operations (integrals and limits if I'm not mistaken) do use this layer, but other don't (I think simplifications mostly don't). Perhaps this is enough motivation to complete the transition, or at least to the degree needed for boolean expressions?

I agree that such cleanup may be beneficial. But IMHO it won't be neither fast nor clean... A distinct task ? 

The relevant conversion functions are

sage.interfaces.maxima_lib.sr_to_max
sage.interfaces.maxima_lib.max_to_sr

They use some dictionaries that would need to be preseeded with appropriate translation values for MAND and MOR. You'd furthermore have to search for occurrences of them in the library to see where they are already used.

Thank you for the information.

Sincerely,

Nils Bruin

unread,
May 6, 2021, 2:17:57 PM5/6/21
to sage-devel
On Thursday, May 6, 2021 at 3:22:32 AM UTC-7 ... wrote:

 So maybe we can use whatever `maxima` does for "^" as a cookie-cutter for logical functions ?

Indeed. If the text-based maxima-to-sage translation already parses into a full expression tree, then including rules of `and` and `or` can be done via the same. You'd just need to decide what to substitute it with on the SR side.
 
The challenge would be that sage calculus never fully completed the transition to the s-expr translation.

That is a different problem. If I understand you correctly, you would like to complete the transition to the library interface and essentially kill the pexpect one, thus dispensing with updating it for logical operators ?

To be clear, maxima_lib also supports a text-based translation layer, shared with the pexpect-based one for the inter-process-communication-based maxima. This sharing is through maxima_abstract, from which both maxima_lib and maxima inherit. With normal calculus use, you only use maxima_lib. So there is no pexpect involved. Just code that is also used for the pexpect-interface.

Currently, our use of maxima_lib in calculus involves a mixture of binary-based conversion and text-based conversion. The binary interface is quite fundamentally linear in expression size; the text interface fundamentally not (just because integers are transformed via decimal, for instance). There is a measurable difference between the two. It's just that usually when maxima is involved, computation time is not dominated by the translation step (but with the intensive use of SR in the manifold code, that might be different nowadays! It would be worth profiling for that). Depending on how extensive the parser is for text-based conversion, it may also be that certain conversions on binary level are easier to implement: the internal expression trees are much more obviously mappable to one another than the string representations. So, ditching the text-based interface could be a win for code maintenance.

This would leave in the dark all code previously written using  pexpect-specific features of the Maxima interface. Not so nice... 

There's no need to take it out. You can just leave it in for legacy support. Note that pexpect-specific features already only work for the maxima-calculus instance to the extent that they are emulated by the library interface. There is no pexpect involved there at all; you really need to make a separate maxima instance to get an interface based on that.

Emmanuel Charpentier

unread,
May 6, 2021, 3:23:57 PM5/6/21
to sage-devel

Dear Nils,

Thanks for your answers.

Note : overloading & and | for SR is probably not a good idea ; it would be way too easy to write problematic code such as :

sage: a < x < b     # This "double comparison" is legal in Sage...
a < x               # but gives a "flow control" interpretation... But the point is :
sage: a < 3 & 4 < b # Precedence for & and integers implies that the "&" is intermpreted first
a < 0               # Result probably unrelated to programer's intent...
sage: a < 3 | 4 < b # Ditto...
a < 7

imagine that 3 and 4 are replaced by function calls potentially returning either integers or symbolic expressions. The meaning of the expression above would depend of this type, with no warning from the interpreter.

Furthermore, there is no prefix operator available for overloading for not implementation… As far as I know, while Sage has a decorator allowing the creation of new infix operators (with a somewhat strange name syntax...), there is nothing for the creatopn of prefix operators

And a question :


Le jeudi 6 mai 2021 à 20:17:57 UTC+2, Nils Bruin a écrit :

On Thursday, May 6, 2021 at 3:22:32 AM UTC-7 ... wrote:

[ Snip… ]

 There is no pexpect involved there at all; you really need to make a separate maxima instance to get an interface based on that.

Huh ?
From maxima_calculus? :

Only one instance of this interface can be instantiated, so the user should not try to instantiate another one, which is anyway set to raise an error:

What do you mean ?

Nils Bruin

unread,
May 6, 2021, 6:26:59 PM5/6/21
to sage-devel
On Thursday, May 6, 2021 at 12:23:57 PM UTC-7 emanuel.c...@gmail.com wrote:

 There is no pexpect involved there at all; you really need to make a separate maxima instance to get an interface based on that.

Huh ?
From maxima_calculus? :

Only one instance of this interface can be instantiated, so the user should not try to instantiate another one, which is anyway set to raise an error:

What do you mean ?

maxima and maxima_calculus are instances of different classes:

sage: a=maxima(1)
sage: type(a)
<class 'sage.interfaces.maxima.MaximaElement'>
sage: b=maxima_calculus(1)
sage: type(b)
<class 'sage.interfaces.maxima_lib.MaximaLibElement'>

You can make multiple instances of the type "maxima", since those are honest pexpect interfaces to another process. That's not true for maxima_lib. I don't think there are any cases left in the calculus and SR code that would instantiate a maxima interface: they all talk to the (unique) maxima_lib interface, which is NOT pexpect based.

It does look like the default "maxima" interface still does have some of the maxima_calculus specific initialization to it (which surprised me a bit):

sage: A=sage.interfaces.maxima.Maxima()
sage: A("domain")
real
sage: maxima("domain")
complex

Emmanuel Charpentier

unread,
May 7, 2021, 9:07:30 AM5/7/21
to sage-devel

Both maxima interfaces mangle Maxima’s boolean operators printing.
The evaluation of a Maxima boolean expression gives nonsensical results in Sage. Using the pexpect interface :

sage: ME=maxima

sage: ME("foo: a and (b or c)")
aand(borc)

This also happens in Maxima’s printing:

sage: ME("print(foo)")
aand(borc)

but the structure has correctly been interpreted :

sage: ME("untree(x) := append([part(x, 0)], maplist(lambda([u], if atom(u) then u else untree(u)), x))")
untree(x):=append([part(x,0)],maplist(lambda([u],ifatom(u)thenuelseuntree(u)),x))
sage: ME("untree(foo)")
["and",a,["or",b,c]]

This happens also in the maxima_calculus interface :

sage: MC=maxima_calculus
sage: MC("foo")
foo
sage: MC("foo: a and (b or c)")
aand(borc)
sage: MC("print(foo)")
aand(borc)
sage: MC("untree(x) := append([part(x, 0)], maplist(lambda([u], if atom(u) then u else untree(u)), x))")
untree(x):=append([part(x,0)],maplist(lambda([u],ifatom(u)thenuelseuntree(u)),x))
sage: MC("untree(foo)")
[\"and\",a,[\"or\",b,c]]

This suggests that the problem may exist in the common parent class Maxima_abstract. Whose code is (yet) impenetrable to me. nOne also notes that this problem also affects "normal" Maxima output (including code, whose whitespace is removed (Yuk !).

Is this problem worth a ticket by itself or should it be tackled in the implementation of Sage logical functions ? In the former case, how to file the "right" informative ticket ?

Message has been deleted

Emmanuel Charpentier

unread,
May 7, 2021, 10:03:02 AM5/7/21
to sage-devel

This also happens in the .interact() manual interactions :

sage: ME.interact()

  --> Switching to Maxima <--

maxima: foo
aand(borc)
maxima: 
  --> Exiting back to Sage <--

sage: MC.interact()

  --> Switching to Maxima_lib <--

maxima_lib: foo
aand(borc)
maxima_lib: 
  --> Exiting back to Sage <--

HTH,

Nils Bruin

unread,
May 7, 2021, 11:54:51 AM5/7/21
to sage-devel
On Friday, May 7, 2021 at 6:07:30 AM UTC-7 emanuel wrote:

Both maxima interfaces mangle Maxima’s boolean operators printing.
The evaluation of a Maxima boolean expression gives nonsensical results in Sage. Using the pexpect interface :

sage: ME=maxima

sage: ME("foo: a and (b or c)")
aand(borc)

This also happens in Maxima’s printing:

sage: ME("print(foo)")
aand(borc)

Yup, see:


strings coming from the maxima interface are stripped of spaces. I guess there must be a benefit to doing so. Clearly, it doesn't seem to be designed with alphanumerical infix operators in mind.

Path to this code:

sage: M=sage.interfaces.maxima.Maxima()
sage: C=M('a and b')
sage: C.str??
sage: C._check_valid??
sage: M._eval_line??

Nils Bruin

unread,
May 7, 2021, 12:50:21 PM5/7/21
to sage-devel
I guess you may be able to make expressions robust against space removal by replacing "and" and "or" with non-alphanumeric "&&" and "||". Those can be recognized also in a non-space-separated string.

Something like the following seems to do the trick:

sage: M=sage.interfaces.maxima.Maxima()
sage: C=M('a and b')
aandb
sage: M._eval_line(':lisp (displa-def mand  dimension-nary  " && ")');
sage: C=M('a and b')
a&&b

Emmanuel Charpentier

unread,
May 7, 2021, 3:37:41 PM5/7/21
to sage-devel

Le vendredi 7 mai 2021 à 17:54:51 UTC+2, Nils Bruin a écrit :

On Friday, May 7, 2021 at 6:07:30 AM UTC-7 emanuel wrote:

Both maxima interfaces mangle Maxima’s boolean operators printing.
The evaluation of a Maxima boolean expression gives nonsensical results in Sage. Using the pexpect interface :

sage: ME=maxima

sage: ME("foo: a and (b or c)")
aand(borc)

This also happens in Maxima’s printing:

sage: ME("print(foo)")kkkkkk
aand(borc)

Thus mangling the printed output, which becomes unparsable (by program or eyeball).

I guess there must be a benefit to doing so.

I guess that this is an instance of “hell is paved with good intents”. This stripping happens line 817 of maxima.py (where any occurence of any number of consecutive whitespace is remplaced by a 0-length string) and line 461 of maxima_lib.py (where a zero-length string joins the representation of each of the result’s components).
Wanna bet that the original intent was ' ' (i. e. a single space) in both cases ?

Clearly, it doesn't seem to be designed with alphanumerical infix operators in mind.

Nor with printed code output (see above). And it’s ugly as hell…

Unless you advise me not to do so (and explain why), I’ll file a ticket on this issue, propose a branch replacing those null strings with a single space, and try to break the damn thing.

Path to this code:

sage: M=sage.interfaces.maxima.Maxima()
sage: C=M('a and b')
sage: C.str??
sage: C._check_valid??
sage: M._eval_line??

Nice shortcut . I’ve learned something tonight…

Thank you very much.

Nils Bruin

unread,
May 7, 2021, 4:41:36 PM5/7/21
to sage-devel
On Friday, May 7, 2021 at 12:37:41 PM UTC-7 emanuel wrote:
Nor with printed code output (see above). And it’s ugly as hell…
I guess maxima can be rather unpredictable in what whitespace it inserts. The output that comes back is not for human consumption, but for parsing. With just function calls and non-alphanumeric operators, the spaces aren't required to separate lexical tokens.
 

Unless you advise me not to do so (and explain why), I’ll file a ticket on this issue, propose a branch replacing those null strings with a single space, and try to break the damn thing.

I'm sure you'll succeed and discover why it was decided to strip out whitespace. Or you could do some archaeology and see if you can find records of why this was done. It looks like the change goes back to 2007:

Emmanuel Charpentier

unread,
May 8, 2021, 6:09:34 PM5/8/21
to sage-devel
Le vendredi 7 mai 2021 à 22:41:36 UTC+2, Nils Bruin a écrit :
On Friday, May 7, 2021 at 12:37:41 PM UTC-7 emanuel wrote:

[ Snip... ] 

Unless you advise me not to do so (and explain why), I’ll file a ticket on this issue, propose a branch replacing those null strings with a single space, and try to break the damn thing.

I'm sure you'll succeed and discover why it was decided to strip out whitespace.

Well, I tried and failed to break a patched version. So Trac#31796 await reviews...
 
Or you could do some archaeology and see if you can find records of why this was done. It looks like the change goes back to 2007:

That was helpful. Thank you..
 
Reply all
Reply to author
Forward
0 new messages