/* This file is auto-generated from countpairs_s_mu_mocks_impl.c.src */
#ifndef DOUBLE_PREC
#define DOUBLE_PREC
#endif
// # -*- mode: c -*-
/* File: countpairs_s_mu_mocks_impl.c.src */
/*
  This file is a part of the Corrfunc package
  Copyright (C) 2015-- Manodeep Sinha (manodeep@gmail.com)
  License: MIT LICENSE. See LICENSE file under the top-level
  directory at https://github.com/manodeep/Corrfunc/
*/

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <gsl/gsl_interp.h>

#include "countpairs_s_mu_mocks_impl_double.h"
#include "countpairs_s_mu_mocks_kernels_double.c"
#include "cellarray_mocks_double.h"
#include "gridlink_mocks_impl_double.h"

#include "defs.h"
#include "utils.h"
#include "cosmology_params.h"
#include "set_cosmo_dist.h"
#include "cpu_features.h"
#include "progressbar.h"
#include "proj_functions_double.h"

#if defined(_OPENMP)
#include <omp.h>
#endif

int interrupt_status_DDsmu_mocks_double=EXIT_SUCCESS;

void interrupt_handler_countpairs_s_mu_mocks_double(int signo)
{
    fprintf(stderr,"Received signal = `%s' (signo = %d). Aborting \n",strsignal(signo), signo);
    interrupt_status_DDsmu_mocks_double = EXIT_FAILURE;
}

int check_ra_dec_cz_s_mu_double(const int64_t N, double *phi, double *theta, double *cz)
{

    if(N==0) {
        return EXIT_SUCCESS;
    }
    if(phi == NULL || theta == NULL || cz == NULL) {
        fprintf(stderr,"Input arrays can not be NULL. Have RA = %p DEC = %p cz = %p\n", phi, theta, cz);
        return EXIT_FAILURE;
    }

    int fix_cz  = 0;
    int fix_ra  = 0;
    int fix_dec = 0;

    const double max_cz_threshold = 10.0;//if I find that max cz is smaller than this threshold, then I will assume z has been supplied rather than cz
    double max_cz = 0.0;
    //Check input cz -> ensure that cz contains cz and not z
    for(int64_t i=0;i<N;i++) {
        if(cz[i] > max_cz) max_cz = cz[i];
        if(phi[i] < 0.0) {
            fix_ra = 1;
        }
        if(theta[i] > 90.0) {
            fix_dec = 1;
        }
        if(theta[i] > 180) {
            fprintf(stderr,"theta[%"PRId64"] = %"REAL_FORMAT"should be less than 180 deg\n", i, theta[i]);
            return EXIT_FAILURE;
        }
    }
    if(max_cz < max_cz_threshold) fix_cz = 1;

    //Only run the loop if something needs to be fixed
    if(fix_cz==1 || fix_ra == 1 || fix_dec == 1) {
        if(fix_ra == 1) {
            fprintf(stderr,"%s> Out of range values found for ra. Expected ra to be in the range [0.0,360.0]. Found ra values in [-180,180] -- fixing that\n", __FUNCTION__);
        }
        if(fix_dec == 1) {
            fprintf(stderr,"%s> Out of range values found for dec. Expected dec to be in the range [-90.0,90.0]. Found dec values in [0,180] -- fixing that\n", __FUNCTION__);
        }
        if(fix_cz == 1)  {
            fprintf(stderr,"%s> Out of range values found for cz. Expected input to be `cz' but found `z' instead. max_cz (found in input) = %"REAL_FORMAT" threshold "
                    "= %"REAL_FORMAT"\n",__FUNCTION__,max_cz,max_cz_threshold);
        }

        for(int64_t i=0;i<N;i++) {
            if(fix_ra==1) {
                phi[i] += (double) 180.0;
            }
            if(fix_dec==1) {
                theta[i] -= (double) 90.0;
            }
            if(fix_cz == 1) {
                cz[i] *= (double) SPEED_OF_LIGHT;//input was z -> convert to cz
            }
        }
    }

    return EXIT_SUCCESS;
}

countpairs_mocks_func_ptr_double countpairs_s_mu_mocks_driver_double(const struct config_options *options)
{

    static countpairs_mocks_func_ptr_double function = NULL;
    static isa old_isa=-1;
    if(old_isa == options->instruction_set) {
        return function;
    }

    /* Array of function pointers */
    countpairs_mocks_func_ptr_double allfunctions[] = {
#ifdef __AVX__
        countpairs_s_mu_mocks_avx_intrinsics_double,
#endif
#ifdef __SSE4_2__
      countpairs_s_mu_mocks_sse_intrinsics_double,
#endif
      countpairs_s_mu_mocks_fallback_double
    };

    const int num_functions = sizeof(allfunctions)/sizeof(void *);
    const int fallback_offset = num_functions - 1;
#if defined(__AVX__) || defined __SSE4_2__
    const int highest_isa = instrset_detect();
#endif
    int curr_offset = 0;

    /* Now check if AVX is supported by the CPU */
    int avx_offset = fallback_offset;
#ifdef __AVX__
    avx_offset = highest_isa >= 7 ? curr_offset:fallback_offset;
    curr_offset++;
#endif

    /* Is the SSE function supported at runtime and enabled at compile-time?*/
    int sse_offset = fallback_offset;
#ifdef __SSE4_2__
    sse_offset = highest_isa >= 6 ? curr_offset:fallback_offset;
    curr_offset++;
#endif
    if( curr_offset != fallback_offset) {
      fprintf(stderr,"ERROR: Bug in code (current offset = %d *should equal* fallback function offset = %d)\n",
              curr_offset, fallback_offset);
      return NULL;
    }

    int function_dispatch=0;
    /* Check that cpu supports feature */
    if(options->instruction_set >= 0) {
        switch(options->instruction_set) {
        case(AVX512F):
        case(AVX2):
        case(AVX):function_dispatch=avx_offset;break;
        case(SSE42): function_dispatch=sse_offset;break;
        default:function_dispatch=fallback_offset;break;
        }
    }

    if(function_dispatch >= num_functions) {
      fprintf(stderr,"In %s> ERROR: Could not resolve the correct function.\n Function index = %d must lie between [0, %d)\n",
              __FUNCTION__, function_dispatch, num_functions);
      return NULL;
    }
    function = allfunctions[function_dispatch];
    old_isa = options->instruction_set;

    if(options->verbose){
        // This must be first (AVX/SSE may be aliased to fallback)
        if(function_dispatch == fallback_offset){
            fprintf(stderr,"Using fallback kernel\n");
        } else if(function_dispatch == avx_offset){
            fprintf(stderr,"Using AVX kernel\n");
        } else if(function_dispatch == sse_offset){
            fprintf(stderr,"Using SSE kernel\n");
        } else {
            printf("Unknown kernel!\n");
        }
    }

    return function;
}

int countpairs_mocks_s_mu_double(const int64_t ND1, double *ra1, double *dec1, double *czD1,
                                 const int64_t ND2, double *ra2, double *dec2, double *czD2,
                                 const int numthreads,
                                 const int autocorr,
                                 const char *sbinfile,
                                 const double max_mu,
                                 const int nmu_bins,
                                 const int cosmology,
                                 results_countpairs_mocks_s_mu *results,
                                 struct config_options *options, struct extra_options *extra)
{

    if(options->float_type != sizeof(double)) {
        fprintf(stderr,"ERROR: In %s> Can only handle arrays of size=%zu. Got an array of size = %zu\n",
                __FUNCTION__, sizeof(double), options->float_type);
        return EXIT_FAILURE;
    }

    // If no extra options were passed, create dummy options
    // This allows us to pass arguments like "extra->weights0" below;
    // they'll just be NULLs, which is the correct behavior
    struct extra_options dummy_extra;
    if(extra == NULL){
      weight_method_t dummy_method = NONE;
      dummy_extra = get_extra_options(dummy_method);
      extra = &dummy_extra;
    }

    int need_weightavg = extra->weight_method != NONE;

    options->sort_on_z = 1;
    struct timeval t0;
    if(options->c_api_timer) {
        gettimeofday(&t0, NULL);
    }
    if(options->fast_divide_and_NR_steps >= MAX_FAST_DIVIDE_NR_STEPS) {
        fprintf(stderr, ANSI_COLOR_MAGENTA"Warning: The number of requested Newton-Raphson steps = %u is larger than max. allowed steps = %u."
                " Switching to a standard divide"ANSI_COLOR_RESET"\n",
                options->fast_divide_and_NR_steps, MAX_FAST_DIVIDE_NR_STEPS);
        options->fast_divide_and_NR_steps = 0;
    }

    //Check inputs
    if(ND1 == 0 || (autocorr == 0 && ND2 == 0)) {
        return EXIT_SUCCESS;
    }

    //Check inputs
    int status1 = check_ra_dec_cz_s_mu_double(ND1, ra1, dec1, czD1);
    if(status1 != EXIT_SUCCESS) {
        return status1;
    }
    if(autocorr==0) {
        int status2 = check_ra_dec_cz_s_mu_double(ND2, ra2, dec2, czD2);
        if(status2 != EXIT_SUCCESS) {
            return status2;
        }
    }

#if defined(_OPENMP)
    omp_set_num_threads(numthreads);
#else
    (void) numthreads;
#endif

    if(options->max_cells_per_dim == 0) {
        fprintf(stderr,"Warning: Max. cells per dimension is set to 0 - resetting to `NLATMAX' = %d\n", NLATMAX);
        options->max_cells_per_dim = NLATMAX;
    }
    for(int i=0;i<3;i++) {
        if(options->bin_refine_factors[i] < 1) {
            fprintf(stderr,"Warning: bin refine factor along axis = %d *must* be >=1. Instead found bin refine factor =%d\n",
                    i, options->bin_refine_factors[i]);
            reset_bin_refine_factors(options);
            break;/* all factors have been reset -> no point continuing with the loop */
        }
    }

    /* setup interrupt handler -> mostly useful during the python execution.
       Let's Ctrl-C abort the extension  */
    SETUP_INTERRUPT_HANDLERS(interrupt_handler_countpairs_s_mu_mocks_double);

    //Try to initialize cosmology - code will exit if comoslogy is not implemented.
    //Putting in a different scope so I can call the variable status
    {
        int status = init_cosmology(cosmology);
        if(status != EXIT_SUCCESS) {
            return status;
        }
    }

    /***********************
     *initializing the  bins
     ************************/
    double *supp;
    int nsbin;
    double smin,smax;
    setup_bins(sbinfile,&smin,&smax,&nsbin,&supp);
    if( ! (smin > 0.0 && smax > 0.0 && smin < smax && nsbin > 0)) {
        fprintf(stderr,"Error: Could not setup with S bins correctly. (smin = %lf, smax = %lf, with nbins = %d). Expected non-zero smin/smax with smax > smin and nbins >=1 \n",
                smin, smax, nsbin);
        return EXIT_FAILURE;
    }

    if(max_mu <= 0.0 || max_mu > 1.0) {
        fprintf(stderr,"Error: max_mu (max. value for the cosine of the angle with line of sight) must be greater than 0 and at most 1).\n"
                "The passed value is max_mu = %lf. Please change it to be > 0 and <= 1.0\n", max_mu);
        return EXIT_FAILURE;
    }

    if(nmu_bins < 1 ) {
        fprintf(stderr,"Error: Number of mu bins = %d must be at least 1\n", nmu_bins);
        return EXIT_FAILURE;
    }

    //Change cz into co-moving distance
    double *D1 = NULL, *D2 = NULL;
    if(options->is_comoving_dist == 0) {
        D1 = my_malloc(sizeof(*D1),ND1);
        D2 = autocorr == 0 ? my_malloc(sizeof(*D2),ND2):D1;
    } else {
        D1 = czD1;
        D2 = autocorr == 0 ? czD2:czD1;
    }

    if(D1 == NULL || D2 == NULL) {
        free(D1);free(D2);
        return EXIT_FAILURE;
    }

    if(options->is_comoving_dist == 0) {
        //Setup variables to do the cz->comoving distance
        double czmax = 0.0;
        const double inv_speed_of_light = 1.0/SPEED_OF_LIGHT;
        get_max_double(ND1, czD1, &czmax);
        if(autocorr == 0) {
            get_max_double(ND2, czD2, &czmax);
        }
        const double zmax = czmax * inv_speed_of_light + 0.01;

        const int workspace_size = 10000;
        double *interp_redshift  = my_calloc(sizeof(*interp_redshift), workspace_size);//the interpolation is done in 'z' and not in 'cz'
        double *interp_comoving_dist = my_calloc(sizeof(*interp_comoving_dist),workspace_size);
        int Nzdc = set_cosmo_dist(zmax, workspace_size, interp_redshift, interp_comoving_dist, cosmology);
        if(Nzdc < 0) {
            free(interp_redshift);free(interp_comoving_dist);
            return EXIT_FAILURE;
        }

        gsl_interp *interpolation;
        gsl_interp_accel *accelerator;
        accelerator =  gsl_interp_accel_alloc();
        interpolation = gsl_interp_alloc (gsl_interp_linear,Nzdc);
        gsl_interp_init(interpolation, interp_redshift, interp_comoving_dist, Nzdc);
        for(int64_t i=0;i<ND1;i++) {
            D1[i] = gsl_interp_eval(interpolation, interp_redshift, interp_comoving_dist, czD1[i]*inv_speed_of_light, accelerator);
        }

        if(autocorr==0) {
            for(int64_t i=0;i<ND2;i++) {
                D2[i] = gsl_interp_eval(interpolation, interp_redshift, interp_comoving_dist, czD2[i]*inv_speed_of_light, accelerator);
            }
        }
        free(interp_redshift);free(interp_comoving_dist);
        gsl_interp_free(interpolation);
        gsl_interp_accel_free(accelerator);
    }

    double *X1 = my_malloc(sizeof(*X1), ND1);
    double *Y1 = my_malloc(sizeof(*Y1), ND1);
    double *Z1 = my_malloc(sizeof(*Z1), ND1);
    if(X1 == NULL || Y1 == NULL || Z1 == NULL) {
        free(X1);free(Y1);free(Z1);
        return EXIT_FAILURE;
    }
    for(int64_t i=0;i<ND1;i++) {
        X1[i] = D1[i]*COSD(dec1[i])*COSD(ra1[i]);
        Y1[i] = D1[i]*COSD(dec1[i])*SIND(ra1[i]);
        Z1[i] = D1[i]*SIND(dec1[i]);
    }

    double *X2,*Y2,*Z2;
    if(autocorr==0) {
        X2 = my_malloc(sizeof(*X2), ND2);
        Y2 = my_malloc(sizeof(*Y2), ND2);
        Z2 = my_malloc(sizeof(*Z2), ND2);
        for(int64_t i=0;i<ND2;i++) {
            X2[i] = D2[i]*COSD(dec2[i])*COSD(ra2[i]);
            Y2[i] = D2[i]*COSD(dec2[i])*SIND(ra2[i]);
            Z2[i] = D2[i]*SIND(dec2[i]);
        }
    } else {
        X2 = X1;
        Y2 = Y1;
        Z2 = Z1;
    }

    double supp_sqr[nsbin];
    for(int i=0; i < nsbin;i++) {
        supp_sqr[i] = supp[i]*supp[i];
    }
    const double mu_max = (double) max_mu;

    double xmin=1e10,ymin=1e10,zmin=1e10;
    double xmax=-1e10,ymax=-1e10,zmax=-1e10;
    get_max_min_data_double(ND1, X1, Y1, Z1, &xmin, &ymin, &zmin, &xmax, &ymax, &zmax);

    if(autocorr==0) {
        get_max_min_data_double(ND2, X2, Y2, Z2, &xmin, &ymin, &zmin, &xmax, &ymax, &zmax);
    }

    const double xdiff = xmax-xmin;
    const double ydiff = ymax-ymin;
    const double zdiff = zmax-zmin;
    if(get_bin_refine_scheme(options) == BINNING_DFL) {
        if(smax < 0.05*xdiff) {
            options->bin_refine_factors[0] = 1;
      }
        if(smax < 0.05*ydiff) {
            options->bin_refine_factors[1] = 1;
        }
        if(smax < 0.05*zdiff) {
            options->bin_refine_factors[2] = 1;
        }
    }

    /*---Create 3-D lattice--------------------------------------*/
    int nmesh_x=0,nmesh_y=0,nmesh_z=0;
    cellarray_mocks_index_particles_double *lattice1 = gridlink_mocks_index_particles_double(ND1, X1, Y1, Z1, D1, &(extra->weights0),
                                                                                xmin, xmax, ymin, ymax, zmin, zmax,
                                                                                smax, smax, smax,
                                                                                options->bin_refine_factors[0],
                                                                                options->bin_refine_factors[1],
                                                                                options->bin_refine_factors[2],
                                                                                &nmesh_x, &nmesh_y, &nmesh_z,
                                                                                options);
    if(lattice1 == NULL) {
        return EXIT_FAILURE;
    }

    /* If there too few cells (BOOST_CELL_THRESH is ~10), and the number of cells can be increased, then boost bin refine factor by ~1*/
    const double avg_np = ((double)ND1)/(nmesh_x*nmesh_y*nmesh_z);
    const int8_t max_nmesh = fmax(nmesh_x, fmax(nmesh_y, nmesh_z));
    if((max_nmesh <= BOOST_CELL_THRESH || avg_np >= BOOST_NUMPART_THRESH)
        && max_nmesh < options->max_cells_per_dim) {
      fprintf(stderr,"%s> gridlink seems inefficient. nmesh = (%d, %d, %d); avg_np = %.3g. ", __FUNCTION__, nmesh_x, nmesh_y, nmesh_z, avg_np);
      if(get_bin_refine_scheme(options) == BINNING_DFL) {
            fprintf(stderr,"Boosting bin refine factor - should lead to better performance\n");
            // Only boost the first two dimensions.  Prevents excessive refinement.
            for(int i=0;i<2;i++) {
              options->bin_refine_factors[i] += BOOST_BIN_REF;
            }

            free_cellarray_mocks_index_particles_double(lattice1, nmesh_x * (int64_t) nmesh_y * nmesh_z);
            lattice1 = gridlink_mocks_index_particles_double(ND1, X1, Y1, Z1, D1, &(extra->weights0),
                                                             xmin, xmax, ymin, ymax, zmin, zmax,
                                                             smax, smax, smax,
                                                             options->bin_refine_factors[0],
                                                             options->bin_refine_factors[1],
                                                             options->bin_refine_factors[2],
                                                             &nmesh_x, &nmesh_y, &nmesh_z,
                                                             options);
            if(lattice1 == NULL) {
                return EXIT_FAILURE;
            }
        } else {
            fprintf(stderr,"Boosting bin refine factor could have helped. However, since custom bin refine factors "
                  "= (%d, %d, %d) are being used - continuing with inefficient mesh\n", options->bin_refine_factors[0],
                  options->bin_refine_factors[1], options->bin_refine_factors[2]);

        }
    }

    cellarray_mocks_index_particles_double *lattice2 = NULL;
    if(autocorr==0) {
        int ngrid2_x=0,ngrid2_y=0,ngrid2_z=0;
        lattice2 = gridlink_mocks_index_particles_double(ND2, X2, Y2, Z2, D2, &(extra->weights1),
                                                         xmin, xmax,
                                                         ymin, ymax,
                                                         zmin, zmax,
                                                         smax, smax, smax,
                                                         options->bin_refine_factors[0],
                                                         options->bin_refine_factors[1],
                                                         options->bin_refine_factors[2],
                                                         &ngrid2_x, &ngrid2_y, &ngrid2_z, options);
        if(lattice2 == NULL) {
            return EXIT_FAILURE;
        }
        if( ! (nmesh_x == ngrid2_x && nmesh_y == ngrid2_y && nmesh_z == ngrid2_z) ) {
            fprintf(stderr,"Error: The two sets of 3-D lattices do not have identical bins. First has dims (%d, %d, %d) while second has (%d, %d, %d)\n",
                    nmesh_x, nmesh_y, nmesh_z, ngrid2_x, ngrid2_y, ngrid2_z);
            return EXIT_FAILURE;
        }
    } else {
        lattice2 = lattice1;
    }
    free(X1);free(Y1);free(Z1);
    if(autocorr == 0) {
        free(X2);free(Y2);free(Z2);
    }

    if(options->is_comoving_dist == 0) {
        free(D1);
        if(autocorr == 0) {
            free(D2);
        }
    }

    const int64_t totncells = (int64_t) nmesh_x * (int64_t) nmesh_y * (int64_t) nmesh_z;
    {
        int status = assign_ngb_cells_mocks_index_particles_double(lattice1, lattice2, totncells,
                                                                   options->bin_refine_factors[0], options->bin_refine_factors[1], options->bin_refine_factors[2],
                                                                   nmesh_x, nmesh_y, nmesh_z,
                                                                   autocorr);
        if(status != EXIT_SUCCESS) {
            free_cellarray_mocks_index_particles_double(lattice1, totncells);
            if(autocorr == 0) {
                free_cellarray_mocks_index_particles_double(lattice2, totncells);
            }
            free(supp);
            return EXIT_FAILURE;
        }
    }
    /*---Gridlink-variables----------------*/
    const int totnbins = (nmu_bins+1)*(nsbin+1);
    const int nprojbins = nsbin-1;
#if defined(_OPENMP)
    uint64_t **all_npairs = (uint64_t **) matrix_calloc(sizeof(uint64_t), numthreads, totnbins);
    double **all_savg = NULL;
    if(options->need_avg_sep){
        all_savg = (double **) matrix_calloc(sizeof(double),numthreads,totnbins);
    }
    double **all_weightavg = NULL;
    if(need_weightavg) {
      all_weightavg = (double **) matrix_calloc(sizeof(double),numthreads,totnbins);
    }
    double **all_projpairs = (double **) matrix_calloc(sizeof(double),numthreads,nprojbins);
    double **all_projpairs_tensor = (double **) matrix_calloc(sizeof(double),numthreads,nprojbins*nprojbins);

#else //USE_OMP
    uint64_t npairs[totnbins];
    double savg[totnbins], weightavg[totnbins], projpairs[nprojbins];
    double projpairs_tensor[nprojbins*nprojbins];

    for(int i=0; i <totnbins;i++) {
        npairs[i] = 0;
        if(options->need_avg_sep) {
            savg[i] = ZERO;
        }
        if(need_weightavg) {
            weightavg[i] = ZERO;
        }
    }
    for(int i=0;i<nprojbins;i++) {
        projpairs[i] = ZERO;
        for(int j=0;j<nprojbins;j++) {
            projpairs_tensor[i*nprojbins+j] = ZERO;
        }
    }
#endif //USE_OMP

    /* runtime dispatch - get the function pointer */
    countpairs_mocks_func_ptr_double countpairs_s_mu_mocks_function_double = countpairs_s_mu_mocks_driver_double(options);
    if(countpairs_s_mu_mocks_function_double == NULL) {
        return EXIT_FAILURE;
    }

    int interrupted=0,numdone=0, abort_status=EXIT_SUCCESS;
    if(options->verbose) {
        init_my_progressbar(totncells,&interrupted);
    }

#if defined(_OPENMP)
#pragma omp parallel shared(numdone, abort_status, interrupt_status_DDsmu_mocks_double)
    {
        const int tid = omp_get_thread_num();
        uint64_t npairs[totnbins];
        double savg[totnbins], weightavg[totnbins], projpairs[nprojbins];
        double projpairs_tensor[nprojbins*nprojbins];

        for(int i=0;i<totnbins;i++) {
            npairs[i] = 0;
            if(options->need_avg_sep) {
                savg[i] = ZERO;
            }
            if(need_weightavg) {
                weightavg[i] = ZERO;
            }
        }
        for(int i=0;i<nprojbins;i++) {
            projpairs[i] = ZERO;
            for(int j=0;j<nprojbins;j++) {
                projpairs_tensor[i*nprojbins+j] = ZERO;
            }
        }

#pragma omp for  schedule(dynamic)
#endif//USE_OMP

        /*---Loop-over-Data1-particles--------------------*/
        for(int64_t index1=0;index1<totncells;index1++) {

#if defined(_OPENMP)
#pragma omp flush (abort_status, interrupt_status_DDsmu_mocks_double)
#endif
            if(abort_status == EXIT_SUCCESS && interrupt_status_DDsmu_mocks_double == EXIT_SUCCESS) {
                //omp cancel was introduced in omp 4.0 - so this is my way of checking if loop needs to be cancelled
                /* If the verbose option is not enabled, avoid outputting anything unnecessary*/
                if(options->verbose) {
#if defined(_OPENMP)
                    if (omp_get_thread_num() == 0)
#endif
                        my_progressbar(numdone,&interrupted);

#if defined(_OPENMP)
#pragma omp atomic
#endif
                    numdone++;
                }

                const cellarray_mocks_index_particles_double *first  = &(lattice1[index1]);
                if(first->nelements == 0) {
                    continue;
                }
                double *x1 = first->x;
                double *y1 = first->y;
                double *z1 = first->z;
                double *d1 = first->cz;
                const weight_struct_double *weights1 = &(first->weights);
                const int64_t N1 = first->nelements;

                if(autocorr == 1) {
                    int same_cell = 1;
                    double *this_savg = options->need_avg_sep ? &(savg[0]):NULL;
                    double *this_weightavg = need_weightavg ? weightavg:NULL;
                    const int status = countpairs_s_mu_mocks_function_double(N1, x1, y1, z1, d1, weights1,
                                                                             N1, x1, y1, z1, d1, weights1,
                                                                             same_cell,
                                                                             options->fast_divide_and_NR_steps,
                                                                             smax, smin, nsbin,
                                                                             nmu_bins, supp_sqr, mu_max,
                                                                             this_savg, npairs, projpairs,
                                                                             projpairs_tensor,
                                                                             this_weightavg, extra->weight_method);
                    /* This actually causes a race condition under OpenMP - but mostly
                       I care that an error occurred - rather than the exact value of
                       the error status */
                    abort_status |= status;
                }

                for(int64_t ngb=0;ngb<first->num_ngb;ngb++){
                    const cellarray_mocks_index_particles_double *second = first->ngb_cells[ngb];
                    if(second->nelements == 0) {
                        continue;
                    }
                    const int same_cell = 0;
                    double *x2 = second->x;
                    double *y2 = second->y;
                    double *z2 = second->z;
                    double *d2 = second->cz;
                    const weight_struct_double *weights2 = &(second->weights);
                    const int64_t N2 = second->nelements;
                    double *this_savg = options->need_avg_sep ? &(savg[0]):NULL;
                    double *this_weightavg = need_weightavg ? weightavg:NULL;
                    const int status = countpairs_s_mu_mocks_function_double(N1, x1, y1, z1, d1, weights1,
                                                                             N2, x2, y2, z2, d2, weights2,
                                                                             same_cell,
                                                                             options->fast_divide_and_NR_steps,
                                                                             smax, smin, nsbin,
                                                                             nmu_bins, supp_sqr, mu_max,
                                                                             this_savg, npairs, projpairs,
                                                                             projpairs_tensor,
                                                                             this_weightavg, extra->weight_method);
                    /* This actually causes a race condition under OpenMP - but mostly
                       I care that an error occurred - rather than the exact value of
                       the error status */
                    abort_status |= status;
                }//loop over ngb cells
            }//abort_status check
        }//i loop over ND1 particles
#if defined(_OPENMP)
        for(int i=0;i<totnbins;i++) {
            all_npairs[tid][i] = npairs[i];
            if(options->need_avg_sep) {
                all_savg[tid][i] = savg[i];
            }
            if(need_weightavg) {
                all_weightavg[tid][i] = weightavg[i];
            }
        }
        for (int i=0;i<nprojbins;i++) {
            all_projpairs[tid][i] = projpairs[i];
            for(int j=0;j<nprojbins;j++) {
                all_projpairs_tensor[tid][i*nprojbins+j] = projpairs_tensor[i*nprojbins+j];
            }
        }
    }//close the omp parallel region
#endif//USE_OMP

    free_cellarray_mocks_index_particles_double(lattice1,totncells);
    if(autocorr == 0) {
        free_cellarray_mocks_index_particles_double(lattice2,totncells);
    }

    if(abort_status != EXIT_SUCCESS || interrupt_status_DDsmu_mocks_double != EXIT_SUCCESS) {
        /* Cleanup memory here if aborting */
        free(supp);
#if defined(_OPENMP)
        matrix_free((void **) all_npairs, numthreads);
        if(options->need_avg_sep) {
            matrix_free((void **) all_savg, numthreads);
        }
        if(need_weightavg) {
            matrix_free((void **) all_weightavg, numthreads);
        }
        matrix_free((void **) all_projpairs, numthreads);
        matrix_free((void **) all_projpairs_tensor, numthreads);
#endif
        return EXIT_FAILURE;
    }

    if(options->verbose) {
        finish_myprogressbar(&interrupted);
    }

#if defined(_OPENMP)
    uint64_t npairs[totnbins];
    double savg[totnbins], weightavg[totnbins], projpairs[nprojbins];
    double projpairs_tensor[nprojbins*nprojbins];
    for(int i=0;i<totnbins;i++) {
        npairs[i] = 0;
        if(options->need_avg_sep) {
            savg[i] = ZERO;
        }
        if(need_weightavg) {
            weightavg[i] = ZERO;
        }
    }
    for(int i=0;i<nprojbins;i++) {
        projpairs[i] = ZERO;
        for(int j=0;j<nprojbins;j++) {
            projpairs_tensor[i*nprojbins+j] = ZERO;
        }
    }

