def equify(f): """ Returns the equation rewritten as a symbolic function to give negative values when True, positive when False. EXAMPLES:: sage: from sage.plot.contour_plot import equify sage: var('x, y') (x, y) sage: equify(x^2 < 2) x^2 - 2 sage: equify(x^2 > 2) -x^2 + 2 sage: equify(x*y > 1) -x*y + 1 sage: equify(y > 0) -y sage: f=equify(lambda x,y: x>y) sage: f(1,2) 1 sage: f(2,1) -1 """ import operator from sage.calculus.all import symbolic_expression from sage.symbolic.expression import is_Expression if not is_Expression(f): return lambda x, y: -1 if f(x, y) else 1 op = f.operator() if op is operator.gt or op is operator.ge: return symbolic_expression(f.rhs() - f.lhs()) else: return symbolic_expression(f.lhs() - f.rhs())
def equify(f): """ Returns the equation rewritten as a symbolic function to give negative values when True, positive when False. EXAMPLES:: sage: from sage.plot.contour_plot import equify sage: var('x, y') (x, y) sage: equify(x^2 < 2) x^2 - 2 sage: equify(x^2 > 2) -x^2 + 2 sage: equify(x*y > 1) -x*y + 1 sage: equify(y > 0) -y sage: f=equify(lambda x,y: x>y) sage: f(1,2) 1 sage: f(2,1) -1 """ import operator from sage.calculus.all import symbolic_expression from sage.symbolic.expression import is_Expression if not is_Expression(f): return lambda x,y: -1 if f(x,y) else 1 op = f.operator() if op is operator.gt or op is operator.ge: return symbolic_expression(f.rhs() - f.lhs()) else: return symbolic_expression(f.lhs() - f.rhs())
def set_output(self, value): self._has_output = True self._type = str(type(value)) from sage.plot.graphics import is_Graphics from sage.symbolic.expression import is_Expression if is_Graphics(value): self._text = str(value) self._png = 'graphics_'+str(self.index())+'.png' self._mime = CommandLog.OUTPUT_PNG value.save(self._png, transparent=True) elif is_Expression(value): self._text = str(value) self._latex = str(value._latex_()) self._mime = CommandLog.OUTPUT_LATEX else: self._text = value.__repr__() self._mime = CommandLog.OUTPUT_TEXT
def _eval_(self, x): """ EXAMPLES:: sage: sec(pi/4) sqrt(2) sage: sec(x).subs(x==pi/4) sqrt(2) sage: sec(pi/7) sec(1/7*pi) sage: sec(x) sec(x) """ cos_x = cos(x) if is_Expression(cos_x) and cos_x.operator() is cos: return None else: return 1/cos_x
def _eval_(self, x): """ EXAMPLES:: sage: csc(pi/4) sqrt(2) sage: csc(x).subs(x==pi/4) sqrt(2) sage: csc(pi/7) csc(1/7*pi) sage: csc(x) csc(x) """ sin_x = sin(x) if is_Expression(sin_x) and sin_x.operator() is sin: return None else: return 1/sin_x
def _eval_(self, x): """ EXAMPLES:: sage: cot(pi/4) 1 sage: cot(x).subs(x==pi/4) 1 sage: cot(pi/7) cot(1/7*pi) sage: cot(x) cot(x) """ tan_x = tan(x) if is_Expression(tan_x) and tan_x.operator() is tan: return None else: return 1/tan_x
def is_CallableSymbolicExpression(x): r""" Returns ``True`` if ``x`` is a callable symbolic expression. EXAMPLES:: sage: from sage.symbolic.callable import is_CallableSymbolicExpression sage: var('a x y z') (a, x, y, z) sage: f(x,y) = a + 2*x + 3*y + z sage: is_CallableSymbolicExpression(f) True sage: is_CallableSymbolicExpression(a+2*x) False sage: def foo(n): return n^2 ... sage: is_CallableSymbolicExpression(foo) False """ from sage.symbolic.expression import is_Expression return is_Expression(x) and isinstance(x.parent(), CallableSymbolicExpressionRing_class)
def implicit_plot(f, xrange, yrange, **options): r""" ``implicit_plot`` takes a function of two variables, `f(x,y)` and plots the curve `f(x,y) = 0` over the specified ``xrange`` and ``yrange`` as demonstrated below. ``implicit_plot(f, (xmin, xmax), (ymin, ymax), ...)`` ``implicit_plot(f, (x, xmin, xmax), (y, ymin, ymax), ...)`` INPUT: - ``f`` -- a function of two variables or equation in two variables - ``(xmin, xmax)`` -- 2-tuple, the range of ``x`` values or ``(x,xmin,xmax)`` - ``(ymin, ymax)`` -- 2-tuple, the range of ``y`` values or ``(y,ymin,ymax)`` The following inputs must all be passed in as named parameters: - ``plot_points`` -- integer (default: 150); number of points to plot in each direction of the grid - ``fill`` -- boolean (default: ``False``); if ``True``, fill the region `f(x,y) < 0`. - ``linewidth`` -- integer (default: None), if a single integer all levels will be of the width given, otherwise the levels will be plotted with the widths in the order given. - ``linestyle`` -- string (default: None), the style of the line to be plotted, one of: ``"solid"``, ``"dashed"``, ``"dashdot"`` or ``"dotted"``, respectively ``"-"``, ``"--"``, ``"-."``, or ``":"``. - ``color`` -- string (default: ``blue``), the color of the plot. Colors are defined in :mod:`sage.plot.colors`; try ``colors?`` to see them all. - ``legend_label`` -- the label for this item in the legend - ``base`` - (default: 10) the base of the logarithm if a logarithmic scale is set. This must be greater than 1. The base can be also given as a list or tuple ``(basex, basey)``. ``basex`` sets the base of the logarithm along the horizontal axis and ``basey`` sets the base along the vertical axis. - ``scale`` -- (default: ``"linear"``) string. The scale of the axes. Possible values are ``"linear"``, ``"loglog"``, ``"semilogx"``, ``"semilogy"``. The scale can be also be given as single argument that is a list or tuple ``(scale, base)`` or ``(scale, basex, basey)``. The ``"loglog"`` scale sets both the horizontal and vertical axes to logarithmic scale. The ``"semilogx"`` scale sets the horizontal axis to logarithmic scale. The ``"semilogy"`` scale sets the vertical axis to logarithmic scale. The ``"linear"`` scale is the default value when :class:`~sage.plot.graphics.Graphics` is initialized. EXAMPLES: A simple circle with a radius of 2. Note that since the input function is an expression, we need to explicitly declare the variables in 3-tuples for the range:: sage: var("x y") (x, y) sage: implicit_plot(x^2+y^2-2, (x,-3,3), (y,-3,3)) Graphics object consisting of 1 graphics primitive I can do the same thing, but using a callable function so I don't need to explicitly define the variables in the ranges, and filling the inside:: sage: f(x,y) = x^2 + y^2 - 2 sage: implicit_plot(f, (-3, 3), (-3, 3),fill=True) Graphics object consisting of 1 graphics primitive The same circle but with a different line width:: sage: implicit_plot(f, (-3,3), (-3,3), linewidth=6) Graphics object consisting of 1 graphics primitive And again the same circle but this time with a dashdot border:: sage: implicit_plot(f, (-3,3), (-3,3), linestyle='dashdot') Graphics object consisting of 1 graphics primitive You can also plot an equation:: sage: var("x y") (x, y) sage: implicit_plot(x^2+y^2 == 2, (x,-3,3), (y,-3,3)) Graphics object consisting of 1 graphics primitive You can even change the color of the plot:: sage: implicit_plot(x^2+y^2 == 2, (x,-3,3), (y,-3,3), color="red") Graphics object consisting of 1 graphics primitive Here is a beautiful (and long) example which also tests that all colors work with this:: sage: G = Graphics() sage: counter = 0 sage: for col in colors.keys(): # long time ... G += implicit_plot(x^2+y^2==1+counter*.1, (x,-4,4),(y,-4,4),color=col) ... counter += 1 sage: G.show(frame=False) We can define a level-`n` approximation of the boundary of the Mandelbrot set:: sage: def mandel(n): ... c = polygen(CDF, 'c') ... z = 0 ... for i in range(n): ... z = z*z + c ... def f(x, y): ... val = z(CDF(x, y)) ... return val.norm() - 4 ... return f The first-level approximation is just a circle:: sage: implicit_plot(mandel(1), (-3, 3), (-3, 3)) Graphics object consisting of 1 graphics primitive A third-level approximation starts to get interesting:: sage: implicit_plot(mandel(3), (-2, 1), (-1.5, 1.5)) Graphics object consisting of 1 graphics primitive The seventh-level approximation is a degree 64 polynomial, and ``implicit_plot`` does a pretty good job on this part of the curve. (``plot_points=200`` looks even better, but it takes over a second.) :: sage: implicit_plot(mandel(7), (-0.3, 0.05), (-1.15, -0.9),plot_points=50) Graphics object consisting of 1 graphics primitive When making a filled implicit plot using a python function rather than a symbolic expression the user should increase the number of plot points to avoid artifacts:: sage: implicit_plot(lambda x,y: x^2+y^2-2, (x,-3,3), (y,-3,3), fill=True, plot_points=500) # long time Graphics object consisting of 1 graphics primitive An example of an implicit plot on 'loglog' scale:: sage: implicit_plot(x^2+y^2 == 200, (x,1,200), (y,1,200), scale='loglog') Graphics object consisting of 1 graphics primitive TESTS:: sage: f(x,y) = x^2 + y^2 - 2 sage: implicit_plot(f, (-3, 3), (-3, 3),fill=5) Traceback (most recent call last): ... ValueError: fill=5 is not supported """ from sage.symbolic.expression import is_SymbolicEquation if is_SymbolicEquation(f): if f.operator() != operator.eq: raise ValueError( "input to implicit plot must be function or equation") f = f.lhs() - f.rhs() linewidths = options.pop('linewidth', None) linestyles = options.pop('linestyle', None) if 'color' in options: options['cmap'] = [options.pop('color', None)] if options['fill'] is True: options.pop('fill') options.pop('contours', None) options.pop('cmap', None) from sage.symbolic.expression import is_Expression if not is_Expression(f): return region_plot(lambda x, y: f(x, y) < 0, xrange, yrange, borderwidth=linewidths, borderstyle=linestyles, **options) else: return region_plot(f < 0, xrange, yrange, borderwidth=linewidths, borderstyle=linestyles, **options) elif options['fill'] is False: return contour_plot(f, xrange, yrange, linewidths=linewidths, linestyles=linestyles, **options) else: raise ValueError("fill=%s is not supported" % options['fill'])
def solve_mod(eqns, modulus, solution_dict=False): r""" Return all solutions to an equation or list of equations modulo the given integer modulus. Each equation must involve only polynomials in 1 or many variables. By default the solutions are returned as `n`-tuples, where `n` is the number of variables appearing anywhere in the given equations. The variables are in alphabetical order. INPUT: - ``eqns`` - equation or list of equations - ``modulus`` - an integer - ``solution_dict`` - bool (default: False); if True or non-zero, return a list of dictionaries containing the solutions. If there are no solutions, return an empty list (rather than a list containing an empty dictionary). Likewise, if there's only a single solution, return a list containing one dictionary with that solution. EXAMPLES:: sage: var('x,y') (x, y) sage: solve_mod([x^2 + 2 == x, x^2 + y == y^2], 14) [(4, 2), (4, 6), (4, 9), (4, 13)] sage: solve_mod([x^2 == 1, 4*x == 11], 15) [(14,)] Fermat's equation modulo 3 with exponent 5:: sage: var('x,y,z') (x, y, z) sage: solve_mod([x^5 + y^5 == z^5], 3) [(0, 0, 0), (0, 1, 1), (0, 2, 2), (1, 0, 1), (1, 1, 2), (1, 2, 0), (2, 0, 2), (2, 1, 0), (2, 2, 1)] We can solve with respect to a bigger modulus if it consists only of small prime factors:: sage: [d] = solve_mod([5*x + y == 3, 2*x - 3*y == 9], 3*5*7*11*19*23*29, solution_dict = True) sage: d[x] 12915279 sage: d[y] 8610183 For cases where there are relatively few solutions and the prime factors are small, this can be efficient even if the modulus itself is large:: sage: sorted(solve_mod([x^2 == 41], 10^20)) [(4538602480526452429,), (11445932736758703821,), (38554067263241296179,), (45461397519473547571,), (54538602480526452429,), (61445932736758703821,), (88554067263241296179,), (95461397519473547571,)] We solve a simple equation modulo 2:: sage: x,y = var('x,y') sage: solve_mod([x == y], 2) [(0, 0), (1, 1)] .. warning:: The current implementation splits the modulus into prime powers, then naively enumerates all possible solutions (starting modulo primes and then working up through prime powers), and finally combines the solution using the Chinese Remainder Theorem. The interface is good, but the algorithm is very inefficient if the modulus has some larger prime factors! Sage *does* have the ability to do something much faster in certain cases at least by using Groebner basis, linear algebra techniques, etc. But for a lot of toy problems this function as is might be useful. At least it establishes an interface. TESTS: Make sure that we short-circuit in at least some cases:: sage: solve_mod([2*x==1], 2*next_prime(10^50)) [] Try multi-equation cases:: sage: x, y, z = var("x y z") sage: solve_mod([2*x^2 + x*y, -x*y+2*y^2+x-2*y, -2*x^2+2*x*y-y^2-x-y], 12) [(0, 0), (4, 4), (0, 3), (4, 7)] sage: eqs = [-y^2+z^2, -x^2+y^2-3*z^2-z-1, -y*z-z^2-x-y+2, -x^2-12*z^2-y+z] sage: solve_mod(eqs, 11) [(8, 5, 6)] Confirm that modulus 1 now behaves as it should:: sage: x, y = var("x y") sage: solve_mod([x==1], 1) [(0,)] sage: solve_mod([2*x^2+x*y, -x*y+2*y^2+x-2*y, -2*x^2+2*x*y-y^2-x-y], 1) [(0, 0)] """ from sage.rings.all import Integer, Integers, crt_basis from sage.symbolic.expression import is_Expression from sage.misc.all import cartesian_product_iterator from sage.modules.all import vector from sage.matrix.all import matrix if not isinstance(eqns, (list, tuple)): eqns = [eqns] eqns = [eq if is_Expression(eq) else (eq.lhs() - eq.rhs()) for eq in eqns] modulus = Integer(modulus) if modulus < 1: raise ValueError("the modulus must be a positive integer") vars = list(set(sum([list(e.variables()) for e in eqns], []))) vars.sort(key=repr) if modulus == 1: # degenerate case ans = [tuple(Integers(1)(0) for v in vars)] return ans factors = modulus.factor() crt_basis = vector(Integers(modulus), crt_basis([p**i for p, i in factors])) solutions = [] has_solution = True for p, i in factors: solution = _solve_mod_prime_power(eqns, p, i, vars) if len(solution) > 0: solutions.append(solution) else: has_solution = False break ans = [] if has_solution: for solution in cartesian_product_iterator(solutions): solution_mat = matrix(Integers(modulus), solution) ans.append( tuple( c.dot_product(crt_basis) for c in solution_mat.columns())) # if solution_dict == True: # Relaxed form suggested by Mike Hansen (#8553): if solution_dict: sol_dict = [dict(zip(vars, solution)) for solution in ans] return sol_dict else: return ans
def __call__(self, arg): if isinstance(arg, self.element_class) and arg.parent() is self: return arg if is_Expression(arg): arg = self._subs_jet_vars(arg) return self.element_class(self, self._polynomial_ring(arg))
def solve(f, *args, **kwds): r""" Algebraically solve an equation or system of equations (over the complex numbers) for given variables. Inequalities and systems of inequalities are also supported. INPUT: - ``f`` - equation or system of equations (given by a list or tuple) - ``*args`` - variables to solve for. - ``solution_dict`` - bool (default: False); if True or non-zero, return a list of dictionaries containing the solutions. If there are no solutions, return an empty list (rather than a list containing an empty dictionary). Likewise, if there's only a single solution, return a list containing one dictionary with that solution. EXAMPLES:: sage: x, y = var('x, y') sage: solve([x+y==6, x-y==4], x, y) [[x == 5, y == 1]] sage: solve([x^2+y^2 == 1, y^2 == x^3 + x + 1], x, y) [[x == -1/2*I*sqrt(3) - 1/2, y == -sqrt(-1/2*I*sqrt(3) + 3/2)], [x == -1/2*I*sqrt(3) - 1/2, y == sqrt(-1/2*I*sqrt(3) + 3/2)], [x == 1/2*I*sqrt(3) - 1/2, y == -sqrt(1/2*I*sqrt(3) + 3/2)], [x == 1/2*I*sqrt(3) - 1/2, y == sqrt(1/2*I*sqrt(3) + 3/2)], [x == 0, y == -1], [x == 0, y == 1]] sage: solve([sqrt(x) + sqrt(y) == 5, x + y == 10], x, y) [[x == -5/2*I*sqrt(5) + 5, y == 5/2*I*sqrt(5) + 5], [x == 5/2*I*sqrt(5) + 5, y == -5/2*I*sqrt(5) + 5]] sage: solutions=solve([x^2+y^2 == 1, y^2 == x^3 + x + 1], x, y, solution_dict=True) sage: for solution in solutions: print solution[x].n(digits=3), ",", solution[y].n(digits=3) -0.500 - 0.866*I , -1.27 + 0.341*I -0.500 - 0.866*I , 1.27 - 0.341*I -0.500 + 0.866*I , -1.27 - 0.341*I -0.500 + 0.866*I , 1.27 + 0.341*I 0.000 , -1.00 0.000 , 1.00 Whenever possible, answers will be symbolic, but with systems of equations, at times approximations will be given, due to the underlying algorithm in Maxima:: sage: sols = solve([x^3==y,y^2==x],[x,y]); sols[-1], sols[0] ([x == 0, y == 0], [x == (0.309016994375 + 0.951056516295*I), y == (-0.809016994375 - 0.587785252292*I)]) sage: sols[0][0].rhs().pyobject().parent() Complex Double Field If ``f`` is only one equation or expression, we use the solve method for symbolic expressions, which defaults to exact answers only:: sage: solve([y^6==y],y) [y == e^(2/5*I*pi), y == e^(4/5*I*pi), y == e^(-4/5*I*pi), y == e^(-2/5*I*pi), y == 1, y == 0] sage: solve( [y^6 == y], y)==solve( y^6 == y, y) True .. note:: For more details about solving a single equations, see the documentation for its solve. :: sage: from sage.symbolic.expression import Expression sage: Expression.solve(x^2==1,x) [x == -1, x == 1] We must solve with respect to actual variables:: sage: z = 5 sage: solve([8*z + y == 3, -z +7*y == 0],y,z) Traceback (most recent call last): ... TypeError: 5 is not a valid variable. If we ask for dictionaries containing the solutions, we get them:: sage: solve([x^2-1],x,solution_dict=True) [{x: -1}, {x: 1}] sage: solve([x^2-4*x+4],x,solution_dict=True) [{x: 2}] sage: res = solve([x^2 == y, y == 4],x,y,solution_dict=True) sage: for soln in res: print "x: %s, y: %s"%(soln[x], soln[y]) x: 2, y: 4 x: -2, y: 4 If there is a parameter in the answer, that will show up as a new variable. In the following example, ``r1`` is a real free variable (because of the ``r``):: sage: solve([x+y == 3, 2*x+2*y == 6],x,y) [[x == -r1 + 3, y == r1]] Especially with trigonometric functions, the dummy variable may be implicitly an integer (hence the ``z``):: sage: solve([cos(x)*sin(x) == 1/2, x+y == 0],x,y) [[x == 1/4*pi + pi*z68, y == -1/4*pi - pi*z68]] Expressions which are not equations are assumed to be set equal to zero, as with `x` in the following example:: sage: solve([x, y == 2],x,y) [[x == 0, y == 2]] If ``True`` appears in the list of equations it is ignored, and if ``False`` appears in the list then no solutions are returned. E.g., note that the first ``3==3`` evaluates to ``True``, not to a symbolic equation. :: sage: solve([3==3, 1.00000000000000*x^3 == 0], x) [x == 0] sage: solve([1.00000000000000*x^3 == 0], x) [x == 0] Here, the first equation evaluates to ``False``, so there are no solutions:: sage: solve([1==3, 1.00000000000000*x^3 == 0], x) [] Completely symbolic solutions are supported:: sage: var('s,j,b,m,g') (s, j, b, m, g) sage: sys = [ m*(1-s) - b*s*j, b*s*j-g*j ]; sage: solve(sys,s,j) [[s == 1, j == 0], [s == g/b, j == (b - g)*m/(b*g)]] sage: solve(sys,(s,j)) [[s == 1, j == 0], [s == g/b, j == (b - g)*m/(b*g)]] sage: solve(sys,[s,j]) [[s == 1, j == 0], [s == g/b, j == (b - g)*m/(b*g)]] Inequalities can be also solved:: sage: solve(x^2>8,x) [[x < -2*sqrt(2)], [x > 2*sqrt(2)]] Use use_grobner if no solution is obtained from to_poly_solve:: sage: x,y=var('x y'); c1(x,y)=(x-5)^2+y^2-16; c2(x,y)=(y-3)^2+x^2-9 sage: solve([c1(x,y),c2(x,y)],[x,y]) [[x == -9/68*sqrt(55) + 135/68, y == -15/68*sqrt(5)*sqrt(11) + 123/68], [x == 9/68*sqrt(55) + 135/68, y == 15/68*sqrt(5)*sqrt(11) + 123/68]] TESTS:: sage: solve([sin(x)==x,y^2==x],x,y) [sin(x) == x, y^2 == x] sage: solve(0==1,x) Traceback (most recent call last): ... TypeError: The first argument must be a symbolic expression or a list of symbolic expressions. Test if the empty list is returned, too, when (a list of) dictionaries (is) are requested (#8553):: sage: solve([0==1],x) [] sage: solve([0==1],x,solution_dict=True) [] sage: solve([x==1,x==-1],x) [] sage: solve([x==1,x==-1],x,solution_dict=True) [] sage: solve((x==1,x==-1),x,solution_dict=0) [] Relaxed form, suggested by Mike Hansen (#8553):: sage: solve([x^2-1],x,solution_dict=-1) [{x: -1}, {x: 1}] sage: solve([x^2-1],x,solution_dict=1) [{x: -1}, {x: 1}] sage: solve((x==1,x==-1),x,solution_dict=-1) [] sage: solve((x==1,x==-1),x,solution_dict=1) [] This inequality holds for any real ``x`` (trac #8078):: sage: solve(x^4+2>0,x) [x < +Infinity] """ from sage.symbolic.expression import is_Expression if is_Expression(f): # f is a single expression ans = f.solve(*args,**kwds) return ans if not isinstance(f, (list, tuple)): raise TypeError("The first argument must be a symbolic expression or a list of symbolic expressions.") if len(f)==1 and is_Expression(f[0]): # f is a list with a single expression return f[0].solve(*args,**kwds) # f is a list of such expressions or equations from sage.symbolic.ring import is_SymbolicVariable if len(args)==0: raise TypeError, "Please input variables to solve for." if is_SymbolicVariable(args[0]): variables = args else: variables = tuple(args[0]) for v in variables: if not is_SymbolicVariable(v): raise TypeError, "%s is not a valid variable."%v try: f = [s for s in f if s is not True] except TypeError: raise ValueError, "Unable to solve %s for %s"%(f, args) if any(s is False for s in f): return [] from sage.calculus.calculus import maxima m = maxima(f) try: s = m.solve(variables) except: # if Maxima gave an error, try its to_poly_solve try: s = m.to_poly_solve(variables) except TypeError, mess: # if that gives an error, raise an error. if "Error executing code in Maxima" in str(mess): raise ValueError, "Sage is unable to determine whether the system %s can be solved for %s"%(f,args) else: raise
def solve(f, *args, **kwds): r""" Algebraically solve an equation or system of equations (over the complex numbers) for given variables. Inequalities and systems of inequalities are also supported. INPUT: - ``f`` - equation or system of equations (given by a list or tuple) - ``*args`` - variables to solve for. - ``solution_dict`` - bool (default: False); if True or non-zero, return a list of dictionaries containing the solutions. If there are no solutions, return an empty list (rather than a list containing an empty dictionary). Likewise, if there's only a single solution, return a list containing one dictionary with that solution. There are a few optional keywords if you are trying to solve a single equation. They may only be used in that context. - ``multiplicities`` - bool (default: False); if True, return corresponding multiplicities. This keyword is incompatible with ``to_poly_solve=True`` and does not make any sense when solving inequalities. - ``explicit_solutions`` - bool (default: False); require that all roots be explicit rather than implicit. Not used when solving inequalities. - ``to_poly_solve`` - bool (default: False) or string; use Maxima's ``to_poly_solver`` package to search for more possible solutions, but possibly encounter approximate solutions. This keyword is incompatible with ``multiplicities=True`` and is not used when solving inequalities. Setting ``to_poly_solve`` to 'force' (string) omits Maxima's solve command (useful when some solutions of trigonometric equations are lost). EXAMPLES:: sage: x, y = var('x, y') sage: solve([x+y==6, x-y==4], x, y) [[x == 5, y == 1]] sage: solve([x^2+y^2 == 1, y^2 == x^3 + x + 1], x, y) [[x == -1/2*I*sqrt(3) - 1/2, y == -sqrt(-1/2*I*sqrt(3) + 3/2)], [x == -1/2*I*sqrt(3) - 1/2, y == sqrt(-1/2*I*sqrt(3) + 3/2)], [x == 1/2*I*sqrt(3) - 1/2, y == -sqrt(1/2*I*sqrt(3) + 3/2)], [x == 1/2*I*sqrt(3) - 1/2, y == sqrt(1/2*I*sqrt(3) + 3/2)], [x == 0, y == -1], [x == 0, y == 1]] sage: solve([sqrt(x) + sqrt(y) == 5, x + y == 10], x, y) [[x == -5/2*I*sqrt(5) + 5, y == 5/2*I*sqrt(5) + 5], [x == 5/2*I*sqrt(5) + 5, y == -5/2*I*sqrt(5) + 5]] sage: solutions=solve([x^2+y^2 == 1, y^2 == x^3 + x + 1], x, y, solution_dict=True) sage: for solution in solutions: print("{} , {}".format(solution[x].n(digits=3), solution[y].n(digits=3))) -0.500 - 0.866*I , -1.27 + 0.341*I -0.500 - 0.866*I , 1.27 - 0.341*I -0.500 + 0.866*I , -1.27 - 0.341*I -0.500 + 0.866*I , 1.27 + 0.341*I 0.000 , -1.00 0.000 , 1.00 Whenever possible, answers will be symbolic, but with systems of equations, at times approximations will be given, due to the underlying algorithm in Maxima:: sage: sols = solve([x^3==y,y^2==x],[x,y]); sols[-1], sols[0] ([x == 0, y == 0], [x == (0.3090169943749475 + 0.9510565162951535*I), y == (-0.8090169943749475 - 0.5877852522924731*I)]) sage: sols[0][0].rhs().pyobject().parent() Complex Double Field If ``f`` is only one equation or expression, we use the solve method for symbolic expressions, which defaults to exact answers only:: sage: solve([y^6==y],y) [y == 1/4*sqrt(5) + 1/4*I*sqrt(2*sqrt(5) + 10) - 1/4, y == -1/4*sqrt(5) + 1/4*I*sqrt(-2*sqrt(5) + 10) - 1/4, y == -1/4*sqrt(5) - 1/4*I*sqrt(-2*sqrt(5) + 10) - 1/4, y == 1/4*sqrt(5) - 1/4*I*sqrt(2*sqrt(5) + 10) - 1/4, y == 1, y == 0] sage: solve( [y^6 == y], y)==solve( y^6 == y, y) True Here we demonstrate very basic use of the optional keywords for a single expression to be solved:: sage: ((x^2-1)^2).solve(x) [x == -1, x == 1] sage: ((x^2-1)^2).solve(x,multiplicities=True) ([x == -1, x == 1], [2, 2]) sage: solve(sin(x)==x,x) [x == sin(x)] sage: solve(sin(x)==x,x,explicit_solutions=True) [] sage: solve(abs(1-abs(1-x)) == 10, x) [abs(abs(x - 1) - 1) == 10] sage: solve(abs(1-abs(1-x)) == 10, x, to_poly_solve=True) [x == -10, x == 12] .. note:: For more details about solving a single equation, see the documentation for the single-expression :meth:`~sage.symbolic.expression.Expression.solve`. :: sage: from sage.symbolic.expression import Expression sage: Expression.solve(x^2==1,x) [x == -1, x == 1] We must solve with respect to actual variables:: sage: z = 5 sage: solve([8*z + y == 3, -z +7*y == 0],y,z) Traceback (most recent call last): ... TypeError: 5 is not a valid variable. If we ask for dictionaries containing the solutions, we get them:: sage: solve([x^2-1],x,solution_dict=True) [{x: -1}, {x: 1}] sage: solve([x^2-4*x+4],x,solution_dict=True) [{x: 2}] sage: res = solve([x^2 == y, y == 4],x,y,solution_dict=True) sage: for soln in res: print("x: %s, y: %s" % (soln[x], soln[y])) x: 2, y: 4 x: -2, y: 4 If there is a parameter in the answer, that will show up as a new variable. In the following example, ``r1`` is a real free variable (because of the ``r``):: sage: solve([x+y == 3, 2*x+2*y == 6],x,y) [[x == -r1 + 3, y == r1]] Especially with trigonometric functions, the dummy variable may be implicitly an integer (hence the ``z``):: sage: solve([cos(x)*sin(x) == 1/2, x+y == 0],x,y) [[x == 1/4*pi + pi*z79, y == -1/4*pi - pi*z79]] Expressions which are not equations are assumed to be set equal to zero, as with `x` in the following example:: sage: solve([x, y == 2],x,y) [[x == 0, y == 2]] If ``True`` appears in the list of equations it is ignored, and if ``False`` appears in the list then no solutions are returned. E.g., note that the first ``3==3`` evaluates to ``True``, not to a symbolic equation. :: sage: solve([3==3, 1.00000000000000*x^3 == 0], x) [x == 0] sage: solve([1.00000000000000*x^3 == 0], x) [x == 0] Here, the first equation evaluates to ``False``, so there are no solutions:: sage: solve([1==3, 1.00000000000000*x^3 == 0], x) [] Completely symbolic solutions are supported:: sage: var('s,j,b,m,g') (s, j, b, m, g) sage: sys = [ m*(1-s) - b*s*j, b*s*j-g*j ]; sage: solve(sys,s,j) [[s == 1, j == 0], [s == g/b, j == (b - g)*m/(b*g)]] sage: solve(sys,(s,j)) [[s == 1, j == 0], [s == g/b, j == (b - g)*m/(b*g)]] sage: solve(sys,[s,j]) [[s == 1, j == 0], [s == g/b, j == (b - g)*m/(b*g)]] Inequalities can be also solved:: sage: solve(x^2>8,x) [[x < -2*sqrt(2)], [x > 2*sqrt(2)]] We use ``use_grobner`` in Maxima if no solution is obtained from Maxima's ``to_poly_solve``:: sage: x,y=var('x y'); c1(x,y)=(x-5)^2+y^2-16; c2(x,y)=(y-3)^2+x^2-9 sage: solve([c1(x,y),c2(x,y)],[x,y]) [[x == -9/68*sqrt(55) + 135/68, y == -15/68*sqrt(55) + 123/68], [x == 9/68*sqrt(55) + 135/68, y == 15/68*sqrt(55) + 123/68]] TESTS:: sage: solve([sin(x)==x,y^2==x],x,y) [sin(x) == x, y^2 == x] sage: solve(0==1,x) Traceback (most recent call last): ... TypeError: The first argument must be a symbolic expression or a list of symbolic expressions. Test if the empty list is returned, too, when (a list of) dictionaries (is) are requested (:trac:`8553`):: sage: solve([SR(0)==1],x) [] sage: solve([SR(0)==1],x,solution_dict=True) [] sage: solve([x==1,x==-1],x) [] sage: solve([x==1,x==-1],x,solution_dict=True) [] sage: solve((x==1,x==-1),x,solution_dict=0) [] Relaxed form, suggested by Mike Hansen (:trac:`8553`):: sage: solve([x^2-1],x,solution_dict=-1) [{x: -1}, {x: 1}] sage: solve([x^2-1],x,solution_dict=1) [{x: -1}, {x: 1}] sage: solve((x==1,x==-1),x,solution_dict=-1) [] sage: solve((x==1,x==-1),x,solution_dict=1) [] This inequality holds for any real ``x`` (:trac:`8078`):: sage: solve(x^4+2>0,x) [x < +Infinity] Test for user friendly input handling :trac:`13645`:: sage: poly.<a,b> = PolynomialRing(RR) sage: solve([a+b+a*b == 1], a) Traceback (most recent call last): ... TypeError: The first argument to solve() should be a symbolic expression or a list of symbolic expressions, cannot handle <... 'bool'> sage: solve([a, b], (1, a)) Traceback (most recent call last): ... TypeError: 1 is not a valid variable. sage: solve([x == 1], (1, a)) Traceback (most recent call last): ... TypeError: (1, a) are not valid variables. Test that the original version of a system in the French Sage book now works (:trac:`14306`):: sage: var('y,z') (y, z) sage: solve([x^2 * y * z == 18, x * y^3 * z == 24, x * y * z^4 == 6], x, y, z) [[x == 3, y == 2, z == 1], [x == (1.337215067... - 2.685489874...*I), y == (-1.700434271... + 1.052864325...*I), z == (0.9324722294... - 0.3612416661...*I)], ...] """ from sage.symbolic.expression import is_Expression if is_Expression(f): # f is a single expression ans = f.solve(*args, **kwds) return ans if not isinstance(f, (list, tuple)): raise TypeError( "The first argument must be a symbolic expression or a list of symbolic expressions." ) if len(f) == 1: # f is a list with a single element if is_Expression(f[0]): # if its a symbolic expression call solve method of this expression return f[0].solve(*args, **kwds) # otherwise complain raise TypeError("The first argument to solve() should be a symbolic " "expression or a list of symbolic expressions, " "cannot handle %s" % repr(type(f[0]))) # f is a list of such expressions or equations from sage.symbolic.ring import is_SymbolicVariable if len(args) == 0: raise TypeError("Please input variables to solve for.") if is_SymbolicVariable(args[0]): variables = args else: variables = tuple(args[0]) for v in variables: if not is_SymbolicVariable(v): raise TypeError("%s is not a valid variable." % repr(v)) try: f = [s for s in f if s is not True] except TypeError: raise ValueError("Unable to solve %s for %s" % (f, args)) if any(s is False for s in f): return [] from sage.calculus.calculus import maxima m = maxima(f) try: s = m.solve(variables) except Exception: # if Maxima gave an error, try its to_poly_solve try: s = m.to_poly_solve(variables) except TypeError as mess: # if that gives an error, raise an error. if "Error executing code in Maxima" in str(mess): raise ValueError( "Sage is unable to determine whether the system %s can be solved for %s" % (f, args)) else: raise if len( s ) == 0: # if Maxima's solve gave no solutions, try its to_poly_solve try: s = m.to_poly_solve(variables) except Exception: # if that gives an error, stick with no solutions s = [] if len(s) == 0: # if to_poly_solve gave no solutions, try use_grobner try: s = m.to_poly_solve(variables, 'use_grobner=true') except Exception: # if that gives an error, stick with no solutions s = [] sol_list = string_to_list_of_solutions(repr(s)) # Relaxed form suggested by Mike Hansen (#8553): if kwds.get('solution_dict', False): if len(sol_list ) == 0: # fixes IndexError on empty solution list (#8553) return [] if isinstance(sol_list[0], list): sol_dict = [ dict([[eq.left(), eq.right()] for eq in solution]) for solution in sol_list ] else: sol_dict = [{eq.left(): eq.right()} for eq in sol_list] return sol_dict else: return sol_list
def _eval_(self, n, x): """ The :meth:`_eval_()` method decides which evaluation suits best for the given input, and returns a proper value. EXAMPLES:: sage: var('n,x') (n, x) sage: chebyshev_T(5,x) 16*x^5 - 20*x^3 + 5*x sage: chebyshev_T(64, x) 2*(2*(2*(2*(2*(2*x^2 - 1)^2 - 1)^2 - 1)^2 - 1)^2 - 1)^2 - 1 sage: chebyshev_T(n,-1) (-1)^n sage: chebyshev_T(-7,x) 64*x^7 - 112*x^5 + 56*x^3 - 7*x sage: chebyshev_T(3/2,x) chebyshev_T(3/2, x) sage: R.<t> = QQ[] sage: chebyshev_T(2,t) 2*t^2 - 1 sage: chebyshev_U(2,t) 4*t^2 - 1 sage: parent(chebyshev_T(4, RIF(5))) Real Interval Field with 53 bits of precision sage: RR2 = RealField(5) sage: chebyshev_T(100000,RR2(2)) 8.9e57180 sage: chebyshev_T(5,Qp(3)(2)) 2 + 3^2 + 3^3 + 3^4 + 3^5 + O(3^20) sage: chebyshev_T(100001/2, 2) doctest:...: RuntimeWarning: mpmath failed, keeping expression unevaluated chebyshev_T(100001/2, 2) sage: chebyshev_U._eval_(1.5, Mod(8,9)) is None True """ # n is an integer => evaluate algebraically (as polynomial) if n in ZZ: n = ZZ(n) # Expanded symbolic expression only for small values of n if is_Expression(x) and n.abs() < 32: return self.eval_formula(n, x) return self.eval_algebraic(n, x) if is_Expression(x) or is_Expression(n): # Check for known identities try: return self._eval_special_values_(n, x) except ValueError: # Don't evaluate => keep symbolic return None # n is not an integer and neither n nor x is symbolic. # We assume n and x are real/complex and evaluate numerically try: import sage.libs.mpmath.all as mpmath return self._evalf_(n, x) except mpmath.NoConvergence: warnings.warn("mpmath failed, keeping expression unevaluated", RuntimeWarning) return None except Exception: # Numerical evaluation failed => keep symbolic return None
def solve(f, *args, **kwds): r""" Algebraically solve an equation or system of equations (over the complex numbers) for given variables. Inequalities and systems of inequalities are also supported. INPUT: - ``f`` - equation or system of equations (given by a list or tuple) - ``*args`` - variables to solve for. - ``solution_dict`` - bool (default: False); if True or non-zero, return a list of dictionaries containing the solutions. If there are no solutions, return an empty list (rather than a list containing an empty dictionary). Likewise, if there's only a single solution, return a list containing one dictionary with that solution. EXAMPLES:: sage: x, y = var('x, y') sage: solve([x+y==6, x-y==4], x, y) [[x == 5, y == 1]] sage: solve([x^2+y^2 == 1, y^2 == x^3 + x + 1], x, y) [[x == -1/2*I*sqrt(3) - 1/2, y == -1/2*sqrt(-I*sqrt(3) + 3)*sqrt(2)], [x == -1/2*I*sqrt(3) - 1/2, y == 1/2*sqrt(-I*sqrt(3) + 3)*sqrt(2)], [x == 1/2*I*sqrt(3) - 1/2, y == -1/2*sqrt(I*sqrt(3) + 3)*sqrt(2)], [x == 1/2*I*sqrt(3) - 1/2, y == 1/2*sqrt(I*sqrt(3) + 3)*sqrt(2)], [x == 0, y == -1], [x == 0, y == 1]] sage: solve([sqrt(x) + sqrt(y) == 5, x + y == 10], x, y) [[x == -5/2*I*sqrt(5) + 5, y == 5/2*I*sqrt(5) + 5], [x == 5/2*I*sqrt(5) + 5, y == -5/2*I*sqrt(5) + 5]] sage: solutions=solve([x^2+y^2 == 1, y^2 == x^3 + x + 1], x, y, solution_dict=True) sage: for solution in solutions: print solution[x].n(digits=3), ",", solution[y].n(digits=3) -0.500 - 0.866*I , -1.27 + 0.341*I -0.500 - 0.866*I , 1.27 - 0.341*I -0.500 + 0.866*I , -1.27 - 0.341*I -0.500 + 0.866*I , 1.27 + 0.341*I 0.000 , -1.00 0.000 , 1.00 Whenever possible, answers will be symbolic, but with systems of equations, at times approximations will be given, due to the underlying algorithm in Maxima:: sage: sols = solve([x^3==y,y^2==x],[x,y]); sols[-1], sols[0] ([x == 0, y == 0], [x == (0.309016994375 + 0.951056516295*I), y == (-0.809016994375 - 0.587785252292*I)]) sage: sols[0][0].rhs().pyobject().parent() Complex Double Field If ``f`` is only one equation or expression, we use the solve method for symbolic expressions, which defaults to exact answers only:: sage: solve([y^6==y],y) [y == e^(2/5*I*pi), y == e^(4/5*I*pi), y == e^(-4/5*I*pi), y == e^(-2/5*I*pi), y == 1, y == 0] sage: solve( [y^6 == y], y)==solve( y^6 == y, y) True .. note:: For more details about solving a single equations, see the documentation for its solve. :: sage: from sage.symbolic.expression import Expression sage: Expression.solve(x^2==1,x) [x == -1, x == 1] We must solve with respect to actual variables:: sage: z = 5 sage: solve([8*z + y == 3, -z +7*y == 0],y,z) Traceback (most recent call last): ... TypeError: 5 is not a valid variable. If we ask for dictionaries containing the solutions, we get them:: sage: solve([x^2-1],x,solution_dict=True) [{x: -1}, {x: 1}] sage: solve([x^2-4*x+4],x,solution_dict=True) [{x: 2}] sage: res = solve([x^2 == y, y == 4],x,y,solution_dict=True) sage: for soln in res: print "x: %s, y: %s"%(soln[x], soln[y]) x: 2, y: 4 x: -2, y: 4 If there is a parameter in the answer, that will show up as a new variable. In the following example, ``r1`` is a real free variable (because of the ``r``):: sage: solve([x+y == 3, 2*x+2*y == 6],x,y) [[x == -r1 + 3, y == r1]] Especially with trigonometric functions, the dummy variable may be implicitly an integer (hence the ``z``):: sage: solve([cos(x)*sin(x) == 1/2, x+y == 0],x,y) [[x == 1/4*pi + pi*z68, y == -1/4*pi - pi*z68]] Expressions which are not equations are assumed to be set equal to zero, as with `x` in the following example:: sage: solve([x, y == 2],x,y) [[x == 0, y == 2]] If ``True`` appears in the list of equations it is ignored, and if ``False`` appears in the list then no solutions are returned. E.g., note that the first ``3==3`` evaluates to ``True``, not to a symbolic equation. :: sage: solve([3==3, 1.00000000000000*x^3 == 0], x) [x == 0] sage: solve([1.00000000000000*x^3 == 0], x) [x == 0] Here, the first equation evaluates to ``False``, so there are no solutions:: sage: solve([1==3, 1.00000000000000*x^3 == 0], x) [] Completely symbolic solutions are supported:: sage: var('s,j,b,m,g') (s, j, b, m, g) sage: sys = [ m*(1-s) - b*s*j, b*s*j-g*j ]; sage: solve(sys,s,j) [[s == 1, j == 0], [s == g/b, j == (b - g)*m/(b*g)]] sage: solve(sys,(s,j)) [[s == 1, j == 0], [s == g/b, j == (b - g)*m/(b*g)]] sage: solve(sys,[s,j]) [[s == 1, j == 0], [s == g/b, j == (b - g)*m/(b*g)]] Inequalities can be also solved:: sage: solve(x^2>8,x) [[x < -2*sqrt(2)], [x > 2*sqrt(2)]] Use use_grobner if no solution is obtained from to_poly_solve:: sage: x,y=var('x y'); c1(x,y)=(x-5)^2+y^2-16; c2(x,y)=(y-3)^2+x^2-9 sage: solve([c1(x,y),c2(x,y)],[x,y]) [[x == -9/68*sqrt(55) + 135/68, y == -15/68*sqrt(5)*sqrt(11) + 123/68], [x == 9/68*sqrt(55) + 135/68, y == 15/68*sqrt(5)*sqrt(11) + 123/68]] TESTS:: sage: solve([sin(x)==x,y^2==x],x,y) [sin(x) == x, y^2 == x] sage: solve(0==1,x) Traceback (most recent call last): ... TypeError: The first argument must be a symbolic expression or a list of symbolic expressions. Test if the empty list is returned, too, when (a list of) dictionaries (is) are requested (#8553):: sage: solve([0==1],x) [] sage: solve([0==1],x,solution_dict=True) [] sage: solve([x==1,x==-1],x) [] sage: solve([x==1,x==-1],x,solution_dict=True) [] sage: solve((x==1,x==-1),x,solution_dict=0) [] Relaxed form, suggested by Mike Hansen (#8553):: sage: solve([x^2-1],x,solution_dict=-1) [{x: -1}, {x: 1}] sage: solve([x^2-1],x,solution_dict=1) [{x: -1}, {x: 1}] sage: solve((x==1,x==-1),x,solution_dict=-1) [] sage: solve((x==1,x==-1),x,solution_dict=1) [] This inequality holds for any real ``x`` (trac #8078):: sage: solve(x^4+2>0,x) [x < +Infinity] """ from sage.symbolic.expression import is_Expression if is_Expression(f): # f is a single expression ans = f.solve(*args, **kwds) return ans if not isinstance(f, (list, tuple)): raise TypeError( "The first argument must be a symbolic expression or a list of symbolic expressions." ) if len(f) == 1 and is_Expression(f[0]): # f is a list with a single expression return f[0].solve(*args, **kwds) # f is a list of such expressions or equations from sage.symbolic.ring import is_SymbolicVariable if len(args) == 0: raise TypeError, "Please input variables to solve for." if is_SymbolicVariable(args[0]): variables = args else: variables = tuple(args[0]) for v in variables: if not is_SymbolicVariable(v): raise TypeError, "%s is not a valid variable." % v try: f = [s for s in f if s is not True] except TypeError: raise ValueError, "Unable to solve %s for %s" % (f, args) if any(s is False for s in f): return [] from sage.calculus.calculus import maxima m = maxima(f) try: s = m.solve(variables) except: # if Maxima gave an error, try its to_poly_solve try: s = m.to_poly_solve(variables) except TypeError, mess: # if that gives an error, raise an error. if "Error executing code in Maxima" in str(mess): raise ValueError, "Sage is unable to determine whether the system %s can be solved for %s" % ( f, args) else: raise
def implicit_plot(f, xrange, yrange, **options): r""" ``implicit_plot`` takes a function of two variables, `f(x,y)` and plots the curve `f(x,y) = 0` over the specified ``xrange`` and ``yrange`` as demonstrated below. ``implicit_plot(f, (xmin, xmax), (ymin, ymax), ...)`` ``implicit_plot(f, (x, xmin, xmax), (y, ymin, ymax), ...)`` INPUT: - ``f`` -- a function of two variables or equation in two variables - ``(xmin, xmax)`` -- 2-tuple, the range of ``x`` values or ``(x,xmin,xmax)`` - ``(ymin, ymax)`` -- 2-tuple, the range of ``y`` values or ``(y,ymin,ymax)`` The following inputs must all be passed in as named parameters: - ``plot_points`` -- integer (default: 150); number of points to plot in each direction of the grid - ``fill`` -- boolean (default: ``False``); if ``True``, fill the region `f(x,y) < 0`. - ``linewidth`` -- integer (default: None), if a single integer all levels will be of the width given, otherwise the levels will be plotted with the widths in the order given. - ``linestyle`` -- string (default: None), the style of the line to be plotted, one of: solid, dashed, dashdot or dotted. - ``color`` -- string (default: ``blue``), the color of the plot. Colors are defined in :mod:`sage.plot.colors`; try ``colors?`` to see them all. - ``legend_label`` -- the label for this item in the legend EXAMPLES: A simple circle with a radius of 2. Note that since the input function is an expression, we need to explicitly declare the variables in 3-tuples for the range:: sage: var("x y") (x, y) sage: implicit_plot(x^2+y^2-2, (x,-3,3), (y,-3,3)) I can do the same thing, but using a callable function so I don't need to explicitly define the variables in the ranges, and filling the inside:: sage: f(x,y) = x^2 + y^2 - 2 sage: implicit_plot(f, (-3, 3), (-3, 3),fill=True) The same circle but with a different line width:: sage: implicit_plot(f, (-3,3), (-3,3), linewidth=6) And again the same circle but this time with a dashdot border:: sage: implicit_plot(f, (-3,3), (-3,3), linestyle='dashdot') You can also plot an equation:: sage: var("x y") (x, y) sage: implicit_plot(x^2+y^2 == 2, (x,-3,3), (y,-3,3)) You can even change the color of the plot:: sage: implicit_plot(x^2+y^2 == 2, (x,-3,3), (y,-3,3), color="red") Here is a beautiful (and long) example which also tests that all colors work with this:: sage: G = Graphics() sage: counter = 0 sage: for col in colors.keys(): # long time ... G += implicit_plot(x^2+y^2==1+counter*.1, (x,-4,4),(y,-4,4),color=col) ... counter += 1 sage: G.show(frame=False) We can define a level-`n` approximation of the boundary of the Mandelbrot set:: sage: def mandel(n): ... c = polygen(CDF, 'c') ... z = 0 ... for i in range(n): ... z = z*z + c ... def f(x, y): ... val = z(CDF(x, y)) ... return val.norm() - 4 ... return f The first-level approximation is just a circle:: sage: implicit_plot(mandel(1), (-3, 3), (-3, 3)) A third-level approximation starts to get interesting:: sage: implicit_plot(mandel(3), (-2, 1), (-1.5, 1.5)) The seventh-level approximation is a degree 64 polynomial, and ``implicit_plot`` does a pretty good job on this part of the curve. (``plot_points=200`` looks even better, but it takes over a second.) :: sage: implicit_plot(mandel(7), (-0.3, 0.05), (-1.15, -0.9),plot_points=50) When making a filled implicit plot using a python function rather than a symbolic expression the user should increase the number of plot points to avoid artifacts:: sage: implicit_plot(lambda x,y: x^2+y^2-2, (x,-3,3), (y,-3,3), fill=True, plot_points=500) # long time TESTS:: sage: f(x,y) = x^2 + y^2 - 2 sage: implicit_plot(f, (-3, 3), (-3, 3),fill=5) Traceback (most recent call last): ... ValueError: fill=5 is not supported """ from sage.symbolic.expression import is_SymbolicEquation if is_SymbolicEquation(f): if f.operator() != operator.eq: raise ValueError, "input to implicit plot must be function or equation" f = f.lhs() - f.rhs() linewidths = options.pop('linewidth', None) linestyles = options.pop('linestyle', None) if 'color' in options: options['cmap']=[options.pop('color', None)] if options['fill'] is True: options.pop('fill') options.pop('contours',None) options.pop('cmap',None) from sage.symbolic.expression import is_Expression if not is_Expression(f): return region_plot(lambda x,y: f(x,y)<0, xrange, yrange, borderwidth=linewidths, borderstyle=linestyles, **options) else: return region_plot(f<0, xrange, yrange, borderwidth=linewidths, borderstyle=linestyles, **options) elif options['fill'] is False: return contour_plot(f, xrange, yrange, linewidths=linewidths, linestyles=linestyles, **options) else: raise ValueError("fill=%s is not supported" % options['fill'])
def region_plot(f, xrange, yrange, plot_points, incol, outcol, bordercol, borderstyle, borderwidth, alpha, **options): r""" ``region_plot`` takes a boolean function of two variables, `f(x,y)` and plots the region where f is True over the specified ``xrange`` and ``yrange`` as demonstrated below. ``region_plot(f, (xmin, xmax), (ymin, ymax), ...)`` INPUT: - ``f`` -- a boolean function or a list of boolean functions of two variables - ``(xmin, xmax)`` -- 2-tuple, the range of ``x`` values OR 3-tuple ``(x,xmin,xmax)`` - ``(ymin, ymax)`` -- 2-tuple, the range of ``y`` values OR 3-tuple ``(y,ymin,ymax)`` - ``plot_points`` -- integer (default: 100); number of points to plot in each direction of the grid - ``incol`` -- a color (default: ``'blue'``), the color inside the region - ``outcol`` -- a color (default: ``None``), the color of the outside of the region If any of these options are specified, the border will be shown as indicated, otherwise it is only implicit (with color ``incol``) as the border of the inside of the region. - ``bordercol`` -- a color (default: ``None``), the color of the border (``'black'`` if ``borderwidth`` or ``borderstyle`` is specified but not ``bordercol``) - ``borderstyle`` -- string (default: 'solid'), one of ``'solid'``, ``'dashed'``, ``'dotted'``, ``'dashdot'``, respectively ``'-'``, ``'--'``, ``':'``, ``'-.'``. - ``borderwidth`` -- integer (default: None), the width of the border in pixels - ``alpha`` -- (default: 1) How transparent the fill is. A number between 0 and 1. - ``legend_label`` -- the label for this item in the legend - ``base`` - (default: 10) the base of the logarithm if a logarithmic scale is set. This must be greater than 1. The base can be also given as a list or tuple ``(basex, basey)``. ``basex`` sets the base of the logarithm along the horizontal axis and ``basey`` sets the base along the vertical axis. - ``scale`` -- (default: ``"linear"``) string. The scale of the axes. Possible values are ``"linear"``, ``"loglog"``, ``"semilogx"``, ``"semilogy"``. The scale can be also be given as single argument that is a list or tuple ``(scale, base)`` or ``(scale, basex, basey)``. The ``"loglog"`` scale sets both the horizontal and vertical axes to logarithmic scale. The ``"semilogx"`` scale sets the horizontal axis to logarithmic scale. The ``"semilogy"`` scale sets the vertical axis to logarithmic scale. The ``"linear"`` scale is the default value when :class:`~sage.plot.graphics.Graphics` is initialized. EXAMPLES: Here we plot a simple function of two variables:: sage: x,y = var('x,y') sage: region_plot(cos(x^2+y^2) <= 0, (x, -3, 3), (y, -3, 3)) Graphics object consisting of 1 graphics primitive Here we play with the colors:: sage: region_plot(x^2+y^3 < 2, (x, -2, 2), (y, -2, 2), incol='lightblue', bordercol='gray') Graphics object consisting of 2 graphics primitives An even more complicated plot, with dashed borders:: sage: region_plot(sin(x)*sin(y) >= 1/4, (x,-10,10), (y,-10,10), incol='yellow', bordercol='black', borderstyle='dashed', plot_points=250) Graphics object consisting of 2 graphics primitives A disk centered at the origin:: sage: region_plot(x^2+y^2<1, (x,-1,1), (y,-1,1)) Graphics object consisting of 1 graphics primitive A plot with more than one condition (all conditions must be true for the statement to be true):: sage: region_plot([x^2+y^2<1, x<y], (x,-2,2), (y,-2,2)) Graphics object consisting of 1 graphics primitive Since it doesn't look very good, let's increase ``plot_points``:: sage: region_plot([x^2+y^2<1, x<y], (x,-2,2), (y,-2,2), plot_points=400) Graphics object consisting of 1 graphics primitive To get plots where only one condition needs to be true, use a function. Using lambda functions, we definitely need the extra ``plot_points``:: sage: region_plot(lambda x,y: x^2+y^2<1 or x<y, (x,-2,2), (y,-2,2), plot_points=400) Graphics object consisting of 1 graphics primitive The first quadrant of the unit circle:: sage: region_plot([y>0, x>0, x^2+y^2<1], (x,-1.1, 1.1), (y,-1.1, 1.1), plot_points = 400) Graphics object consisting of 1 graphics primitive Here is another plot, with a huge border:: sage: region_plot(x*(x-1)*(x+1)+y^2<0, (x, -3, 2), (y, -3, 3), incol='lightblue', bordercol='gray', borderwidth=10, plot_points=50) Graphics object consisting of 2 graphics primitives If we want to keep only the region where x is positive:: sage: region_plot([x*(x-1)*(x+1)+y^2<0, x>-1], (x, -3, 2), (y, -3, 3), incol='lightblue', plot_points=50) Graphics object consisting of 1 graphics primitive Here we have a cut circle:: sage: region_plot([x^2+y^2<4, x>-1], (x, -2, 2), (y, -2, 2), incol='lightblue', bordercol='gray', plot_points=200) Graphics object consisting of 2 graphics primitives The first variable range corresponds to the horizontal axis and the second variable range corresponds to the vertical axis:: sage: s,t=var('s,t') sage: region_plot(s>0,(t,-2,2),(s,-2,2)) Graphics object consisting of 1 graphics primitive :: sage: region_plot(s>0,(s,-2,2),(t,-2,2)) Graphics object consisting of 1 graphics primitive An example of a region plot in 'loglog' scale:: sage: region_plot(x^2+y^2<100, (x,1,10), (y,1,10), scale='loglog') Graphics object consisting of 1 graphics primitive TESTS: To check that :trac:`16907` is fixed:: sage: x, y = var('x, y') sage: disc1 = region_plot(x^2+y^2 < 1, (x, -1, 1), (y, -1, 1), alpha=0.5) sage: disc2 = region_plot((x-0.7)^2+(y-0.7)^2 < 0.5, (x, -2, 2), (y, -2, 2), incol='red', alpha=0.5) sage: disc1 + disc2 Graphics object consisting of 2 graphics primitives To check that :trac:`18286` is fixed:: sage: x, y = var('x, y') sage: region_plot([x == 0], (x, -1, 1), (y, -1, 1)) Graphics object consisting of 1 graphics primitive sage: region_plot([x^2+y^2==1, x<y], (x, -1, 1), (y, -1, 1)) Graphics object consisting of 1 graphics primitive """ from sage.plot.all import Graphics from sage.plot.misc import setup_for_eval_on_grid from sage.symbolic.expression import is_Expression from warnings import warn import numpy if not isinstance(f, (list, tuple)): f = [f] feqs = [equify(g) for g in f if is_Expression(g) and g.operator() is operator.eq and not equify(g).is_zero()] f = [equify(g) for g in f if not (is_Expression(g) and g.operator() is operator.eq)] neqs = len(feqs) if neqs > 1: warn("There are at least 2 equations; If the region is degenerated to points, plotting might show nothing.") feqs = [sum([fn**2 for fn in feqs])] neqs = 1 if neqs and not bordercol: bordercol = incol if not f: return implicit_plot(feqs[0], xrange, yrange, plot_points=plot_points, fill=False, \ linewidth=borderwidth, linestyle=borderstyle, color=bordercol, **options) f_all, ranges = setup_for_eval_on_grid(feqs + f, [xrange, yrange], plot_points) xrange,yrange=[r[:2] for r in ranges] xy_data_arrays = numpy.asarray([[[func(x, y) for x in xsrange(*ranges[0], include_endpoint=True)] for y in xsrange(*ranges[1], include_endpoint=True)] for func in f_all[neqs::]],dtype=float) xy_data_array=numpy.abs(xy_data_arrays.prod(axis=0)) # Now we need to set entries to negative iff all # functions were negative at that point. neg_indices = (xy_data_arrays<0).all(axis=0) xy_data_array[neg_indices]=-xy_data_array[neg_indices] from matplotlib.colors import ListedColormap incol = rgbcolor(incol) if outcol: outcol = rgbcolor(outcol) cmap = ListedColormap([incol, outcol]) cmap.set_over(outcol, alpha=alpha) else: outcol = rgbcolor('white') cmap = ListedColormap([incol, outcol]) cmap.set_over(outcol, alpha=0) cmap.set_under(incol, alpha=alpha) g = Graphics() # Reset aspect_ratio to 'automatic' in case scale is 'semilog[xy]'. # Otherwise matplotlib complains. scale = options.get('scale', None) if isinstance(scale, (list, tuple)): scale = scale[0] if scale == 'semilogy' or scale == 'semilogx': options['aspect_ratio'] = 'automatic' g._set_extra_kwds(Graphics._extract_kwds_for_show(options, ignore=['xmin', 'xmax'])) if neqs == 0: g.add_primitive(ContourPlot(xy_data_array, xrange,yrange, dict(contours=[-1e-20, 0, 1e-20], cmap=cmap, fill=True, **options))) else: mask = numpy.asarray([[elt > 0 for elt in rows] for rows in xy_data_array], dtype=bool) xy_data_array = numpy.asarray([[f_all[0](x, y) for x in xsrange(*ranges[0], include_endpoint=True)] for y in xsrange(*ranges[1], include_endpoint=True)], dtype=float) xy_data_array[mask] = None if bordercol or borderstyle or borderwidth: cmap = [rgbcolor(bordercol)] if bordercol else ['black'] linestyles = [borderstyle] if borderstyle else None linewidths = [borderwidth] if borderwidth else None g.add_primitive(ContourPlot(xy_data_array, xrange, yrange, dict(linestyles=linestyles, linewidths=linewidths, contours=[0], cmap=[bordercol], fill=False, **options))) return g
def solve(f, *args, **kwds): r""" Algebraically solve an equation or system of equations (over the complex numbers) for given variables. Inequalities and systems of inequalities are also supported. INPUT: - ``f`` - equation or system of equations (given by a list or tuple) - ``*args`` - variables to solve for. - ``solution_dict`` - bool (default: False); if True or non-zero, return a list of dictionaries containing the solutions. If there are no solutions, return an empty list (rather than a list containing an empty dictionary). Likewise, if there's only a single solution, return a list containing one dictionary with that solution. There are a few optional keywords if you are trying to solve a single equation. They may only be used in that context. - ``multiplicities`` - bool (default: False); if True, return corresponding multiplicities. This keyword is incompatible with ``to_poly_solve=True`` and does not make any sense when solving inequalities. - ``explicit_solutions`` - bool (default: False); require that all roots be explicit rather than implicit. Not used when solving inequalities. - ``to_poly_solve`` - bool (default: False) or string; use Maxima's ``to_poly_solver`` package to search for more possible solutions, but possibly encounter approximate solutions. This keyword is incompatible with ``multiplicities=True`` and is not used when solving inequalities. Setting ``to_poly_solve`` to 'force' (string) omits Maxima's solve command (useful when some solutions of trigonometric equations are lost). EXAMPLES:: sage: x, y = var('x, y') sage: solve([x+y==6, x-y==4], x, y) [[x == 5, y == 1]] sage: solve([x^2+y^2 == 1, y^2 == x^3 + x + 1], x, y) [[x == -1/2*I*sqrt(3) - 1/2, y == -sqrt(-1/2*I*sqrt(3) + 3/2)], [x == -1/2*I*sqrt(3) - 1/2, y == sqrt(-1/2*I*sqrt(3) + 3/2)], [x == 1/2*I*sqrt(3) - 1/2, y == -sqrt(1/2*I*sqrt(3) + 3/2)], [x == 1/2*I*sqrt(3) - 1/2, y == sqrt(1/2*I*sqrt(3) + 3/2)], [x == 0, y == -1], [x == 0, y == 1]] sage: solve([sqrt(x) + sqrt(y) == 5, x + y == 10], x, y) [[x == -5/2*I*sqrt(5) + 5, y == 5/2*I*sqrt(5) + 5], [x == 5/2*I*sqrt(5) + 5, y == -5/2*I*sqrt(5) + 5]] sage: solutions=solve([x^2+y^2 == 1, y^2 == x^3 + x + 1], x, y, solution_dict=True) sage: for solution in solutions: print("{} , {}".format(solution[x].n(digits=3), solution[y].n(digits=3))) -0.500 - 0.866*I , -1.27 + 0.341*I -0.500 - 0.866*I , 1.27 - 0.341*I -0.500 + 0.866*I , -1.27 - 0.341*I -0.500 + 0.866*I , 1.27 + 0.341*I 0.000 , -1.00 0.000 , 1.00 Whenever possible, answers will be symbolic, but with systems of equations, at times approximations will be given, due to the underlying algorithm in Maxima:: sage: sols = solve([x^3==y,y^2==x],[x,y]); sols[-1], sols[0] ([x == 0, y == 0], [x == (0.3090169943749475 + 0.9510565162951535*I), y == (-0.8090169943749475 - 0.5877852522924731*I)]) sage: sols[0][0].rhs().pyobject().parent() Complex Double Field If ``f`` is only one equation or expression, we use the solve method for symbolic expressions, which defaults to exact answers only:: sage: solve([y^6==y],y) [y == 1/4*sqrt(5) + 1/4*I*sqrt(2*sqrt(5) + 10) - 1/4, y == -1/4*sqrt(5) + 1/4*I*sqrt(-2*sqrt(5) + 10) - 1/4, y == -1/4*sqrt(5) - 1/4*I*sqrt(-2*sqrt(5) + 10) - 1/4, y == 1/4*sqrt(5) - 1/4*I*sqrt(2*sqrt(5) + 10) - 1/4, y == 1, y == 0] sage: solve( [y^6 == y], y)==solve( y^6 == y, y) True Here we demonstrate very basic use of the optional keywords for a single expression to be solved:: sage: ((x^2-1)^2).solve(x) [x == -1, x == 1] sage: ((x^2-1)^2).solve(x,multiplicities=True) ([x == -1, x == 1], [2, 2]) sage: solve(sin(x)==x,x) [x == sin(x)] sage: solve(sin(x)==x,x,explicit_solutions=True) [] sage: solve(abs(1-abs(1-x)) == 10, x) [abs(abs(x - 1) - 1) == 10] sage: solve(abs(1-abs(1-x)) == 10, x, to_poly_solve=True) [x == -10, x == 12] .. note:: For more details about solving a single equation, see the documentation for the single-expression :meth:`~sage.symbolic.expression.Expression.solve`. :: sage: from sage.symbolic.expression import Expression sage: Expression.solve(x^2==1,x) [x == -1, x == 1] We must solve with respect to actual variables:: sage: z = 5 sage: solve([8*z + y == 3, -z +7*y == 0],y,z) Traceback (most recent call last): ... TypeError: 5 is not a valid variable. If we ask for dictionaries containing the solutions, we get them:: sage: solve([x^2-1],x,solution_dict=True) [{x: -1}, {x: 1}] sage: solve([x^2-4*x+4],x,solution_dict=True) [{x: 2}] sage: res = solve([x^2 == y, y == 4],x,y,solution_dict=True) sage: for soln in res: print("x: %s, y: %s" % (soln[x], soln[y])) x: 2, y: 4 x: -2, y: 4 If there is a parameter in the answer, that will show up as a new variable. In the following example, ``r1`` is a real free variable (because of the ``r``):: sage: solve([x+y == 3, 2*x+2*y == 6],x,y) [[x == -r1 + 3, y == r1]] Especially with trigonometric functions, the dummy variable may be implicitly an integer (hence the ``z``):: sage: solve([cos(x)*sin(x) == 1/2, x+y == 0],x,y) [[x == 1/4*pi + pi*z79, y == -1/4*pi - pi*z79]] Expressions which are not equations are assumed to be set equal to zero, as with `x` in the following example:: sage: solve([x, y == 2],x,y) [[x == 0, y == 2]] If ``True`` appears in the list of equations it is ignored, and if ``False`` appears in the list then no solutions are returned. E.g., note that the first ``3==3`` evaluates to ``True``, not to a symbolic equation. :: sage: solve([3==3, 1.00000000000000*x^3 == 0], x) [x == 0] sage: solve([1.00000000000000*x^3 == 0], x) [x == 0] Here, the first equation evaluates to ``False``, so there are no solutions:: sage: solve([1==3, 1.00000000000000*x^3 == 0], x) [] Completely symbolic solutions are supported:: sage: var('s,j,b,m,g') (s, j, b, m, g) sage: sys = [ m*(1-s) - b*s*j, b*s*j-g*j ]; sage: solve(sys,s,j) [[s == 1, j == 0], [s == g/b, j == (b - g)*m/(b*g)]] sage: solve(sys,(s,j)) [[s == 1, j == 0], [s == g/b, j == (b - g)*m/(b*g)]] sage: solve(sys,[s,j]) [[s == 1, j == 0], [s == g/b, j == (b - g)*m/(b*g)]] Inequalities can be also solved:: sage: solve(x^2>8,x) [[x < -2*sqrt(2)], [x > 2*sqrt(2)]] We use ``use_grobner`` in Maxima if no solution is obtained from Maxima's ``to_poly_solve``:: sage: x,y=var('x y'); c1(x,y)=(x-5)^2+y^2-16; c2(x,y)=(y-3)^2+x^2-9 sage: solve([c1(x,y),c2(x,y)],[x,y]) [[x == -9/68*sqrt(55) + 135/68, y == -15/68*sqrt(11)*sqrt(5) + 123/68], [x == 9/68*sqrt(55) + 135/68, y == 15/68*sqrt(11)*sqrt(5) + 123/68]] TESTS:: sage: solve([sin(x)==x,y^2==x],x,y) [sin(x) == x, y^2 == x] sage: solve(0==1,x) Traceback (most recent call last): ... TypeError: The first argument must be a symbolic expression or a list of symbolic expressions. Test if the empty list is returned, too, when (a list of) dictionaries (is) are requested (#8553):: sage: solve([SR(0)==1],x) [] sage: solve([SR(0)==1],x,solution_dict=True) [] sage: solve([x==1,x==-1],x) [] sage: solve([x==1,x==-1],x,solution_dict=True) [] sage: solve((x==1,x==-1),x,solution_dict=0) [] Relaxed form, suggested by Mike Hansen (#8553):: sage: solve([x^2-1],x,solution_dict=-1) [{x: -1}, {x: 1}] sage: solve([x^2-1],x,solution_dict=1) [{x: -1}, {x: 1}] sage: solve((x==1,x==-1),x,solution_dict=-1) [] sage: solve((x==1,x==-1),x,solution_dict=1) [] This inequality holds for any real ``x`` (:trac:`8078`):: sage: solve(x^4+2>0,x) [x < +Infinity] Test for user friendly input handling :trac:`13645`:: sage: poly.<a,b> = PolynomialRing(RR) sage: solve([a+b+a*b == 1], a) Traceback (most recent call last): ... TypeError: The first argument to solve() should be a symbolic expression or a list of symbolic expressions, cannot handle <type 'bool'> sage: solve([a, b], (1, a)) Traceback (most recent call last): ... TypeError: 1 is not a valid variable. sage: solve([x == 1], (1, a)) Traceback (most recent call last): ... TypeError: (1, a) are not valid variables. Test that the original version of a system in the French Sage book now works (:trac:`14306`):: sage: var('y,z') (y, z) sage: solve([x^2 * y * z == 18, x * y^3 * z == 24, x * y * z^4 == 6], x, y, z) [[x == 3, y == 2, z == 1], [x == (1.337215067... - 2.685489874...*I), y == (-1.700434271... + 1.052864325...*I), z == (0.9324722294... - 0.3612416661...*I)], ...] """ from sage.symbolic.expression import is_Expression if is_Expression(f): # f is a single expression ans = f.solve(*args,**kwds) return ans if not isinstance(f, (list, tuple)): raise TypeError("The first argument must be a symbolic expression or a list of symbolic expressions.") if len(f)==1: # f is a list with a single element if is_Expression(f[0]): # if its a symbolic expression call solve method of this expression return f[0].solve(*args,**kwds) # otherwise complain raise TypeError("The first argument to solve() should be a symbolic " "expression or a list of symbolic expressions, " "cannot handle %s"%repr(type(f[0]))) # f is a list of such expressions or equations from sage.symbolic.ring import is_SymbolicVariable if len(args)==0: raise TypeError("Please input variables to solve for.") if is_SymbolicVariable(args[0]): variables = args else: variables = tuple(args[0]) for v in variables: if not is_SymbolicVariable(v): raise TypeError("%s is not a valid variable."%repr(v)) try: f = [s for s in f if s is not True] except TypeError: raise ValueError("Unable to solve %s for %s"%(f, args)) if any(s is False for s in f): return [] from sage.calculus.calculus import maxima m = maxima(f) try: s = m.solve(variables) except Exception: # if Maxima gave an error, try its to_poly_solve try: s = m.to_poly_solve(variables) except TypeError as mess: # if that gives an error, raise an error. if "Error executing code in Maxima" in str(mess): raise ValueError("Sage is unable to determine whether the system %s can be solved for %s"%(f,args)) else: raise if len(s)==0: # if Maxima's solve gave no solutions, try its to_poly_solve try: s = m.to_poly_solve(variables) except Exception: # if that gives an error, stick with no solutions s = [] if len(s)==0: # if to_poly_solve gave no solutions, try use_grobner try: s = m.to_poly_solve(variables,'use_grobner=true') except Exception: # if that gives an error, stick with no solutions s = [] sol_list = string_to_list_of_solutions(repr(s)) # Relaxed form suggested by Mike Hansen (#8553): if kwds.get('solution_dict', False): if len(sol_list)==0: # fixes IndexError on empty solution list (#8553) return [] if isinstance(sol_list[0], list): sol_dict=[dict([[eq.left(),eq.right()] for eq in solution]) for solution in sol_list] else: sol_dict=[{eq.left():eq.right()} for eq in sol_list] return sol_dict else: return sol_list
def solve_mod(eqns, modulus, solution_dict = False): r""" Return all solutions to an equation or list of equations modulo the given integer modulus. Each equation must involve only polynomials in 1 or many variables. By default the solutions are returned as `n`-tuples, where `n` is the number of variables appearing anywhere in the given equations. The variables are in alphabetical order. INPUT: - ``eqns`` - equation or list of equations - ``modulus`` - an integer - ``solution_dict`` - bool (default: False); if True or non-zero, return a list of dictionaries containing the solutions. If there are no solutions, return an empty list (rather than a list containing an empty dictionary). Likewise, if there's only a single solution, return a list containing one dictionary with that solution. EXAMPLES:: sage: var('x,y') (x, y) sage: solve_mod([x^2 + 2 == x, x^2 + y == y^2], 14) [(4, 2), (4, 6), (4, 9), (4, 13)] sage: solve_mod([x^2 == 1, 4*x == 11], 15) [(14,)] Fermat's equation modulo 3 with exponent 5:: sage: var('x,y,z') (x, y, z) sage: solve_mod([x^5 + y^5 == z^5], 3) [(0, 0, 0), (0, 1, 1), (0, 2, 2), (1, 0, 1), (1, 1, 2), (1, 2, 0), (2, 0, 2), (2, 1, 0), (2, 2, 1)] We can solve with respect to a bigger modulus if it consists only of small prime factors:: sage: [d] = solve_mod([5*x + y == 3, 2*x - 3*y == 9], 3*5*7*11*19*23*29, solution_dict = True) sage: d[x] 12915279 sage: d[y] 8610183 For cases where there are relatively few solutions and the prime factors are small, this can be efficient even if the modulus itself is large:: sage: sorted(solve_mod([x^2 == 41], 10^20)) [(4538602480526452429,), (11445932736758703821,), (38554067263241296179,), (45461397519473547571,), (54538602480526452429,), (61445932736758703821,), (88554067263241296179,), (95461397519473547571,)] We solve a simple equation modulo 2:: sage: x,y = var('x,y') sage: solve_mod([x == y], 2) [(0, 0), (1, 1)] .. warning:: The current implementation splits the modulus into prime powers, then naively enumerates all possible solutions (starting modulo primes and then working up through prime powers), and finally combines the solution using the Chinese Remainder Theorem. The interface is good, but the algorithm is very inefficient if the modulus has some larger prime factors! Sage *does* have the ability to do something much faster in certain cases at least by using Groebner basis, linear algebra techniques, etc. But for a lot of toy problems this function as is might be useful. At least it establishes an interface. TESTS: Make sure that we short-circuit in at least some cases:: sage: solve_mod([2*x==1], 2*next_prime(10^50)) [] Try multi-equation cases:: sage: x, y, z = var("x y z") sage: solve_mod([2*x^2 + x*y, -x*y+2*y^2+x-2*y, -2*x^2+2*x*y-y^2-x-y], 12) [(0, 0), (4, 4), (0, 3), (4, 7)] sage: eqs = [-y^2+z^2, -x^2+y^2-3*z^2-z-1, -y*z-z^2-x-y+2, -x^2-12*z^2-y+z] sage: solve_mod(eqs, 11) [(8, 5, 6)] Confirm that modulus 1 now behaves as it should:: sage: x, y = var("x y") sage: solve_mod([x==1], 1) [(0,)] sage: solve_mod([2*x^2+x*y, -x*y+2*y^2+x-2*y, -2*x^2+2*x*y-y^2-x-y], 1) [(0, 0)] """ from sage.rings.all import Integer, Integers, crt_basis from sage.symbolic.expression import is_Expression from sage.misc.all import cartesian_product_iterator from sage.modules.all import vector from sage.matrix.all import matrix if not isinstance(eqns, (list, tuple)): eqns = [eqns] eqns = [eq if is_Expression(eq) else (eq.lhs()-eq.rhs()) for eq in eqns] modulus = Integer(modulus) if modulus < 1: raise ValueError("the modulus must be a positive integer") vars = list(set(sum([list(e.variables()) for e in eqns], []))) vars.sort(key=repr) if modulus == 1: # degenerate case ans = [tuple(Integers(1)(0) for v in vars)] return ans factors = modulus.factor() crt_basis = vector(Integers(modulus), crt_basis([p**i for p,i in factors])) solutions = [] has_solution = True for p,i in factors: solution =_solve_mod_prime_power(eqns, p, i, vars) if len(solution) > 0: solutions.append(solution) else: has_solution = False break ans = [] if has_solution: for solution in cartesian_product_iterator(solutions): solution_mat = matrix(Integers(modulus), solution) ans.append(tuple(c.dot_product(crt_basis) for c in solution_mat.columns())) # if solution_dict == True: # Relaxed form suggested by Mike Hansen (#8553): if solution_dict: sol_dict = [dict(zip(vars, solution)) for solution in ans] return sol_dict else: return ans
def to_cartesian(self, func, params=None): """ Returns a 3-tuple of functions, parameterized over ``params``, that represents the cartesian coordinates of the value of ``func``. INPUT: - ``func`` - A function in this coordinate space. Corresponds to the independent variable. - ``params`` - The parameters of func. Corresponds to the dependent variables. EXAMPLE:: sage: from sage.plot.plot3d.plot3d import _ArbitraryCoordinates sage: x, y, z = var('x y z') sage: T = _ArbitraryCoordinates((x + y, x - y, z), z,[x,y]) sage: f(x, y) = 2*x+y sage: T.to_cartesian(f, [x, y]) (x + y, x - y, 2*x + y) sage: [h(1,2) for h in T.to_cartesian(lambda x,y: 2*x+y)] [3, -1, 4] We try to return a function having the same variable names as the function passed in:: sage: from sage.plot.plot3d.plot3d import _ArbitraryCoordinates sage: x, y, z = var('x y z') sage: T = _ArbitraryCoordinates((x + y, x - y, z), z,[x,y]) sage: f(a, b) = 2*a+b sage: T.to_cartesian(f, [a, b]) (a + b, a - b, 2*a + b) sage: t1,t2,t3=T.to_cartesian(lambda a,b: 2*a+b) sage: import inspect sage: inspect.getargspec(t1) ArgSpec(args=['a', 'b'], varargs=None, keywords=None, defaults=None) sage: inspect.getargspec(t2) ArgSpec(args=['a', 'b'], varargs=None, keywords=None, defaults=None) sage: inspect.getargspec(t3) ArgSpec(args=['a', 'b'], varargs=None, keywords=None, defaults=None) sage: def g(a,b): return 2*a+b sage: t1,t2,t3=T.to_cartesian(g) sage: inspect.getargspec(t1) ArgSpec(args=['a', 'b'], varargs=None, keywords=None, defaults=None) sage: t1,t2,t3=T.to_cartesian(2*a+b) sage: inspect.getargspec(t1) ArgSpec(args=['a', 'b'], varargs=None, keywords=None, defaults=None) If we cannot guess the right parameter names, then the parameters are named `u` and `v`:: sage: from sage.plot.plot3d.plot3d import _ArbitraryCoordinates sage: x, y, z = var('x y z') sage: T = _ArbitraryCoordinates((x + y, x - y, z), z,[x,y]) sage: t1,t2,t3=T.to_cartesian(operator.add) sage: inspect.getargspec(t1) ArgSpec(args=['u', 'v'], varargs=None, keywords=None, defaults=None) sage: [h(1,2) for h in T.to_cartesian(operator.mul)] [3, -1, 2] sage: [h(u=1,v=2) for h in T.to_cartesian(operator.mul)] [3, -1, 2] """ from sage.symbolic.expression import is_Expression from sage.rings.real_mpfr import is_RealNumber from sage.rings.integer import is_Integer if params is not None and (is_Expression(func) or is_RealNumber(func) or is_Integer(func)): return self.transform(**{ self.dep_var: func, self.indep_vars[0]: params[0], self.indep_vars[1]: params[1] }) else: # func might be a lambda or a Python callable; this makes it slightly # more complex. import sage.symbolic.ring dep_var_dummy = sage.symbolic.ring.var(self.dep_var) indep_var_dummies = sage.symbolic.ring.var(','.join(self.indep_vars)) transformation = self.transform(**{ self.dep_var: dep_var_dummy, self.indep_vars[0]: indep_var_dummies[0], self.indep_vars[1]: indep_var_dummies[1] }) if params is None: if callable(func): params = _find_arguments_for_callable(func) if params is None: params=['u','v'] else: raise ValueError, "function is not callable" def subs_func(t): # We use eval so that the lambda function has the same # variable names as the original function ll="""lambda {x},{y}: t.subs({{ dep_var_dummy: func({x}, {y}), indep_var_dummies[0]: {x}, indep_var_dummies[1]: {y} }})""".format(x=params[0], y=params[1]) return eval(ll,dict(t=t, func=func, dep_var_dummy=dep_var_dummy, indep_var_dummies=indep_var_dummies)) return map(subs_func, transformation)
def to_cartesian(self, func, params=None): """ Returns a 3-tuple of functions, parameterized over ``params``, that represents the Cartesian coordinates of the value of ``func``. INPUT: - ``func`` - A function in this coordinate space. Corresponds to the independent variable. - ``params`` - The parameters of ``func``. Corresponds to the dependent variables. EXAMPLES:: sage: from sage.plot.plot3d.plot3d import _ArbitraryCoordinates sage: x, y, z = var('x y z') sage: T = _ArbitraryCoordinates((x + y, x - y, z), z,[x,y]) sage: f(x, y) = 2*x+y sage: T.to_cartesian(f, [x, y]) (x + y, x - y, 2*x + y) sage: [h(1,2) for h in T.to_cartesian(lambda x,y: 2*x+y)] [3.0, -1.0, 4.0] We try to return a function having the same variable names as the function passed in:: sage: from sage.plot.plot3d.plot3d import _ArbitraryCoordinates sage: x, y, z = var('x y z') sage: T = _ArbitraryCoordinates((x + y, x - y, z), z,[x,y]) sage: f(a, b) = 2*a+b sage: T.to_cartesian(f, [a, b]) (a + b, a - b, 2*a + b) sage: t1,t2,t3=T.to_cartesian(lambda a,b: 2*a+b) sage: import inspect sage: inspect.getargspec(t1) ArgSpec(args=['a', 'b'], varargs=None, keywords=None, defaults=None) sage: inspect.getargspec(t2) ArgSpec(args=['a', 'b'], varargs=None, keywords=None, defaults=None) sage: inspect.getargspec(t3) ArgSpec(args=['a', 'b'], varargs=None, keywords=None, defaults=None) sage: def g(a,b): return 2*a+b sage: t1,t2,t3=T.to_cartesian(g) sage: inspect.getargspec(t1) ArgSpec(args=['a', 'b'], varargs=None, keywords=None, defaults=None) sage: t1,t2,t3=T.to_cartesian(2*a+b) sage: inspect.getargspec(t1) ArgSpec(args=['a', 'b'], varargs=None, keywords=None, defaults=None) If we cannot guess the right parameter names, then the parameters are named `u` and `v`:: sage: from sage.plot.plot3d.plot3d import _ArbitraryCoordinates sage: x, y, z = var('x y z') sage: T = _ArbitraryCoordinates((x + y, x - y, z), z,[x,y]) sage: t1,t2,t3=T.to_cartesian(operator.add) sage: inspect.getargspec(t1) ArgSpec(args=['u', 'v'], varargs=None, keywords=None, defaults=None) sage: [h(1,2) for h in T.to_cartesian(operator.mul)] [3.0, -1.0, 2.0] sage: [h(u=1,v=2) for h in T.to_cartesian(operator.mul)] [3.0, -1.0, 2.0] The output of the function ``func`` is coerced to a float when it is evaluated if the function is something like a lambda or python callable. This takes care of situations like f returning a singleton numpy array, for example. sage: from numpy import array sage: v_phi=array([ 0., 1.57079637, 3.14159274, 4.71238911, 6.28318548]) sage: v_theta=array([ 0., 0.78539819, 1.57079637, 2.35619456, 3.14159274]) sage: m_r=array([[ 0.16763356, 0.25683223, 0.16649297, 0.10594339, 0.55282422], ....: [ 0.16763356, 0.19993708, 0.31403568, 0.47359696, 0.55282422], ....: [ 0.16763356, 0.25683223, 0.16649297, 0.10594339, 0.55282422], ....: [ 0.16763356, 0.19993708, 0.31403568, 0.47359696, 0.55282422], ....: [ 0.16763356, 0.25683223, 0.16649297, 0.10594339, 0.55282422]]) sage: import scipy.interpolate sage: f=scipy.interpolate.RectBivariateSpline(v_phi,v_theta,m_r) sage: spherical_plot3d(f,(0,2*pi),(0,pi)) Graphics3d Object """ from sage.symbolic.expression import is_Expression from sage.rings.real_mpfr import is_RealNumber from sage.rings.integer import is_Integer if params is not None and (is_Expression(func) or is_RealNumber(func) or is_Integer(func)): return self.transform( **{ self.dep_var: func, self.indep_vars[0]: params[0], self.indep_vars[1]: params[1] }) else: # func might be a lambda or a Python callable; this makes it slightly # more complex. import sage.symbolic.ring dep_var_dummy = sage.symbolic.ring.var(self.dep_var) indep_var_dummies = sage.symbolic.ring.var(','.join( self.indep_vars)) transformation = self.transform( **{ self.dep_var: dep_var_dummy, self.indep_vars[0]: indep_var_dummies[0], self.indep_vars[1]: indep_var_dummies[1] }) if params is None: if callable(func): params = _find_arguments_for_callable(func) if params is None: params = ['u', 'v'] else: raise ValueError("function is not callable") def subs_func(t): # We use eval so that the lambda function has the same # variable names as the original function ll = """lambda {x},{y}: t.subs({{ dep_var_dummy: float(func({x}, {y})), indep_var_dummies[0]: float({x}), indep_var_dummies[1]: float({y}) }})""".format(x=params[0], y=params[1]) return eval( ll, dict(t=t, func=func, dep_var_dummy=dep_var_dummy, indep_var_dummies=indep_var_dummies)) return [subs_func(_) for _ in transformation]
def region_plot(f, xrange, yrange, plot_points, incol, outcol, bordercol, borderstyle, borderwidth, alpha, **options): r""" ``region_plot`` takes a boolean function of two variables, `f(x,y)` and plots the region where f is True over the specified ``xrange`` and ``yrange`` as demonstrated below. ``region_plot(f, (xmin, xmax), (ymin, ymax), ...)`` INPUT: - ``f`` -- a boolean function or a list of boolean functions of two variables - ``(xmin, xmax)`` -- 2-tuple, the range of ``x`` values OR 3-tuple ``(x,xmin,xmax)`` - ``(ymin, ymax)`` -- 2-tuple, the range of ``y`` values OR 3-tuple ``(y,ymin,ymax)`` - ``plot_points`` -- integer (default: 100); number of points to plot in each direction of the grid - ``incol`` -- a color (default: ``'blue'``), the color inside the region - ``outcol`` -- a color (default: ``None``), the color of the outside of the region If any of these options are specified, the border will be shown as indicated, otherwise it is only implicit (with color ``incol``) as the border of the inside of the region. - ``bordercol`` -- a color (default: ``None``), the color of the border (``'black'`` if ``borderwidth`` or ``borderstyle`` is specified but not ``bordercol``) - ``borderstyle`` -- string (default: 'solid'), one of ``'solid'``, ``'dashed'``, ``'dotted'``, ``'dashdot'``, respectively ``'-'``, ``'--'``, ``':'``, ``'-.'``. - ``borderwidth`` -- integer (default: None), the width of the border in pixels - ``alpha`` -- (default: 1) How transparent the fill is. A number between 0 and 1. - ``legend_label`` -- the label for this item in the legend - ``base`` - (default: 10) the base of the logarithm if a logarithmic scale is set. This must be greater than 1. The base can be also given as a list or tuple ``(basex, basey)``. ``basex`` sets the base of the logarithm along the horizontal axis and ``basey`` sets the base along the vertical axis. - ``scale`` -- (default: ``"linear"``) string. The scale of the axes. Possible values are ``"linear"``, ``"loglog"``, ``"semilogx"``, ``"semilogy"``. The scale can be also be given as single argument that is a list or tuple ``(scale, base)`` or ``(scale, basex, basey)``. The ``"loglog"`` scale sets both the horizontal and vertical axes to logarithmic scale. The ``"semilogx"`` scale sets the horizontal axis to logarithmic scale. The ``"semilogy"`` scale sets the vertical axis to logarithmic scale. The ``"linear"`` scale is the default value when :class:`~sage.plot.graphics.Graphics` is initialized. EXAMPLES: Here we plot a simple function of two variables:: sage: x,y = var('x,y') sage: region_plot(cos(x^2+y^2) <= 0, (x, -3, 3), (y, -3, 3)) Graphics object consisting of 1 graphics primitive Here we play with the colors:: sage: region_plot(x^2+y^3 < 2, (x, -2, 2), (y, -2, 2), incol='lightblue', bordercol='gray') Graphics object consisting of 2 graphics primitives An even more complicated plot, with dashed borders:: sage: region_plot(sin(x)*sin(y) >= 1/4, (x,-10,10), (y,-10,10), incol='yellow', bordercol='black', borderstyle='dashed', plot_points=250) Graphics object consisting of 2 graphics primitives A disk centered at the origin:: sage: region_plot(x^2+y^2<1, (x,-1,1), (y,-1,1)) Graphics object consisting of 1 graphics primitive A plot with more than one condition (all conditions must be true for the statement to be true):: sage: region_plot([x^2+y^2<1, x<y], (x,-2,2), (y,-2,2)) Graphics object consisting of 1 graphics primitive Since it doesn't look very good, let's increase ``plot_points``:: sage: region_plot([x^2+y^2<1, x<y], (x,-2,2), (y,-2,2), plot_points=400) Graphics object consisting of 1 graphics primitive To get plots where only one condition needs to be true, use a function. Using lambda functions, we definitely need the extra ``plot_points``:: sage: region_plot(lambda x,y: x^2+y^2<1 or x<y, (x,-2,2), (y,-2,2), plot_points=400) Graphics object consisting of 1 graphics primitive The first quadrant of the unit circle:: sage: region_plot([y>0, x>0, x^2+y^2<1], (x,-1.1, 1.1), (y,-1.1, 1.1), plot_points = 400) Graphics object consisting of 1 graphics primitive Here is another plot, with a huge border:: sage: region_plot(x*(x-1)*(x+1)+y^2<0, (x, -3, 2), (y, -3, 3), incol='lightblue', bordercol='gray', borderwidth=10, plot_points=50) Graphics object consisting of 2 graphics primitives If we want to keep only the region where x is positive:: sage: region_plot([x*(x-1)*(x+1)+y^2<0, x>-1], (x, -3, 2), (y, -3, 3), incol='lightblue', plot_points=50) Graphics object consisting of 1 graphics primitive Here we have a cut circle:: sage: region_plot([x^2+y^2<4, x>-1], (x, -2, 2), (y, -2, 2), incol='lightblue', bordercol='gray', plot_points=200) Graphics object consisting of 2 graphics primitives The first variable range corresponds to the horizontal axis and the second variable range corresponds to the vertical axis:: sage: s,t=var('s,t') sage: region_plot(s>0,(t,-2,2),(s,-2,2)) Graphics object consisting of 1 graphics primitive :: sage: region_plot(s>0,(s,-2,2),(t,-2,2)) Graphics object consisting of 1 graphics primitive An example of a region plot in 'loglog' scale:: sage: region_plot(x^2+y^2<100, (x,1,10), (y,1,10), scale='loglog') Graphics object consisting of 1 graphics primitive TESTS: To check that :trac:`16907` is fixed:: sage: x, y = var('x, y') sage: disc1 = region_plot(x^2+y^2 < 1, (x, -1, 1), (y, -1, 1), alpha=0.5) sage: disc2 = region_plot((x-0.7)^2+(y-0.7)^2 < 0.5, (x, -2, 2), (y, -2, 2), incol='red', alpha=0.5) sage: disc1 + disc2 Graphics object consisting of 2 graphics primitives To check that :trac:`18286` is fixed:: sage: x, y = var('x, y') sage: region_plot([x == 0], (x, -1, 1), (y, -1, 1)) Graphics object consisting of 1 graphics primitive sage: region_plot([x^2+y^2==1, x<y], (x, -1, 1), (y, -1, 1)) Graphics object consisting of 1 graphics primitive """ from sage.plot.all import Graphics from sage.plot.misc import setup_for_eval_on_grid from sage.symbolic.expression import is_Expression from warnings import warn import numpy if not isinstance(f, (list, tuple)): f = [f] feqs = [ equify(g) for g in f if is_Expression(g) and g.operator() is operator.eq and not equify(g).is_zero() ] f = [ equify(g) for g in f if not (is_Expression(g) and g.operator() is operator.eq) ] neqs = len(feqs) if neqs > 1: warn( "There are at least 2 equations; If the region is degenerated to points, plotting might show nothing." ) feqs = [sum([fn**2 for fn in feqs])] neqs = 1 if neqs and not bordercol: bordercol = incol if not f: return implicit_plot(feqs[0], xrange, yrange, plot_points=plot_points, fill=False, \ linewidth=borderwidth, linestyle=borderstyle, color=bordercol, **options) f_all, ranges = setup_for_eval_on_grid(feqs + f, [xrange, yrange], plot_points) xrange, yrange = [r[:2] for r in ranges] xy_data_arrays = numpy.asarray( [[[func(x, y) for x in xsrange(*ranges[0], include_endpoint=True)] for y in xsrange(*ranges[1], include_endpoint=True)] for func in f_all[neqs::]], dtype=float) xy_data_array = numpy.abs(xy_data_arrays.prod(axis=0)) # Now we need to set entries to negative iff all # functions were negative at that point. neg_indices = (xy_data_arrays < 0).all(axis=0) xy_data_array[neg_indices] = -xy_data_array[neg_indices] from matplotlib.colors import ListedColormap incol = rgbcolor(incol) if outcol: outcol = rgbcolor(outcol) cmap = ListedColormap([incol, outcol]) cmap.set_over(outcol, alpha=alpha) else: outcol = rgbcolor('white') cmap = ListedColormap([incol, outcol]) cmap.set_over(outcol, alpha=0) cmap.set_under(incol, alpha=alpha) g = Graphics() # Reset aspect_ratio to 'automatic' in case scale is 'semilog[xy]'. # Otherwise matplotlib complains. scale = options.get('scale', None) if isinstance(scale, (list, tuple)): scale = scale[0] if scale == 'semilogy' or scale == 'semilogx': options['aspect_ratio'] = 'automatic' g._set_extra_kwds( Graphics._extract_kwds_for_show(options, ignore=['xmin', 'xmax'])) if neqs == 0: g.add_primitive( ContourPlot( xy_data_array, xrange, yrange, dict(contours=[-1e-20, 0, 1e-20], cmap=cmap, fill=True, **options))) else: mask = numpy.asarray([[elt > 0 for elt in rows] for rows in xy_data_array], dtype=bool) xy_data_array = numpy.asarray([[ f_all[0](x, y) for x in xsrange(*ranges[0], include_endpoint=True) ] for y in xsrange(*ranges[1], include_endpoint=True)], dtype=float) xy_data_array[mask] = None if bordercol or borderstyle or borderwidth: cmap = [rgbcolor(bordercol)] if bordercol else ['black'] linestyles = [borderstyle] if borderstyle else None linewidths = [borderwidth] if borderwidth else None g.add_primitive( ContourPlot( xy_data_array, xrange, yrange, dict(linestyles=linestyles, linewidths=linewidths, contours=[0], cmap=[bordercol], fill=False, **options))) return g
def to_cartesian(self, func, params=None): """ Returns a 3-tuple of functions, parameterized over ``params``, that represents the Cartesian coordinates of the value of ``func``. INPUT: - ``func`` - A function in this coordinate space. Corresponds to the independent variable. - ``params`` - The parameters of func. Corresponds to the dependent variables. EXAMPLE:: sage: from sage.plot.plot3d.plot3d import _ArbitraryCoordinates sage: x, y, z = var('x y z') sage: T = _ArbitraryCoordinates((x + y, x - y, z), z,[x,y]) sage: f(x, y) = 2*x+y sage: T.to_cartesian(f, [x, y]) (x + y, x - y, 2*x + y) sage: [h(1,2) for h in T.to_cartesian(lambda x,y: 2*x+y)] [3.0, -1.0, 4.0] We try to return a function having the same variable names as the function passed in:: sage: from sage.plot.plot3d.plot3d import _ArbitraryCoordinates sage: x, y, z = var('x y z') sage: T = _ArbitraryCoordinates((x + y, x - y, z), z,[x,y]) sage: f(a, b) = 2*a+b sage: T.to_cartesian(f, [a, b]) (a + b, a - b, 2*a + b) sage: t1,t2,t3=T.to_cartesian(lambda a,b: 2*a+b) sage: import inspect sage: inspect.getargspec(t1) ArgSpec(args=['a', 'b'], varargs=None, keywords=None, defaults=None) sage: inspect.getargspec(t2) ArgSpec(args=['a', 'b'], varargs=None, keywords=None, defaults=None) sage: inspect.getargspec(t3) ArgSpec(args=['a', 'b'], varargs=None, keywords=None, defaults=None) sage: def g(a,b): return 2*a+b sage: t1,t2,t3=T.to_cartesian(g) sage: inspect.getargspec(t1) ArgSpec(args=['a', 'b'], varargs=None, keywords=None, defaults=None) sage: t1,t2,t3=T.to_cartesian(2*a+b) sage: inspect.getargspec(t1) ArgSpec(args=['a', 'b'], varargs=None, keywords=None, defaults=None) If we cannot guess the right parameter names, then the parameters are named `u` and `v`:: sage: from sage.plot.plot3d.plot3d import _ArbitraryCoordinates sage: x, y, z = var('x y z') sage: T = _ArbitraryCoordinates((x + y, x - y, z), z,[x,y]) sage: t1,t2,t3=T.to_cartesian(operator.add) sage: inspect.getargspec(t1) ArgSpec(args=['u', 'v'], varargs=None, keywords=None, defaults=None) sage: [h(1,2) for h in T.to_cartesian(operator.mul)] [3.0, -1.0, 2.0] sage: [h(u=1,v=2) for h in T.to_cartesian(operator.mul)] [3.0, -1.0, 2.0] The output of the function `func` is coerced to a float when it is evaluated if the function is something like a lambda or python callable. This takes care of situations like f returning a singleton numpy array, for example. sage: from numpy import array sage: v_phi=array([ 0., 1.57079637, 3.14159274, 4.71238911, 6.28318548]) sage: v_theta=array([ 0., 0.78539819, 1.57079637, 2.35619456, 3.14159274]) sage: m_r=array([[ 0.16763356, 0.25683223, 0.16649297, 0.10594339, 0.55282422], ... [ 0.16763356, 0.19993708, 0.31403568, 0.47359696, 0.55282422], ... [ 0.16763356, 0.25683223, 0.16649297, 0.10594339, 0.55282422], ... [ 0.16763356, 0.19993708, 0.31403568, 0.47359696, 0.55282422], ... [ 0.16763356, 0.25683223, 0.16649297, 0.10594339, 0.55282422]]) sage: import scipy.interpolate sage: f=scipy.interpolate.RectBivariateSpline(v_phi,v_theta,m_r) sage: spherical_plot3d(f,(0,2*pi),(0,pi)) Graphics3d Object """ from sage.symbolic.expression import is_Expression from sage.rings.real_mpfr import is_RealNumber from sage.rings.integer import is_Integer if params is not None and (is_Expression(func) or is_RealNumber(func) or is_Integer(func)): return self.transform(**{ self.dep_var: func, self.indep_vars[0]: params[0], self.indep_vars[1]: params[1] }) else: # func might be a lambda or a Python callable; this makes it slightly # more complex. import sage.symbolic.ring dep_var_dummy = sage.symbolic.ring.var(self.dep_var) indep_var_dummies = sage.symbolic.ring.var(','.join(self.indep_vars)) transformation = self.transform(**{ self.dep_var: dep_var_dummy, self.indep_vars[0]: indep_var_dummies[0], self.indep_vars[1]: indep_var_dummies[1] }) if params is None: if callable(func): params = _find_arguments_for_callable(func) if params is None: params=['u','v'] else: raise ValueError("function is not callable") def subs_func(t): # We use eval so that the lambda function has the same # variable names as the original function ll="""lambda {x},{y}: t.subs({{ dep_var_dummy: float(func({x}, {y})), indep_var_dummies[0]: float({x}), indep_var_dummies[1]: float({y}) }})""".format(x=params[0], y=params[1]) return eval(ll,dict(t=t, func=func, dep_var_dummy=dep_var_dummy, indep_var_dummies=indep_var_dummies)) return [subs_func(_) for _ in transformation]