Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create Tutorials “tutorial-for-dprng” #195

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions src/doc/tutorials/tutorial-for-dprng.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
layout: layouts/page.njk
title: Tutorial for DPRNG
author: Timothy Kaler
date: 2022-12-09T21:21:12.291Z
attribution: true
---
OpenCilk 2.0 is a programming framework that supports both pedigrees and deterministic pseudorandom number generation (DPRNG). To enable these features in a Cilk program, simply link the program with the `-lopencilk-pedigrees` library. No recompiling is necessary.

There are three options for using DPRNG with OpenCilk 2.0:

1. Use the OpenCilk runtime's built-in DPRNG. To generate a pseudorandom number, call the `__cilkrts_get_dprand()` function. This function returns a 64-bit unsigned integer, although the return value will be one of 2^64-59 possible results. The pseudorandom number generator can be reseeded at the beginning of the program by calling `__cilkrts_dprand_set_seed(seed)`, where `seed` is a 64-bit unsigned integer. The `__cilkrts_get_dprand()` function is easy to use and fast, but it offers only limited control over the DPRNG.
2. Use an existing DPRNG library that uses pedigrees. The only known such library is the DotMix implementation from CilkPub. CilkPub is implemented entirely in C++, so it may not be useful for programs written in other languages.
3. Write your own DPRNG library that uses pedigrees. The library can read the pedigree of the current strand by calling `__cilkrts_get_pedigree()`, which returns the pedigree as a singly-linked list. This option allows for flexible implementation of a new DPRNG in C or C++, but it may be more complicated than necessary.

Here is a usage example for implementing DPRNG with OpenCilk 2.0 using the built-in pseudorandom number generator:

```cilkcpp#
#include <stdio.h>
#include <stdint.h>
#include <cilk/cilk.h>
#include <cilk/cilk_api.h>
#include <math.h>

// Generate a pseudorandom number using the built-in DPRNG in OpenCilk 2.0
uint64_t generate_random_number() {
return __cilkrts_get_dprand();
}

// Reseed the built-in DPRNG in OpenCilk 2.0
void reseed_dprng(uint64_t seed) {
__cilkrts_dprand_set_seed(seed);
}

// Identity function for the reducer
void zero(void *v) {
*(int *)v = 0;
}

// Reduce function for the reducer
void plus(void *l, void *r) {
*(int *)l += *(int *)r;
}

int main() {
// Reseed the DPRNG with a value of 12345
reseed_dprng(12345);

// Set the number of iterations to use for the Monte Carlo simulation
int num_iterations = 1000000;

// Set the radius of the circle
double radius = 1.0;

// Create reducer objects for the counters for the number of points inside and outside the circle
int cilk_reducer(zero, plus) inside_circle = 0;
int cilk_reducer(zero, plus) outside_circle = 0;

// Run the Monte Carlo simulation in parallel
cilk_for(int i = 0; i < num_iterations; i++) {
// Generate two pseudorandom numbers for the x and y coordinates
double x = (double)generate_random_number() / UINT64_MAX;
double y = (double)generate_random_number() / UINT64_MAX;

// Compute the distance of the point from the origin
double dist = sqrt(x * x + y * y);

// Increment the counter for points inside or outside the circle based on the distance
if (dist <= radius) {
inside_circle++;
} else {
outside_circle++;
}
}

// Compute the ratio of points inside the circle to
// the total number of points
double ratio = (double)inside_circle / (double)(inside_circle + outside_circle);

// Use this ratio to compute an estimate of pi
double pi_estimate = 4.0 * ratio;

// Print the result
printf("Estimate of pi: %.10f\n", pi_estimate);

return 0;
}
```

The example above runs a Monte Carlo simulation to generate points in a two-dimensional plane. The points are generated using the `generate_random_number()` function, which uses the `__cilkrts_get_dprand()` function to generate pseudorandom numbers. The coordinates of the points are computed by scaling the pseudorandom numbers to the range $\[0, 1]$. The distance of each point from the origin is then computed, and the point is counted as inside or outside the circle with radius 1 based on its distance from the origin. After all points have been generated and counted, the ratio of points inside the circle to the total number of points is computed and used to estimate the value of $\pi$. The result is printed to the console.

This example uses OpenCilk 2.0's reducer syntax to avoid race conditions. The `inside_circle` and `outside_circle` counters are declared as reducer objects using the `cilk_reducer` syntax, and the `zero` and `plus` functions are specified as the identity and reduce functions, respectively. This ensures that the counters are updated atomically and that their values are consistent across all strands.