    for(int i=0;i<numthreads;i++) {
        for(int j=0;j<totnbins;j++) {
            npairs[j] += all_npairs[i][j];
            if(options->need_avg_sep) {
                savg[j] += all_savg[i][j];
            }
            if(need_weightavg) {
                weightavg[j] += all_weightavg[i][j];
            }
        }
        for(int j=0;j<nprojbins;j++) {
            projpairs[j] += all_projpairs[i][j];
            for(int k=0;k<nprojbins;k++) {
                projpairs_tensor[j*nprojbins+k] += all_projpairs_tensor[i][j*nprojbins+k];
            }
        }
    }
    matrix_free((void **) all_npairs, numthreads);
    if(options->need_avg_sep) {
        matrix_free((void **) all_savg, numthreads);
    }
    if(need_weightavg) {
        matrix_free((void **) all_weightavg, numthreads);
    }
    matrix_free((void **) all_projpairs, numthreads);
    matrix_free((void **) all_projpairs_tensor, numthreads);

#endif //USE_OMP

    //The code does not double count for autocorrelations
    //which means the npairs and savg values need to be doubled;
    if(autocorr == 1) {
        const uint64_t int_fac = 2;
        const double dbl_fac = (double) 2.0;
        for(int i=0;i<totnbins;i++) {
            npairs[i] *= int_fac;
            if(options->need_avg_sep) {
                savg[i] *= dbl_fac;
            }
            if(need_weightavg) {
                weightavg[i] *= dbl_fac;
            }
        }
        //TODO: do i also want to double this? think so
        for(int i=0;i<nprojbins;i++) {
            projpairs[i] *= dbl_fac;
            for(int j=0;j<nprojbins;j++) {
                projpairs_tensor[i*nprojbins+j] *= dbl_fac;
            }
        }

    }

    for(int i=0;i<totnbins;i++) {
        if(npairs[i] > 0) {
            if(options->need_avg_sep) {
                savg[i] /= (double) npairs[i] ;
            }
            if(need_weightavg) {
                weightavg[i] /= (double) npairs[i];
            }
        }
    }
    // don't need proj_pairs here, not averaging

    results->nsbin   = nsbin;
    results->nmu_bins = nmu_bins;
    results->mu_max = max_mu;//NOTE max_mu which is double and not mu_max (which might be float)
    results->mu_min = ZERO;
    results->npairs = my_malloc(sizeof(*(results->npairs)), totnbins);
    results->projpairs = my_malloc(sizeof(*(results->npairs)), nprojbins);
    results->projpairs_tensor = my_malloc(sizeof(*(results->npairs)), nprojbins*nprojbins);
    results->supp   = my_malloc(sizeof(*(results->supp))  , nsbin);
    results->savg  = my_malloc(sizeof(*(results->savg)) , totnbins);
    results->weightavg  = my_calloc(sizeof(double)  , totnbins);
    if(results->npairs == NULL || results->supp == NULL || results->savg == NULL || results->weightavg == NULL || results->projpairs == NULL) {
        free_results_mocks_s_mu(results);
        free(supp);
        return EXIT_FAILURE;
    }

    for(int i=0;i<nsbin;i++) {
        results->supp[i] = supp[i];
        for(int j=0;j<nmu_bins;j++) {
            const int index = i*(nmu_bins+1) + j;
            if( index >= totnbins ) {
                fprintf(stderr, "ERROR: In %s> index = %d must be in range [0, %d)\n", __FUNCTION__, index, totnbins);
                free_results_mocks_s_mu(results);
                free(supp);
                return EXIT_FAILURE;
            }
            results->npairs[index] = npairs[index];
            results->savg[index] = ZERO;
            results->weightavg[index] = ZERO;
            if(options->need_avg_sep) {
                results->savg[index] = savg[index];
            }
            if(need_weightavg) {
                results->weightavg[index] = weightavg[index];
            }
        }
    }
    for(int i=0;i<nprojbins;i++) {
        results->projpairs[i] = projpairs[i];
        for(int j=0;j<nprojbins;j++) {
                results->projpairs_tensor[i*nprojbins+j] = projpairs_tensor[i*nprojbins+j];
            }
    }
    free(supp);

    /* reset interrupt handlers to default */
    RESET_INTERRUPT_HANDLERS();
    reset_bin_refine_factors(options);

    if(options->c_api_timer) {
        struct timeval t1;
        gettimeofday(&t1, NULL);
        options->c_api_time = ADD_DIFF_TIME(t0, t1);
    }

    return EXIT_SUCCESS;
}


#ifndef COVAR_H_
#define COVAR_H_

#include <stdio.h>
#include <gsl/gsl_statistics_double.h>
#include <gsl/gsl_matrix_double.h>
#include <vector>

// \TODO: trim excess comments
// \TODO: split this into an implementation file

/*
 *  Okay, so here's what we need
 *  1) A data structure to store the data points (vector?)
 *  2) A way of packaging the data points (struct of floats?)
 *  3) A method to push new data points onto the data structure
 *
 *  So the above is an API towards collecting data. On the ROS side,
 *  1) As new messages from the ROS topic arrives, we need the
 *     data_structure.push() to happen
 *
 *  Next, we need a method for generating the covariance matrix. This
 *  should happen after the data collection period has expired. What needs
 *  to happen when we generate the covariance matrix?
 *  1) Convert the data_structure into a gsl_matrix
 *  2) Don't forget to allocate another gsl_matrix which will hold the
 *     resulting covariance matrix.
 *  3) Double for-loop to generate the variance-covariance value for each
 *     element of the matrix
 */

// the covariance matrix of our odometry topic tracks six variables
// as in, think of each data point as a six-dimensional point with the
// following axes: x, y, z, roll, pitch, yaw
#define ODOM_COVAR_MAT_VARSIZE 6

// struct odom_struct
// {
//     double x;
//     double y;
//     double z;
//     double roll;
//     double pitch;
//     double yaw;
// };

// Since we're traversing through the data points using a loop, maybe it'd
// be better to use an array of doubles rather than a struct
// double * data_point = new data_point[6];
// We establish the convention here:
// index: 0  1  2  3    4      5
// axis:  x, y, z, roll, pitch, yaw

// However, from a maintainability perspective, relying on a double
// pointer is bad - there is no way of guaranteeing the fixed six-element
// size that we want for data point
// \TODO: create a better container (i.e. class) for each data point that
//        has as a member method
//        1) the square bracket operator, for fetching an element

class covar
{
    private:
        gsl_matrix *covar_mat;
        std::vector<double*> *data_points;

    public:
        covar()
        {
            data_points = new std::vector<double*>;
            covar_mat = gsl_matrix_alloc(ODOM_COVAR_MAT_VARSIZE,
                                         ODOM_COVAR_MAT_VARSIZE);
        }

        ~covar()
        {
            delete covar_mat;
            covar_mat = 0;
            delete data_points;
            data_points = 0;
        }

        // We expect the input to be a double array with six elements
        bool insert(double* data_point)
        {
            // double * temp = new double[6];
            data_points->push_back(data_point);
            return true;
        }

        void print_dp()
        {
            for (int i = 0; i < data_points->size(); i++)
            {
                printf("%i: %f %f %f %f %f %f\n",
                            i,
                            (*data_points)[i][0],
                            (*data_points)[i][1],
                            (*data_points)[i][2],
                            (*data_points)[i][3],
                            (*data_points)[i][4],
                            (*data_points)[i][5]);
            }
        }

        void print_mat()
        {
            for (int i = 0; i < covar_mat->size1; i++)
            {
                printf("\n");
                for (int j = 0; j < covar_mat->size2; j++)
                {
                    printf("%f", covar_mat->data[covar_mat->size2*i+j]);
                    if(j < covar_mat->size2 - 1) printf(", ");
                }
            }
            printf("\n");
        }

        // \TODO: if covar_mat not available, deny get_mat
        bool get_mat(double* array)
        {
            for(int i = 0; i < 36; i++)
                array[i] = gsl_matrix_get(covar_mat,
                                          i/6,  //row
                                          i%6); //column

            return true;
        }

        bool generate_covar()
        {
            /* no point in generating a covariance matrix if we don't have
             * the data points to do so
             */
            int i, j;
            int rsize = data_points->size();

            if(rsize <= 1) return false;

            gsl_vector_view a, b;
            gsl_matrix *A;
            A = gsl_matrix_alloc(rsize, ODOM_COVAR_MAT_VARSIZE);
            /* the covariance matrix should already be instantiated by the
             * class constructor
             */
            // covar_mat = gsl_matrix_alloc(ODOM_COVAR_MAT_VARSIZE,
            //                              ODOM_COVAR_MAT_VARSIZE);

            // convert data_points into gsl_matrix A
            for ( i = 0; i < rsize; i++)
                for ( j = 0; j < ODOM_COVAR_MAT_VARSIZE; j++)
                    gsl_matrix_set (A, i, j, (*data_points)[i][j]);

            // generate the var-covar values based on A
            // insert these values into covar_mat
            for (i = 0; i < A->size2; i++) {
                for (j = 0; j < A->size2; j++) {
                a = gsl_matrix_column (A, i);
                b = gsl_matrix_column (A, j);
                double cov = gsl_stats_covariance(a.vector.data,
                                                    a.vector.stride,
                                                    b.vector.data,
                                                    b.vector.stride,
                                                    rsize);
                gsl_matrix_set (covar_mat, i, j, cov);
                }
            }

            return true;
        }
};

#endif // COVAR_H_


#include <gsl/gsl_math.h>
#include "gsl_cblas.h"
#include "cblas.h"

float
cblas_sdsdot (const int N, const float alpha, const float *X, const int incX,
	      const float *Y, const int incY)
{
#define INIT_VAL  alpha
#define ACC_TYPE  double
#define BASE float
#include "source_dot_r.h"
#undef ACC_TYPE
#undef BASE
#undef INIT_VAL
}


/* Copyright 2018,2019, Alistair Boyle, 3-clause BSD License */
#include "config.h"
#include <string.h> /* memset */
#include <stdlib.h> /* free */
#include <lapacke.h> /* inv: dgetrf, dgetri */
#include <math.h> /* fabs, sqrt */
#include <limits.h> /* MAX_INT */
#include <assert.h> /* assert */
#include <stdio.h> /* printf */

