SymPy package to interface with Python's SymPy library through PyCall.

The basic idea is that a new type – Sym – is made to hold symbolic objects. For this type, the basic functions from SymPy and appropriate functions of Julia are overloaded for Sym objects so that the expressions are treated symbolically and not evaluated immediately. Instances of this type are created by the constructor Sym, the function symbols or the macro @vars.

On loading, a priviledged set of the functions from the sympy module are defined as generic functions with their first argument narrowed to symbolic types. Others may be accessed by qualification, as in sympy.trigsimp. Calling import_from(sympy) will import the rest. SymPy methods are called through Python's dot-call syntax. To find documentation on SymPy functions and methods, one should refer to SymPy's website.

Plotting is provided through Plots recipes. For details, see the help page for sympy_plotting.

The package documentation provides many examples.

Access to SymPy's help system for most functions is available through SymPy.Doc.


IM is a symbolic im


oo is a symbolic infinity. Example: integrate(exp(-x), x, 0, oo).


Variable from pyimport("sympy"). Numerous methods are available through Python's dot-call syntax.


Plotting of symbolic objects.

The Plots package provide a uniform interface to many of Julia's plotting packages. SymPy plugs into Plots' "recipes."

The basic goal is that when Plots provides an interface for function objects, this package extends the interface to symbolic expressions.

In particular:

  • plot(ex::Sym, a, b; kwargs...) will plot a function evaluating ex over [a,b]

Example. Here we use the default backend for Plots to make a plot:

using Plots
@syms x
plot(x^2 - 2x, 0, 4)
  • plot(ex1, ex2, a, b; kwargs...) will plot the two expressions in a parametric plot over the interval [a,b].


@syms x
plot(sin(2x), cos(3x), 0, 4pi) ## also

For a few backends (those that support :path3d) a third symbolic expression may be added to have a 3d parametric plot rendered:

plot(sin(x), cos(x), x, 0, 4pi) # helix in 3d
  • plot(xs, ys, expression) will make a contour plot (for many backends).
@syms x y
plot(range(0,stop=5, length=50), range(0,stop=5, length=50), x*y)
  • To plot the surface z=ex(x,y) over a region we have Plots.surface. For example,
@syms x y
surface(-5:5, -5:5, 25 - x^2 - y^2)
  • a vectorfield plot can (inefficiently but directly) be produced following this example:
function vfieldplot(fx, fy; xlim=(-5,5), ylim=(-5,5), n=8)
    xs = range(xlim[1], stop=xlim[2], length=n)
    ys = range(ylim[1], stop=ylim[2], length=n)

    us = vec([x for x in xs, y in ys])
    vs = vec([y for x in xs, y in ys])
    fxs = vec([fx(x,y) for x in xs, y in ys])
    fys = vec([fy(x,y) for x in xs, y in ys])

    mxs = maximum(abs.(filter(!isinf, filter(!isnan, fxs))))
    mys = maximum(abs.(filter(!isinf, filter(!isnan, fys))))
    d = 1/2 * max((xlim[2]-xlim[1])/mxs, (ylim[2]-ylim[1])/mys) / n

    quiver(us, vs, quiver=(fxs.*d, fys.*d))

fx = (x + y) / sqrt(x^2 + y^2)
fy = (x - y) / sqrt(x^2 + y^2)
vfieldplot(fx, fy)

  • To plot two or more functions at once, the style plot([ex1, ex2], a, b) does not work. Rather, use plot(ex1, a, b); plot!(ex2), as in:
@syms x
plot(sin(x), 0, 2pi)

Some graphics provided by SymPy are available if PyPlot is installed, such as:

  • sympy.plotting.plot3d_parametric_surface
  • sympy.plotting.plot_implicit

Plot the parametrically defined surface [exs[1](u,v), exs[2](u,v), exs[3](u,v)] over [a0,a1] x [b0,b1]. The specification of the variables uses a tuple of the form (Sym, Real, Real) following the style of SymPy in integrate, say, where disambiguation of variable names is needed.

@syms theta, phi
r = 1
sympy.plotting.plot3d_parametric_surface((r*sin(theta)*sin(phi), r*sin(theta)*cos(phi), r*cos(theta)),
                        (theta, 0, pi), (phi, 0, pi/2))
  • sympy.plotting.plot_implicit(equation, (xvar, x0, x1), (yvar, y0, y1)) will plot implicitly the equation.
