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).
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.