[R-sig-dyn-mod] Automating the computation of maximum Lyapunov exponents from time series outputs of ODE models

Nicola Gambaro n|co|@ @end|ng |rom g@mb@ro@co@uk
Sat Aug 14 16:03:54 CEST 2021


I have an ODE model for which I have to explore the impact of different combinations of parameter values. I would like one of my diagnostics to be the maximum Lyapunov exponent of the output time series.

I have already created a function that solves the ODE system for all given parameter ranges. The output dataframe I have has several columns: the first one contains the time steps, then come the time series of the state variables and finally all the parameter values used to calculate those time series.

I’ll now show you what function I currently have and why it doesn’t work:

automated_lyapunov <- function(outputs, #output from odesolver as dataframe
                                                    n_variables, #number of state variables
                                                    parameter, #the parameter whose sensitivity we want to analyse (as character, e.g. “alpha”)
                                                    nominal, #nominal values for parameters (must be df)
                                                    t_cutoff = 100, #eliminate beginning of time series, i.e. “initial shock"
                                                    end = 2000,
                                                    frequency = 1,
                                                    prop = 0.01,
                                                    r_lyap = 0.02583,
                                                    regression_range = c(0,3)){
 require(purrr)
 require(dplyr)
 require(tidyr)
 require(stringr)
 outputs <- outputs %>% filter(time > t_cutoff)
 colnames(nominal) <- c("parameter", "value")
 nominal <- nominal %>% filter(parameter != !!parameter)
 conditions <- nominal %>% pivot_wider(names_from = parameter, values_from = value)
 param.step <- plottable_outputs %>% semi_join(conditions)
 variables <- colnames(param.step[,1:n_variables+1]) 

 calc <- function(filtered_outputs,
                  parameter,
                  variables){
   means <- filtered_outputs %>% 
     group_by(.dots = parameter) %>% 
     summarise(across(all_of(variables), 
                      list(mean=mean, sd=sd)), .groups="drop") %>% 
     rename_with(function(x) str_sub(x,1,1), ends_with("mean"))
   return(means)
 }

 means <- calc(filtered_outputs = param.step,
               parameter = parameter,
               variables = variables) #outputs means and sd's of all variables given chosen param
 means <- means %>%
   rename_with(~ gsub("([[:alpha:]])", "\\1_mean", .), !contains("sd") & !c(1)) %>%
   pivot_longer(!parameter, names_to = c("stock", ".value"), names_pattern = "([[:alpha:]])_(.*)")


   require(nonlinearTseries)
   require(tseriesChaos)
   require(fields)
   lyapunov <- function(param.step,
                       parameter,
                       iteration,
                       t_cutoff,
                       end,
                       frequency,
                       variables,
                       lyapunov,
                       r_lyap,
                       regression_range){
     range <- unique(param.step[[parameter]]) #range in the parameter values
     it_var <- variables[iteration]   #iterating state variable (e.g. “x”, “y”, etc)

    calc_lyapunov <- function(param.step,
                                              parameter,
                                              it,
                                              it_var,
                                              range,
                                              t_cutoff,
                                              frequency,
                                              end,
                                              r_lyap,
                                             regression_range){
       selected <- param.step %>% filter(.data[[parameter[[1]]]] == range[it])
       selected <- selected[[it_var]]
       ts <- ts(selected, frequency = frequency, end = end,
                start = t_cutoff + frequency)

         possibly_timeLag <- possibly(timeLag, otherwise = "error”) #error catching not to break the loop
         tau <- possibly_timeLag(ts, technique = "ami", lag.max = 1000, do.plot = FALSE)
         if(is.character(tau) == TRUE){
           tau <- possibly_timeLag(ts, technique = "acf", lag.max = 1000, do.plot = FALSE)
           if(is.character(tau) == TRUE){
             ml.est <- "acf error"
           } else {
             possibly_emb <- possibly(estimateEmbeddingDim, otherwise = "error")
             emb.dim <- possibly_emb(ts, time.lag = tau, max.embedding.dim = 50, do.plot = FALSE)
             if(is_character(emb.dim) == TRUE){
               ml.est <- "emb error 1"
             } else {
               mx <- embedd(ts, emb.dim, tau)
               dist <- rdist(mx)
               eps <- r_lyap * max(dist)
               ml <- maxLyapunov(ts,
                                 min.embedding.dim = emb.dim,
                                 max.embedding.dim = emb.dim + 3,
                                 time.lag = tau,
                                 radius = eps,
                                 theiler.window = 1,
                                 max.time.steps = end,
                                 sampling.period = frequency,
                                 do.plot = FALSE)
               remove(dist)
               ml.est <- estimate(ml, 
                                  regression.range = regression_range,
                                  do.plot = TRUE)
             }
           }
         } else {
           possibly_emb <- possibly(estimateEmbeddingDim, otherwise = "error")
           emb.dim <- possibly_emb(ts, time.lag = tau, max.embedding.dim = 50, do.plot = FALSE)
           if(is_character(emb.dim) == TRUE){
             ml.est <- "emb error 2"
           } else {
             mx <- embedd(ts, emb.dim, tau)
             dist <- rdist(mx)
             eps <- r_lyap * max(dist)
             ml <- maxLyapunov(ts,
                               min.embedding.dim = emb.dim,
                               max.embedding.dim = emb.dim + 3,
                               time.lag = tau,
                               radius = eps,
                               theiler.window = 1,
                               max.time.steps = end,
                               sampling.period = frequency,
                               do.plot = FALSE)
             remove(dist)
             ml.est <- estimate(ml, 
                                regression.range = regression_range,
                                do.plot = TRUE)
           }
         }
         lyap <- data.frame(x1 = range[it],
                            x2 = it_var,
                            x3 = ml.est)
         lyap <- rename(lyap,
                        !!parameter := 1,
                        "stock" = 2,
                        "MLE" = 3)
         return(lyap)
       }


     all_range <- map_dfr(1:length(range), ~calc_lyapunov(param.step = param.step,
                                                               parameter = parameter,
                                                               range = range,
                                                               it_var = it_var,
                                                               it = .,
                                                               t_cutoff = t_cutoff,
                                                               frequency = frequency,
                                                               end = end,
                                                               r_lyap = r_lyap,
                                                               regression_range = regression_range))
     return(all_range)
   }
   all_vars <- map_dfr(1:length(variables), ~lyapunov(param.step = param.step,
                                                     parameter = parameter,
                                                     iteration = .,
                                                     t_cutoff = t_cutoff,
                                                     end = end,
                                                     frequency = frequency,
                                                     variables = variables,
                                                     r_lyap = r_lyap,
                                                     regression_range = regression_range))
   all_diag <- merge(means, all_vars)  #merge means and lyapunovs of all state variables (“stocks”) for the whole parameter range
   return(all_diag)
 }
}


When I manually run the code to compute the lyapunov exponent of a time series, it works. However, when I do it with this function, I get all the mean values for all state variables but on the “MLE” column I always only get “emb error 2”. Why doesn’t it work? Is there a correct (and better) way of doing this? I would be very grateful if someone could show me how to build a function that automatically computes MLEs.

Thank you very much,

Nicola


More information about the R-sig-dynamic-models mailing list