@syms x y
sympy.plotting.plot_implicit(Eq(x^2+ y^2,3), (x, -2, 2), (y, -2, 2))  # draw a circle

Use to find (partial) derivatives.


@syms x y u()
Dx = Differential(x)
Dx(u(x,y))  # resolves to diff(u(x,y),x)
Dx(u)       # will evaluate diff(u(x), x)

A type and constructor to create symbolic functions. Such objects can be used for specifying differential equations. The macro @syms is also available for constructing SymFunctions (@syms f())


julia> using SymPy

julia> u = SymFunction("u");

julia> @syms v();

Alternatively, we can pass a comma separated string of variable names to create more than one at a time.

julia> F,G,H = SymFunction("F, G, H")
3-element Vector{SymFunction}:

For symbolic functions not wrapped in the SymFunction type, the sympy.Function constructor can be used, as can the symbols function to construct symbolic functions (F=sympy.Function("F", real=true); F = sympy.symbols("F", cls=sympy.Function, real=true)).

julia> @syms u(), v()::real, t
(u, v, t)

julia> sqrt(u(t)^2), sqrt(v(t)^2) # real values have different simplification rules
(sqrt(u(t)^2), Abs(v(t)))

Such functions are undefined functions in SymPy, and can be used symbolically, such as with taking derivatives:

julia> @syms x y u()
(x, y, u)

julia> diff(u(x), x) |> string
"Derivative(u(x), x)"

julia> diff(u(x, y), x) |> string
"Derivative(u(x, y), x)"

Here is one way to find the second derivative of an inverse function to f, utilizing the SymFunction class and the convenience Differential function:

@syms f() f⁻¹() x
D = Differential(x) # ∂(f) is diff(f(x),x)
D² = D∘D
u1 = solve(diff((f⁻¹∘f)(x), x) ~ 1, D(f⁻¹)(f(x)))[1]
u2 = solve(diff((f⁻¹∘f)(x), x,2) ~ 0, D²(f⁻¹)(f(x)))[1]
u2(D(f⁻¹)(f(x)) => u1) # f''/[f']^3

Type to store a SymPy matrix, as created by sympy.ImmutableMatrix.

These have 0-based indexing defined for them to match SymPy

The traditional infix mathmatical operations are defined, but no dot broadcasting.

The convert(Matrix{Sym}, M) call is useful to covert to a Julia matrix


VectorField(fx, fy): create an object that can be plotted as a vector field.

A vectorfield plot draws arrows at grid points proportional to [fx(x_i,y_i), fy(x_i,y_i)] to visualize the field generated by [fx, fy].

The plot command: plot(VectorField(fx, fy), xlims=(-5,5), ylims=(-5,5), n=8) will draw the vectorfield. This uses the default values, so the same graph would be rendered by plot(VectorField(fx,fy)).

To faciliate the visualization of solution to the ODE y' = F(x, y(x)), the call plot(VectorField(F)) will work. (The order is x then y, though often this is written as F(y(x),x).)

SymPy objects can be passed to VectorField, but this is a bit fragile, as they must each have two variables so that they can be called with two variables. (E.g., y(1,2) will be 1 not 2, as might be intended.)


using Plots

fx(x,y) = sin(y); fy(x,y) = cos(y)
plot(VectorField(fx, fy), xlims=(-2pi, 2pi), ylims=(-2pi,2pi))

# plot field of y' = 3y*x over (-5,5) x (-5,5)
F(x,y) = 3*y*x

