Skip to content

Commit cba4e97

Browse files
authored
[research] Add a new problem of nbody simulation (#76)
1 parent b354f61 commit cba4e97

File tree

21 files changed

+1447
-0
lines changed

21 files changed

+1447
-0
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Baseline for N-body simulation - simple OpenMP parallel brute-force
2+
// O(N²) approach with parallel outer loop
3+
// Solutions should aim to beat this baseline
4+
5+
#include "world.h"
6+
#include <omp.h>
7+
8+
class BaselineSimulator : public Simulator {
9+
private:
10+
int numThreads = 16;
11+
12+
public:
13+
void init(int numParticles, StepParameters params) override {
14+
omp_set_num_threads(numThreads);
15+
}
16+
17+
void simulateStep(std::vector<Particle> &particles,
18+
std::vector<Particle> &newParticles,
19+
StepParameters params) override {
20+
#pragma omp parallel for schedule(dynamic, 16)
21+
for (int i = 0; i < (int)particles.size(); i++) {
22+
auto pi = particles[i];
23+
Vec2 force = Vec2(0.0f, 0.0f);
24+
25+
for (size_t j = 0; j < particles.size(); j++) {
26+
if (j == (size_t)i) continue;
27+
if ((pi.position - particles[j].position).length() < params.cullRadius) {
28+
force += computeForce(pi, particles[j], params.cullRadius);
29+
}
30+
}
31+
32+
newParticles[i] = updateParticle(pi, force, params.deltaTime);
33+
}
34+
}
35+
};
36+
37+
Simulator* createBaselineSimulator() {
38+
return new BaselineSimulator();
39+
}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
// Benchmark harness for N-body simulation
2+
// This file includes the submitted solution and baseline, then runs benchmarks
3+
4+
#include "world.h"
5+
#include "timing.h"
6+
#include <iostream>
7+
#include <fstream>
8+
#include <iomanip>
9+
#include <cstring>
10+
#include <cmath>
11+
#include <algorithm>
12+
#include <omp.h>
13+
14+
// Forward declaration for baseline factory
15+
Simulator* createBaselineSimulator();
16+
17+
struct BenchmarkOptions {
18+
int numParticles = 10000;
19+
int numIterations = 5;
20+
float spaceSize = 100.0f;
21+
int numRuns = 1; // Temporarily set to 1 for optimization testing
22+
bool checkCorrectness = true;
23+
std::string outputFile = "result.txt";
24+
};
25+
26+
BenchmarkOptions parseOptions(int argc, char** argv) {
27+
BenchmarkOptions opts;
28+
for (int i = 1; i < argc; i++) {
29+
if (strcmp(argv[i], "-n") == 0 && i + 1 < argc) {
30+
opts.numParticles = atoi(argv[++i]);
31+
} else if (strcmp(argv[i], "-i") == 0 && i + 1 < argc) {
32+
opts.numIterations = atoi(argv[++i]);
33+
} else if (strcmp(argv[i], "-s") == 0 && i + 1 < argc) {
34+
opts.spaceSize = (float)atof(argv[++i]);
35+
} else if (strcmp(argv[i], "-r") == 0 && i + 1 < argc) {
36+
opts.numRuns = atoi(argv[++i]);
37+
} else if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) {
38+
opts.outputFile = argv[++i];
39+
} else if (strcmp(argv[i], "--no-check") == 0) {
40+
opts.checkCorrectness = false;
41+
}
42+
}
43+
return opts;
44+
}
45+
46+
bool checkForCorrectness(const World& refW, const World& w, float tolerance = 1e-2f) {
47+
if (w.particles.size() != refW.particles.size()) {
48+
std::cerr << "Mismatch: number of particles " << w.particles.size()
49+
<< " does not match reference " << refW.particles.size() << std::endl;
50+
return false;
51+
}
52+
53+
for (size_t i = 0; i < w.particles.size(); i++) {
54+
auto errorX = std::abs(w.particles[i].position.x - refW.particles[i].position.x);
55+
auto errorY = std::abs(w.particles[i].position.y - refW.particles[i].position.y);
56+
if (errorX > tolerance || errorY > tolerance) {
57+
std::cerr << "Mismatch at index " << i
58+
<< ": result (" << w.particles[i].position.x << ", "
59+
<< w.particles[i].position.y << ")"
60+
<< " should be (" << refW.particles[i].position.x << ", "
61+
<< refW.particles[i].position.y << ")" << std::endl;
62+
return false;
63+
}
64+
}
65+
return true;
66+
}
67+
68+
double runSimulation(World& world, Simulator* sim,
69+
StepParameters params, int numIterations) {
70+
Timer timer;
71+
timer.reset();
72+
73+
// Initialize simulator at the start of each run (clean state)
74+
sim->init(world.particles.size(), params);
75+
76+
for (int iter = 0; iter < numIterations; iter++) {
77+
world.newParticles.resize(world.particles.size());
78+
sim->simulateStep(world.particles, world.newParticles, params);
79+
world.particles.swap(world.newParticles);
80+
}
81+
82+
return timer.elapsed();
83+
}
84+
85+
int main(int argc, char** argv) {
86+
BenchmarkOptions opts = parseOptions(argc, argv);
87+
88+
// Note: Solutions can set their own thread count via omp_set_num_threads()
89+
std::cout << "Max OpenMP threads available: " << omp_get_max_threads() << std::endl;
90+
91+
StepParameters params;
92+
params.cullRadius = opts.spaceSize / 4.0f;
93+
params.deltaTime = 0.2f;
94+
95+
std::cout << "N-Body Simulation Benchmark" << std::endl;
96+
std::cout << "Particles: " << opts.numParticles << std::endl;
97+
std::cout << "Iterations: " << opts.numIterations << std::endl;
98+
std::cout << "Space size: " << opts.spaceSize << std::endl;
99+
std::cout << "Cull radius: " << params.cullRadius << std::endl;
100+
std::cout << std::endl;
101+
102+
// Create simulators
103+
Simulator* baselineSim = createBaselineSimulator();
104+
Simulator* solutionSim = createSimulator();
105+
106+
// Benchmark sequential baseline
107+
std::vector<double> seqTimes;
108+
for (int run = 0; run < opts.numRuns; run++) {
109+
World seqWorld;
110+
seqWorld.generateRandom(opts.numParticles, opts.spaceSize);
111+
double elapsed = runSimulation(seqWorld, baselineSim, params, opts.numIterations);
112+
seqTimes.push_back(elapsed);
113+
std::cout << "Sequential run " << (run + 1) << ": " << std::fixed
114+
<< std::setprecision(4) << elapsed << "s" << std::endl;
115+
}
116+
117+
// Sort and get median
118+
std::sort(seqTimes.begin(), seqTimes.end());
119+
double seqMedian = seqTimes[opts.numRuns / 2];
120+
std::cout << "Sequential median: " << std::fixed << std::setprecision(4)
121+
<< seqMedian << "s" << std::endl << std::endl;
122+
123+
// Benchmark submitted solution
124+
std::vector<double> parTimes;
125+
World finalWorld;
126+
for (int run = 0; run < opts.numRuns; run++) {
127+
World parWorld;
128+
parWorld.generateRandom(opts.numParticles, opts.spaceSize);
129+
double elapsed = runSimulation(parWorld, solutionSim, params, opts.numIterations);
130+
parTimes.push_back(elapsed);
131+
std::cout << "Solution run " << (run + 1) << ": " << std::fixed
132+
<< std::setprecision(4) << elapsed << "s" << std::endl;
133+
if (run == opts.numRuns - 1) {
134+
finalWorld = parWorld;
135+
}
136+
}
137+
138+
std::sort(parTimes.begin(), parTimes.end());
139+
double parMedian = parTimes[opts.numRuns / 2];
140+
std::cout << "Solution median: " << std::fixed << std::setprecision(4)
141+
<< parMedian << "s" << std::endl << std::endl;
142+
143+
// Check correctness
144+
bool correct = true;
145+
if (opts.checkCorrectness) {
146+
std::cout << "Checking correctness..." << std::endl;
147+
World refWorld;
148+
refWorld.generateRandom(opts.numParticles, opts.spaceSize);
149+
runSimulation(refWorld, baselineSim, params, opts.numIterations);
150+
151+
World testWorld;
152+
testWorld.generateRandom(opts.numParticles, opts.spaceSize);
153+
runSimulation(testWorld, solutionSim, params, opts.numIterations);
154+
155+
correct = checkForCorrectness(refWorld, testWorld);
156+
if (correct) {
157+
std::cout << "Correctness check: PASSED" << std::endl;
158+
} else {
159+
std::cout << "Correctness check: FAILED" << std::endl;
160+
}
161+
}
162+
163+
// Calculate speedup
164+
double speedup = seqMedian / parMedian;
165+
std::cout << std::endl;
166+
std::cout << "Speedup: " << std::fixed << std::setprecision(3) << speedup << "x" << std::endl;
167+
168+
// Write results
169+
std::ofstream outFile(opts.outputFile);
170+
if (outFile) {
171+
outFile << "sequential_time=" << std::fixed << std::setprecision(6) << seqMedian << std::endl;
172+
outFile << "parallel_time=" << std::fixed << std::setprecision(6) << parMedian << std::endl;
173+
outFile << "speedup=" << std::fixed << std::setprecision(6) << speedup << std::endl;
174+
outFile << "correct=" << (correct ? "true" : "false") << std::endl;
175+
outFile.close();
176+
}
177+
178+
// Cleanup
179+
delete baselineSim;
180+
delete solutionSim;
181+
182+
return correct ? 0 : 1;
183+
}

0 commit comments

Comments
 (0)