Hydrologist C Job Examples
Hydrologist C Job Examples
This page contains C examples tailored to hydrologic analysis on Pantarhei, implementing the same SCS Curve Number workflow as the Python hydrology examples — data generation, runoff estimation, Clark routing, baseflow separation, and peak discharge — and an MPI-enabled parallel version that distributes rows across ranks using MPI_Scatterv and MPI_Gatherv.
Hydrology Toy Example
This example demonstrates hydrologic calculations using the SCS Curve Number method in C. It processes 50,000 synthetic data points with the same compute-heavy operations as the Python version so you can directly compare serial performance between the two languages.
Step 1: Generate synthetic data (generate_hydrology_data.c):
// generate_hydrology_data.c
/*
* Generate synthetic hydrology data for the toy example.
* Creates hydrology_data_50k.csv with 50,000 records including
* SCS Curve Number parameters for compute-heavy hydrologic calculations.
*/
#include <stdio.h>
#include <stdlib.h>
#define N 50000
/* Uniform random value in [lo, hi) */
static double rand_uniform(double lo, double hi) {
return lo + (hi - lo) * ((double)rand() / RAND_MAX);
}
int main(void) {
srand(42); /* fixed seed for reproducibility */
FILE *fp = fopen("hydrology_data_50k.csv", "w");
if (!fp) { perror("fopen"); return 1; }
/* Write CSV header */
fprintf(fp, "precipitation_mm,curve_number,time_concentration,"
"contributing_area,antecedent_moisture\n");
for (int i = 0; i < N; i++) {
double precip = rand_uniform(0.0, 100.0); /* mm, daily precipitation */
int cn = 40 + rand() % 55; /* 40=forest, 65=grassland, 90+=urban */
double tc = rand_uniform(10.0, 120.0); /* minutes */
double area = rand_uniform(0.1, 50.0); /* km² */
double moisture = rand_uniform(0.3, 0.8); /* 0–1, antecedent saturation */
fprintf(fp, "%.4f,%d,%.4f,%.4f,%.4f\n",
precip, cn, tc, area, moisture);
}
fclose(fp);
printf("Generated %d synthetic data points\n", N);
printf("Saved to hydrology_data_50k.csv\n");
return 0;
}
Step 2: Calculate runoff (hydrology_toy_c.c):
// hydrology_toy_c.c
/*
* Hydrology Toy Example - SCS Curve Number Runoff Calculation in C
* Reads hydrology_data_50k.csv and performs SCS runoff, Clark routing,
* baseflow separation, and peak discharge on every record.
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#define MAX_ROWS 50000
#define LINE_LEN 256
typedef struct {
double precipitation_mm;
int curve_number;
double time_concentration;
double contributing_area;
double antecedent_moisture;
} HydroInput;
typedef struct {
double runoff_scs;
double routed_runoff;
double direct_runoff;
double baseflow;
double recession_index;
double peak_discharge;
} HydroResult;
/*
* SCS Curve Number runoff: Q = (P - Ia)^2 / (P - Ia + S)
* where S = (25400/CN) - 254 and Ia = 0.2 * S
*/
double scs_runoff(double P, int CN) {
double S = (25400.0 / CN) - 254.0;
double Ia = 0.2 * S;
if (P <= Ia) return 0.0;
return (P - Ia) * (P - Ia) / (P - Ia + S);
}
/* Clark routing through storage reaches */
double clark_routing(double runoff, double tc, double duration_step) {
int num_reaches = (int)(tc / duration_step);
if (num_reaches < 2) num_reaches = 2;
double routed = runoff;
for (int i = 0; i < num_reaches; i++) {
double storage_coeff = tc / num_reaches;
routed *= (1.0 - exp(-duration_step / storage_coeff));
}
return routed;
}
/* Baseflow separation using 30-day exponential recession */
void baseflow_separation(double total_runoff, double antecedent_moisture,
double *direct_runoff, double *baseflow,
double *recession_index) {
double recession_constant = 0.95 - (antecedent_moisture * 0.3);
*direct_runoff = total_runoff * (1.0 - antecedent_moisture);
*baseflow = total_runoff * antecedent_moisture;
*recession_index = 0.0;
for (int day = 1; day <= 30; day++)
*recession_index += (*baseflow) * pow(recession_constant, day);
}
/* Peak discharge using rational-style intensity approximation */
double calculate_peak_discharge(double runoff, double area_sqkm, double tc) {
double intensity = 100.0 / pow(tc + 10.0, 0.7);
double area_m2 = area_sqkm * 1e6;
double runoff_m3 = (runoff / 1000.0) * area_m2;
double peak_factor = 0.0;
for (int k = 0; k < 50; k++) {
double t = tc * k / 49.0;
peak_factor += sin(M_PI * t / tc) * intensity / 50.0;
}
return (runoff_m3 * peak_factor) / (tc * 60.0);
}
int main(void) {
printf("============================================================\n");
printf("Hydrology Toy Example - SCS Curve Number Calculation (C)\n");
printf("============================================================\n");
/* Read CSV */
FILE *fp = fopen("hydrology_data_50k.csv", "r");
if (!fp) { perror("fopen"); return 1; }
HydroInput *inputs = malloc(MAX_ROWS * sizeof(HydroInput));
HydroResult *results = malloc(MAX_ROWS * sizeof(HydroResult));
if (!inputs || !results) { fprintf(stderr, "malloc failed\n"); return 1; }
char line[LINE_LEN];
fgets(line, LINE_LEN, fp); /* skip header */
int n = 0;
while (n < MAX_ROWS && fgets(line, LINE_LEN, fp)) {
sscanf(line, "%lf,%d,%lf,%lf,%lf",
&inputs[n].precipitation_mm,
&inputs[n].curve_number,
&inputs[n].time_concentration,
&inputs[n].contributing_area,
&inputs[n].antecedent_moisture);
n++;
}
fclose(fp);
printf("Loaded %d data points\n\n", n);
/* Process every record */
clock_t t0 = clock();
for (int i = 0; i < n; i++) {
HydroInput *in = &inputs[i];
HydroResult *out = &results[i];
out->runoff_scs = scs_runoff(in->precipitation_mm, in->curve_number);
out->routed_runoff = clark_routing(out->runoff_scs, in->time_concentration, 1.0);
baseflow_separation(out->routed_runoff, in->antecedent_moisture,
&out->direct_runoff, &out->baseflow, &out->recession_index);
out->peak_discharge = calculate_peak_discharge(
out->direct_runoff, in->contributing_area, in->time_concentration);
}
double elapsed = (double)(clock() - t0) / CLOCKS_PER_SEC;
/* Summary statistics */
double sum_precip = 0, sum_scs = 0, sum_routed = 0;
double sum_direct = 0, sum_base = 0, sum_peak = 0, max_peak = 0;
for (int i = 0; i < n; i++) {
sum_precip += inputs[i].precipitation_mm;
sum_scs += results[i].runoff_scs;
sum_routed += results[i].routed_runoff;
sum_direct += results[i].direct_runoff;
sum_base += results[i].baseflow;
sum_peak += results[i].peak_discharge;
if (results[i].peak_discharge > max_peak)
max_peak = results[i].peak_discharge;
}
printf("Results:\n");
printf(" Total records processed : %d\n", n);
printf(" Mean precipitation : %.2f mm\n", sum_precip / n);
printf(" Mean SCS runoff : %.2f mm\n", sum_scs / n);
printf(" Mean routed runoff : %.2f mm\n", sum_routed / n);
printf(" Mean direct runoff : %.2f mm\n", sum_direct / n);
printf(" Mean baseflow : %.2f mm\n", sum_base / n);
printf(" Mean peak discharge : %.4f m3/s\n", sum_peak / n);
printf(" Max peak discharge : %.4f m3/s\n", max_peak);
printf("\nComputation time : %.4f s\n", elapsed);
printf("Average per record: %.4f ms\n", elapsed / n * 1000.0);
/* Write results CSV */
FILE *out_fp = fopen("hydrology_results_50k_c.csv", "w");
if (out_fp) {
fprintf(out_fp, "precipitation_mm,curve_number,runoff_scs,routed_runoff,"
"direct_runoff,baseflow,recession_index,peak_discharge\n");
for (int i = 0; i < n; i++)
fprintf(out_fp, "%.4f,%d,%.4f,%.4f,%.4f,%.4f,%.4f,%.6f\n",
inputs[i].precipitation_mm, inputs[i].curve_number,
results[i].runoff_scs, results[i].routed_runoff,
results[i].direct_runoff, results[i].baseflow,
results[i].recession_index, results[i].peak_discharge);
fclose(out_fp);
printf("Results saved to hydrology_results_50k_c.csv\n");
}
free(inputs);
free(results);
printf("============================================================\n");
printf("Example completed!\n");
printf("============================================================\n");
return 0;
}
Job submission script (hydrology_toy_c_job.sh):
#!/bin/bash
# hydrology_toy_c_job.sh
#SBATCH -J hydrology_toy_c
#SBATCH -p normal
#SBATCH --nodes=1
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=4
#SBATCH -t 00:30:00
#SBATCH --output=hydrology_toy_c-%J.txt
module purge
# Step 1: Compile both programs
gcc -O2 -o generate_hydrology_data generate_hydrology_data.c
gcc -O2 -o hydrology_toy_c hydrology_toy_c.c -lm
# Step 2: Generate synthetic data
./generate_hydrology_data
# Step 3: Run SCS runoff calculation
./hydrology_toy_c
Submit the job:
sbatch hydrology_toy_c_job.sh
Hydrology MPI C Example
This example implements the same SCS Curve Number workflow in parallel using MPI. Rank 0 reads the CSV and distributes rows to all ranks with MPI_Scatterv. Each rank processes its local chunk independently, then MPI_Gatherv collects results on rank 0.
MPI program (hydrology_mpi_c.c):
// hydrology_mpi_c.c
/*
* Hydrology MPI Example - Parallel SCS Runoff Calculation in C
* Reads hydrology_data_50k.csv, scatters rows across MPI ranks,
* and gathers results on rank 0.
*/
#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#define MAX_ROWS 50000
#define LINE_LEN 256
/* Store curve_number as double so the struct maps cleanly to 5 MPI_DOUBLEs */
typedef struct {
double precipitation_mm;
double curve_number;
double time_concentration;
double contributing_area;
double antecedent_moisture;
} HydroInput;
typedef struct {
double runoff_scs;
double routed_runoff;
double direct_runoff;
double baseflow;
double recession_index;
double peak_discharge;
} HydroResult;
double scs_runoff(double P, int CN) {
double S = (25400.0 / CN) - 254.0;
double Ia = 0.2 * S;
if (P <= Ia) return 0.0;
return (P - Ia) * (P - Ia) / (P - Ia + S);
}
double clark_routing(double runoff, double tc, double ds) {
int nr = (int)(tc / ds);
if (nr < 2) nr = 2;
double r = runoff;
for (int i = 0; i < nr; i++)
r *= (1.0 - exp(-ds / (tc / nr)));
return r;
}
void baseflow_separation(double total, double moisture,
double *dr, double *bf, double *ri) {
double rc = 0.95 - moisture * 0.3;
*dr = total * (1.0 - moisture);
*bf = total * moisture;
*ri = 0.0;
for (int d = 1; d <= 30; d++) *ri += (*bf) * pow(rc, d);
}
double calculate_peak_discharge(double runoff, double area_sqkm, double tc) {
double intensity = 100.0 / pow(tc + 10.0, 0.7);
double rm3 = (runoff / 1000.0) * area_sqkm * 1e6;
double pf = 0.0;
for (int k = 0; k < 50; k++) {
double t = tc * k / 49.0;
pf += sin(M_PI * t / tc) * intensity / 50.0;
}
return rm3 * pf / (tc * 60.0);
}
void compute_rows(HydroInput *in, HydroResult *out, int n) {
for (int i = 0; i < n; i++) {
out[i].runoff_scs = scs_runoff(in[i].precipitation_mm, (int)in[i].curve_number);
out[i].routed_runoff = clark_routing(out[i].runoff_scs, in[i].time_concentration, 1.0);
baseflow_separation(out[i].routed_runoff, in[i].antecedent_moisture,
&out[i].direct_runoff, &out[i].baseflow, &out[i].recession_index);
out[i].peak_discharge = calculate_peak_discharge(
out[i].direct_runoff, in[i].contributing_area, in[i].time_concentration);
}
}
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);
/* Contiguous MPI types for HydroInput (5 doubles) and HydroResult (6 doubles) */
MPI_Datatype mpi_input, mpi_result;
MPI_Type_contiguous(5, MPI_DOUBLE, &mpi_input);
MPI_Type_contiguous(6, MPI_DOUBLE, &mpi_result);
MPI_Type_commit(&mpi_input);
MPI_Type_commit(&mpi_result);
HydroInput *all_in = NULL;
HydroResult *all_out = NULL;
int total = 0;
if (rank == 0) {
printf("============================================================\n");
printf("Hydrology MPI Example - Parallel SCS Runoff (C)\n");
printf("Running on %d MPI ranks\n", size);
printf("============================================================\n");
FILE *fp = fopen("hydrology_data_50k.csv", "r");
if (!fp) { perror("fopen"); MPI_Abort(MPI_COMM_WORLD, 1); }
all_in = malloc(MAX_ROWS * sizeof(HydroInput));
all_out = malloc(MAX_ROWS * sizeof(HydroResult));
char line[LINE_LEN];
fgets(line, LINE_LEN, fp); /* skip header */
while (total < MAX_ROWS && fgets(line, LINE_LEN, fp)) {
int cn;
sscanf(line, "%lf,%d,%lf,%lf,%lf",
&all_in[total].precipitation_mm, &cn,
&all_in[total].time_concentration,
&all_in[total].contributing_area,
&all_in[total].antecedent_moisture);
all_in[total].curve_number = (double)cn;
total++;
}
fclose(fp);
printf("Loaded %d data points\n", total);
printf("Distributing %d rows across %d ranks\n\n", total, size);
}
/* Broadcast total row count to all ranks */
MPI_Bcast(&total, 1, MPI_INT, 0, MPI_COMM_WORLD);
/* Build per-rank chunk sizes and displacements */
int *sendcounts = malloc(size * sizeof(int));
int *displs = malloc(size * sizeof(int));
int offset = 0;
for (int r = 0; r < size; r++) {
sendcounts[r] = total / size + (r < total % size ? 1 : 0);
displs[r] = offset;
offset += sendcounts[r];
}
int local_n = sendcounts[rank];
HydroInput *local_in = malloc(local_n * sizeof(HydroInput));
HydroResult *local_out = malloc(local_n * sizeof(HydroResult));
/* Scatter input rows */
MPI_Scatterv(all_in, sendcounts, displs, mpi_input,
local_in, local_n, mpi_input, 0, MPI_COMM_WORLD);
MPI_Barrier(MPI_COMM_WORLD);
double t0 = MPI_Wtime();
compute_rows(local_in, local_out, local_n);
MPI_Barrier(MPI_COMM_WORLD);
double elapsed = MPI_Wtime() - t0;
/* Gather results back to rank 0 */
MPI_Gatherv(local_out, local_n, mpi_result,
all_out, sendcounts, displs, mpi_result, 0, MPI_COMM_WORLD);
if (rank == 0) {
double sum_scs = 0, sum_routed = 0, sum_direct = 0;
double sum_base = 0, sum_peak = 0, max_peak = 0;
for (int i = 0; i < total; i++) {
sum_scs += all_out[i].runoff_scs;
sum_routed += all_out[i].routed_runoff;
sum_direct += all_out[i].direct_runoff;
sum_base += all_out[i].baseflow;
sum_peak += all_out[i].peak_discharge;
if (all_out[i].peak_discharge > max_peak)
max_peak = all_out[i].peak_discharge;
}
printf("Results:\n");
printf(" Total records processed : %d\n", total);
printf(" Mean SCS runoff : %.2f mm\n", sum_scs / total);
printf(" Mean routed runoff : %.2f mm\n", sum_routed / total);
printf(" Mean direct runoff : %.2f mm\n", sum_direct / total);
printf(" Mean baseflow : %.2f mm\n", sum_base / total);
printf(" Mean peak discharge : %.4f m3/s\n", sum_peak / total);
printf(" Max peak discharge : %.4f m3/s\n", max_peak);
printf("\nComputation time: %.4f s (MPI ranks: %d)\n", elapsed, size);
FILE *out_fp = fopen("hydrology_results_50k_mpi_c.csv", "w");
if (out_fp) {
fprintf(out_fp, "runoff_scs,routed_runoff,direct_runoff,"
"baseflow,recession_index,peak_discharge\n");
for (int i = 0; i < total; i++)
fprintf(out_fp, "%.4f,%.4f,%.4f,%.4f,%.4f,%.6f\n",
all_out[i].runoff_scs, all_out[i].routed_runoff,
all_out[i].direct_runoff, all_out[i].baseflow,
all_out[i].recession_index, all_out[i].peak_discharge);
fclose(out_fp);
printf("Results saved to hydrology_results_50k_mpi_c.csv\n");
}
free(all_in);
free(all_out);
printf("============================================================\n");
printf("MPI C Example completed!\n");
printf("============================================================\n");
}
free(sendcounts); free(displs);
free(local_in); free(local_out);
MPI_Type_free(&mpi_input);
MPI_Type_free(&mpi_result);
MPI_Finalize();
return 0;
}
Job submission script (hydrology_mpi_c_job.sh):
#!/bin/bash
# hydrology_mpi_c_job.sh
#SBATCH -J hydrology_mpi_c
#SBATCH -p normal
#SBATCH --nodes=1
#SBATCH --ntasks-per-node=4
#SBATCH --cpus-per-task=1
#SBATCH -t 00:30:00
#SBATCH --output=hydrology_mpi_c-%J.txt
module purge
module load MVAPICH
echo "Use the CSV already generated by the serial job."
echo "No need to regenerate — hydrology_data_50k.csv is reused."
# Compile MPI program
mpicc -O2 -o hydrology_mpi_c hydrology_mpi_c.c -lm
# Run with 4 MPI ranks
echo "Starting MPI SCS runoff calculation..."
mpirun -np 4 ./hydrology_mpi_c
echo "MPI C job completed!"
Submit the job:
sbatch hydrology_mpi_c_job.sh
Key differences between the serial and MPI C examples:
- Data Distribution:
MPI_Scattervdistributes rows unevenly-safe (handles remainders) — each rank receivestotal/sizeortotal/size + 1rows - MPI Derived Types:
MPI_Type_contiguousmaps each struct to a block ofMPI_DOUBLEvalues, lettingMPI_Scatterv/MPI_Gathervtransfer entire records in one call - Timing:
MPI_Wtime()measures wall-clock time across all ranks (more accurate thanclock()for parallel code) - Load Balancing: Remainder rows are distributed one-per-rank to ranks 0 through
total % size − 1 - Scalability: Increase
--ntasks-per-nodeand--nodesto scale further; the computation is embarrassingly parallel — no inter-rank communication during processing
Key differences between C and Python versions:
- No CSV library: C reads and parses each line manually with
fgets+sscanf - Explicit memory management:
malloc/freefor input and result arrays; Python handles this automatically - Output file: C writes
hydrology_results_50k_c.csv(serial) andhydrology_results_50k_mpi_c.csv(MPI) to distinguish from the Python output files - Performance: The C serial version is typically 5–20× faster than the row-by-row Python
applyloop for the same dataset
Additional Resources
For the Python equivalent of these examples, see the Hydrologist Python Job Examples page.
For complete source code and dataset generation scripts, visit the hydrology toy examples repository: