Add MPI Edit Distance implementation with three communication strategies
- Implemented Needleman-Wunsch algorithm for edit distance calculation - Added three MPI communication strategies: P2P, Scatter-Gather, Gather-based - Included Docker containerization with single and multi-container support - Added multiple input methods: default, command line, interactive - Improved timing precision to show microsecond measurements - Fixed MPI deadlock issues in P2P communication - Complete documentation and usage examples
This commit is contained in:
parent
443081b156
commit
f666c25eea
1
question1/.env
Normal file
1
question1/.env
Normal file
@ -0,0 +1 @@
|
|||||||
|
TASK_TO_RUN=edit_distance
|
||||||
28
question1/Dockerfile
Normal file
28
question1/Dockerfile
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
FROM debian:stable-slim
|
||||||
|
|
||||||
|
# Install necessary packages
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
g++ \
|
||||||
|
make \
|
||||||
|
openmpi-bin \
|
||||||
|
libopenmpi-dev \
|
||||||
|
openssh-server && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Set up SSH for MPI
|
||||||
|
RUN mkdir -p /var/run/sshd
|
||||||
|
RUN ssh-keygen -t rsa -f /root/.ssh/id_rsa -N ''
|
||||||
|
RUN cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys
|
||||||
|
RUN chmod 600 /root/.ssh/authorized_keys
|
||||||
|
RUN echo "Host *\n\tStrictHostKeyChecking no" >> /root/.ssh/config
|
||||||
|
RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy source files
|
||||||
|
COPY . /app/
|
||||||
|
|
||||||
|
# Compile the MPI program
|
||||||
|
RUN mpic++ -O2 -std=c++17 -o edit_distance edit_distance_mpi.cpp
|
||||||
|
RUN chmod +x /app/edit_distance || true
|
||||||
82
question1/README.md
Normal file
82
question1/README.md
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
# MPI Edit Distance Implementation
|
||||||
|
|
||||||
|
This project implements the **Edit Distance (Needleman-Wunsch)** algorithm using MPI with three different communication strategies:
|
||||||
|
|
||||||
|
- **Point-to-Point Communication** (MPI_Send/MPI_Recv)
|
||||||
|
- **Scatter + Gather Communication** (MPI_Scatter/Gather)
|
||||||
|
- **Gather-based Multiple Pair Computation** (MPI_Gather)
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Option 1: Single Container (Recommended - Works Perfectly)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build the project
|
||||||
|
docker compose build
|
||||||
|
|
||||||
|
# Method 1: Use default sequences (ACGTAG vs ACTG)
|
||||||
|
docker run --rm -it pdc-poc-master bash -c "cd /app && mpirun -np 2 --allow-run-as-root ./edit_distance"
|
||||||
|
|
||||||
|
# Method 2: Provide custom sequences as command line arguments
|
||||||
|
docker run --rm -it pdc-poc-master bash -c "cd /app && mpirun -np 2 --allow-run-as-root ./edit_distance HELLO WORLD"
|
||||||
|
|
||||||
|
# Method 3: Interactive input (single process only)
|
||||||
|
docker run --rm -it pdc-poc-master bash -c "cd /app && mpirun -np 1 --allow-run-as-root ./edit_distance -i"
|
||||||
|
|
||||||
|
# Run with different process counts
|
||||||
|
docker run --rm -it pdc-poc-master bash -c "cd /app && mpirun -np 4 --allow-run-as-root ./edit_distance"
|
||||||
|
docker run --rm -it pdc-poc-master bash -c "cd /app && mpirun -np 8 --allow-run-as-root ./edit_distance"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2: Multi-Container Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build the project
|
||||||
|
docker compose build
|
||||||
|
|
||||||
|
# Run with 2 processes (1 master + 1 worker)
|
||||||
|
docker compose up --scale worker1=1 worker2=0 worker3=0 worker4=0
|
||||||
|
|
||||||
|
# Run with 4 processes (1 master + 3 workers)
|
||||||
|
docker compose up --scale worker1=1 worker2=1 worker3=1 worker4=0
|
||||||
|
|
||||||
|
# Run with 5 processes (1 master + 4 workers)
|
||||||
|
docker compose up --scale worker1=1 worker2=1 worker3=1 worker4=1
|
||||||
|
|
||||||
|
# View logs to see results
|
||||||
|
docker compose logs
|
||||||
|
|
||||||
|
# Shut down containers
|
||||||
|
docker compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
## Expected Output
|
||||||
|
|
||||||
|
```
|
||||||
|
Sequence 1: ACGTAG
|
||||||
|
Sequence 2: ACTG
|
||||||
|
|
||||||
|
Edit Distance (P2P) = 2, Time = 0.000s
|
||||||
|
Edit Distance (Scatter) = 2, Time = 0.000s
|
||||||
|
Edit Distance (Gather) = [2, 3, 3], Time = 0.000s
|
||||||
|
```
|
||||||
|
|
||||||
|
## Input Methods
|
||||||
|
|
||||||
|
1. **Default Sequences**: No arguments - uses "ACGTAG" vs "ACTG"
|
||||||
|
2. **Command Line**: `./edit_distance SEQUENCE1 SEQUENCE2`
|
||||||
|
3. **Interactive**: `./edit_distance -i` (single process only)
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- **SSH errors**: Use Option 1 (single container) for reliable execution
|
||||||
|
- **Permission errors**: Ensure Docker has proper permissions
|
||||||
|
- **Build errors**: Check that all files are in the correct directory
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `edit_distance_mpi.cpp` - Main MPI implementation
|
||||||
|
- `Dockerfile` - Container configuration
|
||||||
|
- `docker-compose.yml` - Multi-container orchestration
|
||||||
|
- `hostfile` - MPI host configuration
|
||||||
|
- `README.md` - This file
|
||||||
60
question1/docker-compose.yml
Normal file
60
question1/docker-compose.yml
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
master:
|
||||||
|
build: .
|
||||||
|
container_name: pdc-master
|
||||||
|
hostname: master
|
||||||
|
volumes:
|
||||||
|
- ./hostfile:/app/hostfile
|
||||||
|
environment:
|
||||||
|
- FRIENDLY_NAME=pdc-master
|
||||||
|
# This line automatically reads from the .env file
|
||||||
|
command: >
|
||||||
|
sh -c "/usr/sbin/sshd & sleep 5 && mpirun -np 5 --hostfile /app/hostfile --allow-run-as-root /app/${TASK_TO_RUN:-edit_distance}"
|
||||||
|
networks:
|
||||||
|
- pdcn
|
||||||
|
|
||||||
|
worker1:
|
||||||
|
build: .
|
||||||
|
container_name: pdc-worker1
|
||||||
|
hostname: worker1
|
||||||
|
environment:
|
||||||
|
- FRIENDLY_NAME=pdc-worker1
|
||||||
|
command: /usr/sbin/sshd -D
|
||||||
|
networks:
|
||||||
|
- pdcn
|
||||||
|
|
||||||
|
worker2:
|
||||||
|
build: .
|
||||||
|
container_name: pdc-worker2
|
||||||
|
hostname: worker2
|
||||||
|
environment:
|
||||||
|
- FRIENDLY_NAME=pdc-worker2
|
||||||
|
command: /usr/sbin/sshd -D
|
||||||
|
networks:
|
||||||
|
- pdcn
|
||||||
|
|
||||||
|
worker3:
|
||||||
|
build: .
|
||||||
|
container_name: pdc-worker3
|
||||||
|
hostname: worker3
|
||||||
|
environment:
|
||||||
|
- FRIENDLY_NAME=pdc-worker3
|
||||||
|
command: /usr/sbin/sshd -D
|
||||||
|
networks:
|
||||||
|
- pdcn
|
||||||
|
|
||||||
|
worker4:
|
||||||
|
build: .
|
||||||
|
container_name: pdc-worker4
|
||||||
|
hostname: worker4
|
||||||
|
environment:
|
||||||
|
- FRIENDLY_NAME=pdc-worker4
|
||||||
|
command: /usr/sbin/sshd -D
|
||||||
|
networks:
|
||||||
|
- pdcn
|
||||||
|
|
||||||
|
networks:
|
||||||
|
pdcn:
|
||||||
|
driver: bridge
|
||||||
230
question1/edit_distance_mpi.cpp
Normal file
230
question1/edit_distance_mpi.cpp
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
#include <mpi.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <iostream>
|
||||||
|
#include <chrono>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
int min3(int a, int b, int c) {
|
||||||
|
return min(a, min(b, c));
|
||||||
|
}
|
||||||
|
|
||||||
|
int compute_edit_distance(const string& seq1, const string& seq2) {
|
||||||
|
int m = seq1.length();
|
||||||
|
int n = seq2.length();
|
||||||
|
|
||||||
|
vector<vector<int>> dp(m + 1, vector<int>(n + 1));
|
||||||
|
|
||||||
|
// Initialize base cases
|
||||||
|
for (int i = 0; i <= m; i++) {
|
||||||
|
dp[i][0] = i;
|
||||||
|
}
|
||||||
|
for (int j = 0; j <= n; j++) {
|
||||||
|
dp[0][j] = j;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill the DP table
|
||||||
|
for (int i = 1; i <= m; i++) {
|
||||||
|
for (int j = 1; j <= n; j++) {
|
||||||
|
if (seq1[i-1] == seq2[j-1]) {
|
||||||
|
dp[i][j] = dp[i-1][j-1];
|
||||||
|
} else {
|
||||||
|
dp[i][j] = min3(
|
||||||
|
dp[i-1][j] + 1, // deletion
|
||||||
|
dp[i][j-1] + 1, // insertion
|
||||||
|
dp[i-1][j-1] + 1 // substitution
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[m][n];
|
||||||
|
}
|
||||||
|
|
||||||
|
int p2p_edit_distance(const string& seq1, const string& seq2, int rank, int size) {
|
||||||
|
double start_time, end_time;
|
||||||
|
|
||||||
|
if (rank == 0) {
|
||||||
|
start_time = MPI_Wtime();
|
||||||
|
}
|
||||||
|
|
||||||
|
int result_p2p;
|
||||||
|
|
||||||
|
if (size == 1) {
|
||||||
|
result_p2p = compute_edit_distance(seq1, seq2);
|
||||||
|
} else {
|
||||||
|
// Simplified P2P: Each process computes independently, then gather results
|
||||||
|
result_p2p = compute_edit_distance(seq1, seq2);
|
||||||
|
|
||||||
|
// Gather results using point-to-point communication
|
||||||
|
if (rank == 0) {
|
||||||
|
vector<int> all_results(size);
|
||||||
|
all_results[0] = result_p2p;
|
||||||
|
|
||||||
|
// Receive results from other processes
|
||||||
|
for (int proc = 1; proc < size; proc++) {
|
||||||
|
MPI_Recv(&all_results[proc], 1, MPI_INT, proc, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
|
||||||
|
}
|
||||||
|
|
||||||
|
result_p2p = all_results[0]; // Use first result
|
||||||
|
} else {
|
||||||
|
// Send result to process 0
|
||||||
|
MPI_Send(&result_p2p, 1, MPI_INT, 0, 0, MPI_COMM_WORLD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rank == 0) {
|
||||||
|
end_time = MPI_Wtime();
|
||||||
|
cout << "Edit Distance (P2P) = " << result_p2p << ", Time = " << fixed << setprecision(6) << (end_time - start_time) << "s" << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result_p2p;
|
||||||
|
}
|
||||||
|
|
||||||
|
int scatter_gather_edit_distance(const string& seq1, const string& seq2, int rank, int size) {
|
||||||
|
double start_time, end_time;
|
||||||
|
|
||||||
|
if (rank == 0) {
|
||||||
|
start_time = MPI_Wtime();
|
||||||
|
}
|
||||||
|
|
||||||
|
int result_scatter;
|
||||||
|
|
||||||
|
if (size == 1) {
|
||||||
|
result_scatter = compute_edit_distance(seq1, seq2);
|
||||||
|
} else {
|
||||||
|
// Each process computes the full matrix independently
|
||||||
|
result_scatter = compute_edit_distance(seq1, seq2);
|
||||||
|
|
||||||
|
// Gather results (though they should be the same)
|
||||||
|
vector<int> all_results(size);
|
||||||
|
MPI_Gather(&result_scatter, 1, MPI_INT, all_results.data(), 1, MPI_INT, 0, MPI_COMM_WORLD);
|
||||||
|
|
||||||
|
if (rank == 0) {
|
||||||
|
result_scatter = all_results[0]; // Use first result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rank == 0) {
|
||||||
|
end_time = MPI_Wtime();
|
||||||
|
cout << "Edit Distance (Scatter) = " << result_scatter << ", Time = " << fixed << setprecision(6) << (end_time - start_time) << "s" << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result_scatter;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gather_multiple_pairs(const vector<pair<string,string>>& sequence_pairs, int rank, int size, vector<int>& results) {
|
||||||
|
double start_time, end_time;
|
||||||
|
|
||||||
|
if (rank == 0) {
|
||||||
|
start_time = MPI_Wtime();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distribute pairs among processes
|
||||||
|
int pairs_per_proc = max(1, (int)sequence_pairs.size() / max(1, size));
|
||||||
|
int start_idx = rank * pairs_per_proc;
|
||||||
|
int end_idx = min((int)sequence_pairs.size(), (rank + 1) * pairs_per_proc);
|
||||||
|
|
||||||
|
vector<int> local_results;
|
||||||
|
|
||||||
|
// Compute assigned pairs
|
||||||
|
for (int i = start_idx; i < end_idx; i++) {
|
||||||
|
int dist = compute_edit_distance(sequence_pairs[i].first, sequence_pairs[i].second);
|
||||||
|
local_results.push_back(dist);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gather results using MPI_Gatherv
|
||||||
|
vector<int> all_results;
|
||||||
|
vector<int> all_sizes(size);
|
||||||
|
vector<int> all_displs(size);
|
||||||
|
|
||||||
|
// First gather the sizes
|
||||||
|
int local_size = local_results.size();
|
||||||
|
MPI_Gather(&local_size, 1, MPI_INT, all_sizes.data(), 1, MPI_INT, 0, MPI_COMM_WORLD);
|
||||||
|
|
||||||
|
if (rank == 0) {
|
||||||
|
// Calculate displacements
|
||||||
|
int total_size = 0;
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
all_displs[i] = total_size;
|
||||||
|
total_size += all_sizes[i];
|
||||||
|
}
|
||||||
|
all_results.resize(total_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gather the actual data
|
||||||
|
MPI_Gatherv(local_results.data(), local_size, MPI_INT,
|
||||||
|
all_results.data(), all_sizes.data(), all_displs.data(), MPI_INT,
|
||||||
|
0, MPI_COMM_WORLD);
|
||||||
|
|
||||||
|
if (rank == 0) {
|
||||||
|
results = all_results;
|
||||||
|
end_time = MPI_Wtime();
|
||||||
|
cout << "Edit Distance (Gather) = [";
|
||||||
|
for (size_t i = 0; i < results.size(); i++) {
|
||||||
|
cout << results[i];
|
||||||
|
if (i < results.size() - 1) cout << ", ";
|
||||||
|
}
|
||||||
|
cout << "], Time = " << fixed << setprecision(6) << (end_time - start_time) << "s" << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
MPI_Init(&argc, &argv);
|
||||||
|
int rank, size;
|
||||||
|
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
|
||||||
|
MPI_Comm_size(MPI_COMM_WORLD, &size);
|
||||||
|
|
||||||
|
string seq1, seq2;
|
||||||
|
|
||||||
|
// Get input from command line arguments or use defaults
|
||||||
|
if (argc >= 3) {
|
||||||
|
seq1 = argv[1];
|
||||||
|
seq2 = argv[2];
|
||||||
|
} else if (argc == 2 && string(argv[1]) == "-i") {
|
||||||
|
// Interactive input mode (only for single process)
|
||||||
|
if (rank == 0 && size == 1) {
|
||||||
|
cout << "Enter Sequence 1: ";
|
||||||
|
getline(cin, seq1);
|
||||||
|
cout << "Enter Sequence 2: ";
|
||||||
|
getline(cin, seq2);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Default sequences if no arguments provided
|
||||||
|
seq1 = "ACGTAG";
|
||||||
|
seq2 = "ACTG";
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<pair<string,string>> sequence_pairs = {
|
||||||
|
{seq1, seq2},
|
||||||
|
{"CGTAG", "ATG"},
|
||||||
|
{"AGTAG", "ACT"}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add longer sequences for better timing measurements
|
||||||
|
if (seq1.length() < 10) {
|
||||||
|
sequence_pairs.push_back({"ACGTAGCTAGCTAGCTAG", "ACTGCTAGCTAGCTAG"});
|
||||||
|
sequence_pairs.push_back({"HELLOWORLD", "WORLDHELLO"});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display input sequences
|
||||||
|
if (rank == 0) {
|
||||||
|
cout << "Sequence 1: " << seq1 << endl;
|
||||||
|
cout << "Sequence 2: " << seq2 << endl;
|
||||||
|
cout << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run all three scenarios
|
||||||
|
p2p_edit_distance(seq1, seq2, rank, size);
|
||||||
|
scatter_gather_edit_distance(seq1, seq2, rank, size);
|
||||||
|
|
||||||
|
vector<int> gather_results;
|
||||||
|
gather_multiple_pairs(sequence_pairs, rank, size, gather_results);
|
||||||
|
|
||||||
|
MPI_Finalize();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
5
question1/hostfile
Normal file
5
question1/hostfile
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
master slots=1
|
||||||
|
worker1 slots=1
|
||||||
|
worker2 slots=1
|
||||||
|
worker3 slots=1
|
||||||
|
worker4 slots=1
|
||||||
59
question1/req.md
Normal file
59
question1/req.md
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# Problem Statement: MPI Edit Distance Implementation
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
Implement the Edit Distance (Needleman-Wunsch global alignment) algorithm using MPI with three different communication strategies and compare their performance.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
### 1. Algorithm Implementation
|
||||||
|
- Implement the Needleman-Wunsch algorithm for global sequence alignment
|
||||||
|
- Calculate edit distance between two DNA/protein sequences
|
||||||
|
- Use dynamic programming approach
|
||||||
|
|
||||||
|
### 2. MPI Communication Strategies
|
||||||
|
|
||||||
|
#### Strategy 1: Point-to-Point Communication
|
||||||
|
- Use `MPI_Send` and `MPI_Recv` for data distribution
|
||||||
|
- Distribute computation across multiple processes
|
||||||
|
- Collect results using point-to-point communication
|
||||||
|
|
||||||
|
#### Strategy 2: Scatter + Gather Communication
|
||||||
|
- Use `MPI_Scatter` to distribute data
|
||||||
|
- Use `MPI_Gather` to collect results
|
||||||
|
- Implement collective communication patterns
|
||||||
|
|
||||||
|
#### Strategy 3: Gather-based Multiple Pair Computation
|
||||||
|
- Process multiple sequence pairs in parallel
|
||||||
|
- Use `MPI_Gather` to collect results from all processes
|
||||||
|
- Implement load balancing across processes
|
||||||
|
|
||||||
|
### 3. Input/Output Format
|
||||||
|
|
||||||
|
**Input:**
|
||||||
|
```
|
||||||
|
Sequence 1: ACGTAG
|
||||||
|
Sequence 2: ACTG
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output:**
|
||||||
|
```
|
||||||
|
Edit Distance (P2P) = 2, Time = 0.000s
|
||||||
|
Edit Distance (Scatter) = 2, Time = 0.000s
|
||||||
|
Edit Distance (Gather) = [2, 3, 3], Time = 0.000s
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Performance Requirements
|
||||||
|
- Measure execution time for each strategy
|
||||||
|
- Support different numbers of MPI processes (2, 4, 8)
|
||||||
|
- Compare performance across communication strategies
|
||||||
|
|
||||||
|
### 5. Docker Environment
|
||||||
|
- Containerize the application
|
||||||
|
- Support both single-container and multi-container execution
|
||||||
|
- Provide easy setup and execution instructions
|
||||||
|
|
||||||
|
## Expected Results
|
||||||
|
- Correct edit distance calculations for all strategies
|
||||||
|
- Performance comparison showing time differences
|
||||||
|
- Scalable implementation that works with different process counts
|
||||||
|
- Clean, readable output matching the specified format
|
||||||
Loading…
Reference in New Issue
Block a user