In [69]:
Julia()


Out[69]:
true

In [70]:
Pkg.update()


INFO: Updating METADATA...
INFO: Updating cache of DataFrames...
INFO: Updating cache of DataFrames...
INFO: Updating Geomechanics master...
INFO: Updating Interact ipywidgets-4...
INFO: Updating Stan master...
INFO: Updating ClobberingReload master...
INFO: Updating EDxMIT15053 master...
INFO: Updating Sundials master...
INFO: Updating Atom master...
INFO: Updating RadauBVP master...
INFO: Updating BHAAnalysis master...
INFO: Updating EDxMIT201 master...
INFO: Updating Tokenize master...
INFO: Updating Graphs master...
INFO: Updating Symata master...
INFO: Updating Espresso master...
INFO: Updating EDxMath226 master...
INFO: Updating Media master...
INFO: Updating Mamba master...
INFO: Updating CSoM master...
INFO: Updating TP master...
INFO: Updating OhMyREPL master...
WARNING: Package NMfE: skipping update (dirty)...
INFO: Updating ClassicalLaminateTheory master...
INFO: Updating Plots master...
INFO: Updating Gadfly master...
INFO: Updating Unitful master...
INFO: Updating MathLink master...
INFO: Updating TikzGraphs master...
INFO: Updating CodeTools master...
INFO: Updating BHACases master...
INFO: Updating BHAPlotRecipes master...
INFO: Updating Klara master...
INFO: Updating Requires master...
INFO: Computing changes...
INFO: No packages to install, update or remove

In [71]:
using Symata

In [73]:
isymata()

Interface with Julia

This notebook gives examples of Symata interacting with its host language Julia.

Writing and using Julia functions

It is easy to write elegant, fast, powerful, flexible, Julia code from within Symata.

As a first example, we multiply powers of elements in two arrays and sum the results. We first try two different methods using pure Symata. Then we write a Julia function to do the same thing.

Here are the arrays.


In [74]:
x1 = Range(10.0^3)
y1 = Range(10.0^3);

Define a procedural function in Symata to compute the sum.


In [75]:
g(x_, y_) := Module([s=0],
        begin
          For(i=1, i<=Length(x), i += 1, s += x[i]^2 * y[i]^(-3))
          s
        end)

Apply this function and time the result.


In [76]:
g(x1,y1)

resultS1 = Timing(g(x1,y1))


Out[76]:
$$ \left[ 0.087759966,7.485470860550343 \right] $$

In general, it is faster to use mapping and functional methods in Symata. We can compute the sum like this.


In [77]:
Apply(Plus, x1^2 / y1^3)

resultS2 = Timing(Apply(Plus, x1^2 / y1^3))


Out[77]:
$$ \left[ 0.064353072,7.485470860550343 \right] $$

The second method is indeed a bit faster.


In [78]:
resultS1[1]/resultS2[1]


Out[78]:
$$ 1.3637261326079353 $$

Writing a Julia function while in Symata mode

Define a Julia function to do the sum. We choose a functional method.


In [79]:
jfunc = J( (x,y) -> sum(u -> u[1]^2 / u[2]^(3), zip(x,y)) );

In [80]:
jfunc(x1,y1)
Timing(jfunc(x1,y1))

resultJ = Timing(jfunc(x1,y1))


Out[80]:
$$ \left[ 0.000462344,7.485470860550343 \right] $$

The Julia function is much faster.


In [81]:
[resultS1[1], resultS2[1] ] / resultJ[1]


Out[81]:
$$ \left[ 189.81530202619695,139.18872527814787 \right] $$

We will explain later why it is possible to write such simple and fast code that operates on Symata expressions.

The function f also works on symbolic expressions.


In [82]:
jfunc([a+b, c+d],[u+v,y+z])


Out[82]:
$$ \frac{ \left( c + d \right) ^{2}}{ \left( \text{"dog"} + y \right) ^{3}} + \frac{ \left( b + newargs \! \left( 5 \right) \right) ^{2}}{ \left( u + v \right) ^{3}} $$

Evaluating Symata expressions from Julia

The macro @sym evaluates Symata code while in Julia.

First, we switch to Julia mode.


In [83]:
Julia();

Create a Symata expression and bind it to the Julia variable expr.


In [84]:
expr = @sym a + b


Out[84]:
:b + newargs(5)

