hpc-lab-code/lab3/nbody/nbody_par.cpp
2026-01-21 18:30:58 +08:00

302 lines
9.8 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <cmath>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <vector>
#include <mpi.h>
#include <assert.h>
using namespace std;
// 常量定义
const double G = 6.67430e-11; // 引力常数 (m^3 kg^-1 s^-2)
const double DT = 0.01; // 时间步长
const int TMAX = 100; // 总时间步数
const double mass_scale = 1e24; // 质量缩放因子
const double dist_scale = 1e8; // 距离缩放因子
const double vel_scale = 1e3; // 速度缩放因子
// 三维向量结构体
struct Vec3 {
double x, y, z;
Vec3() : x(0), y(0), z(0) {}
Vec3(double x, double y, double z) : x(x), y(y), z(z) {}
Vec3 operator+(const Vec3 &other) const {
return Vec3(x + other.x, y + other.y, z + other.z);
}
Vec3 operator-(const Vec3 &other) const {
return Vec3(x - other.x, y - other.y, z - other.z);
}
Vec3 operator*(double scalar) const {
return Vec3(x * scalar, y * scalar, z * scalar);
}
Vec3 operator/(double scalar) const {
return Vec3(x / scalar, y / scalar, z / scalar);
}
double magnitude() const { return sqrt(x * x + y * y + z * z); }
};
// 天体结构体
struct Body {
double mass; // 质量
Vec3 position; // 位置
Vec3 velocity; // 速度
Vec3 force; // 受力
};
// 初始化天体系统
void init_bodies(vector<Body> &bodies, int n, bool verbose=false) {
// 中心天体(类似太阳)
bodies[0].mass = 1000 * mass_scale;
bodies[0].position = Vec3(0, 0, 0);
bodies[0].velocity = Vec3(0, 0, 0);
// 其他天体(类似行星)
for (int i = 1; i < n; i++) {
bodies[i].mass = (1.0 + i * 0.5) * mass_scale;
double angle = 2.0 * M_PI * i / n;
double radius = (1.0 + i * 0.5) * dist_scale;
bodies[i].position = Vec3(radius * cos(angle), radius * sin(angle), 0.0);
// 给予切向速度以形成轨道
double orbital_speed = sqrt(G * bodies[0].mass / radius);
bodies[i].velocity =
Vec3(-orbital_speed * sin(angle), orbital_speed * cos(angle), 0.0);
}
// 输出初始状态
if(verbose){
cout << fixed << setprecision(6);
cout << "\n初始状态:" << endl;
for (int i = 0; i < n; i++) {
cout << "天体 " << i << ": 质量=" << bodies[i].mass / mass_scale
<< "e24 kg, "
<< "位置=(" << bodies[i].position.x / dist_scale << ", "
<< bodies[i].position.y / dist_scale << ", "
<< bodies[i].position.z / dist_scale << ")e8 m" << endl;
}
}
}
// 计算local_particles中每个物体受到all_particles中所有物体的作用力
// 并更新local_particles中物体的速度和位置
void compute_local_forces(vector<Body>& local_particles,
const vector<Body>& all_particles,
int local_start) {
for (size_t i = 0; i < local_particles.size(); i++) {
Vec3 total_force(0, 0, 0);
int global_idx = local_start + i;
// 计算all_particles中所有物体对local_particles[i]的作用力
for (size_t j = 0; j < all_particles.size(); j++) {
// 跳过自己
if (global_idx == static_cast<int>(j)) continue;
// 计算从物体i指向物体j的向量
Vec3 r_vec = all_particles[j].position - local_particles[i].position;
double distance = r_vec.magnitude();
// 避免除以零
if (distance < 1e-10) continue;
// 计算引力大小
double force_magnitude = G * local_particles[i].mass * all_particles[j].mass
/ (distance * distance);
// 计算力的方向并累加
Vec3 force_direction = r_vec / distance;
total_force = total_force + force_direction * force_magnitude;
}
// 更新local_particles[i]的速度和位置
Vec3 v_new = local_particles[i].velocity + total_force * DT / local_particles[i].mass;
Vec3 x_new = local_particles[i].position + v_new * DT;
local_particles[i].velocity = v_new;
local_particles[i].position = x_new;
}
}
void get_rank_info(int rank_id,
int bodies_count, // total number of bodies
int world_size, // total number of processes
int& send_size, // number of bodies to be sent from `rank_id` process
int& send_offset // offset of bodies to be sent from `rank_id` process
) {
int particles_per_proc = bodies_count / world_size;
int remainder = bodies_count % world_size;
if (rank_id < remainder) {
send_size = particles_per_proc + 1;
send_offset = rank_id * (particles_per_proc + 1);
} else {
send_size = particles_per_proc;
send_offset = rank_id * particles_per_proc + remainder;
}
// for np = 2 and bodies_count = 5
// rank_id=0: send_size=3, send_offset=0
// rank_id=1: send_size=2, send_offset=3
}
int main(int argc, char **argv) {
MPI_Init(&argc, &argv);
// 获取进程数量和当前进程rank
int world_size, world_rank;
bool verbose=false;
MPI_Comm_size(MPI_COMM_WORLD, &world_size);
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
// 从命令行参数获取天体数量
int n = 4; // 默认4个天体
if (argc > 1) {
n = atoi(argv[1]);
}
if (argc > 2) {
verbose = (strcmp(argv[2], "--verbose") == 0 || strcmp(argv[2], "-v") == 0);
}
// 只有rank 0打印初始信息
if (world_rank == 0) {
cout << "N体问题并行模拟" << endl;
cout << "天体数量: " << n << endl;
cout << "进程数量: " << world_size << endl;
cout << "时间步长: " << DT << " s" << endl;
cout << "总步数: " << TMAX << endl;
cout << "----------------------------------------" << endl;
}
// 定义Body的MPI数据类型
// Body结构包含: mass(1) + position(3) + velocity(3) + force(3) = 10个double
MPI_Datatype MPI_BODY;
MPI_Type_contiguous(10, MPI_DOUBLE, &MPI_BODY);
MPI_Type_commit(&MPI_BODY);
// ============================================
// 步骤1: 获取分配给本进程的物体的初始信息local_particles
// 步骤2: 获取应用程序中所有物体的信息all_particles
// ============================================
vector<Body> all_particles(n);
vector<Body> local_particles;
// 计算每个进程分配到的物体数量
int particles_per_proc = n / world_size;
int remainder = n % world_size;
int local_start, local_count;
if (world_rank < remainder) {
local_count = particles_per_proc + 1;
local_start = world_rank * local_count;
} else {
local_count = particles_per_proc;
local_start = world_rank * particles_per_proc + remainder;
}
// Rank 0初始化所有物体
if (world_rank == 0) {
init_bodies(all_particles, n, verbose);
}
// 广播所有物体的初始信息到所有进程
MPI_Bcast(all_particles.data(), n, MPI_BODY, 0, MPI_COMM_WORLD);
// 每个进程提取自己负责的物体
local_particles.resize(local_count);
for (int i = 0; i < local_count; i++) {
local_particles[i] = all_particles[local_start + i];
}
if (world_rank == 0) {
cout << "\n开始模拟..." << endl;
}
// 创建发送和接收缓冲区信息
vector<int> all_send_size(world_size);
vector<int> all_send_offset(world_size);
for (int r = 0; r < world_size; r++) {
get_rank_info(r, n, world_size, all_send_size[r], all_send_offset[r]);
#ifdef DEBUG
if (world_rank == 0) { // 只让rank 0打印
cout << "Process " << r << " will send "
<< all_send_size[r] << " bodies starting from offset "
<< all_send_offset[r] << endl;
}
#endif
}
double start_time = MPI_Wtime();
vector<Body> send_buf(local_count); // 使用local_count确定大小
#ifdef DEBUG
if (verbose || world_rank == 0) {
cout << fixed << setprecision(6);
cout << "\n进程 " << world_rank << " 负责天体 " << local_start
<< "" << (local_start + local_count - 1) << endl;
}
#endif
// ============================================
// 主循环N体模拟
// ============================================
for (int t = 0; t < TMAX; t++) {
// ------------------------------------------
// 计算所有物体对分配给本进程的物体的作用力
// 并据此更新local_particles的本进程的物体信息
// ------------------------------------------
compute_local_forces(local_particles, all_particles, local_start);
// ------------------------------------------
// 将本进程信息local_particles保存到发送缓冲区send_buf
// 同时更新all_particles中的部分信息
// ------------------------------------------
send_buf = local_particles;
// 更新all_particles中本进程负责的部分信息
for (int i = 0; i < local_count; i++) {
all_particles[local_start + i] = local_particles[i];
}
// ------------------------------------------
// 环形通信对每个进程进行m-1次通信
// ------------------------------------------
MPI_Allgatherv(send_buf.data(), local_count,
MPI_BODY, all_particles.data(),
all_send_size.data(), all_send_offset.data(),
MPI_BODY, MPI_COMM_WORLD);
// 每10步输出一次状态仅rank 0
if (verbose && (t + 1) % 10 == 0 && world_rank == 0) {
cout << "时间步 " << t + 1 << ":" << endl;
for (int i = 0; i < n; i++) {
cout << " 天体 " << i << ": "
<< "位置=(" << all_particles[i].position.x / dist_scale << ", "
<< all_particles[i].position.y / dist_scale << ", "
<< all_particles[i].position.z / dist_scale << ")e8 m, "
<< "速度=(" << all_particles[i].velocity.x / vel_scale << ", "
<< all_particles[i].velocity.y / vel_scale << ", "
<< all_particles[i].velocity.z / vel_scale << ")e3 m/s" << endl;
}
}
}
if (world_rank == 0) {
cout << "" << endl;
double end_time = MPI_Wtime();
cout << "模拟用时: " << end_time - start_time << "" << endl;
cout << "\n模拟完成!" << endl;
}
MPI_Type_free(&MPI_BODY);
MPI_Finalize();
return 0;
}