Skip to content

Commit

Permalink
feat: add math/MontecarloPi2 implementation (TheAlgorithms#448)
Browse files Browse the repository at this point in the history
* feat: Parallel MonteCarloPi using channels

* test: Parallel MonteCarloPi using channels

* doc: MonteCarloPi2 documentation

* doc: fix typo

* feat: rename MonteCarloPi2 to MonteCarloPiConcurrent

Co-authored-by: Taj <[email protected]>
  • Loading branch information
paul-leydier and tjgurwara99 authored Nov 27, 2021
1 parent 83832b6 commit c85f931
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 4 deletions.
74 changes: 70 additions & 4 deletions math/pi/montecarlopi.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
// montecarlopi.go
// description: Calculating pi by the Monte Carlo method
// details:
// implementation of Monte Carlo Algorithm for the calculating of Pi - [Monte Carlo method](https://en.wikipedia.org/wiki/Monte_Carlo_method)
// author(s) [red_byte](https://github.com/i-redbyte)
// implementations of Monte Carlo Algorithm for the calculating of Pi - [Monte Carlo method](https://en.wikipedia.org/wiki/Monte_Carlo_method)
// author(s): [red_byte](https://github.com/i-redbyte), [Paul Leydier] (https://github.com/paul-leydier)
// see montecarlopi_test.go

package pi

import (
"math/rand"
"time"
"fmt" // Used for error formatting
"math/rand" // Used for random number generation in Monte Carlo method
"runtime" // Used to get information on available CPUs
"time" // Used for seeding the random number generation
)

func MonteCarloPi(randomPoints int) float64 {
Expand All @@ -25,3 +27,67 @@ func MonteCarloPi(randomPoints int) float64 {
pi := float64(inside) / float64(randomPoints) * 4
return pi
}

// MonteCarloPiConcurrent approximates the value of pi using the Monte Carlo method.
// Unlike the MonteCarloPi function (first version), this implementation uses
// goroutines and channels to parallelize the computation.
// More details on the Monte Carlo method available at https://en.wikipedia.org/wiki/Monte_Carlo_method.
// More details on goroutines parallelization available at https://go.dev/doc/effective_go#parallel.
func MonteCarloPiConcurrent(n int) (float64, error) {
numCPU := runtime.GOMAXPROCS(0)
c := make(chan int, numCPU)
pointsToDraw, err := splitInt(n, numCPU) // split the task in sub-tasks of approximately equal sizes
if err != nil {
return 0, err
}

// launch numCPU parallel tasks
for _, p := range pointsToDraw {
go drawPoints(p, c)
}

// collect the tasks results
inside := 0
for i := 0; i < numCPU; i++ {
inside += <-c
}
return float64(inside) / float64(n) * 4, nil
}

// drawPoints draws n random two-dimensional points in the interval [0, 1), [0, 1) and sends through c
// the number of points which where within the circle of center 0 and radius 1 (unit circle)
func drawPoints(n int, c chan<- int) {
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
inside := 0
for i := 0; i < n; i++ {
x, y := rnd.Float64(), rnd.Float64()
if x*x+y*y <= 1 {
inside++
}
}
c <- inside
}

// splitInt takes an integer x and splits it within an integer slice of length n in the most uniform
// way possible.
// For example, splitInt(10, 3) will return []int{4, 3, 3}, nil
func splitInt(x int, n int) ([]int, error) {
if x < n {
return nil, fmt.Errorf("x must be < n - given values are x=%d, n=%d", x, n)
}
split := make([]int, n)
if x%n == 0 {
for i := 0; i < n; i++ {
split[i] = x / n
}
} else {
limit := x % n
for i := 0; i < limit; i++ {
split[i] = x/n + 1
}
for i := limit; i < n; i++ {
split[i] = x / n
}
}
return split, nil
}
56 changes: 56 additions & 0 deletions math/pi/montecarlopi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,59 @@ func BenchmarkMonteCarloPi(b *testing.B) {
MonteCarloPi(100000000)
}
}

func TestSplitInt(t *testing.T) {
testCases := []struct {
name string
x int
n int
expectedResult []int
expectedError bool
}{
{"multiple", 10, 5, []int{2, 2, 2, 2, 2}, false},
{"n=1", 10, 1, []int{10}, false},
{"x=10, n=3", 10, 3, []int{4, 3, 3}, false},
{"x=10, n=7", 10, 7, []int{2, 2, 2, 1, 1, 1, 1}, false},
{"n>x", 10, 11, nil, true},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
res, err := splitInt(testCase.x, testCase.n)
for i := 0; i < len(testCase.expectedResult); i++ {
if res[i] != testCase.expectedResult[i] {
t.Fatalf("unexpected result at index %d: %d - expected %d", i, res[i], testCase.expectedResult[i])
}
}
if testCase.expectedError {
if err == nil {
t.Fatal("expected an error, got nil")
}
} else {
if err != nil {
t.Fatalf("unexpected error - %s", err)
}
}
})
}
}

func TestMonteCarloPi2(t *testing.T) {
res, err := MonteCarloPiConcurrent(100000000)
if err != nil {
t.Errorf("unexpected error %s", err)
}
result := fmt.Sprintf("%.2f", res)
t.Log(result)
if result != "3.14" {
t.Errorf("Wrong result! Expected:%f, returned:%s ", 3.1415, result)
}
}

func BenchmarkMonteCarloPi2(b *testing.B) {
for i := 0; i < b.N; i++ {
_, err := MonteCarloPiConcurrent(100000000)
if err != nil {
b.Fatalf("unexpected error - %s", err)
}
}
}

0 comments on commit c85f931

Please sign in to comment.