#include "matrix.h"
#include "model.h"

/* references:
 * [1] A. Boyle, PhD Thesis, 2016, Geophysical Applications of Electrical Impedance Tomography, Carleton University
 */

#ifdef UNIT_TESTING
extern void * _mock_test_malloc(const size_t size, const char * file, const int line);
extern void _test_free(void * const ptr, const char * file, const int line);
#define malloc(size) _mock_test_malloc(size, __FILE__, __LINE__)
#define free(ptr) _test_free(ptr, __FILE__, __LINE__)
#endif

#define SUCCESS 1
#define FAILURE 0

static void init_mesh(mesh * m)
{
    memset(m, 0, sizeof(mesh));
}

static void init_model(model * m)
{
    memset(m, 0, sizeof(model));
}

model * malloc_model()
{
    model * m = malloc(sizeof(model));
    if(m) {
        init_model(m);
    }
    return m;
}

static void free_mesh(mesh * m)
{
    free(m->nodes);
    free(m->elems);
    free(m->matidx);
    free(m->surfaceelems);
    free(m->bc);
    free(m->pmap_param);
    free(m->pmap_elem);
    free(m->pmap_frac);
}

void reset_model(model * m)
{
    assert(m != NULL);
    free(m->data);
    free(m->params);
    free(m->stimmeas);
    free(m->measgain);
    free(m->elec_to_sys);
    free(m->zcbc);
    free(m->zc);
    free_mesh(&(m->fwd));
    free_mesh(&(m->rec));
    init_model(m);
}

void * free_model(model * m)
{
    if(m == NULL) {
        return NULL;
    }
    reset_model(m);
    free(m);
    return NULL;
}

int set_model_data(model * m, int rows, int cols)
{
    assert(rows > 0);
    assert(cols > 0);
    assert(m != NULL);
    m->n_data[0] = rows;
    m->n_data[1] = cols;
    free(m->data);
    m->data = malloc(sizeof(double) * rows * cols);
    if(m->data == NULL) {
        return FAILURE;
    }
    return SUCCESS;
}

int set_model_params(model * m, int rows, int cols)
{
    assert(rows > 0);
    assert(cols > 0);
    assert(m != NULL);
    m->n_params[0] = rows;
    m->n_params[1] = cols;
    free(m->params);
    m->params = malloc(sizeof(double) * rows * cols);
    if(m->params == NULL) {
        return FAILURE;
    }
    return SUCCESS;
}

int set_model_stimmeas(model * m, int rows)
{
    assert(rows > 0);
    assert(m != NULL);
    m->n_stimmeas = rows;
    m->n_elec = 0;
    free(m->elec_to_sys);
    m->elec_to_sys = NULL;
    free(m->stimmeas);
    m->stimmeas = malloc(sizeof(int) * rows * 4);
    if(m->stimmeas == NULL) {
        return FAILURE;
    }
    free(m->measgain);
    m->measgain = malloc(sizeof(double) * rows);
    if(m->measgain == NULL) {
        return FAILURE;
    }
    return SUCCESS;
}

void set_mesh_dim(mesh * m, int dim)
{
    assert(dim == 2 || dim == 3);
    assert(m != NULL);
    free_mesh(m);
    init_mesh(m);
    m->dim = dim;
}

int set_mesh_nodes(mesh * m, int n_nodes)
{
    assert(n_nodes >= 0);
    assert(m != NULL);
    assert(m->dim == 2 || m->dim == 3);
    m->n_nodes = n_nodes;
    free(m->nodes);
    m->nodes = malloc(sizeof(double) * n_nodes * m->dim);
    if(m->nodes == NULL) {
        return FAILURE;
    }
    return SUCCESS;
}

int set_mesh_elems(mesh * m, int n_elems)
{
    assert(n_elems >= 0);
    assert(m != NULL);
    assert(m->dim == 2 || m->dim == 3);
    m->n_elems = n_elems;
    free(m->elems);
    m->elems = malloc(sizeof(int) * n_elems * (m->dim + 1));
    if(m->elems == NULL) {
        return FAILURE;
    }
    free(m->matidx);
    m->matidx = malloc(sizeof(int) * n_elems);
    if(m->matidx == NULL) {
        return FAILURE;
    }
    return SUCCESS;
}

int set_mesh_surfaceelems(mesh * m, int n_se)
{
    assert(n_se >= 0);
    assert(m != NULL);
    assert(m->dim == 2 || m->dim == 3);
    m->n_se = n_se;
    free(m->surfaceelems);
    m->surfaceelems = malloc(sizeof(int) * n_se * m->dim);
    if(m->surfaceelems == NULL) {
        return FAILURE;
    }
    free(m->bc);
    m->bc = malloc(sizeof(int) * n_se);
    if(m->bc == NULL) {
        return FAILURE;
    }
    return SUCCESS;
}

int set_mesh_pmap(mesh * m, int n_pmap)
{
    assert(n_pmap >= 0);
    assert(m != NULL);
    m->n_pmap = n_pmap;
    free(m->pmap_param);
    m->pmap_param = malloc(sizeof(int) * n_pmap);
    if(m->pmap_param == NULL) {
        return FAILURE;
    }
    free(m->pmap_elem);
    m->pmap_elem = malloc(sizeof(int) * n_pmap);
    if(m->pmap_elem == NULL) {
        return FAILURE;
    }
    free(m->pmap_frac);
    m->pmap_frac = malloc(sizeof(double) * n_pmap);
    if(m->pmap_frac == NULL) {
        return FAILURE;
    }
    return SUCCESS;
}

int set_model_zc(model * m, int rows)
{
    assert(rows > 0);
    assert(m != NULL);
    m->n_zc = rows;
    free(m->zc);
    m->zc = malloc(sizeof(double) * rows);
    if(m->zc == NULL) {
        return FAILURE;
    }
    free(m->zcbc);
    m->zcbc = malloc(sizeof(int) * rows);
    if(m->zcbc == NULL) {
        return FAILURE;
    }
    return SUCCESS;
}

int calc_elec_zc_map(model * m)
{
    assert(m != NULL);
    assert(m->n_elec > 0);
    double * tmp = malloc(sizeof(double) * m->n_elec);
    if(tmp == NULL) {
        return FAILURE;
    }
    if((m->n_zc == m->n_elec) && (m->zcbc == NULL)) {
        free(tmp);
        return SUCCESS;
    }
    for(int i = 0; i < m->n_elec; i++) {
        tmp[i] = 1e-2; /* default zc = 0.01 Ω/m² */
    }
    for(int i = 0; i < m->n_zc; i++) {
        /* TODO for now we assume that boundary condition number = electrode# */
        const int elec = m->zcbc[i];
        assert(elec > 0);
        assert(elec <= m->n_elec);
        tmp[elec - 1] = m->zc[i];
    }
    free(m->zcbc);
    free(m->zc);
    m->zcbc = NULL;
    m->zc = tmp;
    m->n_zc = m->n_elec;
    return SUCCESS;
}

double * inv(int n, double A[n][n])
{
    double * Ap = &(A[0][0]);
    int ipiv[n + 1];
    memset(ipiv, 0, (n + 1)*sizeof(int));
    if(LAPACKE_dgetrf(LAPACK_ROW_MAJOR, n, n, Ap, n, ipiv) ||
       LAPACKE_dgetri(LAPACK_ROW_MAJOR, n, Ap, n, ipiv)) {
        return NULL;
    }
    return Ap;
}

double det(int n, double A[n][n])
{
    if(n == 2) {
        return A[0][0] * A[1][1] - A[0][1] * A[1][0];
    }
    double d = 0;
    double sign = 1.0;
    for(int i = 0; i < n; i++, sign *= -1.0) {
        /* construct sub-matrix B */
        double B[n - 1][n - 1];
        for(int j = 1; j < n; j++) {
            for(int k = 0, s = 0; k < n; k++) {
                if(k != i) {
                    B[j - 1][s] = A[j][k];
                    s++;
                }
            }
        }
        d += sign * A[0][i] * det(n - 1, B); /* (-1)^n * a1i * |B| */
    }
    return d;
}

/* shape function
 * Se from [1] eq (C.31) -- first-order tetrahedral/triangular elements
 * Se is symmetric, only the lower triangular portion is calculated (E1^T E1 is symmetric)
 * inputs: nd = # of dimensions (2=2D, 3=3D),
 *         N = ptrs to node coordinates (nd+1 nodes with nd double precision values)
 *             ex: {&n[120], &n[332], &n[556], &n[733]} for 3D, where n[120] = {1.1, 0.1, 3.2}, etc.
 *             NOTE: nodes should be in increasing order so that calc_sys_elem_ij
 *                   gives an upper triangular matrix in the global node numbering
 * output: Se = replaces Se[0..t-1] for t = n(n+1)/2 entries with n=nd+1 elements as
 *         upper triangular symmetric matrix, row-major order
 * returns: NULL if matrix inverse fails, Se otherwise
 */
static double calc_elem_vol(const int n, double const * const * const nodes, double E[n][n])
{
    for(int i = 0; i < n; i++) {
        E[i][0] = 1;
        for(int j = 1; j < n; j++) {
            E[i][j] = nodes[i][j - 1];
        }
    }
    if(inv(n, E) == NULL) {
        return -1.0;
    }
    return fabs(det(n, E));
}

static double * calc_sys_elem_v(const int nd, double const * const * const nodes, double * Se)
{
    const int n = nd + 1;
    double E[n][n]; /* from [1] eq (C.33) */
    const double detE = calc_elem_vol(n, nodes, E);
    if(detE < 0) {
        return NULL;
    }
    int ndf = 1; /* = !nd = factorial(number of dimensions) */
    for(int i = 2; i <= nd; i++ ) {
        ndf *= i;
    }
    const double c = 1 / (ndf * detE);
    /* Se = c * E1^T E1, where E1 is E without the top row */
    int m = 0;
    for(int i = 0; i < n; i++ ) {
        for(int j = i; j < n; j++ ) {
            Se[m] = 0;
            for(int k = 1; k < n; k++) {
                Se[m] += E[k][i] * E[k][j];
            }
            /* TODO pull this out and multiply by conductivity D, to save some multiply operations */
            Se[m] *= c;
            m++;
        }
    }
    return Se;
}

