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

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=
x
8
-25;​​e=
y
8
-25;​​d=
1
99
(
2
e
+
2
k
);​​q=
k Sin[
2
d
-t]
2 Cos[5 y]
+
x
3
;​​c=
d
2
-
t
8
;​​q Sin[c]+e Sin[d+k-t]+200,Cos[c] 9 d+q+
y
8
+200
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=
-
y
8
-25sint+
1
99
-
2
x
8
-25
-
2
y
8
-25
-
x
8
+25-sin
t
8
+
1
198
-
2
x
8
-25
-
2
y
8
-25
x
3
-
1
2
x
8
-25sec(5y)sint-
2

2

x
8
-25
+
2

y
8
-25

9801
+200,cos
t
8
+
1
198
-
2
x
8
-25
-
2
y
8
-25
-
1
2
x
8
-25sec(5y)sint-
2

2

x
8
-25
+
2

y
8
-25

9801
+
1
11
2
x
8
-25
+
2
y
8
-25
+
x
3
+
y
8
+200
It is a function that gives as output a pair of coordinates for every value of
t
, which will be visualized as a white dot. The input consists of three variables,
x
and
y
for the position and
t
for the time. We go make a canvas of
400x400
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.
Let us inspect what the function
a[x,y,t]
actually gives. Here is the value of the first time point at
t=0
at the starting position
{x=100,y=99}
:
In[]:=
a[100,99,0]
Out[]=
200+
101
8
Sin
58999
6336
+Sin
20201
12672

100
3
-
25
4
Sec[495]Sin
408080401
40144896
,200+Cos
20201
12672

157139
2112
-
25
4
Sec[495]Sin
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
t=0
becomes
t=0.
, or by using the numerical value function N[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
2
x
+4
2
y
for the real parameter values
x
and
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)}
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
Argument count: 3
Argument types:
{_Real,_Real,_Real}

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
Argument count: 3
Argument types:
{_Real,_Real,_Real}

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

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-home-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

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

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

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