Blog

Pi day exercise: Calculating pi with expressions!

The Monte Carlo method is an effective technique for calculating probabilities.

Author's photo
Piyush Gupta
Director, Customer Enablement
March 14, 2024|4 min read

In this fun blog, whose concept was "engineered" by our very own Joshua Pollack, we demonstrate how to use expressions in Aerospike to calculate the value of Pi using a Monte Carlo technique.

While the example is fun and rather contrived, it highlights an underlying technique of using expressions in Aerospike to compute incoming data values for thresholds and apply conditional executions to real-time situations such as fraud detection.

What is Pi?

Pi (often represented as the Greek letter π) is “one of the most well-known mathematical constants [and] is the ratio of a circle’s circumference to its diameter. For any circle, the distance around the edge is a little more than three times the distance across. Typing π into a calculator and pressing ENTER will yield the result 3.141592654, not because this value is exact, but because a calculator’s display is often limited to 10 digits.”

Calculating the value of Pi

The Monte Carlo method to compute the value of Pi uses the probability of a random point whose x and y coordinates are between -1.0 and 1.0 falling within the enclosing square of side 2.0 versus within the circle at center (0.0, 0.0) with radius r = 1.0.

The probability of being in the circle versus the encompassing square is π*r2/(2r)2. This article describes this simple technique quite nicely.

The technique reduces to: Estimated value of Pi = 4 * number of points inside the circle / total number of points.

We can apply the ratio to just the top quadrant of the circle and rectangle.

We can use the Java random number generator to produce x and y coordinates of a point between 0.0 and 1.0.

We will do this computation in a single Aerospike record that we initialize as follows:

AerospikeClient client = new AerospikeClient("127.0.0.1", 3000);

//Initialize our single aggregation record, center of circle 

Key key1 = new Key("test", "points", 1); 
Bin xcoord = new Bin("x", 0.0);
Bin ycoord = new Bin("y", 0.0);
Bin inCount = new Bin("inCount", 1.0);
Bin totalCount = new Bin ("count", 1.0);
WritePolicy wPolicy = new WritePolicy();
client.put(wPolicy, key1, xcoord, ycoord, inCount, totalCount );
//Check
System.out.println(client.get(null, key1));

Output:

(gen:1),(exp:448491015),(bins:(x:0.0),(y:0.0),(inCount:1.0),(count:1.0))

As we insert the next random values of the (x,y) pair, we will use expressions to compute whether the value lies within the circle or outside and in the same write transaction, update the inCount and the total count.

We use another expression-based call to get the estimated value of Pi (expected to be 3.14xx).

//Insert a record using a class based construct
class Point {
    
  public void insertPoint (Key key1, double xval, double yval, boolean bCheck) {
    Bin xcoord = new Bin("x", xval);
    Bin ycoord = new Bin("y", yval);
    WritePolicy wPolicy1 = new WritePolicy();
      
    Expression inCircleExp = Exp.build(
       Exp.cond(
        Exp.le(
          Exp.add(
            Exp.mul(Exp.floatBin("x"), Exp.floatBin("x")), 
            Exp.mul(Exp.floatBin("y"), Exp.floatBin("y"))
          ),
          Exp.val(1.0)
        ),
       Exp.add(Exp.floatBin("inCount"), Exp.val(1.0)),
       Exp.floatBin("inCount")
       ));
        
    Expression totalCountExp = Exp.build(Exp.add(Exp.floatBin("count"), Exp.val(1.0)));
 
    Record record = client.operate( wPolicy1, key1,   
          Operation.put(xcoord),
          Operation.put(ycoord),                   
          ExpOperation.write("inCount", inCircleExp, ExpWriteFlags.DEFAULT),
          ExpOperation.write("count", totalCountExp, ExpWriteFlags.DEFAULT)
                                  );                 
    if(bCheck){
      System.out.println(client.get(null, key1));
    }   
  }
    
  public void getPi(Key key1){
      WritePolicy wPolicy = new WritePolicy();
      Expression piExp = Exp.build(
          Exp.mul(Exp.val(4.0), 
                  Exp.div(Exp.floatBin("inCount"),Exp.floatBin("count"))
                 ));
      
      Record record = client.operate(wPolicy, key1, ExpOperation.read("pi", piExp, ExpReadFlags.DEFAULT));
      System.out.println("Pi : " + record.getValue("pi"));
  }
  
}

Let's try this code construct:

//Check
Point p = new Point();
p.insertPoint(key1, 0.71, 0.71, true);  //Out of circle, in rectangle

Output:

(gen:2),(exp:448490510),(bins:(x:0.71),(y:0.71),(inCount:1.0),(count:2.0))

Let's scale it up and insert 10,000 random points. This takes an imperceptible amount of time on Aerospike.

//Insert random points with x,y between 0.0 and 1.0

Random rnd1 = new Random(0);  

for(int i=1;i<10000;i++){
  double xval = rnd1.nextDouble();
  double yval = rnd1.nextDouble();
  p.insertPoint(key1, xval, yval, false);  
}

And now for our estimate of Pi:

p.getPi(key1);

Output:

Pi : 3.1424

Happy Pi Day!

In the above example, getPi() is shown here as a separate call. However, it can be made part of the return value in the write api (insertPoint()).

Instead of our pair of x and y, if you had 100s of such parameters that you would want to compute a net score to make a go/nogo decision, the above code constructs can be easily extended. In this example, we had no need to retain the 10,000 random values. However, if the data needs to be retained, consider using one of our collection data types, such as a Map or a List.

Get the most out of your data with Aerospike Expressions

To learn more, watch our Optimizing query performance with Aerospike Expressions webinar with Chief Developer Advocate Tim Faulkes.