Search code examples
concurrencyerlangspawnmontecarlopi

Erlang Concurrent Montecarlo Pi Estimation


For an assignment, I am required to estimate pi in Erlang using the Montecarlo method, but with a specified number of actors and iterations. I have a working version (adapted from https://programmingpraxis.com/2009/10/09/calculating-pi/) that does not use concurrency and thus takes one argument, N = number of iterations (points). I am trying to add to it by creating another montecarlo() function that takes two arguments, N = # of iterations and X = # of actors. I am having trouble figuring out how to use a (pseudo)loop to spawn each actor.

The previous version returns the pi estimation, but after I figure out the spawning, I assume I would have to average the return values from each actor for the final pi estimation.

Here is what I have:

-module(pi).
-export([montecarlo/1, montecarlo/2]).

montecarlo(N, X)->
    NumIterPerActor = N div X,
    %io:fwrite("Number of actors = ~w~n",[X]),
    %io:fwrite("Number of iterations per actor = ~w~n",[NumIterPerActor]),
    lists:seq(1, X),
    spawn(pi, montecarlo, [NumIterPerActor]).

montecarlo(N)->
    montecarlo(N,0,0).

montecarlo(0,InCircle,NumPoints)->
    PiEst = 4*InCircle / NumPoints,
    io:fwrite("Pi = ~w~n", [PiEst]);

montecarlo(N,InCircle,NumPoints)->
    Xcoord = rand:uniform(),
    Ycoord = rand:uniform(),
    montecarlo(N-1,if Xcoord*Xcoord + Ycoord*Ycoord < 1 -> InCircle + 1; true -> InCircle end,NumPoints + 1).

From researching the problem, I have seen using map(), but to my understanding, you are creating a new function inside of map(), rather than using ones already implemented.

EDIT:

I didn't receive any suggestions before the deadline (my fault for waiting so long), so I sought help from a classmate and came up with a recursive solution:

-module(pi).
-export([montecarlo/1, montecarlo/2, assign/3, compute/2, addPi/3]).

montecarlo(N, X)->
    NumIterPerActor = N div X,  %number of iterations for each actor
    io:fwrite("Number of actors = ~w~n",[X]),
    io:fwrite("Number of iterations per actor = ~w~n",[NumIterPerActor]),
    %Actors = lists:seq(1, X),   %generate desired number of actors
    %Pi1 = spawn(pi, montecarlo, [NumIterPerActor]),
    %Pi2 = spawn(pi, montecarlo, [NumIterPerActor]).
    ReceiverID = spawn(pi, addPi, [0, X, X]),
    assign(ReceiverID, X, NumIterPerActor).

assign(ReceiverID, 1, Iter)->
    spawn(pi, compute, [Iter, ReceiverID]);

assign(ReceiverID, X, Iter)->
    spawn(pi, compute, [Iter, ReceiverID]),
    assign(ReceiverID, X-1, Iter).

compute(Iter, ReceiverID)->
    Est = montecarlo(Iter),
    ReceiverID ! {Est}.

addPi(Pies, X, 0)->
    FinalPi = Pies/X,
    io:fwrite("Pi = ~w~n", [FinalPi]);

addPi(Pies, X, Rem)->
    receive
        {Estimate} ->
            addPi(Pies+Estimate, X, Rem-1)  
    end.

montecarlo(N)->
    montecarlo(N,0,0).

montecarlo(0,InCircle,NumPoints)->
    4*InCircle / NumPoints;
    %io:fwrite("Pi = ~w~n", [PiEst]);

montecarlo(N,InCircle,NumPoints)->
    Xcoord = rand:uniform(),
    Ycoord = rand:uniform(),
    montecarlo(N-1,if Xcoord*Xcoord + Ycoord*Ycoord < 1 -> InCircle + 1; true -> InCircle end,NumPoints + 1).

Solution

  • The lists:foreach/2 seems like a good fit when you need to do something iteratively with side effects such as spawning a process.

    You could map/2 the NumIterPerActor into a IterList list and use foreach/2 to spawn the processes iteratively over it.

    montecarlo(N, X) ->
        NumIterPerActor = N div X,
        IterList = lists:map(fun(_) -> NumIterPerActor end, lists:seq(1, X)),
        lists:foreach(fun(IPA) -> spawn(?MODULE, montecarlo, [IPA]) end, IterList).
    

    To answer your last question, note that inside map/2 or foreach/2 you could wrap any function call inside a lambda function and pass the relevant arguments to it.