# solve function output function of variable for which to solve

Here is an example:

solve(3*z^2/(2-12*z^2+sqrt(1 - 12*z^2)) == u, z)


Output is:

[z == -sqrt(1/3)*sqrt((sqrt(-12*z^2 + 1)*u + 2*u)/(4*u + 1)), z == sqrt(1/3)*sqrt((sqrt(-12*z^2 + 1)*u + 2*u)/(4*u + 1))]


As you can see, the solution is a function of both u and z. Surely this is a bug? Otherwise, if I am doing something incorrectly, can someone please let me know what has gone wrong.

edit retag close merge delete

Sort by ยป oldest newest most voted

Naive approach:

sage: z, u = SR.var("z, u")
sage: Ex = 3*z^2 / (2 - 12*z^2 + sqrt(1 - 12*z^2)) - u
sage: %time Sol0 = solve(Ex, z)
CPU times: user 1.47 s, sys: 146 ms, total: 1.62 s
Wall time: 1.59 s
sage: Sol0
[z == -sqrt(1/3)*sqrt((sqrt(-12*z^2 + 1)*u + 2*u)/(4*u + 1)),
z == sqrt(1/3)*sqrt((sqrt(-12*z^2 + 1)*u + 2*u)/(4*u + 1))]


Your equation has a rational function with radicals of a polynomial in your variable of interest. Sage's default solver is unable to solve it, and returns a (set of) equation(s) equivalent to the original one (an implicit solution) :

Let's try a variant of Maxima's solver (see solve? for details):

sage: %time Sol1 = solve(Ex, z, to_poly_solve=True)
CPU times: user 2min 15s, sys: 959 ms, total: 2min 16s
Wall time: 1min 35s
sage: Sol1
[z == -sqrt(1/3)*sqrt((sqrt(-12*z^2 + 1)*u + 2*u)/(4*u + 1)),
z == sqrt(1/3)*sqrt((sqrt(-12*z^2 + 1)*u + 2*u)/(4*u + 1))]


Takes its sweet time, and returns the same (non-)solution.

Force the use of the alternate algorithm :

sage: %time Sol2 = solve(Ex, z, to_poly_solve="force")
CPU times: user 30.9 s, sys: 243 ms, total: 31.1 s
Wall time: 21.8 s
sage: Sol2
[]


SOL...

Let's get rid of the radicals, by squaring the equations until there is no radical. It can be done manually, or you can a Sympy utility for this:

sage: from sympy.solvers.solvers import unrad
sage: foo
(48*u**2*z**4 - 12*u**2*z**2 + u**2 + 24*u*z**4 - 4*u*z**2 + 3*z**4, [])


foo[0] is a (Sympy) polynomial in u and z whose roots are a superset of those of Ex (see unrad? for details). We can try to solve it for z to get a set of candidate solutions (by squaring, we may have introduced spurious solutions...) :

sage: %time CSol = solve(foo[0]._sage_(), z); CSol
CPU times: user 15.2 ms, sys: 0 ns, total: 15.2 ms
Wall time: 15.2 ms
[z == -sqrt(2*u^2 + 1/3*sqrt(-12*u^2 + 1)*u + 2/3*u)/(4*u + 1),
z == sqrt(2*u^2 + 1/3*sqrt(-12*u^2 + 1)*u + 2/3*u)/(4*u + 1),
z == -sqrt(2*u^2 - 1/3*sqrt(-12*u^2 + 1)*u + 2/3*u)/(4*u + 1),
z == sqrt(2*u^2 - 1/3*sqrt(-12*u^2 + 1)*u + 2/3*u)/(4*u + 1)]


However, filtering the "real" solutions isn't easy: Sage is currently unable to check those candidate solutions:

sage: [Ex.subs(s).is_zero() for s in CSol]
[False, False, False, False]


This filtering may be possible by numerical checks in a "likely" region of u values. Rapid checks for u in the [-5, 5] real interval found no valid solution.

Using Sympy directly returns a set of possible implicit solutions:

sage: solve(Ex, z, algorithm="sympy")
Complement(ConditionSet(z, Eq(u/3 + z**2/(12*z**2 - sqrt(1 - 12*z**2) - 2), 0), {-sqrt(6*u**2/(48*u**2 + 24*u + 3) - u*sqrt(1 - 12*u**2)/(48*u**2 + 24*u + 3) + 2*u/(48*u**2 + 24*u + 3)), sqrt(6*u**2/(48*u**2 + 24*u + 3) - u*sqrt(1 - 12*u**2)/(48*u**2 + 24*u + 3) + 2*u/(48*u**2 + 24*u + 3)), -sqrt(6*u**2/(48*u**2 + 24*u + 3) + u*sqrt(1 - 12*u**2)/(48*u**2 + 24*u + 3) + 2*u/(48*u**2 + 24*u + 3)), sqrt(6*u**2/(48*u**2 + 24*u + 3) + u*sqrt(1 - 12*u**2)/(48*u**2 + 24*u + 3) + 2*u/(48*u**2 + 24*u + 3))}), ConditionSet(z, Eq(12*z**2 - sqrt(1 - 12*z**2) - 2, 0), {-sqrt(2)*3**(1/4)/8 - sqrt(2)*3**(3/4)/24 + I*(-sqrt(2)*3**(1/4)/8 + sqrt(2)*3**(3/4)/24), -sqrt(2)*3**(1/4)/8 - sqrt(2)*3**(3/4)/24 + I*(-sqrt(2)*3**(3/4)/24 + sqrt(2)*3**(1/4)/8), sqrt(2)*3**(3/4)/24 + sqrt(2)*3**(1/4)/8 + I*(-sqrt(2)*3**(1/4)/8 + sqrt(2)*3**(3/4)/24), sqrt(2)*3**(3/4)/24 + sqrt(2)*3**(1/4)/8 + I*(-sqrt(2)*3**(3/4)/24 + sqrt(2)*3**(1/4)/8)}))


