This page was last updated: April 15, 2025
Monte Carlo simulation is used to determine the range of outcomes for a series of parameters, each of which has a probability distribution showing how likely each option is to happen. In this project, you will take a scenario and develop a Monte Carlo simulation of it, determining how likely a particular output is to happen.
Clearly, this is very parallelizable -- it is the same computation being run on many permutations of the input parameters. You will run this with OpenMP, testing it on different numbers of threads (at least 1, 2, 4, 6, and 8).
A castle sits on top of a cliff. An amateur band of merceneries is attempting to destroy it.
Normally this would be a pretty straightforward geometric calculation, but these are amateurs. What makes them such amateurs you ask? It's because they are not very good at estimating distances, and not very good at aiming their cannon. They can only determine the 5 input parameters within certain ranges.
Your job is to figure out the probability that these doofuses will actually hit the castle. This is a job for multicore Monte Carlo simulation!
Variable | Meaning | Range |
---|---|---|
g | Ground distance to the cliff face | 10. - 20. |
h | Height of the cliff face | 20. - 30. |
d | Upper deck distance to the castle | 10. - 20. |
v | Cannonball initial velocity | 20. - 30. |
θ | Cannon firing angle in degrees | 70. - 80. |
TOL is how close the cannonball needs to come to the castle to demolish it.
GRAVITY is the acceleration due to gravity. Be sure the minus sign stays there.
−B + √ B2 − 4AC |
2A |
−B - √ B2 − 4AC |
2A |
#include <stdio.h> #define _USE_MATH_DEFINES #include <math.h> #include <stdlib.h> #include <time.h> #include <omp.h> #ifndef F_PI #define F_PI (float)M_PI #endif // print debugging messages? #ifndef DEBUG #define DEBUG false #endif // setting the number of threads to use: // (this a default value -- it can also be set from the outside by your script) #ifndef NUMT #define NUMT 2 #endif // setting the number of trials in the monte carlo simulation: // (this a default value -- it can also be set from the outside by your script) #ifndef NUMTRIALS #define NUMTRIALS 50000 #endif // how many tries to discover the maximum performance: #ifndef NUMTRIES #define NUMTRIES 30 #endif // ranges for the random numbers: const float GMIN = 10.0; // ground distance in meters const float GMAX = 20.0; // ground distance in meters const float HMIN = 20.0; // cliff height in meters const float HMAX = 30.0; // cliff height in meters const float DMIN = 10.0; // distance to castle in meters const float DMAX = 20.0; // distance to castle in meters const float VMIN = 20.0; // intial cnnonball velocity in meters / sec const float VMAX = 30.0; // intial cnnonball velocity in meters / sec const float THMIN = 70.0; // cannonball launch angle in degrees const float THMAX = 80.0; // cannonball launch angle in degrees const float GRAVITY = -9.8; // acceleraion due to gravity in meters / sec^2 const float TOL = 5.0; // tolerance in cannonball hitting the castle in meters // castle is destroyed if cannonball lands between d-TOL and d+TOL // function prototypes: float Ranf( float, float ); int Ranf( int, int ); void TimeOfDaySeed( ); // degrees-to-radians: inline float Radians( float degrees ) { return (F_PI/180.f) * degrees; } // main program: int main( int argc, char *argv[ ] ) { #ifndef _OPENMP fprintf( stderr, "No OpenMP support!\n" ); return 1; #endif TimeOfDaySeed( ); // seed the random number generator omp_set_num_threads( NUMT ); // set the number of threads to use in parallelizing the for-loop:` // better to define these here so that the rand() calls don't get into the thread timing: float *vs = new float [NUMTRIALS]; float *ths = new float [NUMTRIALS]; float * gs = new float [NUMTRIALS]; float * hs = new float [NUMTRIALS]; float * ds = new float [NUMTRIALS]; // fill the random-value arrays: for( int n = 0; n < NUMTRIALS; n++ ) { vs[n] = Ranf( VMIN, VMAX ); ths[n] = Ranf( THMIN, THMAX ); gs[n] = Ranf( GMIN, GMAX ); hs[n] = Ranf( HMIN, HMAX ); ds[n] = Ranf( DMIN, DMAX ); } // get ready to record the maximum performance and the probability: double maxPerformance = 0.; // must be declared outside the NUMTRIES loop int numHits; // must be declared outside the NUMTRIES loop // looking for the maximum performance: for( int tries = 0; tries < NUMTRIES; tries++ ) { double time0 = omp_get_wtime( ); numHits = 0; #pragma omp parallel for ????? for( int n = 0; n < NUMTRIALS; n++ ) { // randomize everything: float v = vs[n]; float thr = Radians( ths[n] ); float vx = v * cos(thr); float vy = v * sin(thr); float g = gs[n]; float h = hs[n]; float d = ds[n]; // see if the ball doesn't even reach the cliff:` float t = ????? float x = ????? if( x <= g ) { if( DEBUG ) fprintf( stderr, "Ball doesn't even reach the cliff\n" ); } else { // see if the ball hits the vertical cliff face: t = ????? float y = ????? if( y <= h ) { if( DEBUG ) fprintf( stderr, "Ball hits the cliff face\n" ); } else { // the ball hits the upper deck: // the time solution for this is a quadratic equation of the form: // At^2 + Bt + C = 0. // where 'A' multiplies time^2 // 'B' multiplies time // 'C' is a constant float A = ????? float B = ????? float C = ????? float disc = B*B - 4.f*A*C; // quadratic formula discriminant // ball doesn't go as high as the upper deck: // this should "never happen" ... :-) if( disc < 0. ) { if( DEBUG ) fprintf( stderr, "Ball doesn't reach the upper deck.\n" ); exit( 1 ); // something is wrong... } // successfully hits the ground above the cliff: // get the intersection: float sqrtdisc = sqrtf( disc ); float t1 = (-B + sqrtdisc ) / ( 2.f*A ); // time to intersect high ground float t2 = (-B - sqrtdisc ) / ( 2.f*A ); // time to intersect high ground // only care about the second intersection float tmax = t1; if( t2 > t1 ) tmax = t2; // how far does the ball land horizontlly from the edge of the cliff? float upperDist = vx * tmax - g; // see if the ball hits the castle: if( fabs( upperDist - d ) ≤ TOL ) { if( DEBUG ) fprintf( stderr, "Hits the castle at upperDist = %8.3f\n", upperDist ); ????? } else { if( DEBUG ) fprintf( stderr, "Misses the castle at upperDist = %8.3f\n", upperDist ); } } // if ball clears the cliff face } // if ball gets as far as the cliff face } // for( # of monte carlo trials ) double time1 = omp_get_wtime( ); double megaTrialsPerSecond = (double)NUMTRIALS / ( time1 - time0 ) / 1000000.; if( megaTrialsPerSecond > maxPerformance ) maxPerformance = megaTrialsPerSecond; } // for ( # of timing tries ) float probability = (float)numHits/(float)( NUMTRIALS ); // just get for the last run // uncomment this if you want to print output to a ready-to-use CSV file: // #define CSV #ifdef CSV fprintf(stderr, "%2d , %8d , %6.2lf\n", NUMT, NUMTRIALS, maxPerformance); #else fprintf(stderr, "%2d threads : %8d trials ; probability = %6.2f%% ; megatrials/sec = %6.2lf\n", NUMT, NUMTRIALS, 100.*probability, maxPerformance); #endif return 0; }
For your own information, print out: (1) the number of threads, (2) the number of trials, (3) the probability of destroying the castle, and (4) the MegaTrialsPerSecond.
For creating a ready-to-use CSV file, print out:
(1) the number of threads,
(2) the number of trials,
and (3) the MegaTrialsPerSecond.
Printing this as a single line with commas between the numbers lets you
import these lines right into Excel.
To choose a random number between two floats or two ints, use:
#include <stdlib.h> float Ranf( float low, float high ) { float r = (float) rand(); // 0 - RAND_MAX float t = r / (float) RAND_MAX; // 0. - 1. return low + t * ( high - low ); } int Ranf( int ilow, int ihigh ) { float low = (float)ilow; float high = ceil( (float)ihigh ); return (int) Ranf(low,high); } // call this if you want to force your program to use // a different random number sequence every time you run it: void TimeOfDaySeed( ) { time_t now; time( &now ); struct tm n; struct tm jan01; #ifdef WIN32 localtime_s( &now, &n ); localtime_s( &now, &jan01 ); #else n = *localtime(&now); jan01 = *localtime(&now); #endif jan01.tm_mon = 0; jan01.tm_mday = 1; jan01.tm_hour = 0; jan01.tm_min = 0; jan01.tm_sec = 0; double seconds = difftime( now, mktime(&jan01) ); unsigned int seed = (unsigned int)( 1000.*seconds ); // milliseconds srand( seed ); }
Turn in your PDF file and your cpp file on Canvas. Go to the Canvas Week #1 or Week #2 modules, scroll down to the Projects, go to the Project #1 row and click on Submit. When you get the Project #1 Assignment page, click on the Start Assignment black button in the upper-right corner. Upload your files.
Feature | Points |
---|---|
Provide a close estimate of the actual probability | 10 |
Good graph of performance vs. number of trials | 25 |
Good graph of performance vs. number of threads | 25 |
Compute Fp, the Parallel Fraction (show your work) | 20 |
Compute Smax, the Maximum Speedup (show your work) | 20 |
Potential Total | 100 |