Hi Oscar,
Sorry for the late reply, I opened the two issues:
https://github.com/sympy/sympy/issues/28331
https://github.com/sympy/sympy/issues/28332
Not quite sure if the format and the text is fine, let me know if I need
to modify anything.
Thanks very much for the detailed explanation on solve vs solveset. I
found it very interesting, and I agree with the points. For my usecase I
definitely need to use solve, because I need the solutions afterwards.
I'm writing an implementation for my very special and restricted usecase
of systems, where I might need to convert between dicts and sets, mainly
to keep track of the potential infinite solutions:
{x[0]: x[2], x[1]: x[2], x[2]: x[2]} == {x[0]: x[1], x[1]: x[1], x[2]:
x[1]} is False for dicts, but they both represent the same FiniteSet if
the domain for x[1] and x[2] are the same. Is there some code in sympy
which converts between these in certain cases, or I can just go with my
(rather naive) approach with something like:
def subs_to_fset(subs:list[dict],
variables:Iterator[sympy.Symbol]) -> sympy.FiniteSet:
if any(len(d) != len(variables) for d in subs):
raise SubstitutionSizeNotMatcingNumberOfVariablesError
return sympy.FiniteSet(*[tuple(d[v] for v in variables) for d in
subs])
def fset_to_subs(fset:sympy.FiniteSet,
variables:Iterator[sympy.Symbol]) -> tuple[tuple]:
if any(len(p) != len(variables) for p in fset):
raise FiniteSetDimensionNotMatcingNumberOfVariablesError
return tuple(tuple(zip(variables, p)) for p in fset)
You also mention that there is no benefit using linsolve and nonlinsolve
vs solve. Do you mean that for the performance, as well, or only for
capability? To me performance may matter, and the awkward conversion
between sets and list[dicts] might be worth it in case linsolve is
significantly faster than solve. But if they essentially have the same
underlying code, then solve is far superior for its output.
Thanks for the pointers on how to handle systems w Min/Max. For now I
ended up using an extra domain parameter that restricts the potential
solution onto an interval (in my special usecase that's the situation),
and since all non-MinMax equations are linear, I can use some heuristics
to compute upper and lower bounds for each variable based on what
subsolutions / substitutions I found so far, and intersect that with the
domain of interval of possible solutions. In certain cases (apparently a
lot of the cases for my systems) this provides a solution to a lot of
variables, just like in the case for the minimal example I provided. This
way I can significantly reduce the number of equations having MinMax in
them.
For some big systems there still are many of those MinMax equations,
though (many meaning ~20, but that is already >1mil different cases to
solve for). So for those I'll try your suggested approach of recursively
solve one equation and substitute back, but that seems to be less
efficient for now that simply enumerating all potential 2**20-many
possibilities. I must have some silly mistake in my code, which I'll have
to check.
In any case, thank you very much for all the help!
Thanks,
Gábor
On Sat, 9 Aug 2025 at 17:31, Gábor Horváth <hungaborhorv...@gmail.com> wrote:
On Sat, 9 Aug 2025 at 13:26, Gábor Horváth <hungaborhorv...@gmail.com> wrote:
So my questions are:
1) Is the expected behaviour for sympy.solve for the Piecewise case to throw a
NaN comparison exception, or is this a sign of some underlying issue in
sympy.solve for Piecewise functions?
No, that is just a bug.
Ok, thanks. Should I report it in some official way somewhere (if yes,
where and how), or has this email already taken care of the bugreport?
Maybe I should open an issue on https://github.com/sympy/sympy/issues ?
Yes, please open an issue there.
I think that the expected behaviour here is to raise an error but it
should be an explicit error like "equation type not supported".
2) Is sympy.solve expected to rewrite Min and Max to Piecewise at some point in
the future? I found some piece of code in solvers/solvers.py:944-957 which does
some sort of rewriting for Abs, but didn't find any for Min or Max.
It probably does make sense to rewrite Min and Max as Piecewise if
they contain the variables of interest.
Ok, thanks. Same question as above: should I open some issue on
https://github.com/sympy/sympy/issues or elsewhere, or this is already
taken care of by this email?
Yes, please open an issue. It can be the same issue.
3) From some reading on the sympy docs page, I figured solveset is expected to
be the future solver. Is there a direct way I can apply solveset on a system,
rather than on one equation?
This documentation needs to be changed. The solveset function is not
going to replace solve. It was originally intended as a replacement
for solve but was then misdesigned as something different that cannot
replace solve because it does not provide comparable functionality or
interface.
Ah, I see. This was the page I found first:
https://lidavidm.github.io/sympy/modules/solvers/solveset.html
That is an old version hosted from someone's outdated fork. Can you
open a separate sympy github issue about that? We can tag the owner of
the fork and ask them to remove this. It isn't possible to open an
issue in their repo as it has issues disabled I think:
https://github.com/lidavidm/sympy
BTW, are there some old threads somewhere maybe where such discussions
can be read? I would be curious what you mean by misdesign, and what were
the discussion and decision points around solve an solveset.
Some will be here on the mailing list which you can search. Others
will be on GitHub.
What I mean by misdesign here is not that solveset is misdesigned in
general but rather that if the intention was to replace solve then it
was misdesigned for that purpose. It is not possible to replace solve
with solveset because they just don't do the same thing.
The purpose of a function like solve is that it turns an implicit
representation of the solutions (equations to be solved) into an
explicit representation which is a list of assignments like x = ..., y
= .... Its return value is therefore a list of dicts each of which
could be passed to subs.
There are some limitations in this explicit format which mean that it
cannot always be fully mathematically correct/complete. For example
your system of equations has the solution set:
{(a, a) for a in [2, oo)}
The best solve can do to represent that solution set is to return
[{x: y}]
but that loses the information that y >= 2 is a constraint on the
solutions. It could be possible to extend solve to also return the
condition like
[({x: y}), y >= 2)]
but that would be an incompatible change in its output format.
With solveset the intention is that the output is always
mathematically correct/complete but then by design it does not
necessarily return an explicit representation of the solutions which
contradicts the basic purpose of solve. There is a tradeoff here where
you have to choose between having explicit representations but with
some attached explicit or implicit conditions vs not having an
explicit representation at all.
The purpose of the solve function is to have the explicit
representations subject to the fact that they may be incomplete or not
always valid whereas the purpose of solveset is to be always complete
and valid and therefore not necessarily give an explicit
representation of the solutions. These are contradictory goals and so
in a basic way solveset cannot replace solve.
There are other reasons why solveset cannot replace solve. Another
reason is that solveset returns output in an unstructured form that is
not easily usable even when the solutions can be given explicitly.
There are some places in the sympy codebase that try to use solveset
and they always have awkward scaffolding code that basically tries to
turn the output of solveset into something that looks like the output
of solve. This is because the sets that solveset returns are only
really nice just to "look at". If you actually want to use the
solutions of the equation for something then you want an explicit
representation i.e. you basically want solve rather than solveset.
An even more obvious reason why solveset cannot replace solve is that
it only handles single univariate equations rather than systems of
equations. It has not even been designed in a way that it could be
extended to handle systems of equations in future.
There is also mention in the docs about FiniteSet handling tuples
corresponds to a finite subset of points in the multi-dimensional space.
That suggests that solveset was intended to be able to solve multi-variate
equations or even systems. Is that indeed the case, or only some special
cases (like linsolve) can really handle multi-variate equations/systems?
The linsolve and nonlinsolve functions do handle systems of equations
and are useful. They also return a somewhat awkward output format and
do not really handle infinite solution sets in a coherent way which
undermines the benefit of not simply returning a list of dicts like
solve does. I am not sure that there is any particular benefit in
using either of these functions rather than just using solve. They are
basically just new implementations of the way that solve handles
systems and perhaps have cleaner code but otherwise do not
fundamentally increase capability or usefulness.
In general I think that what you should really want in a situation
like this is something like Mathematica's Reduce function rather than
Solve. Unfortunately SymPy has several versions of solve but nothing
like reduce.
Hm, all are very good points. I have some very crude code to solve these
systems exactly as you suggest, by splitting at the various Min and Max
occurrences, solving the underlying linear system (with linsolve), and
then check if the obtained solutions indeed solve the original system with
Min and Max.
That said, somehow my code is rather slow, especially when there are many
Max occurrences. I feel that sympy.solve is faster when it doesn't fail,
so somehow sympy handles Piecewise functions pretty fast. I guess I'll
have to do some profiling on where I can still gain on performance.
Your comment also made me realize that in my systems there are some
underlying assumptions, as well, as inequalities. For the above system I
quickly checked and the corresponding assumption would be 1<=Min(x,y) and
Max(x,y)<=2. With that assumption the solution becomes unique. I guess
I'll need to handle these assumptions in my code, as well. (sympy.solve
dies with the same NaN comparison error even with the extended
assumptions)
Okay, so your actual system here is:
x = Max(y, 1)
y = Max(2, x)
1<=Min(x,y)
Max(x,y)<=2
If I wanted to make a solver for systems like this then I would make
something that turns it into separate systems of inequalities that
don't involve Min and Max. For example x = Max(y, 1) can be separated
into two disjoint cases represented by pairs of inequalities:
(1)
x = y
y > 1
(2)
x = 1
y <= 1
You can do the same for each Max and Min. That will give you 8 (2^3)
systems of linear equations and inequalities. To solve those first
solve the linear equations and then substitute into the inequalities.
Then Fourier-Motzkin can solve the remaining system of inequalities.
If you have a large number of Max/Min then the approach I just
described might be inefficient but it depends how many of the Max/Min
are actually distinct. In the above case Max(x,y) and Min(x,y) can
both switch in the same condition x>y. That is why we get 2^3 cases
rather than 2^4. If your 2^n here is large then a better approach is
something that works by progressively solving and substituting e.g.
for case (1) above you substitute x = y into all remaining
inequalities before splitting any of the other Min/Max at which point
they simplify significantly.
If SymPy were to have a solver for this kind of system it would be one
of the inequality solvers but I don't think any of those handles a
multivariate system as is needed here. Ideally SymPy would have a
reduce function that would be able to handle exactly this kind of
system but it does not. Much of the effort that went into solveset
would have been better spent on a reduce function which would have
then been a good foundation for building a new version of solve.
--
Oscar
--
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/CAHVvXxQ%3D0t4J9hqjhW43wYezd5%2BBwxKN8aeG6oBWmjkJU9hapQ%40mail.gmail.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/279a8d23-5f79-9cb8-1649-eb8dda4fa0f1%40SamsungLaptop.WORKGROUP.