There are julia functions corresponding to many Symata expression heads.


In [85]:
Expand(expr^2)


Out[85]:
:b^2 + newargs(5)^2 + 2:b*newargs(5)

Return to Symata mode


In [86]:
isymata()

Calling an existing Julia function

To define the Julia function, we used the Symata function J(). The arguments of J are interpreted as pure Julia code, with no translation. It is as if we temporarily enter Julia mode. In fact, we could have defined the function in Julia. Let's try that.

First, we enter Julia mode.


In [87]:
Julia();

Everything we type will be interpreted as Julia language expressions. We write the Julia function. We will explain later how the function works.


In [88]:
fj(x,y) = sum(u -> u[1]^2 / u[2]^(3), zip(x,y));


WARNING: Method definition fj(Any, Any) in module Main at In[16]:1 overwritten at In[88]:1.

Return to Symata mode.


In [89]:
isymata();

We set the Symata variable fj to the Julia function fj. The Julia function was written in the Main module. (NB we may change this so Julia functions are evaluated in the Symata module)


In [90]:
fj = J( Main.fj );

fj(x1,y1)


Out[90]:
$$ 7.485470860550343 $$

We can use J() in this way to call any existing Julia function...

time()

  Get the system time in seconds since the epoch, with fairly high (typically,   
  microsecond) resolution.

In [91]:
J(time)()


Out[91]:
$$ 1.480191633558618e9 $$

Julia functions for Symata

How does the Julia function

jfunc = (x,y) -> sum(u -> u[1]^2 / u[2]^(3), zip(x,y))

work ?

As in Symata, x -> body defines a pure, or anonymous, function. zip returns a list of pairs of elements from two lists. In fact, it returns a virtual list, called an iterator, which is more efficient. These pairs are supplied sequentially to the to the function, and the results are summed. No intermediate arrays are formed.

Symata expressions are iterable objects in Julia. Most Julia code that operates on iterable objects will work with Symata expressions. zip takes two iterable objects and returns an iterable object. In Julia, Symata expressions are of type Mxpr. Notice that we did not write Mxpr anywhere in the code. The first time jfunc is called with Symata expressions, Julia compiles a method to handle just this case. The compiler is typically very good at writing code optimized for the input type.

All of this means that the author of Symata wrote no code to implement zip or sum for Symata expressions.

In fact jfunc can be called with many types objects. To demonstrate this, we perform the sum operation on a Symata list and a Julia Array.

We set the Symata variable y2 to a Julia Array of 1000 numbers.


In [92]:
y2 = J(linspace(1,1000.0,1000))


Out[92]:
$$ linspace(1.0,1000.0,1000) $$

Notice that 1000 numbers were not printed. linspace returns a virtual array, that is an iterator. We call jfunc twice. The first time, Julia compiles a method for the input types which takes some (not much) time.


In [93]:
jfunc(x1,y2)
resultJ2 = Timing(jfunc(x1,y2))


Out[93]:
$$ \left[ 0.000531506,7.485470860550343 \right] $$

Notice that summing over the two types of arrays is a bit slower in this case than using two Symata arrays.


In [94]:
resultJ[1]/resultJ2[1]


Out[94]:
$$ 0.8698754106256562 $$

Now we call jfunc on two Julia abstract arrays.


In [95]:
jfunc(y2,y2)
resultJ3 = Timing(jfunc(y2,y2))


Out[95]:
$$ \left[ 4.936e-5,7.485470860550343 \right] $$

Operating on these Julia arrays is about 10 times faster in this case than including a Symata array.


In [96]:
resultJ2[1]/resultJ3[1]


Out[96]:
$$ 10.767949756888166 $$

Unpack: Importing Julia arrays into Symata

Recall the abstract Julia array that we created above


In [97]:
y2


Out[97]:
$$ linspace(1.0,1000.0,1000) $$

We import y2 into Symata using Unpack. (Unpack currently works only with one dimensional arrays.)


In [98]:
y3 = Unpack(y2);

The result is a Symata list. We check that it is indeed a list of the expected length and the first and last elements.


In [99]:
[Head(y3), Length(y3), y3[1], y3[-1]]


Out[99]:
$$ \left[ \text{List},1000,1.0,1000.0 \right] $$

y3 is equal to y1, which was created with Range.


In [100]:
y3 == y1 == Range(10.0^3)


Out[100]:
$$ \text{True} $$

y3 is not a packed array, but an ordinary Symata array, a list.


In [101]:
y3[1] = "cat"


Out[101]:
$$ \text{"cat"} $$

In [102]:
y3[1:10]


Out[102]:
$$ \left[ \text{"cat"},2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0 \right] $$

Of course, we can unpack not just abstract Julia arrays, but physical arrays as well. The Julia function for converting an abstract array to a physical array is collect.


In [103]:
y4 = J(collect)(y2);

Notice what we did here. J(collect) gets a Symata reference to the Julia function collect. We then call the imported function on the Symata variable y2, which refers to an abstract Julia array.

Now we have a physical Julia array


In [104]:
[Head(y4), y4[1]]


Out[104]:
$$ \left[ \text{Array{Float64,1}},1.0 \right] $$

You can also construct a Symata expression directly in Julia, like this.


In [105]:
y5 = J(mxpr(:List, Any[collect(linspace(1.0,1000,1000))...]));

In [106]:
[Head(y5), y5[-1]]


Out[106]:
$$ \left[ \text{List},1000.0 \right] $$

Pack: Converting Symata arrays to Julia arrays.

Recall that y1 and y3 are both Symata lists.


In [107]:
[Head(y1), Head(y3)]


Out[107]:
$$ \left[ \text{List},\text{List} \right] $$

We unpack these lists to Julia arrays like this.


In [108]:
y6 = Pack(y1)
y7 = Pack(y3);

What type of object was created ?


In [109]:
[Head(y6), Head(y7)]


Out[109]:
$$ \left[ \text{Array{Float64,1}},\text{Array{Any,1}} \right] $$

The first array is of type Float64 and the second of type Any. They are different because, while all elements of y1 are floating point numbers, we set the first element of y7 to "cat". When copying arrays, Julia creates a container of the most specific type that will contain all elements.

Using Symata in Julia code


In [110]:
# isymata()

Switch from Symata to Julia mode


In [111]:
Julia();

The macro @sym interprets and evaluates its argument in Symata.


In [112]:
a = 1        # set a to 1 in Julia
@sym a = 3   # set a to 3 in Symata
println("a in Julia is $a")   # print a in Julia
@sym Println("a in Symata is $a") # print a in Symata


a in Julia is 1
a in Symata is 3

You can assign a value in Symata by using @sym


In [113]:
@sym z = "cat"
@sym z


Out[113]:
"cat"

But @sym will not work inside a function. Use getsymata and setsymata instead


In [114]:
getsymata(:z)


Out[114]:
"cat"

In [115]:
setsymata(:z, "dog")
getsymata(:z)


Out[115]:
"dog"

unpacktoList converts a Julia array to a Symat list.


In [116]:
unpacktoList(linspace(1,4,3))


Out[116]:
[1.0,2.5,4.0]

Parse a string of Symata code


In [117]:
scode = parse("Sqrt(a)")


Out[117]:
:(Sqrt(a))

This is a valid Julia expression, although evaluating it in Julia may cause an error. We translate the expression to Symata and send it through the Symata evaluation sequence.


In [118]:
res = symtranseval(scode)