static void calc_sys_elem_ij(const int nd, int const * const elem, int * ii, int * jj)
{
    const int n = nd + 1;
    int idx = 0;
    for(int i = 0; i < n; i++ ) {
        for(int j = i; j < n; j++ ) {
            int ei = elem[i] - 1;
            int ej = elem[j] - 1;
            /* swap indices (symmetric matrix) if this entry would be in the
             * upper triangular portion of the matrix */
            if(ei < ej) {
                int tmp = ei;
                ei = ej;
                ej = tmp;
            }
            ii[idx] = ei;
            jj[idx] = ej;
            idx++;
        }
    }
}

int calc_sys_elem_n(const int nd)
{
    /* nd=2: 6, nd=3: 10 ... for symmetric (isotropic conductivity)*/
    const int n = nd + 1;
    return n * (n + 1) / 2;
}

/* returns 0 on success, > 0 on failure as the singular element number e+1 */
int calc_sys_elem(mesh const * const m, int * ii, int * jj, double * Se)
{
    const int nd = m->dim;
    const int n_elems = m->n_elems;
    const int * elems = m->elems;
    const double * nodes = m->nodes;
    const int n = calc_sys_elem_n(nd);
    for(int i = 0; i < n_elems; i++) {
        calc_sys_elem_ij(nd, elems, ii, jj);
        double const * node_list [4];
        for(int j = 0; j < nd + 1; j ++) {
            int idx = elems[j] - 1;
            if ( idx < 0 ) {
                idx = 0;
            }
            node_list[j] = &(nodes[idx * nd]);
        }
        if(calc_sys_elem_v(nd, node_list, Se) == NULL) {
            return i + 1;
        }
        elems += (nd + 1);
        ii += n;
        jj += n;
        Se += n;
    }
    return 0;
}

void calc_sys_gnd(const int gnd, size_t nnz, int * ii, int * jj, double * Se)
{
    const int gndidx = gnd - 1;
    int ret = 0;
    for (int i = 0; i < nnz; i++ ) {
        const int is_row = (ii[i] == gndidx);
        const int is_col = (jj[i] == gndidx);
        if ( is_row || is_col) {
            if(ret == 0) {
                /* replace with (gndidx,gndidx)=1.0 */
                ii[i] = gndidx;
                jj[i] = gndidx;
                Se[i] = 1.0;
            }
            else {
                /* drop this entry */
                Se[i] = 0.0;
            }
            ret++;
        }
    }
    assert(ret != 0);
}

#define dot2(a,b) (((a[0])*(b[0])) + ((a[1])*(b[1])))
#define dot3(a,b) (((a[0])*(b[0])) + ((a[1])*(b[1])) + ((a[2])*(b[2])))
#define sum_sq2(a) dot2(a,a)
#define sum_sq3(a) dot3(a,a)
#define cross3i(a,b) (((a[1])*(b[2])) - ((a[2])*(b[1])))
#define cross3j(a,b) (((a[2])*(b[0])) - ((a[0])*(b[2])))
#define cross3k(a,b) (((a[0])*(b[1])) - ((a[1])*(b[0])))

// static void printf_vec(const int nd, double x[nd])
// {
//     printf(" (");
//     int i;
//     for(i = 0; i < nd - 1; i++) {
//         printf("%g, ", x[i]);
//     }
//     printf("%g", x[i]);
//     printf(")\n");
// }

static double calc_elem_area(const int nd, double nodes[nd][nd])
{
    /* len = diff(nodes) .. point to point distances */
    // for(int i = 0; i < nd; i++) {
    //     printf("[%d] ", i); printf_vec(nd, nodes[i]);
    // }
    for(int i = 0; i < nd - 1; i++) {
        for(int j = 0; j < nd; j++) {
            nodes[i][j] -= nodes[i + 1][j]; /* subtract the row below */
        }
        // printf("[%d]-[%d] ", i, i + 1); printf_vec(nd, nodes[i]);
    }
    if(nd == 3) {
        /* cross product of lengths c = a x b*/
        // printf("%gi\n",nodes[0][1]*nodes[1][2] - nodes[0][2]*nodes[1][1]);
        // printf("%gj\n",nodes[0][2]*nodes[1][0] - nodes[0][0]*nodes[1][2]);
        // printf("%gk\n",nodes[0][0]*nodes[1][1] - nodes[0][1]*nodes[1][0]);
        const double tmp[3] = {
            cross3i(nodes[0], nodes[1]),
            cross3j(nodes[0], nodes[1]),
            cross3k(nodes[0], nodes[1]),
        };
        // printf("[0] x [1]: "); printf_vec(nd, tmp);
        assert(dot3(tmp, nodes[0]) == 0); /* confirm c . a = 0 */
        assert(dot3(tmp, nodes[1]) == 0); /* confirm c . b = 0 */
        nodes[0][0] = sum_sq3(tmp);
        // printf("∑[0]²: "); printf_vec(1, nodes[0]);
    }
    else {
        nodes[0][0] = sum_sq2(nodes[0]);
        // printf("∑[0]²: "); printf_vec(1, nodes[0]);
    }
    const double area = sqrt(nodes[0][0]) / (double)(nd - 1);
    return area;
}

static void copy_nodes_from_surfaceelems(mesh const * const m, const int se_idx, const int nd, double nodes[nd][nd])
{
    const int dim = m->dim;
    for(int i = 0; i < dim; i++) {
        const int nidx = m->surfaceelems[se_idx * dim + i] - 1; /* node index */
        for(int j = 0; j < dim; j++) {
            nodes[i][j] = m->nodes[nidx * dim + j];
        }
    }
}

static int max_bc(mesh const * const m)
{
    int max = -1;
    for(int i = 0; i < m->n_se; i++ ) {
        const int bci = m->bc[i];
        if (bci > max) {
            max = bci;
        }
    }
    return max;
}

int calc_elec_to_sys_map(model * m)
{
    assert(m != NULL);
    free(m->elec_to_sys);
    m->n_elec = 0;
    m->elec_to_sys = NULL;
    const int dim = m->fwd.dim;
    const int n_bc = max_bc(&(m->fwd)) + 1;
    if(n_bc < 1) {
        return SUCCESS;
    }
    int cnt[n_bc];
    int nodes[n_bc];
    int pem_nodes[n_bc];
    for(int i = 0; i < n_bc; i++) {
        cnt[i] = 0;
        nodes[i] = 0;
        pem_nodes[i] = 0;
    }
    for(int i = 0; i < m->fwd.n_se; i ++ ) {
        const int bci = m->fwd.bc[i];
        cnt[bci]++;
        for(int j = 0; j < dim; j++) {
            const int nidx = m->fwd.surfaceelems[i * dim + j];
            if(nidx > 0) {
                nodes[bci]++;
                pem_nodes[bci] = nidx - 1;
            }
        }
    }
    /* we assume the BC=0 is the generic boundary with no electrodes, so start at i=1 */
    int n_elec = 0;
    for(int i = 1; i < n_bc; i++) {
        if(cnt[i] > 0) {
            n_elec++;
        }
    }
    m->elec_to_sys = malloc(sizeof(int) * n_elec);
    if(m->elec_to_sys == NULL) {
        return FAILURE;
    }
    m->n_elec = n_elec;
    int n_cem = 0;
    int electrode = 0;
    for(int i = 1; i < n_bc; i++) {
        if((cnt[i] == 1) && (nodes[i] == 1)) { /* PEM */
            m->elec_to_sys[electrode++] = pem_nodes[i];
        }
        else if(cnt[i] > 0) { /* CEM */
            m->elec_to_sys[electrode++] = n_cem + m->fwd.n_nodes;
            n_cem++;
        }
    }
    assert(electrode == n_elec);
    return SUCCESS;
}

static int count_se_for_this_bc(mesh const * const m, int bc)
{
    int n = 0;
    for(int i = 0; i < m->n_se; i ++ ) {
        const int bci = m->bc[i];
        if (bci == bc) {
            n++;
        }
    }
    return n;
}

/* Construct a dense column vector for Neumann (current) stimulus 'amp' on
 * boundary m->bc = bc.
 * Returns number of nodes perturbed: 0 = failure. */
int calc_stim_neumann(mesh const * const m, double amp, int bc, double * b)
{
    assert(m != NULL);
    assert(b != NULL);
    const int dim = m->dim;
    const int n = count_se_for_this_bc(m, bc);
    int cnt_bc_local_nodes = n * dim;
    int list[n];
    double area[n];
    double total_area = 0;
    for(int i = 0, j = 0; i < m->n_se; i ++ ) {
        const int bci = m->bc[i];
        if (bci != bc) {
            continue;
        }
        double tmp[dim][dim];
        copy_nodes_from_surfaceelems(m, i, dim, tmp);
        area[j] = calc_elem_area(dim, tmp);
        total_area += area[j];
        // printf("bc#%d: area[%d]=%g\n", bc, j, area[j]);
        list[j] = i;
        j++;
    }
    /* set boundary condition */
    for(int j = 0; j < n; j++ ) {
        const int i = list[j];
        for(int k = 0; k < dim; k++) {
            const int nidx = m->surfaceelems[i * dim + k] - 1;
            // printf("#%d nidx=%d %g*%g/%g %d\n",j,nidx,amp, area[j],total_area,dim);
            b[ nidx ] += amp * (area[j] / total_area) / (double) dim;
        }
    }
    return cnt_bc_local_nodes;
}

int calc_sys_cem_n(const int nd)
{
    /* nn = 2D: 3x3 sym, 3D: 4x4 sym
     * n_{bc=i} = ∑(n_se_{bc=i})x(nn) for n_se_{bc=i} = number of surface elements with BC=i
     * total CEM system matrix elements is then
     *   n = ∑_{i=1..ne} n_{bc=i} */
    return (nd == 2) ? 6 : 10;
}

