Ebb

is a programming language for writing physical simulations. Ebb programs are performance portable: they can be efficiently executed on both CPUs and GPUs. Ebb is embedded in the Lua programming language using Terra, which can itself be embedded in C/C++ programs as a library.

Ebb code looks like this:

import "ebb"
local L = require "ebblib"

-- load the triangle mesh
local ioOff = require 'ebb.domains.ioOff'
local PN    = require 'ebb.lib.pathname'
local mesh  = ioOff.LoadTrimesh( PN.scriptdir() .. 'bunny.off' )

-- define globals and constants
local timestep   = L.Constant(L.double, 0.45)
local K          = L.Constant(L.double, 1.0)
local max_change = L.Global(L.double, 0.0)

-- define fields: temperature and change in temperature
mesh.vertices:NewField('t', L.double):Load(function(index)
  if index == 0 then return 3000.0 else return 0.0 end
end)
mesh.vertices:NewField('d_t', L.double):Load(0.0)

-- functions executed for-each vertex
local ebb compute_diffusion ( v : mesh.vertices )
  var count = 0.0
  for nv in v.neighbors do
    v.d_t += timestep * K * (nv.t - v.t)
    count += 1.0
  end
  v.d_t = v.d_t / count
end

local ebb apply_update ( v : mesh.vertices )
  v.t += v.d_t
  max_change max= L.fabs(v.d_t)
  v.d_t = 0.0
end

-- the simulation loop
for i = 1,300 do
  if i % 30 == 0 then   max_change:set(0.0) end

  mesh.vertices:foreach(compute_diffusion)
  mesh.vertices:foreach(apply_update)

  if i % 30 == 0 then   print('iter #'..i, max_change:get()) end
end

Adding visualization routines, (in repository version) we can see the result of the above simulation.

Ebb was designed with a flexible data model that allows for encoding a range of different domains. As a non-exhaustive list, Ebb supports triangle meshes, grids, tetrahedral meshes, and particles. For example, here is a similar heat diffusion program written for a grid:

import 'ebb'
local L = require 'ebblib'

local GridLib   = require 'ebb.domains.grid'
local N = 40
local grid = GridLib.NewGrid2d {
  size   = {N, N},
  origin = {-N/2, -N/2},
  width  = {N, N},
  periodic_boundary = {true,true},
}

-- define constants, globals and fields
local timestep    = L.Constant(L.double, 0.45)
local conduction  = L.Constant(L.double, 1.0)
local max_diff    = L.Global(L.double, 0.0)

grid.cells:NewField('t', L.double):Load(function(x_idx, y_idx)
  if x_idx == 4 and y_idx == 6 then return 1000 else return 0 end
end)
grid.cells:NewField('new_t', L.double):Load(0)

-- compute diffusion
local ebb update_temperature ( c : grid.cells )
  var avg   = (1.0/4.0) * ( c(1,0).t + c(-1,0).t + c(0,1).t + c(0,-1).t )
  var diff  = avg - c.t
  c.new_t   = c.t + timestep * conduction * diff
end

-- measure statistic
local ebb measure_max_diff ( c : grid.cells )
  var avg   = (1.0/4.0) * ( c(1,0).t + c(-1,0).t + c(0,1).t + c(0,-1).t )
  var diff  = avg - c.t
  max_diff  max= L.fabs(diff)
end

-- simulation loop
for i=1,360 do
  grid.cells.interior:foreach(update_temperature)
  grid.cells:Swap('t', 'new_t')

  if i % 10 == 0 then -- measure statistics every 10 steps
    max_diff:set(0)
    grid.cells.interior:foreach(measure_max_diff)
    print( 'iteration #'..tostring(i), 'max gradient: ', max_diff:get() )
  end
end

Again, we can visualize this simulation

Furthermore, domain libraries are user-authorable and can be coupled together in user code. For example, Ebb seamlessly supports coupling particles to a grid, or coupling the vertices of a mesh to a grid. By adding the following code to the preceding grid-based heat diffusion, we can set up a particle advection driven by the heat gradient.

-- create and initialize particle relation
local particles = L.NewRelation {
  name = "particles",
  size = N*N,
}

local particle_positions = {}
for yi=0,N-1 do
  for xi=0,N-1 do
    particle_positions[ xi*N + yi + 1 ] = { xi + 0.5,
                                            yi + 0.5 }
  end
end
particles:NewField('pos', L.vec2d):Load(particle_positions)

-- establish link from particles to cells
particles:NewField('cell', grid.cells)
grid.locate_in_cells(particles, 'pos', 'cell')

-- define particle advection
local ebb wrap( x : L.double )
  return L.fmod(x + 100*N, N)
end
local ebb advect_particle_position ( p : particles )
  -- estimate heat gradient using a finite difference
  var c   = p.cell
  var dt  = { c(1,0).t - c(-1,0).t, c(0,1).t - c(0,-1).t }
  -- and move the particle downwards along the gradient
  var pos = p.pos - 0.1 * timestep * dt
  -- wrap around the position...
  p.pos = { wrap(pos[0]), wrap(pos[1]) }
end

To advect the particles, we add two lines to the simulation loop:

for i=1,360 do
  grid.cells:foreach(update_temperature)
  grid.cells:Swap('t', 'new_t')

  particles:foreach(advect_particle_position)
  grid.locate_in_cells(particles, 'pos', 'cell')

  if i % 10 == 0 then -- measure statistics every 10 steps
    ...
  end
end

A visualization of the advection

The tutorials and code repository contain both simpler and more elaborate examples, explained in more detail.

Liszt is …

A project at Stanford University to develop domain-specific languages for physical simulation. Liszt is focused on performance portability. Performance portable programs are programs that can be run efficiently on a variety of different parallel systems/platforms/architectures. (e.g. CPU, GPU, Multi-core, Clusters, Supercomputers)

Documentation and artifacts for the original Liszt language can be found online.

Ebb is the primary DSL for the Liszt project, with specialized DSLs for collision detection and other problems in the works.

Ebb contributors

Gilbert Bernstein Chinmayee Shah Crystal Lemire Matthew Fisher Zach Devito Phil Levis Pat Hanrahan

View On Github
a part of the Liszt project and PSAAP II center at Stanford University