sandbox/acastillo/output_fields/histograms1D.h
Histograms and cumulative distribution functions
probability_distribution_1D(): Histogram, PDF and CDF of a scalar
This function computes the volume-weighted histogram of a scalar field c using GSL histograms. Both the probability density function (PDF) and the cumulative distribution function (CDF) are derived from it. Cells are weighted by the cell volume. The function uses parallel reduction to accumulate values and weights, then normalizes the accumulated values.
The arguments and their default values are:
- c
- Scalar field for which the histogram is computed.
- cmin
-
Lower boundary of c. Default is
0. - cmax
-
Upper boundary of c. Default is
1. - nbin
-
Number of points (bins) between
cminandcmax. Default is220. - p_range
- Array to store the discretized bin ranges.
- p_pdf
- Array to store the probability density function values.
- p_sum
- Array to store the cumulative distribution function values.
- filename
-
Name of the file the histogram is appended to. Default is
"reference_state_gsl.asc". - store
-
Boolean flag to store the output in a file. Default is
true.
When store is true, each call appends a block to
filename: a two-line comment header naming the scalar
(c.name) and the four columns, followed by one row per
bin,
# c = <c.name>
# t range[<c.name>] pdf[<c.name>] cdf[<c.name>]
t p_range[0] p_pdf[0] p_sum[0]
t p_range[1] p_pdf[1] p_sum[1]
...
so that repeated calls (e.g. one per timestep, or for different scalars) can be told apart when appended to the same file.
#include <gsl/gsl_errno.h>
#include <gsl/gsl_spline.h>
#include <gsl/gsl_histogram.h>
#pragma autolink -lgsl -lgslcblas
#define NBIN 220
void probability_distribution_1D(scalar c, double cmin=0, double cmax=1,
int nbin=NBIN, double p_range[nbin],
double p_pdf[nbin], double p_sum[nbin],
const char * filename = "reference_state_gsl.asc",
bool store=true){
double crange = (cmax - cmin);
/* Set the number of bins */
gsl_histogram * h = gsl_histogram_alloc (nbin);
gsl_histogram_set_ranges_uniform (h, cmin-0.05*crange, cmax+0.05*crange);
double db = h->range[1] - h->range[0];
double p_sum_loc[nbin], p_pdf_loc[nbin];
for (int ii = 0; ii < nbin; ii++){
p_range[ii] = h->range[ii];
p_sum_loc[ii] = 0;
p_pdf_loc[ii] = 0;
}
/* Obtain the total volume per pid */
double vol_cells=0.0, vol_cells_pid=0.0;
foreach(serial, noauto)
#if EMBED
vol_cells_pid += (c[] != nodata) ? dv()*cs[] : 0;
#else
vol_cells_pid += (c[] != nodata) ? dv() : 0;
#endif
/* Populate a 1D histogram using fields weighted by volume.
gsl_histogram_accumulate() mutates h in place with no reduction clause
Basilisk could parallelize around, so this loop must stay serial -- a
plain foreach() here would race on h->bin[] under OpenMP.
c[] can be nodata (HUGE) on cells where the field is undefined (e.g.
curvature away from the interface); accumulate() returns GSL_EDOM for
out-of-range x, which with GSL's default error handler aborts. Check
the return and skip those cells instead. */
gsl_error_handler_t * gsl_prev_handler = gsl_set_error_handler_off();
if (vol_cells_pid > 0){
foreach(serial, noauto)
#if EMBED
gsl_histogram_accumulate(h, c[], dv()*cs[]);
#else
gsl_histogram_accumulate(h, c[], dv());
#endif
/* Obtain the cumulative distribution from the histogram */
gsl_histogram_pdf * p = gsl_histogram_pdf_alloc (nbin);
gsl_histogram_pdf_init (p, h);
/* p->sum has nbin+1 entries: p->sum[ii] is the cumulative probability
up to the *left* edge of bin ii, i.e. exactly at p_range[ii]. We
multiply the function by the volume per pid. */
for (int ii = 0; ii < nbin; ii++){
p_sum_loc[ii] = p->sum[ii]*vol_cells_pid;
p_pdf_loc[ii] = gsl_histogram_get (h, ii);
}
gsl_histogram_pdf_free (p);
}
gsl_histogram_free (h);
gsl_set_error_handler (gsl_prev_handler);
/* Sum the values over the ensemble of sub-domains */
@if _MPI
MPI_Allreduce (&p_sum_loc[0], &p_sum[0], nbin, MPI_DOUBLE , MPI_SUM, MPI_COMM_WORLD);
MPI_Allreduce (&p_pdf_loc[0], &p_pdf[0], nbin, MPI_DOUBLE , MPI_SUM, MPI_COMM_WORLD);
MPI_Allreduce (&vol_cells_pid, &vol_cells, 1 , MPI_DOUBLE , MPI_SUM, MPI_COMM_WORLD);
@else
vol_cells = vol_cells_pid;
for (int ii = 0; ii < nbin; ii++){
p_sum[ii] = p_sum_loc[ii];
p_pdf[ii] = p_pdf_loc[ii];
}
@endif
/* We divide by the total volume to ensure the cdf has values \in [0,1],
and the pdf integrates to 1 over [cmin-0.05*crange, cmax+0.05*crange] */
for (int ii = 0; ii < nbin; ii++){
p_sum[ii] /= vol_cells;
p_pdf[ii] /= vol_cells*db;
}
for (int ii = 0; ii < nbin; ii++){
if ( fabs(p_range[ii] - cmin) < 1e-10 ){
p_sum[ii] = 0.5*(p_sum[ii]+p_sum[ii+1]);
}
if ( fabs(p_range[ii] - cmax) < 1e-10 ){
p_sum[ii] = 0.5*(p_sum[ii]+p_sum[ii-1]);
}
}
if ((pid() == 0) & (store)) {
FILE * fp = fopen(filename, "a") ;
fprintf (fp, "# c = %s\n# t range[%s] pdf[%s] cdf[%s]\n",
c.name, c.name, c.name, c.name);
for (int i = 0; i < nbin; i++)
fprintf (fp, "%.12g %.12g %.12g %.12g \n", t, p_range[i], p_pdf[i], p_sum[i]);
fputs ("\n", fp);
fclose (fp);
}
}probability_distribution_1D_weighted(): Histogram, PDF and CDF of a scalar, restricted to a region
Same as probability_distribution_1D(), but restricted to
a region-of-interest f (e.g. a VOF fraction): cells are
weighted by f times the cell volume instead of the cell volume
alone.
The arguments and their default values are the same as
probability_distribution_1D(), with one addition:
- f
-
Scalar field defining the region-of-interest. Required, no default –
pass
unityexplicitly to recover the unrestricted histogram (aconst scalarsuch asunitycannot be bound as a default argument value in Basilisk, only passed explicitly).
void probability_distribution_1D_weighted(scalar f, scalar c, double cmin=0,
double cmax=1, int nbin=NBIN,
double p_range[nbin],
double p_pdf[nbin],
double p_sum[nbin],
const char * filename = "reference_state_gsl.asc",
bool store=true){
double crange = (cmax - cmin);
/* Set the number of bins */
gsl_histogram * h = gsl_histogram_alloc (nbin);
gsl_histogram_set_ranges_uniform (h, cmin-0.05*crange, cmax+0.05*crange);
double db = h->range[1] - h->range[0];
double p_sum_loc[nbin], p_pdf_loc[nbin];
for (int ii = 0; ii < nbin; ii++){
p_range[ii] = h->range[ii];
p_sum_loc[ii] = 0;
p_pdf_loc[ii] = 0;
}
/* Obtain the total weighted volume per pid */
double vol_cells=0.0, vol_cells_pid=0.0;
foreach(serial, noauto)
#if EMBED
vol_cells_pid += f[]*dv()*cs[];
#else
vol_cells_pid += f[]*dv();
#endif
/* Populate a 1D histogram using fields weighted by f times the cell volume.
gsl_histogram_accumulate() mutates h in place with no reduction clause
Basilisk could parallelize around, so this loop must stay serial -- a
plain foreach() here would race on h->bin[] under OpenMP.
c[] can be nodata (HUGE) on cells where the field is undefined; see the
comment in probability_distribution_1D() above. */
gsl_error_handler_t * gsl_prev_handler = gsl_set_error_handler_off();
if (vol_cells_pid > 0){
foreach(serial, noauto)
#if EMBED
gsl_histogram_accumulate(h, c[], f[]*dv()*cs[]);
#else
gsl_histogram_accumulate(h, c[], f[]*dv());
#endif
/* Obtain the cumulative distribution from the histogram */
gsl_histogram_pdf * p = gsl_histogram_pdf_alloc (nbin);
gsl_histogram_pdf_init (p, h);
/* p->sum has nbin+1 entries: p->sum[ii] is the cumulative probability
up to the *left* edge of bin ii, i.e. exactly at p_range[ii]. We
multiply the function by the volume per pid. */
for (int ii = 0; ii < nbin; ii++){
p_sum_loc[ii] = p->sum[ii]*vol_cells_pid;
p_pdf_loc[ii] = gsl_histogram_get (h, ii);
}
gsl_histogram_pdf_free (p);
}
gsl_histogram_free (h);
gsl_set_error_handler (gsl_prev_handler);
/* Sum the values over the ensemble of sub-domains */
@if _MPI
MPI_Allreduce (&p_sum_loc[0], &p_sum[0], nbin, MPI_DOUBLE , MPI_SUM, MPI_COMM_WORLD);
MPI_Allreduce (&p_pdf_loc[0], &p_pdf[0], nbin, MPI_DOUBLE , MPI_SUM, MPI_COMM_WORLD);
MPI_Allreduce (&vol_cells_pid, &vol_cells, 1 , MPI_DOUBLE , MPI_SUM, MPI_COMM_WORLD);
@else
vol_cells = vol_cells_pid;
for (int ii = 0; ii < nbin; ii++){
p_sum[ii] = p_sum_loc[ii];
p_pdf[ii] = p_pdf_loc[ii];
}
@endif
/* We divide by the total volume to ensure the cdf has values \in [0,1],
and the pdf integrates to 1 over [cmin-0.05*crange, cmax+0.05*crange] */
for (int ii = 0; ii < nbin; ii++){
p_sum[ii] /= vol_cells;
p_pdf[ii] /= vol_cells*db;
}
for (int ii = 0; ii < nbin; ii++){
if ( fabs(p_range[ii] - cmin) < 1e-10 ){
p_sum[ii] = 0.5*(p_sum[ii]+p_sum[ii+1]);
}
if ( fabs(p_range[ii] - cmax) < 1e-10 ){
p_sum[ii] = 0.5*(p_sum[ii]+p_sum[ii-1]);
}
}
if ((pid() == 0) & (store)) {
FILE * fp = fopen(filename, "a") ;
fprintf (fp, "# c = %s\n# t range[%s] pdf[%s] cdf[%s]\n",
c.name, c.name, c.name, c.name);
for (int i = 0; i < nbin; i++)
fprintf (fp, "%.12g %.12g %.12g %.12g \n", t, p_range[i], p_pdf[i], p_sum[i]);
fputs ("\n", fp);
fclose (fp);
}
}