The built-in max() is just comparing the objects using <. It works
because in some cases SymPy is able to compute the value of <. Python
lets objects override < but not max().

>>> t = sympy.Symbol('t', nonnegative=True)
>>> t/(t+1) < 1
True

The reason Max() is better is because it can remain symbolic. max()
cannot do that. It has to return one argument or the other. max(a, b)
is basically doing something like

if bool(a >= b):
    return a
else:
    return b

The bool() part in particular means that a >= b cannot be symbolic. It
has to evaluate to either True or False. This is why SymPy raises a
TypeError in the cases where it cannot be determined to be True or
False.

For what it's worth, I don't think it's good that the inequalities do
automatic simplification based on assumptions like this. I would
prefer to remove it, because it's an expensive operation to be done in
an automatic evaluation. It should be done only when calling
ask(t/(t+1) < 1) or something like that.

Similarly, if Max can be improved to compute things better that would
be good, but it would be better to put that code in doit() or
_eval_simplify() rather than happening automatically. The main issue I
see with your suggestion is how it would work when there are more than
just two arguments to Max. This also starts to get into questions
about inequalities and the assumptions in SymPy, which can be a deeper
rabbit hole than you might initially expect.

Aaron Meurer

On Mon, Sep 8, 2025 at 4:17 PM Gábor Horváth <hungaborhorv...@gmail.com> wrote:
>
>
> Hi Team!
>
> Opening a new topic for this.
>
> I played around with sympy's Min/Max, and then once I accidentally
> mistyped to use min/max, ie the built-in function. To my big surprise,
> it didn't throw an error, but actually gave the correct answer:
>
> >>> import sympy
> >>> t = sympy.Symbol('t', nonnegative=True)
> >>> max(t/(t+1), 1)
> 1
>
> I started playing around with it, and apparently, it can even simplify in
> some cases, but not in others:
>
> >>> max(t/(t+1), t**2/(t*(t+1)))
> t/(t + 1)
> >>> max(t/(t+1), t**2/(t**2+t))
> Traceback (most recent call last):
>    File "<stdin>", line 1, in <module>
>    File
> "/home/ghorvath/work/python_venv/lib/python3.9/site-packages/sympy/core/relational.py",
> line 519, in __bool__
>      raise TypeError(
> TypeError: cannot determine truth value of Relational: t**2/(t**2 + t) >
> t/(t + 1)
>
> Now, in the latter, sympy.Max actually works, even though it chooses the
> 'less simple' representation in my view:
>
> >>> sympy.Max(t/(t+1), t**2/(t**2+t))
> t**2/(t**2 + t)
>
> And then there is the case, where both of them fails:
>
> >>> max(t/(t+1), (t+1)/(t+2))
> Traceback (most recent call last):
>    File "<stdin>", line 1, in <module>
>    File
> "/home/ghorvath/work/python_venv/lib/python3.9/site-packages/sympy/core/relational.py",
> line 519, in __bool__
>      raise TypeError(
> TypeError: cannot determine truth value of Relational: (t + 1)/(t + 2) >
> t/(t + 1)
> >>> sympy.Max(t/(t+1), (t+1)/(t+2))
> Max(t/(t + 1), (t + 1)/(t + 2))
>
> Well, technically sympy.Max doesn't fail, it just can't figure out without
> help which is maximal out of the two. That said, with a bit of help, both
> of them are able to find the correct answer:
>
> >>> max(sympy.simplify(t/(t+1) - (t+1)/(t+2)), 0)
> 0
> >>> sympy.Max(sympy.simplify(t/(t+1) - (t+1)/(t+2)), 0)
> 0
>
> So I have a few questions here:
>
> 1a) How come the built-in max knows about sympy's expressions and symbol
> assumptions? Is there some hidden place in sympy's code which overrides
> the built-in behaviour of max? Or the built-in max is actually prepared
> for sympy in some way? Or something else?
> Hm, thinking about it from the error messages above, is the case that > is
> overridden, or actually defined for sympy expressions, and the built-in
> max is simply using that?
>
> 1b) If max is really using sympy's >, how come Max is superior over it?
> (max fails on max(t/(t+1), t**2/(t**2+t)), but Max does not, so it must
> be doing some extra over simply comparing by >)
>
> 2) Would it not make sense for sympy.Max (and correspondingly, Min),
> that whenever it runs to a relational error on a>b, it would try to do
> simplify(a-b)>0? Better yet, shouldn't it be somewhere in the relational
> code? That is, if a>b throws a TypeError, then it tries to do
> simplify(a-b)>0 before caving? If you think it makes sense, I'm happy to
> open an issue on this. Especially if Max is already doing something extra
> over just simply comparing via >, it might make sense to fall back on
> simplify before throwing the TypeError?
>
> Thanks,
> Gábor
>
> --
> You received this message because you are subscribed to the Google Groups 
> "sympy" group.
> To unsubscribe from this group and stop receiving emails from it, send an 
> email to sympy+unsubscr...@googlegroups.com.
> To view this discussion visit 
> https://groups.google.com/d/msgid/sympy/90a86786-fda8-15ad-dacb-3b5c3545a038%40gmail.com.

-- 
You received this message because you are subscribed to the Google Groups 
"sympy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sympy+unsubscr...@googlegroups.com.
To view this discussion visit 
https://groups.google.com/d/msgid/sympy/CAKgW%3D6%2BbbaUJH7U8Dj5kge4ELeTpSmQnV5ys-rHLvXoCqeMBrQ%40mail.gmail.com.

Reply via email to