Search code examples
c++rtmb

How do I include more objective functions in my TMB .cpp file?


The TMB objective functions are seemingly defined in one function block that is saved to a <name>.cpp file. Then, after compiling the file, each objective function is accessed by loading with the command dyn.load(dynlib(<name>)).

Is it possible to store more than one objective function in each .cpp file? For example, the following two objective functions are very similar to each other, but at the moment need to be saved to different files:

// TMB Tutorial but with fixed variance
#include <TMB.hpp>                                // Links in the TMB libraries

template<class Type>
Type objective_function<Type>::operator() ()
{
    DATA_VECTOR(x);                                 // Data vector transmitted from R
    PARAMETER(mu);                                  // Parameter value transmitted from R
    Type sigma = 1.0;

    Type f;                                         // Declare the "objective function" (neg. log. likelihood)
    f = -sum(dnorm(x,mu,sigma,true));               // Use R-style call to normal density

    return f;
}

and

// TMB Tutorial
#include <TMB.hpp>                                // Links in the TMB libraries

template<class Type>
Type objective_function<Type>::operator() ()
{
    DATA_VECTOR(x);                                 // Data vector transmitted from R
    PARAMETER(mu);                                  // Parameter value transmitted from R
    PARAMETER(sigma);                               //                 

    Type f;                                         // Declare the "objective function" (neg. log. likelihood)
    f = -sum(dnorm(x,mu,sigma,true));               // Use R-style call to normal density

    return f;
}

Solution

  • The "map" argument to MakeADFun() allows you to fix parameters at specific values.

    In this example, we only need to compile/load the latter template. First, we'll write the template to a file, compile, and load the resulting DLL.

    library(TMB)
    
    file_conn <- file('test.cpp')
    
    writeLines("
    #include <TMB.hpp>                                
    
    template<class Type>
    Type objective_function<Type>::operator() ()
    {
        DATA_VECTOR(x);                                 
        PARAMETER(mu);                                  
        PARAMETER(sigma);                               
    
        Type f;                                         
        f = -sum(dnorm(x,mu,sigma,true));               
    
        return f;
    }
    ", file_conn)
    close(file_conn)
    
    compile('test.cpp')
    dyn.load(dynlib('test'))
    

    We can use the same DLL to fit models with and without a varying sigma.

    n <- 100
    x <- rnorm(n = n, mean = 0, sd = 1) 
    
    f1 <- MakeADFun(data = list(x = x),
                   parameters = list(mu = 0, sigma = 1),
                   DLL = 'test')
    
    f2 <- MakeADFun(data = list(x = x),
                    parameters = list(mu = 0, sigma = 1),
                    DLL = 'test',
                    map = list(sigma = factor(NA)))
    
    opt1 <- do.call('optim', f1)
    opt2 <- do.call('optim', f2)
    

    When using "map", the specified parameter(s) (sigma in this case) is fixed at the value given in "parameters".

    Post-optimization, we do a sanity check -- the mus ought to be nearly identical.

    > opt1$par
            mu      sigma 
    0.08300554 1.07926521 
    > opt2$par
            mu 
    0.08300712 
    

    Turning random effects on and off is a little more difficult. An example of that is given here, where you can use CppAD::Variable() to check whether to decrement the negative log-likelihood or not.

    For unalike objective functions (not subsets of one another), you could pass a DATA_INTEGER or DATA_STRING into the template, e.g. like they did in glmmTMB here, and pick the objective function depending on the value of that DATA_*.