Observation operators

Description and details of CARDAMOM observation operators

To write a new observation operator for a time series observation:

  1. choose cbf quantity name and model quantity name (e.g., GPP and M_GPP), and type (TIMESERIES_OBS_STRUCT, SINGLE_OBS_STRUCT, TIMESERIES_DRIVER_STRUCT)

  2. in CARDAMOM_DATA_STRUCTURE.c, model quantity name (e.g., M_GPP) is instantiated

  3. in CARDAMOM_NETCDF_DATA_STRUCTURE.c, cbf quantity name (e.g., GPP) is instantiated as a specific type (e.g., TIMESERIES_OBS_STRUCT GPP)

  4. in CARDAMOM_READ NETCDF_DATA.c

    1. cbf quantity name (e.g., "GPP") gets included, with the corresponding function for its type (e.g., DATA->GPP=READ_NETCDF_TIMESERIES_OBS_FIELDS(ncid, "GPP"); for time series type obs, or other for single obs or driver types);

    2. If timeseries obs, also call the preprocess function (e.g., TIMESERIES_OBS_STRUCT_PREPROCESS(&DATA->GPP);)

  5. in DALEC_OBSERVATION_OPERATORS.c,

    1. create boolean flag to support the obsop, (e.g.: bool SUPPORT_GPP_OBS;)

    2. instantiate any sub quantities that might be needed to evaluate the observation operator (e.g., int Rauto_flux; int GPP_flux; where both GPP_flux and Rauto_flux are quantities used in the CUE observation operator)

    3. set the boolean flag to false by default in the obsop data structure (e.g., OBSOPE->SUPPORT_GPP_OBS=false; ) This gets changed in DALEC_1100.c on a case by case basis.

    4. Add to DALEC obsope structure (e.g., if (O->SUPPORT_GPP_OBS){DALEC_OBSOPE_GPP(D, O);})

    5. write the observation operator evaluation as a function of the data structure and the obsop structure, e.g.:

    int DALEC_OBSOPE_GPP(DATA * D, OBSOPE * O){
    
    //GPP timeseries length
    int N=D->ncdf_data.TIME_INDEX.length;
    
    //Time varying GPP
    TIMESERIES_OBS_STRUCT TOBS=D->ncdf_data.GPP;
    if (TOBS.validobs){int n;
        for (n=0;n<N;n++){
            D->M_GPP[n]=D->M_FLUXES[D->nofluxes*n+O->GPP_flux];
            }
        };
    
    
    //Mean GPP
    SINGLE_OBS_STRUCT SOBS=D->ncdf_data.Mean_GPP;
    if (SOBS.validobs){
        int n;
        D->M_Mean_GPP=0;
        for (n=0;n<N;n++){
            D->M_Mean_GPP+=D->M_FLUXES[n*D->nofluxes+O->GPP_flux];
            };
        D->M_Mean_GPP=D->M_Mean_GPP/(double)N;
        }
    
    return 0;
    }
    
  6. in DALEC_ALL_LIKELIHOOD.c,

    1. add to the list of likelihood indices (e.g., int GPP;)

    2. increment likelihood index count (e.g., …20,21,22};)

    3. add to the likelihood function (e.g., if (O->SUPPORT_GPP_OBS){ML[LI.GPP]=CARDAMOM_TIMESERIES_OBS_LIKELIHOOD(&D.ncdf_data.GPP, D.M_GPP);};)

  7. in DALEC_1100.c,

    1. create and assign the model quantities that are needed to evaluate the obsop, matching variable names declared in step b of modifying DALEC_OBSERVATION_OPERATORS.c (e.g., OBSOPE.GPP_flux=F.gpp;) and

    2. turn the obsop on (e.g., OBSOPE.SUPPORT_GPP_OBS=true;)

  8. If this is a completely new variable for the CBF, then in CARDAMOM_READ_BINARY_DATA.c, add a memory allocation (e.g., DATA->M_GPP=calloc(Ntimesteps,sizeof(double));) NOTE: can trigger bus error if skipped

  9. When putting data into the CBF under the new field, also check

    1. unc name matches filter type, etc (e.g., opt_filter=1 takes single_mean_unc, nothing else will work),

    2. filter logic matches obs values ranges (e.g., don’t use opt_unc_type=1 on something with negative values),

    3. fill value encoding & time encoding (check using xarray or ncdump)