Load Symata like this
Symata is a computer language for symbolic mathematics written in Julia. After typing
using Symata, you are in Symata mode and input is interpreted as Symata language expressions.
Here is a Symata expression
In a Jupyter notebook, you can exit Symata mode and enter Julia mode by entering
Julia(). Expressions are then Julia language expressions.
length(zeros(10)) == 10 # A Julia expression
In Jupyter, type
isymata() to leave Julia mode and enter
Symata mode. At the command line REPL, type
= at the beginning of a line to enter
isymata() # we enter Symata mode again
Note: to leave
Symata mode and return to
Julia mode, type
IJulia, or backspace at the command line REPL.
Now we can enter
expr = Cos(π * x)
A variable is set like this.
x = 1/3
Now the cosine is evaluated
x = 1/6
Clear the value of
x and the cosine can no longer be reduced.
It is clear what happened above. If
x is not set to a value, then
Cos(π * x) can't be written in a simpler form. If we set
x to some particular values, then
Cos(π * x) can be reduced to a simpler form.
(you can skip the following the first time through)
But, the reason
Symata understands this is a consquence of the procedure it follows in evaluating (almost) all expressions.
Symata evaluates expressions to a fixed point. More precisely, when an expression is given as input,
Symata descends depth-first evaluating each subexpression to a fixed point and finally the top-level expression to a fixed point. When
Cos(π * x) is first evaluated, each of
x evaluates to itself so that
π * x is already at a fixed point. Since there is no rule for evaluating
Cos(π * x) for fixed
π * x,
Cos(π * x) is also at a fixed point.
x=1/3 means that, whenever
x is encountered, it should evaluate to
1/3. The expression
Out(4) evaluates to the fourth output cell, which is
Cos(π * x). Then
π evaluates to iteself,
x evaluates to
1, so that
π * x evaluates to
π/3. There is a rule saying that
Cos(π/3) evaluates to
Clear(x) says that
x should once again evaluate to itself. Then evaluating
Out(4) follows the same evaluation sequence, leading to
Cos(π * x)
There are several kinds of assignment in Symata. The two most common are
Set immediatley evaluates the right hand side and binds the left hand side to the result.
SetDelayed does not evaluate the right hand side when the assignment is made. It evaluates the right hand side every time the left hand side is subsequently evalutated and then binds the result.
The following demonstrates the difference.
x = 1 a := x b = x c = a d := a
(a = z, [x,a,b,c,d])
Assign two variables at once
[a,b] = [x,y]
Swap two values
[a,b] = [b,a]
Expression is the central concept in Symata. In general, expressions are trees whose branches and leaves are other expressions. You can manipulate these expressions.
Every expression has a
Head. For function-like expressions, the
Head is the function name. For atomic expressions, the
Head usually is a data type.
Map(Head, [x, x + y, [x,y], Cos(x), f(x), 3, 3.0, BI(3), BF(3)])
expr = Expand((x+y)^3)
FullForm(expr) # This shows the internal form. The tree is explicit
expr[2,2,1] # Return a part of the expression by index into the tree
expr[2,2,1] = z; # Replace a part of the expression
Part(expr,2,2,1) # You can do the same thing with Part
Expand((x+y)^3)[4,1] # You can get parts of expressions directly
expr = Expand((x+y)^20); # The semi-colon suppresses printing the return value.
expr[14:18:2] # Parts 14 through 18 with step 2
Define a function that collects an expression's head and arguments in a list.
headargs(f_(args__)) := [f,args]
headargs(a + b^2 + 3)
Rotate the head and arguments to make a new expression.
rotheadargs(f_(args__)) := (Last([args])(f,Splat(Most([args]))))
rotheadargs( a + b + c + d)
rotheadargs( a + b + c + d + g(x))
ClearAll(x,a,b,c,d) # delete definitions from the previous example
a = 1
a := x Definition(a) # This overwrites the previous definition
f(x_) := x^2 f(x_, y_) := x + y Definition(f)
f(x_):=x^2 f(x_,y_):=(x + y)
f(x_):=x^2 f(x_,y_):=(x + y)
Timing((Range(10^6), Null )) # time a single expression
Time toggles the timing of every expression entered. Memory allocated and the number of attempts to apply a user defined rule are also printed.
Time(True) # Enable timing all evaluation. Returns the previous value
0.007442 seconds (999.54 k allocations: 22.885 MiB) tryrule count: downvalue 0, upvalue 0
Time(False); # disable timing all evaluation.
0.000038 seconds (25 allocations: 2.422 KiB) tryrule count: downvalue 0, upvalue 0
Trace(True); # Trace evaluation
2<< False 1<< False
>>1 CompoundExpression((a + b)*(a + b)) >>2 (a + b)*(a + b) >>3 a + b 3<< a + b >>3 a + b 3<< a + b 2<< (a + b)^(1 + 1) >>2 (a + b)^(1 + 1) >>3 1 + 1 3<< 2 2<< (a + b)^2 >>2 (a + b)^2 2<< (a + b)^2 1<< (a + b)^2 >>1 (a + b)^2 1<< (a + b)^2
>>1 CompoundExpression(Trace(False)) >>2 Trace(False)
gives the number of indivisible (
Part can't be taken) elements in
This amounts to counting all the
Heads and all of the arguments that are not of type
Mxpr, that is compound expressions.
A more accurate name is
Attributes(LeafCount) = [Protected]
gives the maximum number of indices required to specify any part of
Attributes(Depth) = [Protected]
FullForm(Expand((a+b)^3)) # Examine the tree
Expand((a+b)^3)[2,2,1] # One of the deepest parts
Integrate( (1+x^2)^(-1), x)
expr = 1/(1+x^2) Integrate(expr, x)
f(x_) := 1/(1+x^2) Integrate(expr, x)
g(x_) = expr # Note we do not use ":="
ClearAll(expr) # We did not use SetDelay, so we can delete expr
Note: Trying to use a compiled (Julia) function
h = J( x -> 1/(1+x^2)) will not work.
ClearAll(f,g,expr) # Integrate(f(y), y) # The integral can no longer be reduced
MatchQ(z,_) # Blank matchs z
Map(MatchQ(_), [1,"string", a+b, 1/3]) # MatchQ does Currying with the first argument
_head is a
Blank that only matches expressions with
Head equal to
FullForm(_Integer) # underscore is shorthand for Blank
Use Currying to define a predicate function
myintq = MatchQ(_Integer);
Not all rational numbers are integers
MatchQ(b^2, _^2) # Match power with exponent equal to 2
MatchQ(b^3, _^_) # Match any power
This failed because
b^1 evaluates to
b, which does not have the structure of a power
The pattern can be complex with blanks deep in an expression.
Map(MatchQ(f(x_^2)), [f(b^2), f(b^3), g(b^2)])
Specify a "function"
Head that must match
Map(MatchQ(_gg), [gg(x+y), gg(x), g(x)])
Define a predicate for positive integers
m = MatchQ(_Integer`Positive`) Map(m, [1,100, 0, -1, 1.0, x])
We can also put a
Condition on a
Pattern. This matches pairs with the first element smaller than the second.
m = MatchQ(Condition([x_, y_], x < y)) Map(m, [[2, 1], [1, 2], [1,2,3], 1])
Patterns can include
m = MatchQ(_Integer | _String) Map(m, [1, "zebra", 1.0])
Repeated(expr) matches one or more occurences of
RepeatedNull matches zero or more occurences.
Rules are used for many things in Symata, including replacement. Replacement is a key ingredient in the implementation of functions.
When applied, this
Rule matches and does a replacement on any expression with
f and a
List of two elements as the sole argument.
f([x_, y_]) => p(x + y)
expr = f([x + y, y]) + f(c) + g([a, b]);
ReplaceAll(expr, f([x_, y_]) => p(x + y))
There are several things to note here.
x_ puts no restrictions on the match; any expression will match. The name of the pattern
x only serves to identify it later during a replacement.
x_ has matched
x+y, but these two uses of
x are not confused in the result. That is, in
x_, the symbol
x is a dummy variable.
f(c) has a matching
Head, but not matching arguments, so
f(c) fails to match. Likewise, the expression
g([a,b]) has matching arguments, but not matching head.
f([x+y,y]) matches, and the replacement is made in (a copy of)
expr. But, Symata alays evaluates expressions to a fixed point. So
y+y is replaced by
2y, and the terms in
expr are rearranged into the canonical order.
Again, be aware that matching is structural.
ReplaceAll([a/b, 1/b^2, 2/b^2] , b^n_ => d(n))
Named patterns that appear in more than one place must match the same expression.
ReplaceAll([b, a, [a, b]], [x_, y_, [x_, y_]] => 1 ) # This does not match
ReplaceAll([a, b, [a, b]] , [x_, y_, [x_, y_]] => 1 ) # This does match
This example uses
ReplaceAll( [a, b, c, d, a, b, b, b], a | b => x)
The arguments of
Sequence are spliced into expressions during evaluation.
An unmatched alternative is replaced by
Sequence(). Upon evaluation to fixed point, this empty sequence is removed.
f(x_, x_ | y_String) := [x,y]
f(2,2) # `y` does not match, so it is removed.
f(2,"cat") # Here the second Alternative matches
f(2,3) # Here the Pattern fails to match.
Patterns in general, can be explicit expressions, with no
h(a | b) := p [h(a), h(b), h(c), h(d)]
ReplaceAll replaces all matching subexpressions. We can also specify the levels. This matches at level 2 and deeper.
Replace(1 + a + f(a) + g(f(a)), a => b, 2)
This replaces only at level 2.
Replace(1 + a + f(a) + g(f(a)), a => b, ) == 1 + a + f(b) + g(f(a))
Rule evaluates the right hand side once, when it is first evaluated.
ReplaceAll([x, x, x, x, x], x => RandomReal() )
RuleDelayed evaluates the right hand side every time it is applie
ReplaceAll([x, x, x, x, x], RuleDelayed(x ,RandomReal()))
Except matches everything except expressions that match its argument. The following applies the replacement at level 1.
# FIXME: This example is broken. Replace([1, 7, "Hi", 3, Indeterminate], Except(_`Numeric`) => 0, 1)
Rule in a
Rules is tried in turn. Matching stops after the first match.
ReplaceRepeated continues applying
Rules until the expression reaches a fixed point.
ReplaceRepeated(x^2 + y^6 , List(x => 2 + a, a => 3))
Up to this point, we have used named patterns only with a single blank, for example
b_. But, we may associate a name with any pattern expression, including a complex (compound) expression.
ReplaceAll( b^c, a::(_^_) => g(a))
Patterns are used to implement optional arguments.
f(x_, y_:3) := x + y [f(a+b,z), f(a+b)]
Condition may be used in definitions like this:
ClearAll(f) f(x_) := Condition(x^2, x > 3)
We can match and replace with a
Head equal to
ReplaceAll(z * y + b , x_ + y_ => x * y)
ReplaceAll(z * y + b + c, x_ + y_ => x * y)
This failed because
Plus with two terms does not match
Plus with three terms. But, we actually do want this to match. Implementing associative-commuatative matching is a major outstanding problem in Symata. Anyone want to give it a try ?
Symata's host language is Julia, a high-performance, compiled language. It can be useful to call Juila code from Symata or to compile Symata code to Julia. Symata is also an open-source project, which means you can alter or add to it directly.
Define a compiled function to a built-in or user-defined Julia function like this
mylog = J(log ) mylog(2, 2)
You can also easily write compiled code like this
f = J((x,y) -> x^2 + y^2) f(3.0,4.0)
Note that we did not specify the data types. Is this really high-performance compiled code ? Yes it is. The function was compiled after we called it with two floating point numbers. If we call the function with two integers, a version (called a method) that is optimized for integers is compiled. A version optimized for an integer and a rational number or any combination of arguments can also be compiled.
[f(3,4), f(3, 1/2)]
We define two versions of the same function to see the difference in performance between compiled functions and functions defined via
(a = Range(0.0,100.,.01), ccossq = J(x -> cos(x)^2), cossq(x_) := cos(x)^2);
We run each test twice because compilation time is included in the first run.
1.119352 seconds (2.99 M allocations: 182.794 MiB, 4.80% gc time) tryrule count: downvalue 10001, upvalue 0
0.512223 seconds (1.93 M allocations: 125.300 MiB, 12.62% gc time) tryrule count: downvalue 10001, upvalue 0
0.052542 seconds (189.87 k allocations: 4.259 MiB) tryrule count: downvalue 0, upvalue 0
0.006194 seconds (175.49 k allocations: 3.522 MiB) tryrule count: downvalue 0, upvalue 0
0.000184 seconds (115 allocations: 54.461 KiB) tryrule count: downvalue 0, upvalue 0
The compiled function is about $500$ times faster in this example. Symata
Patterns can be compiled (automatically) as well, but this has been removed during a rewriting of the
Pattern code that is still underway. With compiled
Patterns, the factor might be closer to $50$.
In this example we calculate an expression and compile it. The compiled code is as efficient hand-coded Julia.
expr = Integrate( x^2 * Exp(x)* Cos(x), x)
expr = Collect(expr, Exp(x))
Now we use
Compile. By default,
Compile does not evauate its arguments. So we ask explicitly for evaluation.
cexpr = Compile([x], Evaluate(expr))
a = Range(0.0, 10.0, 1.0)
a = Range(0.0,10.0,.01);
Timing((Map(cexpr, a), Null))
Symata version 0.4.6 Julia version 1.6.0-DEV.116 Python version 3.8.3 SymPy version 1.5.1