Shadama: A Particle Simulation
Programming Environment for Everyone
Yoshiki Ohshima, Dan Amelang and Vanessa Freudenberg
HARC/Y Combinator Research
We present a prototype of a programming system called Shadama.
Shadama is designed for writing programs that create, control and visualize large numbers of objects.
The basic execution model follows the tradition of StarLogo and its "turtles and patches" abstraction.
Turtles, Termites, and Traffic Jams: Explorations in Massively Parallel Microworlds
This abstraction has been proven to be effective and easy to use.
The primary goal of the language is to facilitate the writing of scientific simulations by students
at the high school level.
The Shadama environment supports liveness. Once changes to the program are saved, the effect on the running simulation is immediate; there is no need to restart the simulation.
Shadama programs are run on the GPU by means of code translation to the OpenGL Shading Language. The OpenGL Shading Language Program data resides entirely on the GPU, which enables high performance.
A prototype of Shadama can be found here. Be aware that Shadama requires WebGL 2 and is affected by the floating point precision provided by your graphics card. Thus, it only works on certain computer systems. From our experience, it seems to work better on computers with an AMD Radeon graphics card and on Chrome or Firefox browsers.
Introduction
The computing power available to us today is astounding. Achieving one teraflop of performance was a milestone for a building-sized supercomputer in the late '90s, but now we expect smart phones to reach this same milestone soon. Computing performance will only continue to increase in the future.
But what are people using this power for? Not to be critical, but people are generally more interested in watching cat videos and playing games than trying to invent self-piloting personal airplanes or discover the graviton. All joking aside, one of the big problems we face today is our struggle to advance science literacy, even within a technologically-advanced society. We need the next generation to embrace science more, not less.
We think now is a good time to redouble our efforts to provide environments where students and even professional programmers can more easily tap into the computing power that is available. To this end, we have been working on a prototype of a programming language intended for high school students to explore scientific concepts.
Science is about making the invisible visible. One notable example of an invisible yet important concept is molecules in a gas. We would like students to learn about this, not by rote memorization, but through actual experiments and model-building. With the help of computers, students can make a dynamic model and understand it more deeply. This kind of environment will help students construct knowledge by doing, in line with the constructivism philosophy of education.
A Science Experiment
Before introducing the Shadama programming environment, we first give an example of a physics experiment that could also be modeled and simulated on a computer.
This movie demonstrates a vacuum cannon. The cannon is a six foot PVC pipe connected to a vacuum pump. We put a ping pong ball inside the pipe, cover the ends with sturdy but breakable material (such as plastic plates), and pump the air out.
The pump reduces the pressure inside the tube to about 20% of full atmospheric pressure.
When the end near the ping pong ball is broken open by a box cutter, the air molecules that rush into the pipe move into the low pressure space and the ping pong ball is pushed out the other end. Our crude setup can accelerate the ball to a speed of over 200 meters per second (about 60% of the speed of sound). Others have built similar setups that can achieve supersonic speed.
An interesting point to understand is that it is just movement of molecules that is causing the ball to shoot out. It is not that the vacuum is sucking molecules into the pipe.
The invisible molecules that cause the ball to shoot out can be made “visible” in a sense not only through this physical experiment, but also through a computer simulation that reproduces the same phenomenon. Creating such simulations is the purpose of our programming environment.
Language Goals
We had the following goals when designing Shadama:
- Easy to learn
- Openness
- Support for large numbers of particles
- Liveness
To make learning Shadama easy, we based our language on StarLogo, whose turtles and patches approach has proved intuitive to young learners. In addition, we expect students familiar with Scratch to learn Shadama easily because of similarites in how object behavior is programmed and how objects can sense values near them spatially.
To encourage openness, we designed Shadama to be capable of implementing complex behavior in the language itself, with as few primitives as possible. We think that a learning environment should be open in the sense that a student should be able to see, change and understand how the various parts of the system work. Although many sophisticated scientific software packages and simulation environments exist, they are presented as immutable black boxes, which limits understanding and stifles curiousity. When additional software support is required, it should be provided in the end-user's programming language, and it should be capable of modification within the end-user's environment.
To support large numbers of particles, we designed the language to execute on graphics hardware. Graphics processors today are capable of computing on massive amounts of data. Unfortunately, this requires a trade-off between ease of use and high performance because there are fundamental limitations in the execution model of today's graphics hardware. Although Shadama has some features to mitigate these limitations, certain behaviors cannot be implemented.
To support liveness, we designed the Shadama environment to show the effects of code changes immediately. Of course, a running simulation should not be updated for each and every key stroke. Instead, the user submits a batch of code changes for the environment to apply.
Shadama in Action
The following movie introduces our prototype programming environment through several examples. For details on the language, see the appendix. The script for the narration of the movie is included below. You can pause the movie at any time to scroll through and read the script.
Implementation
Shadama is built on web technologies, including WebGL 2.0 and OpenGL Shading Language version 3.0. Our code translator is written in Ohm Ohm: A library and language for building parsers, interpreters, compilers, etc. and generates vertex shader, fragment shader and JavaScript code from the Shadama program. All the values for a given breed and property are stored in a single OpenGL texture. These textures are created with the OpenGL type "R32F", to match their use as an array of floating-point scalar values. The values for a given patch and property are similarly represented.
Shadama static functions are translated to Javascript code that runs OpenGL shaders derived from the Shadama methods. For each method, a vertex shader is generated to fetch property values from textures and perform the computation required for that method. A generated fragment shader stores the property values back into textures, using the multiple render targets feature of OpenGL.
Related Work
Previously, one of the authors implemented a particle system called Kedama as an extension of Etoys.
Kedama: A GUI-Based Interactive Massively Parallel Particle Programming System.
Shadama can be considered an attempt to give the same idea new life.
Kedama's target audience was middle school children, while Shadama's target audience is high school students.
The biggest inspiration for Shadama (and Kedama) was Resnick's StarLogo. As such, it provided the basis for the basic organization of objects in Shadama. However, StarLogo has certain features that Shadama does not, as mentioned in the introduction.
Based on Extempore, Swift et al. created a live programming environment for physics.
Live Programming in Scientific Simulation
This environment brings dynamic code swapping and interactive data
inspection to a sophisticated and optimized particle-in-cell
simulation engine. The spirit of this work is much in line with ours,
although their environment uses a third-party engine that cannot be modified from this same environment.
This is a reflection of their focus on empowering scientific researchers.
Such users are already familiar with the concepts behind the simulation and
value the tight interaction offered by this approach.
While it is possible to rewrite parts of the third-party engine in Extempore,
this is beyond the ability of most users, especially our target audience.
Nicky Case's simulation construction environment represents an interesting point in the design space.
Simulating the World (in Emoji)
The system features a user-friendly design for creating many types of open-ended models.
However, it is only designed to handle a few hundred particles and cells.
Programming the GPU from a high-level language is a hot topic. Object Support in an Array-based GPGPU Extension for Ruby Researchers in this area aim for better performance through increasingly sophisticated compilation techniques and the use of the CUDA API to more directly access the GPU. Ease of use is given little consideration. The authors think that Shadama can occupy a unique position by striking a better balance between performance and ease of use.
Some languages for programming massively parallel particle simulations are based on visual programming blocks. StarLogo Nova is a notable example. StarLogo Nova: A Programming Environment For Students and Teachers to Create 3D Games and Simulations for Understanding Complex Systems Shadama is currently text-based because we feel that scientific simulations can be naturally expressed with concise text. However, we have not ruled out other possiblities for syntax, including a visual representation of the program.
Although many simulation environments such as Liquid Fun
LiquidFun: A 2D Rigid-body and Fluid Simulation C++ Library for Games Based Upon Box2D
are available today, they do not provide an end-user language.
Conclusions and Future Work
We have presented an early prototype of a language in which users can make enlightening simulations and intricate visualizations. The liveliness of the environment encourages an exploratory style of programming that enables trying out different ideas quickly. We have also discovered that such an interactive and graphical environment can be motivating even as bugs appear because of the spectacular unintentional visual effects that are produced.
We are considering various improvements to the system. One major addition would be to fully support simulations in 3D, not just 2D. While computation in Shadama only uses scalar values and is agnostic to dimensionality, the primitives and concepts the system currently provides only work well for 2D applications. We will need new ideas to manage 3D spatial data.
We also plan to support more mathematical concepts, such as vectors and matrices. While first-time users may not initially have use for such abstractions, we would like them to eventually learn and use these powerful concepts. Ideally, the environment would gradually introduce the user to new, more difficult approaches.
We would like to conclude this paper by stressing the importance of education. Education raises our awareness and enables us to see our world from new perspectives. In this way, we are empowered with new approaches to solve problems. For the next generation to solve the challenges of the future, their science literacy is imperative. Our aim with Shadama is to leverage the power and ubiquity of computing devices to improve science literacy for high school students by providing an engaging, open environment in which to explore scientific concepts.
References
- Mitchel Resnick. Turtles, Termites, and Traffic Jams: Explorations in Massively Parallel Microworlds. MIT Press, Cambridge, MA, USA, 1994.
- The OpenGL Shading Language (Khronos OpenGL Registry)
- Ohm: A library and language for building parsers, interpreters, compilers, etc. (project page)
- Yoshiki Ohshima. Kedama: A GUI-based Interactive Massively Parallel Particle Programming System. In 2005 IEEE Symposium on Visual Languages and Human-Centric Computing (VL/HCC’05), pages 91–98, Sept 2005.
- Ben Swift, Andrew Sorensen, Henry Gardner, Peter Davis, and Viktor K Decyk. Live Programming in Scientific Simulation. Supercomputing Frontiers and Innovations, 2(4):4–15, March 2015. (DOI)
- LiquidFun: A 2D Rigid-body and Fluid Simulation C++ Library for Games Based Upon Box2D (project page)
- Nicky Case. Simulating the World (in Emoji) (project page)
- Matthias Springer and Hidehiko Masuhara. Object Support in an Array-based GPGPU Extension for Ruby. In Proceedings of the 3rd ACM SIGPLAN International Workshop on Libraries, Languages, and Compilers for Array Programming, ARRAY 2016, pages 25–31, New York, NY, USA, 2016.
- StarLogo Nova: A Programming Environment For Students and Teachers to Create 3D Games and Simulations for Understanding Complex Systems (project page)
Appendix: A Primer for the Shadama Language
The Shadama language uses a turtles and patches abstraction, drawn from the tradition of StarLogo.
Breeds
Turtles are organized into "breeds". Each breed has its own set of properties. A breed is declared in a program with the "breed" statement. For example:
breed MyBreed (x, y)
The above creates a breed of turtle called MyBreed
, and declares that each
turtle in the breed has individual properties x
and y
.
Currently, properties in Shadama can only be scalar floating-point numbers.
Methods provide turtle behavior. The def
statement is used to define
methods, as follows:
def move() { this.x = this.x + 1; this.y = this.y + 1; }
As expected, when this method is invoked on a turtle, the turtle's x and y properties are incremented by one.
Methods can only be invoked from static functions. Methods calls are made by first specifying a breed, then the method name. The method is applied to all turtles in the breed concurrently. For example:
static step() { MyBreed.move(); }
The step
function above calls the move
method on all turtles of the MyBreed
breed.
Shadama provides many built-in features made available through primitive methods:
-
aBreed.setCount(count);
ThesetCount
primitive sets the number of turtles in the breed. For example:static setup() { MyBreed.setCount(10000); }
Invoking this static function will set the number ofMyBreed
turtles to 10,000. In the current implementation, the number of turtles in a breed is limited to 1024 × 1024, or about 1 million. -
aBreed.fillRandom(name, min, max);
ThefillRandom
primitive sets the property specified byname
to be a random number betweenmin
(inclusive) andmax
(exclusive) for each turtle in the breed. For example:static setup() { MyBreed.setCount(10000); MyBreed.fillRandom("x", 0, 100); }
The above will set each of the 10,000 turtlesx
property to be a random floating-point number between 0 and 100. -
aBreed.fillRandomDir(dxName, dyName);
ThefillRandomDir
primitive generates random 2D unit vectors. This is done for each turtle, and the x and y components of the result are stored in thedxName
anddyName
properties of the turtles. For example:static setup() { MyBreed.setCount(10000); MyBreed.fillRandomDir("dx", "dy"); }
The above sets thedx
anddy
properties of the turtles with the x and y components of the randomly generated unit vectors. -
aBreed.fillSpace(xName, yName, xDim, yDim);
The fillSpace primitive first sets the number of turtles in the breed to bexDim
×yDim
. Then, it places the turtles on integral 2D grid points within the (0..xDim
, 0..yDim
) area, storing into thexName
andyName
turtle properties. For example:static setup() { MyBreed.fillSpace("x", "y", 100, 100); }
The above creates 10,000 turtles of the MyBreed breed, setting the turtle'sx
andy
properties to be the integral grid points spanning (0, 0) to (99, 99), inclusive. -
aBreed.fillImage(xName, yName, rName, gName, bName, aName, anImageData);
The fillImage primitive converts a 2D image into a breed of turtles. TheanImageData
argument is a Javascript ImageData object containing the data used to populate the breed. The turtle count is set to beanImageData.width
×anImageData.height
. Similar tofillSpace
, the turtles are placed on the 2D grid points within the image extent. The turtle properties given byrName
,gName
,bName
, andaName
are populated with the RGBA color component values of the image. Note that while the color components inanImageData
range from 0 and 255 (integral values), those values in Shadama will be normalized to range from 0.0 and 1.0, inclusive (floating-point). For example:breed MyBreed (x, y, r, g, b, a) static setup() { MyBreed.fillImage("x", "y", "r", "g", "b", "a", anImage); }
Static function variables, such asanImage
, are described below.
Patches
A patch is a 2D set of cells that store values. Patches are fixed size, 512 × 512 by default. The following declares a patch:
patch Field (nx, ny)
Each cell in this patch stores two values, nx
and ny
.
A patch can be manipulated by passing it as an argument to a method on a breed. As the method is executed for each turtle, turtles can access the patch cell nearest them. No other patch cells are available to them. For example:
breed MyBreed (x, y, r, g, b, a) patch Field (r, g, b, a) def store(field) { field.r = this.r; } static setup() { MyBreed.fillImage("x", "y", "r", "g", "b", "a", anImage); MyBreed.store(Field); }
The above declares a patch called Field which is used as an argument to store
.
When store
is executed for each turtle in MyBreed
, the turtle's r
property
is stored in the r
property of the nearest cell in Field.
A turtle can read values from patch cells as well. For example:
def load(field) { this.r = field.r; }
When the above method is executed, each turtle reads the r
property of the nearest patch cell
and stores the value into its own r
property.
Methods can receive multiple patches as arguments. For example:
patch Field1 (nx, ny, r, g, b, a) patch Field2 (nx, ny, r, g, b, a) def transfer(f1, f2) { f2.r = f1.r; f2.a = f1.a; }
The following code is also valid, and correctly swaps values between patches.
def swap(f1, f2) { f2.r = f1.r; f1.r = f2.r }
Local Variables
The var
statement declares a local variable within a method.
The scope of a local variables is the whole method,
regardless of where in the method it is declared.
In the same method, there can be no more than one declaration for a given variable name.
This is in contrast to variable declarations in JavaScript.
For example:
def average() { var avg = (this.x + this.y) / 2.0; this.x = avg; this.y = avg; }
The above code properly defines and uses a local variable called avg
.
However, the following code would raise an error because the variable diff
is declared in two places:
def gcd() { if (this.a > this.b) { var diff = this.a - this.b; this.a = diff; } else { var diff = this.b - this.a; this.b = diff; }
The variable declaration has to be manually hoisted, as follows:
def gcd() { var diff; if (this.a > this.b) { diff = this.a - this.b; this.a = diff; } else { diff = this.b - this.a; this.b = diff; }
Static Function Variables
Static function variables are declared within static functions, also using the "var" statement. Static function variables are not available to methods, but are visible to all static functions. For example:
static setup() { var begin = 1; } static loop() { if (begin) { begin = 0; } }
The two static functions above refer to the same begin
variable.
Shadama provides the following built-in static function variables:
-
mousemove
: An object whosex
andy
properties refer to the most recent mouse cursor location. -
mousedown
: An object whosex
andy
properties refer to the most recent location where the user pressed the mouse button down. -
mouseup
: An object whosex
andy
properties refer to the most recent location where the user lifted the mouse button up. -
time
: The number of seconds elapsed since the last time thesetup
function was called (in floating-point). -
width, height
: The width and height of the Shadama canvas.
Be aware that mouse event objects are JavaScript objects. Thus, they can't be passed to methods because methods can only take scalar arguments.
There is one additional variable called Display
for invoking certain system primitives.
For example:
static loop() { Display.clear(); MyPatch.draw(); }
The above code clears the canvas and then draws MyPatch
by calling the draw
primitive. Display
has loadProgram
, and clear
.
Parallelism Considerations
It's possible for two or more nearby turtles to write into the same patch cell. Which value gets stored in the patch is non-deterministic.
Also, updates to turtle properties and patch properties are not visible until after the method is run. Consider the following method:
def test() { if (this.r > 0) { this.r = 0; } else { this.r = 1; } this.b = this.r; }
Even though the last line reads this.b = this.r;
, the r
property and b
property will
not be equal after the invocation. This is because the update to the r
property seen earlier does not take effect
until after the method call is finished. Thus, the b
property will have the previous r
value.
The properties this.b
and this.r
can have the same value
through the following use of a local variable:
def test() { var r = this.r; if (r > 0) { r = 0; } else { r = 1; } this.r = r; this.b = r; }
A script can be started by calling start
on it. Likewise, you can stop it stop
, and execute it once with step
.
Control Structures
The if
statement is the only control structure that Shadama supports.
Loops may be supported in a future version of Shadama because the OpenGL shader language version 3.0
does support variable-count loops.
Primitive Functions
There are a number of primitive functions that can be called from methods. Most of them actually result in a direct call to a GLSL built-in function. For example:
def prims(x) { var c = cos(x); var s = step(0.5, x); var a = abs(x); var f = fract(x); // the fraction part of x this.r = c * s * a * f; }
The above code uses several primitive functions to compute a contrived value which
is then stored into the turtle's r
property.
Method Binding
Methods are not defined for any particular breed, but a given method can only be applied to breeds that have the properties referenced in the method. For example:
breed A (x, r) breed B (x, y, r, g) breed C (r) def set() { this.r = 1; this.x = 0; } static test() { A.set(); B.set(); }
The set
method above can be called for both breed A
and breed B
.
This is the not the case for breed C because it does not have an x
property, which is
referenced in set
.
Limitations
The following are important limitations of the Shadama language.
Methods can not take breeds as arguments. For example, the following code is invalid:
breed A (x, y) breed B (x, y) def hit(other) { var diff = other.x - this.x; ... } static step() { A.hit(B); }
The step
function passes breed B as an argument to the hit
method, which is not allowed.
Even if it were, it is not clear which turtle from breed B
should
be bound to the argument other
.
Another limitation is that a given method can either update the turtle's properties or the patch's properties, but not both. For example, the following code is invalid:
def test(field) { field.r = 1; this.r = 1; }
This limitation arises from limitations in WebGL itself. Future versions of WebGL, and thus Shadama, may no longer have this restriction. In the meantime, a workaround is to split the method into two methods — one for updating the patch and one for updating the turtle. It is also possible that a future version of Shadama will automatically perform this code transformation.