Out[118]:
3^(1//2)

Print this as Symata would


In [119]:
symprintln(res)


3^(1/2)

Put this together in a function


In [120]:
function squareroots()
    a = [i for i in 1:9]
    setsymata(:a, unpacktoList(a))
    symprintln(symparseeval("Sqrt(a)"))
    nothing
end

squareroots()


[1,2^(1/2),3^(1/2),2,5^(1/2),2^(1/2)*3^(1/2),7^(1/2),22^(1/2),3]
WARNING: Method definition squareroots() in module Main at In[48]:2 overwritten at In[120]:2.

In [121]:
isymata()

You can translate Symata to Julia like this


In [122]:
ToJuliaString( 3*x^2*y^3 + Cos(1))


Out[122]:
$$ \text{"mplus(mmul(3,mpow(x,2),mpow(y,3)),Cos(1))"} $$

The following gives code that will only work when the Symata module is loaded.


In [123]:
s2 = ToJuliaString( 3*x^2*y^3 + Cos(1), NoSymata => False)


Out[123]:
$$ \text{"mplus(mmul(3,mpow(x,2),mpow(y,3)),Cos(1))"} $$

Symata expressions are of type Mxpr

We assigned a value to a in symata


In [124]:
a = getsymata(:a)


UndefVarError(:a)
WARNING: :( ) for Julia code is deprecated. Use J( ) instead
LoadError: UndefVarError: a not defined
while loading In[124], in expression starting on line 1

In [125]:
typeof(a)


Out[125]:
$$ typeof \! \left( \left[ 1,2,3,4,5,6,7,8,9 \right] \right) $$

mhead returns the head of a symata expression.


In [126]:
mhead(a)


Out[126]:
$$ mhead \! \left( \left[ 1,2,3,4,5,6,7,8,9 \right] \right) $$

margs returns the arguments of a Symata expression.


In [127]:
margs(a)


Out[127]:
$$ margs \! \left( \left[ 1,2,3,4,5,6,7,8,9 \right] \right) $$

Use mxpr to construct a Symata expression


In [128]:
ex = mxpr(J(Cos), mxpr(J(Times), J(Pi), 2))


Out[128]:
$$ mxpr \! \left( Symata.Cos,mxpr \! \left( Symata.Times, \pi ,2 \right) \right) $$

mxpr creates an object but does not evaluate it. Evaluate ex with symeval.


In [129]:
symeval(ex)


Out[129]:
$$ symeval \! \left( mxpr \! \left( Symata.Cos,mxpr \! \left( Symata.Times, \pi ,2 \right) \right) \right) $$

It may be more efficient to create a Symata expression by first filling an array of arguments


In [130]:
a = newargs(5);

In [131]:
copy!(a, [i for i in 1:5])


Expr
  head: Symbol comprehension
  args: Array{Any}((1,))
    1: Expr
      head: Symbol generator
      args: Array{Any}((2,))
        1: Symbol i
        2: Expr
          head: Symbol =
          args: Array{Any}((2,))
            1: Symbol i
            2: Expr
              head: Symbol :
              args: Array{Any}((2,))
                1: Int64 1
                2: Int64 5
              typ: Any
          typ: Any
      typ: Any
  typ: Any
LoadError: extomx: No translation for Expr head 'comprehension' in [i for i = 1:5]
while loading In[131], in expression starting on line 1

In [132]:
mxpra(:List,a)


WARNING: :( ) for Julia code is deprecated. Use J( ) instead
Out[132]:
$$ mxpra \! \left( Symata.List,newargs \! \left( 5 \right) \right) $$

In [133]:
mxpra(:Plus,a)


WARNING: :( ) for Julia code is deprecated. Use J( ) instead
Out[133]:
$$ mxpra \! \left( Symata.Plus,newargs \! \left( 5 \right) \right) $$

In [134]:
symeval(mxpra(:Plus,a))


WARNING: :( ) for Julia code is deprecated. Use J( ) instead
Out[134]:
$$ symeval \! \left( mxpra \! \left( Symata.Plus,newargs \! \left( 5 \right) \right) \right) $$

Instead of mxpr, we used mxpra, which does not copy the array of arguments.

We can create Symata expressions like this


In [63]:
println(mmul(3,2), ", " , mmul(:b, :a))


WARNING: :( ) for Julia code is deprecated. Use J( ) instead
WARNING: :( ) for Julia code is deprecated. Use J( ) instead
UndefVarError(:b)
LoadError: UndefVarError: b not defined
while loading In[63], in expression starting on line 1

mmul, mpow, mplus, mminus are arithemtic methods that can create Symata expressions. When called inside a function with numerical arguments, the compiler will replace them with an efficent Julia method


In [64]:
code_native(mmul,(Int,Int))


Out[64]:
$$ code\text{_}native \! \left( mmul,\text{Int64} \right) $$

In [65]:
code_native(*, (Int,Int))


Out[65]:
$$ code\text{_}native \! \left( \text{Times},\text{Int64} \right) $$

Many Symata functions have equivalents in Julia. These functions both construct Symata expressions and evaluate them.


In [66]:
Cos(mmul(2,Pi))


Out[66]:
$$ \text{Cos} \! \left( mmul \! \left( 2, \pi \right) \right) $$

symmatamath() defines methods allowing you to use * for mmul, etc.


In [67]:
symatamath()


Out[67]:
$$ symatamath \! \left( \right) $$

In [68]:
Cos(2Pi)


Out[68]:
$$ 1 $$

Note that Cos is replaced by efficient Julia methods when possible.