which is no more checkable than the previous ones...

Querying "the competition" (Mathematica) via WolframAlpha may be instructive. My first attempts do not make me optimist...

EDIT: A bit of numerical check leads me to think that the first two solutions are valid in a small region of the Argand plane around 0:

sage: f = lambda t, v: Ex.subs(CSol[2*t + v]).function(u)
sage: cplot = lambda t, v: complex_plot(f(t, v), (-1, 1), (-1, 1), plot_points=200)
sage: graphics_array([[cplot(t, v) for v in range(2)] for t in range(2)])
Launched png viewer for Graphics Array of size 2 x 2


I have (currently) no idea of the reason of this region of validity. Someone with better knowledge of the behaviour of the (complex) rational functions is welcome to explain it...

EDIT 2 : "The competition" gives a formally different answer, which seems to lead to numerically similar validity regions of the roots.

Please disregard a previous version of this edit, which claimed to have found a failure...

sage: foo=mathematica("Sol=Solve[%s, %s]"%tuple(map(lambda u:repr(mathematica(u)), (Ex==0, z))))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In [3], line 1
----> 1 foo=mathematica("Sol=Solve[%s, %s]"%tuple(map(lambda u:repr(mathematica(u)), (Ex==Integer(0), z))))

File /usr/local/sage-10/src/sage/misc/lazy_import.pyx:404, in sage.misc.lazy_import.LazyImport.__call__()
402         True
403     """
--> 404     return self.get_object()(*args, **kwds)
405
406 def __repr__(self):

File /usr/local/sage-10/src/sage/interfaces/interface.py:298, in Interface.__call__(self, x, name)
295         pass
297 if isinstance(x, str):
--> 298     return cls(self, x, name=name)
299 try:
300     # Special methods do not and should not have an option to
301     # set the name directly, as the identifier assigned by the
302     # interface should stay consistent. An identifier with a
303     # user-assigned name might change its value, so we return a
304     # new element.
305     result = self._coerce_from_special_method(x)

File /usr/local/sage-10/src/sage/interfaces/expect.py:1496, in ExpectElement.__init__(self, parent, value, is_name, name)
1494 else:
1495     try:
-> 1496         self._name = parent._create(value, name=name)
1497     # Convert ValueError and RuntimeError to TypeError for
1498     # coercion to work properly.
1499     except (RuntimeError, ValueError) as x:

File /usr/local/sage-10/src/sage/interfaces/interface.py:516, in Interface._create(self, value, name)
514 def _create(self, value, name=None):
515     name = self._next_var_name() if name is None else name
--> 516     self.set(name, value)
517     return name

File /usr/local/sage-10/src/sage/interfaces/mathematica.py:621, in Mathematica.set(self, var, value)
619 out = self._eval_line(cmd, allow_use_file=True)
620 if len(out) > 8:
--> 621     raise TypeError("Error executing code in Mathematica\nCODE:\n\t%s\nMathematica ERROR:\n\t%s" % (cmd, out))

TypeError: Error executing code in Mathematica
CODE:
sage0=Sol=Solve[-u - (3*z^2)/(-2 + 12*z^2 - Sqrt[1 - 12*z^2]) == 0, z];
Mathematica ERROR:
Solve::nongen: There may be values of the parameters for which some or all
solutions are not valid.


Here, Sage wrongly interprets a Mathematica warning as an error, hence the workaround... The solutions can be (manually) transtlated in Sage :

sage: MSol=[{u[1].sage():u[2].sage() for u in s} for s in mathematica("Sol")] ; MSol
[{z: -1/3*sqrt(3)*sqrt(6*u^2/(16*u^2 + 8*u + 1) + 2*u/(16*u^2 + 8*u + 1) - sqrt(-(12*u^2 - 1)*u^2)/(16*u^2 + 8*u + 1))},
{z: 1/3*sqrt(3)*sqrt(6*u^2/(16*u^2 + 8*u + 1) + 2*u/(16*u^2 + 8*u + 1) - sqrt(-(12*u^2 - 1)*u^2)/(16*u^2 + 8*u + 1))},
{z: -sqrt(2*u^2/(16*u^2 + 8*u + 1) + 2/3*u/(16*u^2 + 8*u + 1) + 1/3*sqrt(-12*u^4 + u^2)/(16*u^2 + 8*u + 1))},
{z: sqrt(2*u^2/(16*u^2 + 8*u + 1) + 2/3*u/(16*u^2 + 8*u + 1) + 1/3*sqrt(-12*u^4 + u^2)/(16*u^2 + 8*u + 1))}]


and (numerically) checked :

sage: graphics_array([[complex_plot(Ex.subs(MSol[2*u+v]), (-1, 1), (-1, 1), plot_points=200) for v in range(2)] for u in range(2)])
Launched png viewer for Graphics Array of size 2 x 2


Sorry not to be more helpful.

more