Implementation of the Reaction Diffusion Simulation

blood.png Update – This post was written before the release of Silverlight 3.0b, which provides a number of enhancements relevant to this implementation, such as a WriteableBitmap and Pixel Shaders

Rendering

The first obstacle to implementing the RD simulation is that Silverlight 2.0 does not by default provide a means of generating dynamic images. WPF has a WriteableBitmap, but no equivalent exists in Silverlight. However, it does support PNG streams so we can dynamically update a bitmap by encoding it to PNG on the fly. For this I have used Joe Stegman’s PNGEncoder class, which I have modified slightly to deal with RGB data and to reduce memory usage.

Performance notes

waves.png Performance of the simulator is dominated by the time it takes to update the arrays containing the concentration values of the chemicals. Actual rendering time is negligible, even though the image has to be dynamically encoded to a PNG file in order for Silverlight to display it. There are two ways of implementing 2D arrays in Silverlight with C#, rectangular arrays or C style jagged arrays. If this were a desktop application, the most performant method would be to use rectangular arrays and unsafe access, however that option is not available to us in Silverlight so it turns out that, for safe access, jagged arrays are faster.

For example, originally the simulation step looked like this

for (int j = 0; j < H; j++)
{
 int jm = j != 0 ? j - 1 : H - 1;
 int jp = j != H - 1 ? j + 1 : 0;
 for (int i = 0; i < W; i++)
 {
  int im = i != 0 ? i - 1 : W - 1;
  int ip = i != W - 1 ? i + 1 : 0;
  double d2u = U[j,im]+U[j,ip]+U[jm,i]+U[jp,i]-4*U[j,i];
  double d2v = V[j,im]+V[j,ip]+V[jm,i]+V[jp,i]-4*V[j,i];
  double uv2 = U[j,i]*V[j,i]*V[j,i];
  Uu[j,i] = du*d2u-uv2+F+U[j,i]*one_minus_F;
  Vv[j,i] = dv*d2v+uv2+V[j,i]*one_minus_F_minus_k;
 }
}

On my machine this took on the order of 600ms to perform 100 iterations. Using jagged arrays instead, we have the following

for (int j = 0; j < H; j++)
{
 int jm = j != 0 ? j - 1 : H - 1;
 int jp = j != H - 1 ? j + 1 : 0;
 for (int i = 0; i < W; i++)
 {
  int im = i != 0 ? i - 1 : W - 1;
  int ip = i != W - 1 ? i + 1 : 0;
  double d2u = U[j][im]+U[j][ip]+U[jm][i]+U[jp][i]-4*U[j][i];
  double d2v = V[j][im]+V[j][ip]+V[jm][i]+V[jp][i]-4*V[j][i];
  double uv2 = U[j][i]*V[j][i]*V[j][i];
  Uu[j][i] = du*d2u-uv2+ F+U[j][i]*one_minus_F;
  Vv[j][i] = dv*d2v+uv2+V[j][i]*one_minus_F_minus_k;
 }
}

Just replacing the array type and making no other changes this now takes about 500ms, an improvement of about 13%. However, now that we are using jagged arrays we can do better and move the row accesses out of the inner loop.

for (int j = 0; j < H; j++)
{
 int jm = j != 0 ? j - 1 : H - 1;
 int jp = j != H - 1 ? j + 1 : 0;
 var u_m = U[jm];
 var u = U[j];
 var u_p = U[jp];
 var v_m = V[jm];
 var v = V[j];
 var v_p = V[jp];
 var uu = Uu[j];
 var vv = Vv[j];
 for (int i = 0; i < W; i++)
 {
  int im = i != 0 ? i - 1 : W - 1;
  int ip = i != W - 1 ? i + 1 : 0;
  double d2u = u[im]+u[ip]+u_m[i]+u_p[i]-4*u[i];
  double d2v = v[im]+v[ip]+v_m[i]+v_p[i]-4*v[i];
  double uv2 = u[i]*v[i] *v[i];
  uu[i] = du*d2u-uv2+F+u[i]*one_minus_F;
  vv[i] = dv*d2v+uv2+v[i]*one_minus_F_minus_k;
 }
}

This brings the count down to about 330ms, an improvement of about 40% over the original implementation.

We’ve taken the sequential code about as far as we can go. The next step is to try to take advantage of muliple cores that may be available on the user’s machine.

  • Robert

    An interesting SilverLight article without a sample online? Am I missing something?

  • http://www.planetmarshall.co.uk Andrew

    Hi Robert,

    The sample is in a separate post ( it is linked at the top of the page, sorry if it wasn’t obvious ). You can see it at

    http://www.planetmarshall.co.uk/index.php/2009/03/reaction-diffusion-models/

    I don’t yet have the full code online, as I want to upgrade it to SL3 and to try and get it working with Moonlight.

    Thanks,
    Andrew.