--- title: "Models for circadian clock simulation" author: "Ye Yuan" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Simulation of the circadian clock} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", out.width = "80%", dpi = 300 ) ``` This vignette documents workflow for circadian clock simulation provided by this package. We focus on the classical Leloup & Goldbeter (LG) model of the Drosophila circadian clock of the PER-TIM negative feedback loop. Refer to the package documentation to the different models that this package implemented. Propose addition of new models via Github issues (or PRs). Available models in the package: ```{r} library(clockSim) names(getOdinGen()) ``` Below, we demonstrate how to run different models provided in this package. # LG model simulation scheme LG model supports light input to entrain the clock via time-dependent TIM protein degradation rate. For all LG models implemented in this package, simulation starts with a few days of light-dark entrainment (LD12/12) before releasing into constant dark condition. Time-dependent TIM degradation is configurable by three model user parameters: 1. `LD_HOURS`: Inital LD entrainment length (hours) 2. `VdT_OFF`: TIM protein degradation max rate in dark (nM/h) 3. `VdT_ON`: TIM protein degradation max rate in light (nM/h) # Continuous LG model Now we turn to the continuous LG model. The underlying model is identical to the discrete version. Here, by default Odin adopts flexible-step solver and we do not need to specify step size of the simulation. ```{r} library(clockSim) model <- getOdinGen()$continuous_LG$new() # Running the model - specify the readout times sim_total_hours <- 2400 times <- seq(from = 0, to = sim_total_hours, by = 1) res_cont <- model$run(times) |> as.data.frame() # Plot results # Phase portrait of the full simulation # X = TIM mRNA level (nM) # Y = Nuclear PER-TIM complex level (nM) plot(plot_phase(res_cont, M_T, C_N)) # Time series # subsample the result to 1hr precision for plotting (already TRUE) # X-axis time tick every 24hr # faceted plot of M_T and C_N states (see above) # plot 30-40day (first 10d DD constant condition) res_cont$time <- times plot(plot_timeSeries(res_cont, 24*30, 24*40, 1, 24, M_T, C_N)) # plot 0-10day (first 10d LD condition) plot(plot_timeSeries(res_cont, 0, 240, 1, 24, M_T, C_N)) # Compute clock period and power # Using 30-40day data (i.e., first 10-day in DD constant condition) # provide only M_T series; other series are locked in phase print(compute_period(res_cont$M_T[(24*30):(24*40)])) # FFT method print(compute_period(res_cont$M_T[(24*30):(24*40)], method = "lomb")) # LS periodogram ``` How fast does the simulation run? `run_eta` provides a simple wrapper for reliable timing of the simulation (median taken from repeated calls). ```{r} run_eta(model, times) ``` # Discrete LG model Discrete LG model solves the LG model in difference equation form (iterate over fixed time steps). To run the model, you need to explicitly provide time step length. Model convergence requires sufficiently small time step setting, and 0.002/0.004hr is adequate for default parameters. Below, I show how to run the discrete model and plot its behavior and compute period statistics. ```{r} library(clockSim) model <- getOdinGen()$discrete_LG$new() # Specify time step and total simulation time # (0.002hr step, total 2400hr = 100day) sim_step_hours <- 0.002 steps <- seq(from = 0, to = sim_total_hours/sim_step_hours) model$set_user(STEP_HOURS = sim_step_hours) # Run simulation once res_dis <- model$run(steps) |> as.data.frame() res_dis$time <- res_dis$step*sim_step_hours # Plot results # Phase portrait of the full simulation # subset to 1-hour step to reduce unnecessary load in plotting plot(plot_phase(res_dis |> dplyr::filter(time %% 1 == 0), M_T, C_N)) # Time series plot(plot_timeSeries(res_dis, 24*30, 24*40, 1, 24, M_T, C_N)) plot(plot_timeSeries(res_dis, 0, 240, 1, 24, M_T, C_N)) # Compute clock period and power print(compute_period(res_dis$M_T[(24*30):(24*40)])) # FFT method print(compute_period(res_dis$M_T[(24*30):(24*40)], method = "lomb")) # LS periodogram ``` How fast does the simulation run? Apparently, it depends heavily on time step. ```{r} run_eta(model, steps) ``` Too large time step might cause simulation divergence. Consider the following: ```{r,fig.show="hold",out.width="20%"} model <- getOdinGen()$discrete_LG$new() # Test out a few step size settings sim_step_hours <- c(0.01, 0.1, 0.15, 0.2) for (step_hour in sim_step_hours){ steps <- seq(from = 0, to = sim_total_hours/step_hour) model$set_user(STEP_HOURS = step_hour) res <- model$run(steps) |> as.data.frame() plot( plot_phase(res, M_T, C_N)+ ggplot2::labs(title=paste0("Step=", step_hour))) } ``` # Discrete LG model: how small time step is enough? Discrete LG model results shall agree with the continuous model (in some sense the ground truth). To show how time step affects discrete model accuracy, consider the test below. As can be seen, step size up to 0.004hr seems acceptable (giving >0.9 correlation in simulation of 100 days). ```{r} res_comp <- data.frame( time = res_cont[["time"]], cont = res_cont[["M_T"]] ) # Test out a few step size settings model <- getOdinGen()$discrete_LG$new() sim_step_hours <- c(0.001 * 2^seq(from = 0, to = 3), 0.02) for (step_hour in sim_step_hours){ steps <- seq(from = 0, to = sim_total_hours/step_hour) model$set_user(STEP_HOURS = step_hour) res <- model$run(steps) |> as.data.frame() res$time <- res$step*step_hour res <- res |> dplyr::filter(time %in% res_comp$time) res_comp[[as.character(step_hour)]] <- res[["M_T"]] } # Compute correlation and plot it cors <- sapply(sim_step_hours, \(step_hour){ cor(res_comp[["cont"]], res_comp[[as.character(step_hour)]]) }) plot(sim_step_hours, cors, log = "x") ```