In a recent Wolfram Community post Vitaliy Kaurov, Director of Engagement at Wolfram Research, discovered a nice set of striking mathematical art implementations by the original artist @yuruyurau written in JavaScript (see the credits to @yuruyurau on Vitaliy’s pages).
Vitaliy took the example to show how ChatGPT could transfer the Java code into Wolfram Language.
Here is a follow-up on that post, showing how in particular for heavy math calculations the use of the Compile[ ] is so effective, that the ‘jellyfish’ can even be rendered in real time on Raspberry Pi 5, on which the Wolfram Language runs for free.
And if you are a STEM student (‘Science, Technology, Engineering, Mathematics”), know that Mathematica (Wolfram Language) is cheaper than Netflix on any other computer, see https://www.wolfram.com/mathematica/pricing/students/.
The Jellyfish
The Jellyfish
Here is the original mathematical function, quite impressive how he could find it (!):
In[]:=
a[x_,y_,t_]:=Module{k,e,d,q,c},k=-25;e=-25;d= (+);q=+;c=-;q Sin[c]+e Sin[d+k-t]+200,Cos[c] 9 d+q++200
x
8
y
8
1
99
2
e
2
k
k Sin[-t]
2
d
2 Cos[5 y]
x
3
d
2
t
8
y
8
In the code the math formula is in a nested form, and it is difficult to see what happens. The resulting formula is quite complicated. So let’s view it in the traditional (text book) form:
In[]:=
a[x,y,t]//TraditionalForm
Out[]//TraditionalForm=
--25sint+--25--25-+25-sin+--25--25--25sec(5y)sint-+200,cos+--25--25--25sec(5y)sint-+-25+-25+++200
y
8
1
99
2
x
8
2
y
8
x
8
t
8
1
198
2
x
8
2
y
8
x
3
1
2
x
8
2
+
2
-25
x
8
2
-25
y
8
9801
t
8
1
198
2
x
8
2
y
8
1
2
x
8
2
+
2
-25
x
8
2
-25
y
8
9801
1
11
2
x
8
2
y
8
x
3
y
8
It is a function that gives as output a pair of coordinates for every value of , which will be visualized as a white dot. The input consists of three variables, and for the position and for the time. We go make a canvas of positions (pixels), and we go scan this canvas for all positions 100 ≤ x ≤ 299 in steps of 1, and 99 ≤ y ≤ 299 in steps of 5. In total 8200 positions where the dot position is calculated per frame.
t
x
y
t
400x400
Let us inspect what the function actually gives. Here is the value of the first time point at at the starting position :
a[x,y,t]
t=0
{x=100,y=99}
In[]:=
a[100,99,0]
Out[]=
200+Sin+Sin-Sec[495]Sin,200+Cos-Sec[495]Sin
101
8
58999
6336
20201
12672
100
3
25
4
408080401
40144896
20201
12672
157139
2112
25
4
408080401
40144896
We immediately see a problem: as Mathematica works with symbolic expressions when exact numbers (integers) are involved, this is an expensive operation, both in memory use and time. The situation is sped up when we take real numbers. This is simply done by converting an integer by adding a numerical point after it, so becomes , or by using the numerical value function N[0].
t=0
t=0.
In[]:=
a[100,99,0.]
Out[]=
{256.054,197.765}
We only have to specify one real to force Mathematica to process the formula numerically:
In[]:=
a[100.,99,0]
Out[]=
{256.054,197.765}
Here is a timing experiment, to calculate the complete jellyfish image at 10 time moments:
In[]:=
time=First@Timing[pts=Table[a[x,y,t],{x,99,299,5},{y,100,299},{t,0,9}];]
Out[]=
4.59094
And with reals:
In[]:=
timeNumerical=First@Timing[pts=Table[a[x,y,t],{x,99,299,5},{y,100,299},{t,0.,9.}];]
Out[]=
3.08733
A bit of gain. Can we do more by compiling the function?
When a function is compiled, the code is made into the lowest possible machine code, with pre-specified number types. See for more details the Documentation.
The Compile function is easy to build. For example, f = Compile[{{x,_Real},{y,_Real}},x^2+4 y^2] gives the function f[x,y] that calculates +4 for the real parameter values and .
2
x
2
y
x
y
We first convert the formula to InputForm so it can be used as code in Compile:
In[]:=
a[x,y,t]//InputForm
Out[]//InputForm=
{200 - (-25 + y/8)*Sin[25 + t - x/8 + (-(-25 + x/8)^2 - (-25 + y/8)^2)/99] -
Sin[t/8 + (-(-25 + x/8)^2 - (-25 + y/8)^2)/198]*
(x/3 - ((-25 + x/8)*Sec[5*y]*Sin[t - ((-25 + x/8)^2 + (-25 + y/8)^2)^2/9801])/2),
200 + Cos[t/8 + (-(-25 + x/8)^2 - (-25 + y/8)^2)/198]*(x/3 + ((-25 + x/8)^2 + (-25 + y/8)^2)/11 +
y/8 - ((-25 + x/8)*Sec[5*y]*Sin[t - ((-25 + x/8)^2 + (-25 + y/8)^2)^2/9801])/2)}
Sin[t/8 + (-(-25 + x/8)^2 - (-25 + y/8)^2)/198]*
(x/3 - ((-25 + x/8)*Sec[5*y]*Sin[t - ((-25 + x/8)^2 + (-25 + y/8)^2)^2/9801])/2),
200 + Cos[t/8 + (-(-25 + x/8)^2 - (-25 + y/8)^2)/198]*(x/3 + ((-25 + x/8)^2 + (-25 + y/8)^2)/11 +
y/8 - ((-25 + x/8)*Sec[5*y]*Sin[t - ((-25 + x/8)^2 + (-25 + y/8)^2)^2/9801])/2)}
We copy it into the Compile body, also changing the integer numbers (except the exponents) to Reals (by adding the numerical dot):
In[]:=
aCompiled=Compile[{{x,_Real},{y,_Real},{t,_Real}},{200.-(-25.+y/8.)*Sin[25.+t-x/8.+(-(-25.+x/8.)^2-(-25.+y/8.)^2)/99.]-Sin[t/8.+(-(-25.+x/8.)^2-(-25.+y/8.)^2)/198.]*(x/3.-((-25.+x/8.)*Sec[5.*y]*Sin[t-((-25.+x/8.)^2+(-25.+y/8.)^2)^2/9801.])/2.),200.+Cos[t/8.+(-(-25.+x/8.)^2-(-25.+y/8.)^2)/198.]*(x/3.+((-25.+x/8.)^2+(-25.+y/8.)^2)/11.+y/8.-((-25.+x/8.)*Sec[5.*y]*Sin[t-((-25.+x/8.)^2+(-25.+y/8.)^2)^2/9801.])/2)}]
Out[]=
CompiledFunction
Explanation: it is a function of three arguments, and we need to specify the type of both the arguments and the internally used variables. The typing for the arguments is done at the very beginning: a list (specified by the curly brackets) of lists: {{x,_Real},{y,_Real},{t,_Real}}. The underscore _ means ‘any value’, and _Real means ‘any value, typed as Real’. Then the main body specifies the formula. The typing of internally used variables is done after the main code (see the documentation of Compile), but we don’t have them here. They would have been specified after a comma after the main code, in the same format as the argument typing.
The proof of the pudding is in the eating, so here is the timing test, for 10 frames:
In[]:=
timeCompiled=First@Timing[pts=Table[aCompiled[x,y,t],{x,99,299,5},{y,100,299},{t,0,9}];]
Out[]=
0.083244
How much faster?
In[]:=
timeNumerical
timeCompiled
Out[]=
37.0877
Quite nice!
A remark: why not put the original code in the Compile function? Well, as this code is quite nested, each time a complicated calculation has to be done for each point. The strength of Mathematica is that it can do this necessary expansion of the nesting symbolically.
Here is the timing result if we put the nested code into the Compile function (and now we need to specify the type of the internal variables at the end):
In[]:=
aCompiledNested=Compile[{{x,_Real},{y,_Real},{t,_Real}},k=x/8.-25.;e=y/8.-25.;d=(k^2.+e^2.)/99.;q=x/3.+k/Cos[5.y]Sin[d^2.-t]/2.;c=d/2.-t/8.;out={q*Sin[c]+e*Sin[d+k-t]+200.,(q+y/8.+d*9.)*Cos[c]+200.};out,{{k,_Real},{e,_Real},{d,_Real},{q,_Real},{c,_Real},{out,_Real,1}}]
Out[]=
CompiledFunction
In[]:=
timeCompiledNested=First@Timing[pts=Table[aCompiledNested[x,y,t],{x,99,299,5},{y,100,299},{t,0,10}];]
Out[]=
2.60256
In[]:=
timeNumerical
timeCompiledNested
Out[]=
1.18627
So that could hardly beat Mathematica’s own implementation ...
Interactive visualization
Interactive visualization
We take a background image, e.g. an aquarium, preferably with dark appearance, so the white dots of the jellyfish stand out nicely:
In[]:=
bg=ImageResize[ImageTake[Import["https://thumbs.dreamstime.com/b/saltwater-full-corals-mixed-aquarium-one-most-beautiful-addition-home-coral-reef-aquarium-tank-scene-fishes-118136040.jpg"],{All},{254,800}],{400,400}]
Out[]=
In[]:=
drawc[t_]:=Module[{pointsList},pointsList=Flatten[Table[aCompiled[x,y,t],{y,99,299,5},{x,100,299}],1];Graphics[{{Opacity[0.5],White,PointSize[.002],Point[pointsList]}},Prolog->Inset[Graphics[bg],{0,0},{0,0},420],AspectRatio->1,PlotRange->{{0,400},{0,400}}]]
Here is the interactive motion of the jellyfish:
YouTube
YouTube
See the video recorded from the Raspberry Pi 5 screen on YouTube: https://youtu.be/Vxnj4tx9sBE
And here is the YouTube movie with some music, generated on the Raspberry Pi 5: https://youtu.be/DBTcOB9l7MA
Export the movie
Export the movie
The 503 frames can be exported as a movie to e.g. the notebook’s directory:
On the Raspberry Pi 5: Play the movie in the File Manager, by double clicking on the file name, which starts the embedded VLC Media Player.
CITE THIS NOTEBOOK
CITE THIS NOTEBOOK
Realtime rendering of fast swimming Jellyfish on Raspberry Pi 5
by Bart ter Haar Romeny
Wolfram Community, STAFF PICKS, October 10, 2025
https://community.wolfram.com/groups/-/m/t/3558969
by Bart ter Haar Romeny
Wolfram Community, STAFF PICKS, October 10, 2025
https://community.wolfram.com/groups/-/m/t/3558969