int calc_sys_cem(mesh const * const m, int bc, double zc, int cem_node, int Se_idx, int * ii, int * jj, double * Se)
{
    /* For each segment of the CEM add
     * 2D:    cem   --   n1    n2
     * A_2d = 6     0    -3    -3   * 1/6 * length / zc
     *        0     0     0     0
     *       -3     0     2     1
     *       -3     0     1     2
     * 3D:    cem   --   n1    n2    n3
     * A_3d = 12    0    -4    -4    -4 * 1/12 * area / zc
     *         0    0     0     0     0
     *        -4    0     2     1     1
     *        -4    0     1     2     1
     *        -4    0     1     1     2
     */
    assert(m != NULL);
    const int Se_idx_start = Se_idx;
    const int dim = m->dim;
    const int n = count_se_for_this_bc(m, bc);
    int list[n];
    double area[n];
    double total_area = 0;
    for(int i = 0, j = 0; i < m->n_se; i ++ ) {
        const int bci = m->bc[i];
        if (bci != bc) {
            continue;
        }
        double tmp[dim][dim];
        copy_nodes_from_surfaceelems(m, i, dim, tmp);
        area[j] = calc_elem_area(dim, tmp);
        total_area += area[j];
        // printf("bc#%d: area[%d]=%g\n", bc, j, area[j]);
        list[j] = i;
        j++;
    }
    for(int j = 0; j < n; j++ ) {
        const int i = list[j];
        const double scale = (dim == 2) ? 6.0 : 12.0;
        const double area_zc = area[j] / zc;
        Se[Se_idx] = area_zc;
        ii[Se_idx] = cem_node;
        jj[Se_idx] = cem_node;
        Se_idx++;
        for(int k = 0; k < dim; k++) {
            const int nidx = m->surfaceelems[i * dim + k] - 1;
            Se[Se_idx] = ((dim == 2) ? -3.0 : -4.0) / scale * area_zc;
            ii[Se_idx] = cem_node;
            jj[Se_idx] = nidx;
            Se_idx++;
            Se[Se_idx] = +2.0 / scale * area_zc;
            ii[Se_idx] = nidx;
            jj[Se_idx] = nidx;
            Se_idx++;
            for(int p = 0; p < k; p++) {
                const int pidx = m->surfaceelems[i * dim + p] - 1;
                Se[Se_idx] = +1.0 / scale * area_zc;
                ii[Se_idx] = pidx;
                jj[Se_idx] = nidx;
                Se_idx++;
            }
        }
    }
    const int nnz_added = calc_sys_cem_n(dim) * n;
    assert(Se_idx - Se_idx_start == nnz_added);
    return nnz_added;
}

enum se_type {UNKNOWN, NONE, PEM, CEM};
static int check_bc(mesh const * const msh)
{
    assert(msh != NULL);
    const int dim = msh->dim;
    const int n_bc = max_bc(msh) + 1;
    enum se_type type[n_bc];
    for(int i = 0; i < n_bc; i++) {
        type[i] = UNKNOWN;
    }
    for(int i = 0; i < msh->n_se; i ++ ) {
        const int bci = msh->bc[i];
        /* a PEM is recorded in surfaceelems with a single node
         * as the first entry and the remaining entries as zero;
         * so we test the second entry to see if its zero */
        int cnt = 0;
        for(int j = 0; j < msh->dim; j++) {
            cnt += (msh->surfaceelems[i * dim + j] > 0) ? 1 : 0;
        }
        enum se_type newtype;
        if(cnt == msh->dim) {
            newtype = CEM;
        }
        else if(cnt == 1) {
            newtype = PEM;
        }
        else {
            printf("error: bad number of nodes on boundary %d\n", bci);
            return FAILURE;    /* bad node#s */
        }
        if(type[bci] == UNKNOWN) {
            type[bci] = newtype;
        }
        else if(type[bci] != newtype) {
            printf("error: mixed PEM and CEM on boundary %d\n", bci);
            return FAILURE;    /* mixed PEM and CEM syntax in same  BC */
        }
        else if(newtype == PEM) {
            printf("error: multiple PEM surface elements have the same boundary#%d, see surface element %d\n", bci, i + 1);
            return FAILURE;    /* duplicate PEM for same BC */
        }
        if((newtype == PEM) && (msh->surfaceelems[i * dim + 0] <= 0)) {
            printf("error: for PEM on boundary %d, must have node# on first entry\n", bci);
            return FAILURE;    /* must have se[0] be the node for a PEM */
        }
    }
    /* expect all BC to be used */
    for(int bc = 0; bc < n_bc; bc++) {
        if(type[bc] == UNKNOWN) {
            printf("error: unused boundary %d\n", bc);
            return FAILURE;
        }
    }
    return SUCCESS;
}

int check_model(model const * const mdl)
{
    return check_bc(&(mdl->fwd));
}

static void printf_mesh(mesh const * const msh, int verbosity)
{
    assert(msh != NULL);
    if(msh->n_elems == 0) {
        printf(" (none)\n");
        return;
    }
    printf(" %dD, %d nodes, %d elements, %d surfaces, %d entry parameter map\n",
           msh->dim, msh->n_nodes, msh->n_elems, msh->n_se, msh->n_pmap);
    if(verbosity <= 1) {
        return;
    }
    if(msh->n_elems > 0) {
        printf("    elements\n");
    }
    const int nd = msh->dim;
    const int nd1 = nd + 1;
    for(int i = 0; i < msh->n_elems; i++) {
        printf("     ");
        for(int j = 0; j < nd1; j++) {
            printf(" %-5d", msh->elems[i * nd1 + j]);
        }
        printf("\n");
    }
    if(msh->n_nodes > 0) {
        printf("    nodes\n");
    }
    for(int i = 0; i < msh->n_nodes; i++) {
        printf("     ");
        for(int j = 0; j < nd; j++) {
            printf(" %-+7.3f", msh->nodes[i * nd + j]);
        }
        printf("\n");
    }
    if(msh->n_se > 0) {
        printf("    surface elements\n");
    }
    for(int i = 0; i < msh->n_se; i++) {
        printf("     ");
        for(int j = 0; j < nd; j++) {
            printf(" %-5d", msh->surfaceelems[i * nd + j]);
        }
        if(msh->bc[i] != 0) {
            printf(" (bc#%d)", msh->bc[i]);
        }
        printf("\n");
    }
    if(msh->n_pmap > 0) {
        printf("    parameter map\n");
    }
    for(int i = 0; i < msh->n_pmap; i++) {
        printf("     (%5d,%5d)  = %0.3f\n",
               msh->pmap_elem[i], msh->pmap_param[i],
               msh->pmap_frac[i]);
    }
}

#define plural(a) ((a!=1)?"s":"")
void printf_model(model const * const mdl, int verbosity)
{
    printf("model: %d electrodes, %d measurements,", mdl->n_elec, mdl->n_stimmeas);
    printf(" λ = %g\n", mdl->hp);
    printf("  %d parameter frame%s", mdl->n_params[1], plural(mdl->n_params[1]));
    if(mdl->n_params[1] > 0) {
        printf(" (%d parameter%s/frame)\n", mdl->n_params[0], plural(mdl->n_params[0]));
    }
    else {
        printf("\n");
    }
    printf("  %d measurement frame%s", mdl->n_data[1], plural(mdl->n_data[1]));
    if(mdl->n_data[1] > 0) {
        printf(" (%d measurement%s/frame)\n", mdl->n_data[0], plural(mdl->n_data[0]));
    }
    else {
        printf("\n");
    }
    if((verbosity > 1) && (mdl->n_stimmeas > 0)) {
        printf("  %d stimulus and measurement pairs: A-B M-N (gain)\n", mdl->n_stimmeas);
        for(int i = 0; i < mdl->n_stimmeas; i++) {
            printf("    %d-%d %d-%d (%5.2e)\n",
                   mdl->stimmeas[i * 4 + 0],
                   mdl->stimmeas[i * 4 + 1],
                   mdl->stimmeas[i * 4 + 2],
                   mdl->stimmeas[i * 4 + 3],
                   mdl->measgain[i]);
        }
    }
    if((verbosity > 1) && (mdl->n_zc > 0)) {
        printf("  %d contact impedances\n", mdl->n_zc);
        if(mdl->zcbc == NULL) {
            for(int i = 0; i < mdl->n_zc; i++) {
                printf("    %5.2e (electrode#%d)\n", mdl->zc[i], i + 1);
            }
        }
        else {
            for(int i = 0; i < mdl->n_zc; i++) {
                printf("    %5.2e (bc#%d)\n", mdl->zc[i], mdl->zcbc[i]);
            }
        }
    }
    printf("  forward model:");
    printf_mesh(&(mdl->fwd), verbosity);
    printf("  reconstruction model:");
    printf_mesh(&(mdl->rec), verbosity);
    if((verbosity > 1) && (mdl->n_params[0] > 0)) {
        const int rows = mdl->n_params[0];
        printf("  parameters\n");
        for(int i = 0; i < mdl->n_params[0]; i++) {
            printf("   ");
            for(int j = 0; j < mdl->n_params[1]; j++) {
                printf(" %18.8e", mdl->params[i + rows * j]);
            }
            printf("\n");
        }
    }
    if((verbosity > 1) && (mdl->n_data[0] > 0)) {
        const int rows = mdl->n_data[0];
        printf("  data\n");
        for(int i = 0; i < mdl->n_data[0]; i++) {
            printf("   ");
            for(int j = 0; j < mdl->n_data[1]; j++) {
                printf(" %18.8e", mdl->data[i + rows * j]);
            }
            printf("\n");
        }
    }
}

