Main menu

Simulating a Geiger Counter

The Geiger counter (or Geiger-Müller counter) from The Clustering Illusion is actually quite interesting in itself. I’ve copied it below again. You can start it by clicking the button. It produces an audible “tick” for each simulated decay event, with a decay rate of two events per second.

Although it is quite short, lots of things are happening in the tick function. Read on to learn more.

How Does It Work?

For starters, the script does not need an external audio file. Because the audio fragment (the “tick” sound) is very short (1 ms), I’ve just put it in a Base64 string. This means that the browser doesn’t have to fetch a separate audio file from the server. The string that is passed to the Audio constructor actually contains a complete .wav file.

It is well known that the number of radioactive decay events over a given time period is Poisson distributed. However, the desirable way to implement this in JavaScript is to use the setTimeout function, which means that the time between each event is needed, and that follows an exponential distribution.

Of course, JavaScript wasn’t designed with numerical processing in mind, and doesn’t include a function for drawing random numbers from an exponential distribution. Luckily, working around this is quite easy for the exponential distribution. The following formula (which uses inverse transform sampling) translates a number that is uniformly distributed on the interval \((0,\,1)\) to one that is exponentially distributed on the interval \((0,\,+\infty)\).

\[T=\frac{-\ln(U)}{\lambda}\]

In this formula, \(\lambda\) is the rate parameter, which specifies the number of events per second, the same parameter as in the original Poisson distribution. For the simulated Geiger counter at the top of the page, we have \(\lambda=2\). The formula translates into the statement var next = -Math.log(1 - Math.random()) / lambda;.

The \(\ln\) function is called log in JavaScript. Also note that I’ve used 1 - Math.random() instead of simply Math.random(). This is because Math.random() returns a number in the range \([0,\,1)\), and feeding \(0\) to log makes it blow up. The chances of \(0\) being generated by Math.random() are very low, but avoiding these kinds of errors anyway is known in software engineering as a good thing.

I’ll repeat the warning from The Clustering Illusion here: I am aware that a browser with JavaScript is not ideal for this kind of time-related work, but it is good enough for this demonstration. It does not work very well anymore if you switch to another tab, since the JavaScript in a background tab is given lower priority.