# plot field and solution u' = u*(1-u)
@syms x u()
F(x,y) = y*(1-y)
out = dsolve(u'(x) - F(x, u(x)), x, (u, 0, 1))
plot(VectorField(F), xlims=(0,5), ylims=(0,2))

Use solve to solve algebraic equations.


julia> using SymPy

julia> @syms x y a b c d
(x, y, a, b, c, d)

julia> solve(x^2 + 2x + 1, x) # [-1]
1-element Vector{Sym}:

julia> solve(x^2 + 2a*x + a^2, x) # [-a]
1-element Vector{Sym}:

julia> solve([a*x + b*y-3, c*x + b*y - 1], [x,y]) # Dict(y => (a - 3*c)/(b*(a - c)),x => 2/(a - c))
Dict{Any, Any} with 2 entries:
  y => (a - 3*c)/(a*b - b*c)
  x => 2/(a - c)

A very nice example using solve is a blog entry on Napolean's theorem by Xing Shi Cai.

N(x::Sym, digits::Int)

N can take a precision argument, whichm when given as an integer greater than 16, we try to match the digits of accuracy using BigFloat precision on conversions to floating point.


Convert a Sym value to a numeric Julian value.

In SymPy, N(ex, options...) is identifcal to ex.evalf(options...) and is used to convert expressions into floating-point approximations. A positional precision argument indicates the number of digits, keyword arguments chop can be used to trim floating point roundoff errors and subs for free variable substitution prior to conversions.

For example, symbolic roots can be computed numerically, even if not available symbolically, by calling N on the values.

Using SymPy within Julia makes having two such functions useful:

  • one to do the equivalent of SymPy's evalf call
  • one to convert these expressions back into Julia objects (like convert(T, ex))

We use N to return a Julia object and evalf to return a symbolic object. The type of Julia object is heurisitically identified.


julia> using SymPy

julia> x = Sym("x")

julia> p = subs(x, x, pi)

julia> N(p)                            # float version of pi
π = 3.1415926535897...

julia> p.evalf(60)                     # 60 digits of pi, as a symbolic value

julia> N(p, 60)                        # when a precision is given, "Big" values are returned

julia> r = subs(x,x,1.2)

julia> N(r)                            # float

julia> q = subs(x, x, 1//2)

julia> N(q)                            # 1//2

julia> z = solve(x^2 + 1)[1]           # -ⅈ

julia> N(z)                            # 0 - 1im
0 - 1im

julia> z.evalf()

julia> rts = solve(x^5 - x + 1)
5-element Vector{Sym}:
 CRootOf(x^5 - x + 1, 0)
 CRootOf(x^5 - x + 1, 1)
 CRootOf(x^5 - x + 1, 2)
 CRootOf(x^5 - x + 1, 3)
 CRootOf(x^5 - x + 1, 4)

julia> [r.evalf() for r in rts]          # numeric solutions to quintic
5-element Vector{Sym}:
 -0.181232444469875 - 1.08395410131771⋅ⅈ
 -0.181232444469875 + 1.08395410131771⋅ⅈ
 0.764884433600585 - 0.352471546031726⋅ⅈ
 0.764884433600585 + 0.352471546031726⋅ⅈ

julia> [N(r) for r in rts]
5-element Vector{Number}:
 -0.18123244446987538 - 1.0839541013177107im
 -0.18123244446987538 + 1.0839541013177107im
   0.7648844336005847 - 0.35247154603172626im
   0.7648844336005847 + 0.35247154603172626im

N returns the value unchanged when it has free symbols.


This module mostly implements SymPy's Permutation module.

A permuation can be represented in different ways. Here a permutation is a reaarangment of the values 0, 1, ..., n. For example, the mapping 0->2, 1->3, 2->0, 3->1 can be presented by a vector: sigma = [2,3,0,1] where sigma[i] = j when i -> j. Otheriwse, it can be presented as a product of cycles: (0 2)(1 3) which reads 0 goes to 2 which goes to 0 (wrapping) and 1 goes to 3 and 3 goes to 1.

Either representation can be passed through the Permutation constructor.

For the vector notation – 0-based – it is passed directly to the constructor:

julia> using SymPy

julia> p = Permutation([2,3,0,1])
(0 2)(1 3)

If a range describes a permutation, it can be used as well:

julia> id = Permutation(10:-1:0)
(0 10)(1 9)(2 8)(3 7)(4 6)

Cycle notation can more compactly describe a permuation, it can be passed in as a container of cycles specified through tuples or vectors:

julia>  p = Permutation([(0,2), (1,3)])
(0 2)(1 3)

The latter can be be expresed more quickly as

julia> p = Permutation(0,2)(1,3)
(0 2)(1 3)

This works as a single cycle can be passed to the Permutation constructor with values separated by commas and the "call" method for Permuation objects is overloaded: for a single argument, the mapping i -> j is created (also the notation i^p returns this) but if more than one argument is given, a cycle is created and multiplied on the right by p, so that the above becomes (0,2) * (1,3).

Here are two permutations forming the symmetries of square, naturally represented in the two ways:

julia> flip = Permutation([[0,1],[2,3]])  # or Permutation(0,1)(2,3)
(0 1)(2 3)

julia> rotate = Permutation([1,2,3,0])    # or Permutation(0,1,2,3) in cycle notation
(0 1 2 3)

Operations on permutations include:

  • a function call, p(i) to recover j where i -> j, also i^p.
  • * for multiplication. The convention is (p*q)(i) = q(p(i)) or with the ^ notation: i^(p*q) = (i^p)^q.
  • + for multiplication when p and q commute, where a check on commuting is performed.
  • inv for the inverse permutation.
  • /, where p/q is p * inv(q).
  • p^n for powers. We have inv(p) = p^(-1) and p^order(p) is the identity.
  • p^q for conjugate, defined by inv(q) * p * q.

We can see that a flip is an involution through:

julia> flip^2  # the identity

whereas a rotation is not (as it has order 4)

julia> rotate * rotate
(0 2)(1 3)

julia> rotate.order()

These two operations do not commute:

julia> flip * rotate
(0 2)
julia> rotate * flip  # (1 3)
(1 3)

We can see this is the correct mapping 1 -> 3 with

julia> (1^rotate)^flip, 1^(rotate*flip), flip(rotate(1))
(3, 3, 3)

We can check that flip and rotate^2 do commute:

julia> id = Permutation(3)   # (n) is the identify

julia> flip.commutator(rotate^2) == id

The conjugate for flip and rotate does the inverse of the flip, then rotates, then flips:

julia> rotate^flip
(0 3 2 1)

This is different than flip^rotate. As flip commutes with rotate^2 this will return rotate^2:

julia> (rotate^2)^flip
(0 2)(1 3)

There is no support currently for the Cycle class


Create Permutation group from group generators

A PermutationGroup is one generated by a collection of permutations.

Some pre-defined groups are built-in:

  • SymmetricgGroup(n): S_n or all symmetries of an n-gon
  • CyclicGroup: the group Z_n
  • DihedralGroup: Group formed by a flip and rotation
  • AlternativeGroup: Subgroup of S_n of even elements
  • AbelianGroup: Returns the direct product of cyclic groups with the given orders.


  • use collect(generate(G)) in place of list(G.generate())

create a "wild card" for pattern matching


Returns true, false, or nothing; ask


julia> using SymPy

julia> @vars x y integer=true
(x, y)

julia> ask(𝑄.integer(x*y), And(𝑄.integer(x), 𝑄.integer(y)))

julia> ## really slow isprime:
       filter(x -> ask(𝑄.prime(x)), 1:10)
4-element Vector{Int64}:

doit evaluates objects that are not evaluated by default.


julia> using SymPy

julia> @syms x f()
(x, f)

julia> D = Differential(x)

julia> df = D(f(x))

julia> dfx = subs(df, (f(x), x^2))
d ⎛ 2⎞
──⎝x ⎠

julia> doit(dfx)

Set deep=true to apply doit recursively to force evaluation of nested expressions:

julia> @syms g()

julia> dgfx = g(dfx)
 ⎛d ⎛ 2⎞⎞
g⎜──⎝x ⎠⎟
 ⎝dx    ⎠

julia> doit(dgfx)
 ⎛d ⎛ 2⎞⎞
g⎜──⎝x ⎠⎟
 ⎝dx    ⎠

julia> doit(dgfx, deep=true)

There is also a curried form of doit:

julia> dfx |> doit

julia> dgfx |> doit(deep=true)

dsolve(eqn, var, args..,; ics=nothing, kwargs...)

Call sympy.dsolve.

The initial conditions are specified with a dictionary.


julia> using SymPy

julia> @syms α, x, f(), g()
(α, x, f, g)

julia> ∂ = Differential(x)

julia> eqn = ∂(f(x)) ~ α * x
──(f(x)) = x⋅α
julia> dsolve(eqn)
            x ⋅α
f(x) = C₁ + ────
julia> dsolve(eqn(α=>2); ics=Dict(f(0)=>1)) |> print # fill in parameter, initial condition
Eq(f(x), x^2 + 1)

julia> eqn = ∂(∂(f(x))) ~ -f(x); print(eqn)
Eq(Derivative(f(x), (x, 2)), -f(x))

julia> dsolve(eqn)
f(x) = C₁⋅sin(x) + C₂⋅cos(x)

julia> dsolve(eqn; ics = Dict(f(0)=>1, ∂(f)(0) => -1))
f(x) = -sin(x) + cos(x)

julia> eqn = ∂(∂(f(x))) - f(x) - exp(x);

julia> dsolve(eqn, ics=Dict(f(0) => 1, f(1) => Sym(1//2))) |> print # not just 1//2
Eq(f(x), (x/2 + (-exp(2) - 2 + E)/(-2 + 2*exp(2)))*exp(x) + (-E + 3*exp(2))*exp(-x)/(-2 + 2*exp(2)))


julia> @syms x() y() t g
(x, y, t, g)

julia> ∂ = Differential(t)

julia> eqns = [∂(x(t)) ~ y(t), ∂(y(t)) ~ x(t)]
2-element Vector{Sym}:
 Eq(Derivative(x(t), t), y(t))
 Eq(Derivative(y(t), t), x(t))

julia> dsolve(eqns)
2-element Vector{Sym}:
 Eq(x(t), -C1*exp(-t) + C2*exp(t))
  Eq(y(t), C1*exp(-t) + C2*exp(t))

julia> dsolve(eqns, ics = Dict(x(0) => 1, y(0) => 2))
2-element Vector{Sym}:
 Eq(x(t), 3*exp(t)/2 - exp(-t)/2)
 Eq(y(t), 3*exp(t)/2 + exp(-t)/2)

julia> eqns = [∂(∂(x(t))) ~ 0, ∂(∂(y(t))) ~ -g]
2-element Vector{Sym}:
  Eq(Derivative(x(t), (t, 2)), 0)
 Eq(Derivative(y(t), (t, 2)), -g)

julia> dsolve(eqns)  # can't solve for initial conditions though! (NotAlgebraic)
2-element Vector{Sym}:
           x(t) = C₁ + C₂⋅t
 Eq(y(t), C3 + C4*t - g*t^2/2)

julia> @syms t x() y()
(t, x, y)

julia> eq = (∂(x)(t) ~ x(t)*y(t)*sin(t), ∂(y)(t) ~ y(t)^2 * sin(t))
(Eq(Derivative(x(t), t), x(t)*y(t)*sin(t)), Eq(Derivative(y(t), t), y(t)^2*sin(t)))
julia> dsolve(eq)  # returns a set to be `collect`ed:
PyObject {Eq(x(t), -exp(C1)/(C2*exp(C1) - cos(t))), Eq(y(t), -1/(C1 - cos(t)))}
julia> dsolve(eq) |> collect
2-element Vector{Any}:
 Eq(x(t), -exp(C1)/(C2*exp(C1) - cos(t)))
               Eq(y(t), -1/(C1 - cos(t)))

return elements of a set s as an array, unlike convert(Set,s)


Return vector of free symbols of expression or vector of expressions. The results are orderded by sortperm(string.(fs)).


julia> using SymPy

julia> @syms x y z a
(x, y, z, a)

julia> free_symbols(2*x + a*y) # [a, x, y]
3-element Vector{Sym}:
import_from(module, meths; kwargs...)

Import methods from a python module. Implements functionality of from module import function in Python.

  • module: a python module, such as sympy
  • meths: nothing or a tuple of symbols to import. If nothing, then all member functions of the module are imported (but not constructors or other objects)
  • Ms: additional Julia Modules to import from. By default, a few base modules are searched for to avoid namespace collisions.
  • typ: a symbol indicating variable type for first argument that the new function should be restricted to. For most, the default, :SymbolicObject will be appropriate
  • exclude: when importing all (meths=nothing), this can be used to avoid importing some methods by name. The default has a few to avoid.


import_from(sympy)  # bring in functions from sympy (done `import_sympy`)
import_from(sympy, (:sin, :cos))  # just bring in a few methods
import_from(sympy , (:Wild,), typ=:Any) # Allows `Wild("x")`
import PyCall
PyCall.pyimport_conda("sympy.physics.wigner", "sympy")
lambdify(ex, vars=free_symbols(); 
         fns=Dict(), values=Dict, use_julia_code=false, 

Take a symbolic expression and return a Julia function or expression to build a function.

  • ex::Sym a symbolic expression with 0, 1, or more free symbols

  • vars a container of symbols to use for the function arguments. The default is free_symbols which has a specific ordering. Specifying vars allows this default ordering of arguments to be customized.

  • fns::Dict, vals::Dict: Dictionaries that allow customization of the function that walks the expression ex and creates the corresponding AST for a Julia expression. See SymPy.fn_map and SymPy.val_map for the default mappings of sympy functions and values into Julia's AST.

  • use_julia_code::Bool: use SymPy's conversion to an expression, the default is false

  • invoke_latest=true: if true will call eval and Base.invokelatest to return a function that should not have any world age issue. If false will return a Julia expression that can be evaled to produce a function.


julia> using SymPy

julia> @syms x y z
(x, y, z)

julia> ex = x^2 * sin(x)
x ⋅sin(x)

julia> fn = lambdify(ex);

julia> fn(pi)

julia> ex = x + 2y + 3z
x + 2⋅y + 3⋅z

julia> fn = lambdify(ex);

julia> fn(1,2,3) # order is by free_symbols

julia> ex(x=>1, y=>2, z=>3)

julia> fn = lambdify(ex, (y,x,z));

julia> fn(1,2,3)

!!! Note:

The default produces slower functions due to the calls to eval and Base.invokelatest. In the following g2 (which, as seen, requires additional work to compute) is as fast as calling f (on non symbolic types), whereas g1 is an order of magnitude slower in this example.

julia> @vars x

julia> f(x) = exp(cot(x))
f (generic function with 1 method)

julia> g1 = lambdify(f(x))
#88 (generic function with 1 method)

julia> ex = lambdify(f(x), invoke_latest=false)
:(function var"##271"(x)

julia> @eval g2(x) = ($ex)(x)
g2 (generic function with 1 method)

An alternative, say, is to use GeneralizedGenerated's mk_function, as follows:

julia> using GeneralizedGenerated

julia> body = convert(Expr, f(x))

julia> g3 = mk_function((:x,), (), body)
function = (x;) -> begin

This function will be about 2-3 times slower than f.


Note: if passing variables in use a tuple (e.g., (x,y)) and not a vector (e.g., [x,y]).


Plot an implicit equation

@syms x y
plot_implicit(Eq(x^2+ y^2,3), (x, -2, 2), (y, -2, 2))

Render a parametrically defined surface plot.


@syms u, v
plot_parametric_surface((u*v,u-v,u+v), (u,0,1), (v,0,1))

This uses PyPlot, not Plots for now.


SymPy has dozens of functions to perform various kinds of simplification. There is also one general function called simplify that attempts to apply all of these functions in an intelligent way to arrive at the simplest form of an expression. (See Simplification for details on simplify and other related functionality).

For non-symbolic expressions, simplify returns its first argument.


subs is used to subsitute a value in an expression with another value. Examples:

julia> using SymPy

julia> x,y = symbols("x,y")
(x, y)

julia> ex = (x-y)*(x+2y)
(x - y)⋅(x + 2⋅y)

julia> subs(ex, (y, y^2))
⎛     2⎞ ⎛       2⎞
⎝x - y ⎠⋅⎝x + 2⋅y ⎠

julia> subs(ex, (x,1), (y,2))

julia> subs(ex, (x,y^3), (y,2))

julia> subs(ex, y, 3)
(x - 3)⋅(x + 6)

There is a curried form of subs to use with the chaining |> operator

julia> ex |> subs(x,ℯ)
(ℯ - y)⋅(2⋅y + ℯ)

The use of pairs gives a convenient alternative:

julia> subs(ex, x=>1, y=>2)

julia> ex |> subs(x=>1, y=>2)
symbols(name(s), assumptions...)

Calls sympy.symbols to produce symbolic variables and symbolic functions. An alternate to the recommended @syms, (when applicable)

In sympy sympy.symbols and sympy.Symbol both allow the construction of symbolic variables and functions. The Julia function symbols is an alias for sympy.symbols.

  • Variables are created through x=symbols("x");
  • Assumptions on variables by x=symbols("x", real=true);
  • Multiple symbols x1,x2 = symbols("x[1:3]") can be created. Unlike @syms, the number of variables can be specified with a variable through interpolation.
  • Symbolic functions can be created py passing cls=sympy.Function, symbols("F", cls=sympy.Function, real=true)

Thanks to @alhirzel for the contribution.

!!! Note: The @symfuns macro will be deprecated. The more general @syms macro should be used for constructing symbolic functions of type SymFunction and symbols can be used to construct symbolic functions in general.

@syms a n::integer x::(real,positive)=>"x₀" y[-1:1] u() v()::real w()::(real,positive) y()[1:3]::real

Construct symbolic variables or functions along with specified assumptions. Similar to @vars, sympy.symbols, and sympy.Function, but the specification of the assumptions is more immediate than those interfaces which follow sympy's constructors.

Allows the specification of assumptions on the variables and functions.

  • a type-like annontation, such as n::integer is equivalent to sympy.symbols("n", integer=true). Multiple assumptions are combined using parentheses (e.g., n::(integer,nonnegative).

The possible values for assumptions are: "commutative", "complex", "imaginary", "real", "integer", "odd", "even", "prime", "composite", "zero", "nonzero", "rational", "algebraic", "transcendental", "irrational", "finite", "infinite", "negative", "nonnegative", "positive", "nonpositive", "hermitian", "antihermetian".

  • a tensor declaration form is provided to define arrays of variables, e.g. x[-1:1] or y[1:4, 2:5].

  • a symbolic function can be specified using a pair of parentheses after the name, as in u().

  • The return type of a function can have assumptions specified, as with a variable. E.g., h()::complex. How the symbolic function prints can be set as with a variable, e.g. h()::complex=>"h̄".

  • multiple definitions can be separated by commas

  • How the symbol prints (the __str__() value) can be specified using the syntax =>"name", as in x=>"xₒ"


julia> using SymPy

julia> @syms a b::nonnegative
(a, b)

julia> sqrt(a^2), sqrt(b^2)
(sqrt(a^2), b)
julia> @syms x::prime

julia> ask(𝑄.negative(x)), ask(𝑄.integer(x)), ask(𝑄.even(x))  # (false, true, nothing)
(false, true, nothing)
julia> @syms a[0:5], x
(Sym[a₀, a₁, a₂, a₃, a₄, a₅], x)

julia> sum( aᵢ*x^(i) for (i,aᵢ) ∈ zip(0:5, a)) |> print
a₀ + a₁*x + a₂*x^2 + a₃*x^3 + a₄*x^4 + a₅*x^5
julia> @syms x u() v()::nonnegative
(x, u, v)

julia> sqrt(u(x)^2), sqrt(v(x)^2) # sqrt(u(x)^2), Abs(v(x))
(sqrt(u(x)^2), Abs(v(x)))

!!! Note: Many thanks to @matthieubulte for this contribution.

@vars x y z

Define symbolic values, possibly with names and assumptions


@vars x y
@vars a1=>"α₁"
@vars a b real=true

!!! Note: The @syms macro is recommended as it has a more flexible syntax

SymPy.Doc(f::Symbol, [module=sympy])

Return docstring of f found within the specified module.


SymPy.Doc(:det, sympy.matrices)
## add module to query
SymPy.pyimport_conda("sympy.crypto.crypto", "sympy")
SymPy.Doc(:padded_key, sympy.crypto)
lhs ~ rhs

Specify an equation.

Alternative syntax to Eq(lhs, rhs) or lhs ⩵ rhs (\Equal[tab]) following Symbolics.jl.


Some SymPy Python objects have index notation provided for them through __getitem__. This allows Julia's getindex to dispatch to Python's __getitem__. The index (indices) must be symbolic. This will use 0-based indexing, as it is a simple pass through to Python.


julia> using SymPy

julia> i,j = sympy.symbols("i j", integer=True)
(i, j)

julia> x = sympy.IndexedBase("x")

julia> a = sympy.Sum(x[i], (i, 1, j))
  ╱   x[i]
i = 1

Define getindex for SymPy's ImmutableMatrix class.

SymMatrix is 0-based, like python, not Julia. Use Matrix{Sym} for that.

match(pattern, expression, ...)

Match a pattern against an expression; returns a dictionary of matches.

If a match is unsuccesful, returns an empty dictionary. (SymPy returns "nothing")

The order of the arguments follows Julia's match function, not sympy.match, which can be used directly, otherwise.


occursin(x, G::SymPermutationGroup)

Does G contain x. (In SymPy, this is `contains.)

replace(expression, pattern, value, ...)

From: SymPy Docs

Traverses an expression tree and performs replacement of matching subexpressions from the bottom to the top of the tree. The default approach is to do the replacement in a simultaneous fashion so changes made are targeted only once. If this is not desired or causes problems, simultaneous can be set to false. In addition, if an expression containing more than one Wild symbol is being used to match subexpressions and the exact flag is true, then the match will only succeed if non-zero values are received for each Wild that appears in the match pattern.

Differences from SymPy:

  • "types" are specified via calling func on the head of an expression: func(sin(x)) -> sin, or directly through sympy.sin

  • functions are supported, but only with PyCall commands.

Examples (from the SymPy docs)

julia> using SymPy

julia> x, y, z = symbols("x, y, z")
(x, y, z)

julia> f = log(sin(x)) + tan(sin(x^2)); string(f) # `string(f)` only so doctest can run
"log(sin(x)) + tan(sin(x^2))"

"type" -> "type"

Types are specified through func:

julia> func = SymPy.Introspection.func
func (generic function with 1 method)

julia> replace(f, func(sin(x)), func(cos(x))) |> string  # type -> type
"log(cos(x)) + tan(cos(x^2))"

julia> replace(f, sympy.sin, sympy.cos)  |>  string
"log(cos(x)) + tan(cos(x^2))"

julia> sin(x).replace(sympy.sin, sympy.cos, map=true)
(cos(x), Dict{Any, Any}(sin(x) => cos(x)))

The func function finds the head of an expression (sin and cos above). This could also have been written (perhaps more directly) as:

julia> replace(f, sympy.sin, sympy.cos) |> string
"log(cos(x)) + tan(cos(x^2))"

"type" -> "function"

To replace with a more complicated function, requires some assistance from Python, as an anonymous function must be defined witin Python, not Julia:

julia> import PyCall

julia> ## Anonymous function a -> sin(2a)"""
       from sympy import sin, Mul
       def anonfn(*args):
           return sin(2*Mul(*args))

julia> replace(f, sympy.sin,"anonfn")
                   ⎛   ⎛   2⎞⎞
log(sin(2⋅x)) + tan⎝sin⎝2⋅x ⎠⎠

"pattern" -> "expression"

Using "Wild" variables allows a pattern to be replaced by an expression:

julia> a, b = Wild("a"), Wild("b")
(a_, b_)

julia> replace(f, sin(a), tan(2a)) |> string
"log(tan(2*x)) + tan(tan(2*x^2))"

julia> replace(f, sin(a), tan(a/2)) |> string
"log(tan(x/2)) + tan(tan(x^2/2))"

julia> f.replace(sin(a), a) |> string
"log(x) + tan(x^2)"

julia> (x*y).replace(a*x, a)

In the SymPy docs we have:

Matching is exact by default when more than one Wild symbol is used: matching fails unless the match gives non-zero values for all Wild symbols."

julia> replace(2x + y, a*x+b, b-a)  # y - 2
y - 2

julia> replace(2x + y, a*x+b, b-a, exact=false)  # y + 2/x
y + ─

"pattern" -> "func"

The function is redefined, as a fixed argument is passed:

       from sympy import sin
       def anonfn(a):
           return sin(2*a)

julia> replace(f, sin(a),"anonfn")
                   ⎛   ⎛   2⎞⎞
log(sin(2⋅x)) + tan⎝sin⎝2⋅x ⎠⎠

"func" -> "func"

       def fn1(expr):
           return expr.is_Number

       def fn2(expr):
           return expr**2

julia> replace(2*sin(x^3),"fn1","fn2")
     ⎛ 9⎞
4⋅sin⎝x ⎠
       def fn1(x):
           return x.is_Mul

       def fn2(x):
           return 2*x

julia> replace(x*(x*y + 1),"fn1","fn2")
2⋅x⋅(2⋅x⋅y + 1)

This method imports all functions from mpmath and a priviledged set of functions from sympy, as well as the relational operators.

These functions are narrowed on their first argument being of type SymbolicObject.

A few modules are checked for namespace collisions.

If a function naturally takes an non-Symbolic argument as a first argument, then it can be qualified: e.g. sympy.sin(2) (as opposed to sin(Sym(2))).

If a constructor is needed (which is not a function) then it must be qualified. (E.g. sympy.Function("F"), though for this particular case, there is SymFunction defined for convenience.)