int calc_sys_size(model const * const m)
{
    const int dim = m->fwd.dim;
    const int n_bc = max_bc(&(m->fwd)) + 1;
    enum se_type type[n_bc];
    for(int i = 0; i < n_bc; i++) {
        type[i] = UNKNOWN;
    }
    type[0] = NONE; /* skip bc=0, assuming its the exterior */
    for(int i = 0; i < m->fwd.n_se; i ++ ) {
        const int bci = m->fwd.bc[i];
        if (type[bci] != UNKNOWN) {
            continue;
        }
        type[bci] = (m->fwd.surfaceelems[i * dim + 1] == 0) ? PEM : CEM;
    }
    int n_cem = 0;
    for(int bc = 0; bc < n_bc; bc++) {
        n_cem += (type[bc] == CEM) ? 1 : 0;
    }
    return (m->fwd.n_nodes + n_cem);
}

size_t calc_sys_nnz(model const * const m)
{
    const int dim = m->fwd.dim;
    const int n_bc = max_bc(&(m->fwd)) + 1;
    enum se_type type[n_bc];
    for(int i = 0; i < n_bc; i++) {
        type[i] = UNKNOWN;
    }
    int cnt[n_bc];
    for(int i = 0; i < n_bc; i++) {
        cnt[i] = 0;
    }
    for(int i = 0; i < m->fwd.n_se; i ++ ) {
        const int bci = m->fwd.bc[i];
        type[bci] = (m->fwd.surfaceelems[i * dim + 1] == 0) ? PEM : CEM;
        cnt[bci]++;
    }
    type[0] = NONE; /* force skipping bc=0, assuming its the exterior */
    /* count surfaceelems that are CEM */
    int se_cem = 0;
    for(int bc = 0; bc < n_bc; bc++) {
        se_cem += (type[bc] == CEM) ? cnt[bc] : 0;
    }
    const int ne = m->fwd.n_elems;
    const int se_n = calc_sys_elem_n(dim); /* sparse matrix entries per mesh element */
    const int nnz_per_se = calc_sys_cem_n(dim);
    return (se_n * ne) + (se_cem * nnz_per_se);
}


// Implementation of interface described in viewer_image.h.

#include <math.h>
#include <unistd.h>

#include <gsl/gsl_statistics_double.h>

#include "meta_read_wrapper.h"
#include "utilities.h"
#include "viewer_image.h"

// We use a seperate thread to fill in the stuff that takes some time
// to create.
static gpointer
create_pyramid (ViewerImage *self)
{
  g_assert (self->pixmaps != NULL);

  if ( self->pyramid_cache != NULL ) {
    g_assert (self->scratch_dir == NULL);
    // We never unref this pyramid.  The reason is that we don't want
    // to keep this thread alive just for that purpose, and since the
    // unreffing the pyramid triggers a pyramid_cache_release, it has
    // to be done by its creator.  It will get reclaimed anyway by the
    // OS.
    self->pyramid
      = pyramid_new_using_cache (self->base_name->str, self->pixmaps,
				 self->pyramid_cache);
  }
  else {
    g_assert (self->scratch_dir != NULL);
    g_assert (self->pyramid_cache == NULL);
    self->pyramid = pyramid_new (self->base_name->str, self->pixmaps,
				 self->scratch_dir->str);
  }

  // Uncomment to get debugging snapshots of the pyramid layers.
  // pyramid_export_as_float_blobs (self->pyramid, "pyramid_snapshot");
  // pyramid_export_as_jpeg_files (self->pyramid, "pyramid_snapshot");

  g_mutex_lock (self->pyramid_mutex);
  self->pyramid_ready = TRUE;
  g_mutex_unlock (self->pyramid_mutex);

  // This thread isn't even joinable, but we keep the compiler happy
  // with a return statement anyway.
  return NULL;
}

ViewerImage *
viewer_image_new (const char *base_name, PixMapsSpec *pixmaps, gdouble sigmas,
		  size_t x_offset, size_t y_offset,
		  PyramidCache *pyramid_cache)
{
  ViewerImage *self = g_new (ViewerImage, 1);

  self->base_name = g_string_new (base_name);

  GString *mn = g_string_append (g_string_new (base_name), ".meta");
  GString *dn = g_string_append (g_string_new (base_name), ".img");

  self->meta = meta_read_wrapper (mn->str);
  meta_parameters *md = self->meta; // Convenience alias.

  self->size_x = md->general->sample_count;
  self->size_y = md->general->line_count;

  self->x_offset = x_offset;
  self->y_offset = y_offset;

  self->pixmaps = pix_maps_spec_ref (pixmaps);

  self->sigmas = sigmas;

  g_assert (self->size_x <= INT_MAX);
  gint sx = self->size_x;
  g_assert (self->size_y <= INT_MAX);
  gint sy = self->size_y;

  FILE *df = fopen (dn->str, "r");
  g_assert (df != NULL);

  // We will form the quick look by sampling every pixel_strid'th
  // pixel.  This number reflects a critical tradeoff -- bigger means
  // that we won't have a quick view of sufficiently high resolution
  // ready when the user tries to zoom in on an image, and interactive
  // performance will suffer when we create one on the fly.  On the
  // other hand, setting this number too small may run some machines
  // out of RAM and into virtual memory, which is also likely to
  // degrade interactive performance significantly.  Both these
  // problems go away once we have the pyramid fully biult, but of
  // course this takes a few seconds to do.
  const size_t pixel_stride = 8;

  self->quick_view_stride = pixel_stride;

  // Quick view dimensions.
  self->qvw = ceil ((double) sx / pixel_stride);
  self->qvh = ceil ((double) sy / pixel_stride);

  // Memory for quick_view.
  self->quick_view = g_new (float, self->qvw * self->qvh);

  // Storage for one image row.
  float *row_buffer = g_new (float, sx);

  size_t ii, jj;
  size_t cqvr = 0, cqvc = 0;		// Current quick_view row and column.
  for ( jj = 0 ; jj < sy ; jj += pixel_stride, cqvr++ ) {
    cqvc = 0;
    size_t read_count = fread (row_buffer, sizeof (float), sx, df);
    g_assert (read_count == sx);
    int return_code = fseek (df, sizeof (float)  * sx * (pixel_stride - 1),
			     SEEK_CUR);
    g_assert (return_code == 0);
    for ( ii = 0 ; ii < sx ; ii += pixel_stride, cqvc++ ) {
      float pv = row_buffer[ii];
      if ( G_BYTE_ORDER == G_LITTLE_ENDIAN ) {
	swap_bytes_32 ((unsigned char *) &pv);
      }
      (self->quick_view)[cqvr * self->qvw + cqvc] = pv;
    }
  }

  size_t qvpc = self->qvw * self->qvh;   // Quick view pixel count.

  // Apply pixmaps.
  for ( ii = 0 ; ii < qvpc ; ii++ ) {
    (self->quick_view)[ii]
      = pix_maps_spec_map (self->pixmaps, (self->quick_view)[ii]);
  }

  // Double precision version of quickview data so we can use GSL
  // library functions for statistics.
  double *qvad = g_new (double, qvpc);
  for ( ii = 0 ; ii < qvpc ; ii++ ) {
    qvad[ii] = (self->quick_view)[ii];
  }

  // Compute statistics for OpenGL to use for scaling.  IMPROVEME: it
  // might be possible to use OpenGL to do this or near enough using
  // the faster functions from the OpenGL imaging subset.
  gsl_stats_minmax (&(self->min), &(self->max), qvad, 1, qvpc);
  self->mean = gsl_stats_mean (qvad, 1, qvpc);
  self->sdev = gsl_stats_sd_m (qvad, 1, qvpc, self->mean);

  g_free (qvad);

  g_free (row_buffer);

  if ( pyramid_cache == NULL ) {
    self->pyramid_cache = NULL;
    self->scratch_dir = g_string_new ("/tmp/");
  }
  else {
    self->pyramid_cache = pyramid_cache_ref (pyramid_cache);
    self->scratch_dir = NULL;
  }

  if ( !g_thread_supported () ) {
    g_thread_init (NULL);
  }

  self->pyramid_ready = FALSE;
  self->pyramid_mutex = g_mutex_new ();
  self->pyramid = NULL;

  GThread *pyramid_thread
    = g_thread_create ((GThreadFunc) create_pyramid, self, FALSE, NULL);
  g_assert (pyramid_thread != NULL);
  // We try to keep things snappy for the use during the time before
  // the pyramids are finished.
  g_thread_set_priority (pyramid_thread, G_THREAD_PRIORITY_LOW);

  g_string_free (mn, TRUE);
  g_string_free (dn, TRUE);

  self->reference_count = 1;

  return self;
}

Pyramid *
viewer_image_pyramid (ViewerImage *self)
{
  Pyramid *result = NULL;
  g_mutex_lock (self->pyramid_mutex);
  if ( self->pyramid_ready ) {
    result = self->pyramid;
  }
  g_mutex_unlock (self->pyramid_mutex);

  return result;
}

ViewerImage *
viewer_image_ref (ViewerImage *self)
{
  self->reference_count++;

  return self;
}

void
viewer_image_unref (ViewerImage *self)
{
  self->reference_count--;

  if ( self->reference_count == 0 ) {
    while ( viewer_image_pyramid (self) == NULL ) {
      // oo icky poo a polling loop in a destructor!
      const guint sleep_time = 1;
      sleep (sleep_time);
    }
    viewer_image_free (self);
  }
}

gboolean
viewer_image_free (ViewerImage *self)
{
  if ( viewer_image_pyramid (self) == NULL ) {
    return FALSE;
  }

  g_string_free (self->base_name, TRUE);
  meta_free (self->meta);
  pix_maps_spec_unref (self->pixmaps);
  g_free (self->quick_view);
  if ( self->pyramid_cache != NULL ) {
    g_assert (self->scratch_dir == NULL);
    pyramid_cache_unref (self->pyramid_cache);
    // Note that we deliberately don't unref the pyramid here; see
    // comments in create_pyramid().
  }
  else {
    g_assert (self->scratch_dir != NULL);
    g_assert (self->pyramid_cache == NULL);
    g_string_free (self->scratch_dir, TRUE);
    pyramid_unref (self->pyramid);
  }
  g_mutex_free (self->pyramid_mutex);
  g_free (self);

  return TRUE;
}