OSCR

Computational modeling and preclinical evaluation support targeting somatic instability for Huntington's disease treatment.

Code ↔ Paper

7 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 7 matches
  1. [1] § Results › Simulations of therapeutic targeting of somatic instability predict delayed progression of HD ↔ server.r, lines 888 to 971 · score 0.79 · therapy administration, motor symptoms, HD ISS stage, healthy treated, therapeutic benefit, CAG threshold · Report a mismatch
  2. [2] § Results › Model simulations recapitulate accelerated MSN loss with increasing germline CAG repeat length ↔ ui.r, lines 56 to 116 · score 0.79 · germline CAG repeat, inherited CAG repeat, somatic CAG repeat, phase linear model, CAG repeat expansion, somatic expansion · Report a mismatch
  3. [3] § Results › Model simulations recapitulate accelerated MSN loss with increasing germline CAG repeat length ↔ ui.r, lines 56 to 115 · score 0.78 · germline CAG repeat, inherited CAG repeat, somatic CAG repeat, phase linear model, CAG repeat expansion, somatic expansion · Report a mismatch
  4. [4] § Results › Simulations of therapeutic targeting of somatic instability predict delayed progression of HD ↔ ui.r, lines 56 to 116 · score 0.78 · medium spiny neurons, MSH3 knockdown, HD milestone, somatic CAG repeat, model tracks, motor symptoms · Report a mismatch
  5. [5] § Methods › Simulating CAG repeat expansion in MSNs ↔ server.r, lines 115 to 182 · score 0.77 · annual mutation event, phase linear model, p_exp, transition, probability, contraction · Report a mismatch
  6. [6] § Results › Simulations of therapeutic targeting of somatic instability predict delayed progression of HD ↔ server.r, lines 888 to 971 · score 0.72 · motor symptoms, HD ISS stage, predicted age, Therapeutic benefit, inherited, CAP · Report a mismatch
  7. [7] § Methods › Integrating external datasets ↔ global.r, lines 1 to 22 · score 0.64 · Enroll HD, motor score, HD ISS, phenotype, uhdrs, CAG · Report a mismatch

Paper

Loaded from Europe PMC by your browser; OSCR does not store it: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

R · 1,080 lines · 54 KB · no license · 3 matches

  1. options(gganimate.renderer = gifski_renderer())
  2. server <- function(input, output, session) {
  3. safe_min_age <- function(df, fallback) {
  4. if (nrow(df) == 0) return(fallback)
  5. return(min(df$Age))
  6. }
  7. # Instantiate progress bar
  8. updateProgressBar(session, id = "pbRun", value = 0, title = "Awaiting input...", status = "default")
  9. ########################################
  10. # Somatic Repeat Expansion Model Parameters
  11. ########################################
  12. ##### HD degeneration variables #####
  13. # The below length change mutation variable encodes the ratio of expansion to contraction events (recommend using probability ratio of 1 contraction event per 6 expansion events)
  14. length_change_mutation <- reactive({
  15. c(rep(-1, input$n_contractions), rep(1, input$n_expansions))
  16. })
  17. rate1 <- .035 # 36to55_CAGs Handsaker et al (supp Note 4) 3.51% +/- 0.83%.
  18. rate2 <- .576 # >55 CAGs Handsaker et al (supp Note 4) 57.6% +/- 12.76%.
  19. # Two phase linear model (Handsaker et al)
  20. T1 <- 33.5
  21. T2 <- 55
  22. r1 <- .035
  23. r2 <- .576
  24. # Create the function
  25. twoPhaseLinearModel <- function(x){ r1*max(x-T1,0) + r2*max(x-T2,0)}
  26. # Create plot renderer tracker
  27. renderState <- reactiveValues(animationDone = FALSE, linePlotDone = FALSE, healthyDone = FALSE)
  28. renderState$animationDone <- FALSE
  29. renderState$plotDone <- FALSE
  30. renderState$healthyDone <- FALSE
  31. stored_plots <- reactiveValues(linePlot = NULL, healthyPlot = NULL)
  32. stored_plots$modelComparisonPlot <- NULL
  33. # Handsaker-style CTMC helpers ------------------------------------------------
  34. # In the Handsaker model, p_exp splits the total mutation rate into expansion
  35. # and contraction rates, then a one-year transition matrix is computed using
  36. # expm(Q). This differs from the Latus event-sampling implementation, which
  37. # samples an integer number of mutation events per year.
  38. if (!requireNamespace("expm", quietly = TRUE)) {
  39. install.packages("expm", repos = "https://cran.rstudio.com/")
  40. }
  41. generate_handsaker_Q <- function(pexp, r1_mut, r2_mut, T1 = 33.5, T2 = 72.2, max_cag = 500,
  42. rate_modifier = 1) {
  43. Q <- matrix(0, nrow = max_cag, ncol = max_cag)
  44. for (i in 2:(max_cag - 1)) {
  45. rate <- rate_modifier * (r1_mut * max(i - T1, 0) + r2_mut * max(i - T2, 0))
  46. expansion_rate <- rate * pexp
  47. contraction_rate <- rate * (1 - pexp)
  48. Q[i, i - 1] <- contraction_rate
  49. Q[i, i + 1] <- expansion_rate
  50. Q[i, i] <- -(expansion_rate + contraction_rate)
  51. }
  52. return(Q)
  53. }
  54. simulate_one_ctmc_step <- function(cag, transition_matrix) {
  55. cag <- max(2, min(as.integer(round(cag)), nrow(transition_matrix)))
  56. pdf <- transition_matrix[cag, ]
  57. cdf <- cumsum(pdf)
  58. p <- runif(1)
  59. idx <- which(cdf >= p)[1]
  60. if (is.na(idx)) idx <- length(cdf)
  61. return(idx)
  62. }
  63. simulate_handsaker_lifecycle <- function(ncells, years, initial_cag, age_at_intervention,
  64. transduction_rate, therapeutic_modifier, pexp,
  65. untreated = FALSE, max_cag = 500) {
  66. # Fig. SN4.4 reports effective net expansion rates of 3.51% and 57.6%.
  67. # In the CTMC, net expansion = mutation_rate * (2*p_exp - 1), so convert
  68. # these net rates back to total mutation-rate constants before building Q.
  69. net_factor <- 2 * pexp - 1
  70. if (net_factor <= 0) stop("p_exp must be > 0.5 for positive net expansion")
  71. r1_mut <- 0.035 / net_factor
  72. r2_mut <- 0.576 / net_factor
  73. Q_base <- generate_handsaker_Q(pexp, r1_mut, r2_mut, T1 = 33.5, T2 = 72.2,
  74. max_cag = max_cag, rate_modifier = 1)
  75. Q_tx <- generate_handsaker_Q(pexp, r1_mut, r2_mut, T1 = 33.5, T2 = 72.2,
  76. max_cag = max_cag, rate_modifier = (1 - therapeutic_modifier))
  77. P_base <- expm::expm(Q_base)
  78. P_tx <- expm::expm(Q_tx)
  79. repeat_lengths <- rep(as.integer(initial_cag), ncells)
  80. therapeutic_status <- rep("0", ncells)
  81. out <- data.frame()
  82. for (year in 0:years) {
  83. # Match the existing Latus model: therapy is administered before the
  84. # intervention-year state is recorded, so transduced cells are visible
  85. # at Year == age_at_intervention.
  86. if (!untreated && year == age_at_intervention) {
  87. therapeutic_status <- ifelse(rbinom(ncells, 1, transduction_rate) == 1, "transduced", "0")
  88. }
  89. out <- rbind(out, data.frame(
  90. Year = year,
  91. RepeatLength = repeat_lengths,
  92. CellID = seq_along(repeat_lengths),
  93. Transduction = therapeutic_status,
  94. Model = "Handsaker_CTM"
  95. ))
  96. # Apply one-year transition after recording the current state
  97. for (j in seq_along(repeat_lengths)) {
  98. if (repeat_lengths[j] < max_cag) {
  99. if (!untreated && therapeutic_status[j] == "transduced" && year >= age_at_intervention) {
  100. repeat_lengths[j] <- simulate_one_ctmc_step(repeat_lengths[j], P_tx)
  101. } else {
  102. repeat_lengths[j] <- simulate_one_ctmc_step(repeat_lengths[j], P_base)
  103. }
  104. }
  105. }
  106. }
  107. return(out)
  108. }
  109. # Build descriptive filename prefix from current input parameters
  110. get_file_prefix <- reactive({
  111. paste0("HD_sim",
  112. "_CAG", input$germlineHDRepeatLength,
  113. "_intAge", input$age_at_intervention,
  114. "_life", input$maxLifetime,
  115. "_MSH3kd", input$therapeuticModifier * 100, "pct",
  116. "_transd", input$transductionRate * 100, "pct",
  117. "_nMSN", input$nMSNs_CaudatePutamen,
  118. "_thresh", input$HealthyCellCAG_Threshold,
  119. "_contr", input$n_contractions,
  120. "_exp", input$n_expansions,
  121. "_pexp", input$p_exp,
  122. "_model", input$modelMode)
  123. })
  124. ########################################
  125. # Define object classes
  126. ########################################
  127. # Create object class representing an individual cell
  128. Cell <- R6Class("Cell",
  129. public = list(
  130. repeat_length = NULL,
  131. therapeutic_status = 0,
  132. year_counter = 0,
  133. initialize = function(repeat_length = 0) {
  134. self$repeat_length <- repeat_length
  135. self$therapeutic_status <- 0
  136. self$year_counter <- 0
  137. },
  138. get_repeat_length = function() {
  139. return(self$repeat_length)
  140. },
  141. therapeutic_transduction_event = function() {
  142. transductionOutcome <- rbinom(1,1, input$transductionRate)
  143. if (transductionOutcome == 1) {
  144. self$therapeutic_status <- "transduced"
  145. } else {
  146. self$therapeutic_status <- self$therapeutic_status
  147. }
  148. },
  149. get_therapeutic_transduction_status = function() {
  150. return(self$therapeutic_status)
  151. },
  152. mutate_repeat = function() {
  153. # Compute probability of mutation event
  154. AnnualMutationRate <- twoPhaseLinearModel(self$repeat_length)
  155. # Function to mutate repeat
  156. mutate_length <- function(times) {
  157. self$repeat_length <- self$repeat_length + sum(sample(length_change_mutation() , times, replace = TRUE))
  158. }
  159. if(AnnualMutationRate <= 1){
  160. # single mutation event
  161. if (rbinom(1, 1, AnnualMutationRate) == 1) {
  162. mutate_length(1)
  163. }
  164. } else {
  165. # Multiple mutation events
  166. cycles <- min(as.integer(AnnualMutationRate), 100) # Cap cycles at 100
  167. remainder <- AnnualMutationRate %% 1
  168. # Process full cycles
  169. mutate_length(cycles)
  170. # Process remainder
  171. if (rbinom(1, 1, remainder) == 1) {
  172. mutate_length(1)
  173. }
  174. }
  175. },
  176. mutate_repeat_with_therapy = function() {
  177. # Compute probability of mutation event
  178. # Here we use therapeuticModifier value to reduce the annual mutation rate
  179. AnnualMutationRate <- twoPhaseLinearModel(self$repeat_length) * (1 - input$therapeuticModifier)
  180. # Function to mutate repeat
  181. mutate_length <- function(times) {
  182. self$repeat_length <- self$repeat_length + sum(sample(length_change_mutation() , times, replace = TRUE))
  183. }
  184. if(AnnualMutationRate <= 1){
  185. # single mutation event
  186. if (rbinom(1, 1, AnnualMutationRate) == 1) {
  187. mutate_length(1)
  188. }
  189. } else {
  190. # multiple mutation events
  191. cycles <- min(as.integer(AnnualMutationRate), 100) # Cap cycles at 100
  192. remainder <- AnnualMutationRate %% 1
  193. # Process full cycles
  194. mutate_length(cycles)
  195. # Process remainder
  196. if (rbinom(1, 1, remainder) == 1) {
  197. mutate_length(1)
  198. }
  199. }
  200. },
  201. set_repeat_length = function(new_length) {
  202. self$repeat_length <- new_length
  203. },
  204. describe = function() {
  205. cat("This cell has a repeat length of", self$repeat_length, "\n")
  206. }
  207. )
  208. )
  209. # Create object class representing a population of cells
  210. PopulationManager <- R6Class("PopulationManager",
  211. public = list(
  212. cells = NULL,
  213. initialize = function(cell_collection) {
  214. self$cells <- cell_collection
  215. },
  216. mutate_all_cells = function() {
  217. for (cell in self$cells) {
  218. if (cell$get_repeat_length() <= 300) {
  219. cell$mutate_repeat()
  220. }
  221. }
  222. },
  223. mutate_transduced_cells_with_therapy = function() {
  224. for (cell in self$cells) {
  225. if (cell$get_repeat_length() <= 300) {
  226. if (cell$get_therapeutic_transduction_status() == "transduced") {
  227. cell$mutate_repeat_with_therapy()
  228. } else {
  229. cell$mutate_repeat()
  230. }
  231. }
  232. }
  233. },
  234. administer_gene_therapy = function() {
  235. for (cell in self$cells) {
  236. cell$therapeutic_transduction_event()
  237. }
  238. },
  239. run_lifecycle = function(years) {
  240. data <- data.frame()
  241. for (year in 0:years) {
  242. repeat_lengths <- sapply(self$cells, function(cell) cell$get_repeat_length())
  243. transduction_status <- 0
  244. data <- rbind(data, data.frame(Year = year, RepeatLength = repeat_lengths, CellID = c(1:length(repeat_lengths)), Transduction = transduction_status))
  245. self$mutate_all_cells()
  246. }
  247. return(data)
  248. },
  249. run_lifecycle_with_therapy = function(years, untreated) {
  250. if (untreated != TRUE) {
  251. data <- data.frame()
  252. # run repeat expansion before gene therapy intervention
  253. for (year in 0:(input$age_at_intervention-1)) {
  254. repeat_lengths <- sapply(self$cells, function(cell) cell$get_repeat_length())
  255. transduction_status <- sapply(self$cells, function(cell) cell$get_therapeutic_transduction_status())
  256. data <- rbind(data, data.frame(Year = year, RepeatLength = repeat_lengths, CellID = c(1:length(repeat_lengths)), Transduction = transduction_status))
  257. self$mutate_all_cells()
  258. }
  259. # Administer gene therapy
  260. self$administer_gene_therapy()
  261. # run repeat expansion after gene therapy intervention
  262. for (year in input$age_at_intervention:years) {
  263. repeat_lengths <- sapply(self$cells, function(cell) cell$get_repeat_length())
  264. transduction_status <- sapply(self$cells, function(cell) cell$get_therapeutic_transduction_status())
  265. data <- rbind(data, data.frame(Year = year, RepeatLength = repeat_lengths, CellID = c(1:length(repeat_lengths)), Transduction = transduction_status))
  266. self$mutate_transduced_cells_with_therapy()
  267. }
  268. return(data)
  269. } else {
  270. data <- data.frame()
  271. for (year in 0:years) {
  272. repeat_lengths <- sapply(self$cells, function(cell) cell$get_repeat_length())
  273. transduction_status <- sapply(self$cells, function(cell) cell$get_therapeutic_transduction_status())
  274. data <- rbind(data, data.frame(Year = year, RepeatLength = repeat_lengths, CellID = c(1:length(repeat_lengths)), Transduction = transduction_status))
  275. self$mutate_transduced_cells_with_therapy()
  276. }
  277. return(data)
  278. }
  279. }
  280. )
  281. )
  282. observe({
  283. if (renderState$animationDone && renderState$linePlotDone && renderState$healthyDone) {
  284. updateProgressBar(session, id = "pbRun", value = 100, title = "Complete!", status = "success")
  285. # Re-enable Run button
  286. session$sendCustomMessage(type = "enableButton", message = list(id = "run"))
  287. # Reset flags for next run
  288. renderState$animationDone <- FALSE
  289. renderState$linePlotDone <- FALSE
  290. renderState$healthyDone <- FALSE
  291. }
  292. })
  293. ########################################
  294. # Run Simulation
  295. ########################################
  296. # Simulation must happen within this reactive block
  297. simulation_data <- eventReactive(input$run, {
  298. validate(
  299. need(input$maxLifetime > input$age_at_intervention,
  300. "Error: Max Lifetime must be greater than Age at Intervention."),
  301. )
  302. # Disable run simulation button after it is pressed
  303. session$sendCustomMessage(type = "disableButton", message = list(id = "run"))
  304. # Reset progress bar to 0
  305. updateProgressBar(session, id = "pbRun", value = 0, status = "info", title = "Running...")
  306. # Update progress bar
  307. updateProgressBar(session, id = "pbRun", value = 10, status = "info", title = "Starting simulation...")
  308. Sys.sleep(0.3)
  309. ######## CAP SCORE #############
  310. # Create a CAP score function
  311. # CAP = Age x (CAG-L)+ / K
  312. # (CAG-L)+ is a function that equals x when x >= 0 and 0 otherwise.
  313. CAG_L_fun <- function(x) {
  314. if (x >= 0) {
  315. return(x)
  316. } else {
  317. return(0)
  318. }
  319. }
  320. CAP_Score_Fun <- function(CurrentAge){
  321. L <- 30 # an estimate of the lower limit of CAG expansion values for which toxicity occurs
  322. K <- 6.49
  323. CAP_score <- CurrentAge * CAG_L_fun(input$germlineHDRepeatLength - L) / K
  324. return(list(CAP_score, K))
  325. }
  326. CAP_Score_Lookup_Table <- sapply(c(1:100), function(x){
  327. CAP_Score_Fun(x)[[1]]
  328. })
  329. Predicted_AgeOfOnset <- length(CAP_Score_Lookup_Table[CAP_Score_Lookup_Table <= 100])
  330. # # Compute stage data frames
  331. # stage1_df <- barplot_data_pct_dead_cells_merged[grep("1", barplot_data_pct_dead_cells_merged$HD_ISS_Stage), ]
  332. # stage2_df <- barplot_data_pct_dead_cells_merged[grep("2", barplot_data_pct_dead_cells_merged$HD_ISS_Stage), ]
  333. # stage3_df <- barplot_data_pct_dead_cells_merged[grep("3", barplot_data_pct_dead_cells_merged$HD_ISS_Stage), ]
  334. ### Instantiate Cells ###
  335. # Update progress bar
  336. updateProgressBar(session, id = "pbRun", value = 20, title = "Generating cell populations...")
  337. Sys.sleep(0.3)
  338. # Create cell collection
  339. cell_collection <- list()
  340. cell_collection_untreated <- list()
  341. #Instantiate cell objects with germline repeat length
  342. for (MSN in 1:input$nMSNs_CaudatePutamen) {
  343. MSN <- Cell$new(input$germlineHDRepeatLength)
  344. cell_collection[[length(cell_collection) + 1]] <- MSN
  345. }
  346. # Instantiate cell objects with germline repeat length
  347. for (MSN in 1:input$nMSNs_CaudatePutamen) {
  348. MSN <- Cell$new(input$germlineHDRepeatLength)
  349. cell_collection_untreated[[length(cell_collection_untreated) + 1]] <- MSN
  350. }
  351. ### Create and Manipulate Populations of Cells ###
  352. # Create PopulationManager
  353. population <- PopulationManager$new(cell_collection)
  354. population_untreated <- PopulationManager$new(cell_collection_untreated)
  355. # Update progress bar
  356. updateProgressBar(session, id = "pbRun", value = 30, title = "Running cell mutation simulation...")
  357. Sys.sleep(0.3)
  358. # Run simulation
  359. #if (input$therapy_on == TRUE) {
  360. # Get data for each year in lifetime (therapy on)
  361. plot_data <- population$run_lifecycle_with_therapy(input$maxLifetime, untreated = FALSE)
  362. plot_data_untreated <- population_untreated$run_lifecycle_with_therapy(input$maxLifetime, untreated = TRUE)
  363. #} else {
  364. # plot_data <- population$run_lifecycle(input$maxLifetime)
  365. # plot_data_untreated <- data.frame()
  366. #}
  367. ### Calculate Effect Size ###
  368. # Get pct of cells <= 150
  369. get_healthy_cells_fun <- function(z) {
  370. levels <- levels(as.factor(z$Year))
  371. barplot_data_pct_healthy_cells <- sapply(levels, function(x) {
  372. y <- as.numeric(x)
  373. year_subset <- z[z$Year == y,]
  374. pcthealthy <- (sum(year_subset$RepeatLength <= input$HealthyCellCAG_Threshold, na.rm = TRUE) / length(year_subset$RepeatLength)) * 100
  375. return(c(y, pcthealthy))
  376. } )
  377. barplot_data_pct_healthy_cells <- barplot_data_pct_healthy_cells
  378. barplot_data_pct_healthy_cells <- t(as.data.frame(barplot_data_pct_healthy_cells))
  379. barplot_data_pct_healthy_cells <- as.data.frame(barplot_data_pct_healthy_cells)
  380. colnames(barplot_data_pct_healthy_cells) <- c("Age","pct_healthy_Cells")
  381. return(barplot_data_pct_healthy_cells)
  382. }
  383. barplot_data_pct_healthy_cells <- get_healthy_cells_fun(plot_data)
  384. barplot_data_pct_healthy_cells_untreated <- get_healthy_cells_fun(plot_data_untreated)
  385. barplot_data_pct_healthy_cells_merged <- merge(barplot_data_pct_healthy_cells,barplot_data_pct_healthy_cells_untreated, by = "Age")
  386. colnames(barplot_data_pct_healthy_cells_merged) <- c("Age","HealthyTreated","HealthyUntreated")
  387. # Calculate intersect of CAP-100 with untreated cells
  388. PctHealthCells_at_Predicted_AgeOfOnset <- barplot_data_pct_healthy_cells_untreated[Predicted_AgeOfOnset == barplot_data_pct_healthy_cells_untreated$Age,][1,2]
  389. # # Get Therapeutic benefit (years of delayed onset)
  390. shiftedAge <- min(barplot_data_pct_healthy_cells[PctHealthCells_at_Predicted_AgeOfOnset >= barplot_data_pct_healthy_cells$pct_healthy_Cells,][,1])
  391. therapeuticBenefitYears <- shiftedAge - Predicted_AgeOfOnset
  392. # Update progress bar
  393. updateProgressBar(session, id = "pbRun", value = 40, title = "Running cell mutation simulation...")
  394. Sys.sleep(0.3)
  395. # Calculate therapeutic benefit for treatment across all ages
  396. therapeuticBenefitYears_fun <- function(ages){
  397. shiftedAge <- min(barplot_data_pct_healthy_cells[PctHealthCells_at_Predicted_AgeOfOnset >= barplot_data_pct_healthy_cells$pct_healthy_Cells,][,1])
  398. return(shiftedAge)
  399. }
  400. # Calculate dead cell percentages for treated cells
  401. barplot_data_pct_dead_cells <- get_dead_cells_fun(plot_data)
  402. # Calculate dead cell percentages for untreated cells
  403. barplot_data_pct_dead_cells_untreated <- get_dead_cells_fun(plot_data_untreated)
  404. # Merge the two dead cell data frames by Age
  405. barplot_data_pct_dead_cells_merged <- merge(barplot_data_pct_dead_cells,
  406. barplot_data_pct_dead_cells_untreated,
  407. by = "Age")
  408. colnames(barplot_data_pct_dead_cells_merged) <- c("Age", "DeadTreated", "DeadUntreated")
  409. # Define HD-ISS Stage function
  410. Run_HDISS_score_fun_production <- function(pct_cells_alive, age, cag){
  411. age <- as.numeric(age)
  412. if (age %in% colnames(TotalFunctionalCapacity_Score)) {
  413. #print(paste0("age is ", age, " class is ", class(age)))
  414. # Extract functional capacity and ensure it's a single value
  415. FunctionalCapacityVal <- as.numeric(TotalFunctionalCapacity_Score[grep(paste0('\\b', cag, '\\b'), TotalFunctionalCapacity_Score$CAG), grep(paste0('\\b', age, '\\b'), colnames(TotalFunctionalCapacity_Score))])
  416. if (length(FunctionalCapacityVal) != 1) {
  417. stop("Functional Capacity should be a single value.")
  418. #FunctionalCapacityVal <- 1
  419. } else if (is.na(FunctionalCapacityVal)) {
  420. FunctionalCapacityVal <- 1
  421. } else if (FunctionalCapacityVal == 0) {
  422. FunctionalCapacityVal <- 1
  423. } else {
  424. FunctionalCapacityVal <- as.numeric(FunctionalCapacityVal)
  425. }
  426. #print(paste0("functional capacity is ", FunctionalCapacityVal, " class is ", class(FunctionalCapacityVal)))
  427. # Extract motor score and ensure it's a single value
  428. MotorScoreVal <- as.numeric(Motor_Score[grep(paste0('\\b', cag, '\\b'), Motor_Score$CAG), grep(paste0('\\b', age, '\\b'), colnames(Motor_Score))])
  429. if (length(as.numeric(MotorScoreVal)) != 1) {
  430. stop("Motor Score should be a single value.")
  431. #MotorScoreVal <- 0
  432. } else if (is.na(MotorScoreVal)) {
  433. MotorScoreVal <- 80
  434. } else {
  435. MotorScoreVal <- as.numeric(MotorScoreVal)
  436. }
  437. #print(paste0("Motor score is ", MotorScoreVal, " class s is ", class(MotorScoreVal)))
  438. # Call the function with validated inputs
  439. HD_ISS_Stage <- HD_ISS_Stage_Calc_FUN(
  440. CurrentAge = as.numeric(age),
  441. cag = as.numeric(input$germlineHDRepeatLength),
  442. brainVolume = as.numeric(pct_cells_alive),
  443. motorScore = as.numeric(MotorScoreVal),
  444. functionalCapacity = as.numeric(FunctionalCapacityVal)
  445. )
  446. # Check to confirm the function is working
  447. print(paste0("age is ", age, " ISS stage is ", HD_ISS_Stage, " Motor Score is ", MotorScoreVal, " functionalCapacity is ", FunctionalCapacityVal))
  448. # output only stage
  449. return(HD_ISS_Stage)
  450. } else {
  451. return(NA)
  452. }
  453. }
  454. # Update progress bar
  455. updateProgressBar(session, id = "pbRun", value = 60, title = "Running cell mutation simulation...")
  456. Sys.sleep(0.3)
  457. # Calculate HD-ISS stage across all ages
  458. HDISS_Stages <- apply(barplot_data_pct_dead_cells_merged, 1, function(x){
  459. #x <- as.character(x)
  460. #print(x)
  461. #return(x)
  462. age <- x[1]
  463. #print(paste0("age is ", age))
  464. pctAliveCells <- (100 - x[3]) / 100
  465. #print(paste0("pctAliveCells is ", pctAliveCells))
  466. HDISS_Stage <- Run_HDISS_score_fun_production(pctAliveCells, age, input$germlineHDRepeatLength)
  467. return(HDISS_Stage)
  468. })
  469. barplot_data_pct_dead_cells_merged["HD_ISS_Stage"] <- HDISS_Stages
  470. stage1_df <- barplot_data_pct_dead_cells_merged[grep("1", barplot_data_pct_dead_cells_merged$HD_ISS_Stage),]
  471. stage2_df <- barplot_data_pct_dead_cells_merged[grep("2", barplot_data_pct_dead_cells_merged$HD_ISS_Stage),]
  472. stage3_df <- barplot_data_pct_dead_cells_merged[grep("3", barplot_data_pct_dead_cells_merged$HD_ISS_Stage),]
  473. s1_start <- safe_min_age(stage1_df, input$maxLifetime)
  474. s2_start <- safe_min_age(stage2_df, input$maxLifetime)
  475. s3_start <- safe_min_age(stage3_df, input$maxLifetime)
  476. background_data <- data.frame(
  477. xmin = as.numeric(c(-Inf, s1_start, s2_start, s3_start)),
  478. xmax = as.numeric(c(s1_start, s2_start, s3_start, input$maxLifetime)),
  479. ymin = rep(-Inf, 4),
  480. ymax = rep(Inf, 4),
  481. fill = factor(c("white", "lightgrey", "grey", "darkgrey"),
  482. levels = c("white", "lightgrey", "grey", "darkgrey"))
  483. )
  484. # background_data <- data.frame(
  485. # xmin = as.numeric(c(-Inf, min(stage1_df$Age), min(stage2_df$Age), min(stage3_df$Age))),
  486. # xmax = as.numeric(c(min(stage1_df$Age), min(stage2_df$Age), min(stage3_df$Age), input$maxLifetime)),
  487. # ymin = rep(-Inf, 4),
  488. # ymax = rep(Inf, 4),
  489. # fill = factor(c("white", "lightgrey", "grey", "darkgrey"), levels = c("white", "lightgrey", "grey", "darkgrey"))
  490. # )
  491. # Run Function on healthy cells
  492. barplot_data_pct_healthy_cells <- get_healthy_cells_fun(plot_data)
  493. barplot_data_pct_healthy_cells_untreated <- get_healthy_cells_fun(plot_data_untreated)
  494. barplot_data_pct_healthy_cells_merged <- merge(barplot_data_pct_healthy_cells,barplot_data_pct_healthy_cells_untreated, by = "Age")
  495. colnames(barplot_data_pct_healthy_cells_merged) <- c("Age","HealthyTreated","HealthyUntreated")
  496. linePlot_data <- merge(barplot_data_pct_healthy_cells_merged, barplot_data_pct_dead_cells_merged, by = "Age")
  497. # Combine stages into a data frame with new colors
  498. convertToTreated_fun <- function(x){
  499. selected_line <- linePlot_data[linePlot_data$Age == x,]
  500. pctHealthUntreatedCells <- selected_line[3]
  501. selected_line_2 <- linePlot_data[linePlot_data$HealthyTreated >= pctHealthUntreatedCells[1,1],]
  502. age <- max(selected_line_2$Age) + 1
  503. return(age)
  504. }
  505. background_data_treated <- data.frame(
  506. xmin = as.numeric(c(-Inf, convertToTreated_fun(s1_start), convertToTreated_fun(s2_start), convertToTreated_fun(s3_start))),
  507. xmax = as.numeric(c(convertToTreated_fun(s1_start), convertToTreated_fun(s2_start), convertToTreated_fun(s3_start), input$maxLifetime)),
  508. ymin = rep(-Inf, 4),
  509. ymax = rep(Inf, 4),
  510. fill = factor(c("white", "lightgrey", "grey", "darkgrey"), levels = c("white", "lightgrey", "grey", "darkgrey"))
  511. )
  512. # Update progress bar
  513. updateProgressBar(session, id = "pbRun", value = 70, title = "Rendering output plots...")
  514. Sys.sleep(0.3)
  515. #plot_data_list <- list(plot_data,plot_data_untreated,background_data,background_data_treated)
  516. # Return all needed objects as a list
  517. plot_data_list <- list(
  518. plot_data = plot_data,
  519. plot_data_untreated = plot_data_untreated,
  520. background_data = background_data,
  521. background_data_treated = background_data_treated,
  522. stage1_df = stage1_df,
  523. stage2_df = stage2_df,
  524. stage3_df = stage3_df,
  525. barplot_data_pct_dead_cells_merged = barplot_data_pct_dead_cells_merged,
  526. barplot_data_pct_healthy_cells_merged = barplot_data_pct_healthy_cells_merged,
  527. therapeuticBenefitYears = therapeuticBenefitYears,
  528. linePlot_data = linePlot_data)
  529. return(plot_data_list)
  530. })
  531. ########################################
  532. # Handsaker CTMC comparison simulation
  533. ########################################
  534. handsaker_simulation_data <- eventReactive(input$run, {
  535. validate(
  536. need(input$maxLifetime > input$age_at_intervention,
  537. "Error: Max Lifetime must be greater than Age at Intervention."),
  538. need(input$p_exp > 0.5,
  539. "Error: Handsaker p_exp must be > 0.5 for net expansion.")
  540. )
  541. plot_data_h <- simulate_handsaker_lifecycle(
  542. ncells = input$nMSNs_CaudatePutamen,
  543. years = input$maxLifetime,
  544. initial_cag = input$germlineHDRepeatLength,
  545. age_at_intervention = input$age_at_intervention,
  546. transduction_rate = input$transductionRate,
  547. therapeutic_modifier = input$therapeuticModifier,
  548. pexp = input$p_exp,
  549. untreated = FALSE
  550. )
  551. plot_data_untreated_h <- simulate_handsaker_lifecycle(
  552. ncells = input$nMSNs_CaudatePutamen,
  553. years = input$maxLifetime,
  554. initial_cag = input$germlineHDRepeatLength,
  555. age_at_intervention = input$age_at_intervention,
  556. transduction_rate = input$transductionRate,
  557. therapeutic_modifier = input$therapeuticModifier,
  558. pexp = input$p_exp,
  559. untreated = TRUE
  560. )
  561. healthy_h <- get_healthy_cells_fun(plot_data_h, input$HealthyCellCAG_Threshold)
  562. healthy_h_untreated <- get_healthy_cells_fun(plot_data_untreated_h, input$HealthyCellCAG_Threshold)
  563. healthy_h_merged <- merge(healthy_h, healthy_h_untreated, by = "Age")
  564. colnames(healthy_h_merged) <- c("Age", "HealthyTreated", "HealthyUntreated")
  565. dead_h <- get_dead_cells_fun(plot_data_h)
  566. dead_h_untreated <- get_dead_cells_fun(plot_data_untreated_h)
  567. dead_h_merged <- merge(dead_h, dead_h_untreated, by = "Age")
  568. colnames(dead_h_merged) <- c("Age", "DeadTreated", "DeadUntreated")
  569. line_h <- merge(healthy_h_merged, dead_h_merged, by = "Age")
  570. line_h$Model <- "Handsaker CTMC"
  571. return(list(
  572. plot_data = plot_data_h,
  573. plot_data_untreated = plot_data_untreated_h,
  574. linePlot_data = line_h,
  575. healthy_cells = healthy_h_merged,
  576. dead_cells = dead_h_merged
  577. ))
  578. })
  579. ########################################
  580. # Active model selector
  581. ########################################
  582. active_simulation_data <- reactive({
  583. if (input$modelMode == "handsaker") {
  584. handsaker_simulation_data()
  585. } else {
  586. simulation_data()
  587. }
  588. })
  589. #######################################
  590. # Create and Render Output Plots
  591. #######################################
  592. # Create a CAP score function
  593. # CAP = Age x (CAG-L)+ / K
  594. # (CAG-L)+ is a function that equals x when x >= 0 and 0 otherwise.
  595. CAG_L_fun <- function(x) {
  596. if (x >= 0) {
  597. return(x)
  598. } else {
  599. return(0)
  600. }
  601. }
  602. CAP_Score_Fun <- function(CurrentAge, repLen){
  603. L <- 30 # an estimate of the lower limit of CAG expansion values for which toxicity occurs
  604. K <- 6.49
  605. CAP_score <- CurrentAge * CAG_L_fun(repLen - L) / K
  606. return(list(CAP_score, K))
  607. }
  608. GetPredAgeOfOnset <- function(repLen){
  609. CAP_Score_Lookup_Table <- sapply(c(1:100), function(x){
  610. CAP_Score_Fun(x, repLen)[[1]]
  611. })
  612. Predicted_AgeOfOnset <- length(CAP_Score_Lookup_Table[CAP_Score_Lookup_Table <= 100])
  613. return(Predicted_AgeOfOnset)
  614. }
  615. ### Create animated plot of cell loss ###
  616. make_repeat_animation <- function(plot_data, title_prefix, repeat_len, highlight, max_life) {
  617. threshold <- input$HealthyCellCAG_Threshold
  618. if (highlight != TRUE) {
  619. p <- ggplot(plot_data, aes(x = RepeatLength, y = CellID)) +
  620. geom_point(aes(color = RepeatLength), alpha = 0.7) +
  621. geom_vline(xintercept = threshold, color = "red", linetype = "dashed") +
  622. scale_color_gradient2(low = "forestgreen", mid = "red", high = "red", midpoint = 200) +
  623. labs(title = title_prefix,
  624. subtitle = "Year: {frame_time}. CAP Score: {round(CAP_Score_Fun(frame_time, repeat_len)[[1]], 2)}",
  625. x = "Repeat Length",
  626. y = "") +
  627. theme_minimal() +
  628. theme(axis.text.y = element_blank(), axis.ticks.y = element_blank()) +
  629. coord_cartesian(xlim = c(0, 285)) +
  630. transition_time(Year) +
  631. ease_aes('linear')
  632. } else {
  633. p <- ggplot(plot_data, aes(x = RepeatLength, y = CellID)) +
  634. geom_point(aes(color = RepeatLength), alpha = 0.7) +
  635. geom_point(data = subset(plot_data, Transduction == "transduced"),
  636. aes(x = RepeatLength, y = CellID),
  637. color = "violet", shape = 21, size = 3, fill = NA) +
  638. geom_vline(xintercept = threshold, color = "red", linetype = "dashed") +
  639. scale_color_gradient2(low = "forestgreen", mid = "red", high = "red", midpoint = 200) +
  640. labs(title = title_prefix,
  641. subtitle = "Year: {frame_time}. CAP Score: {round(CAP_Score_Fun(frame_time, repeat_len)[[1]], 2)}",
  642. x = "Repeat Length",
  643. y = "") +
  644. theme_minimal() +
  645. theme(axis.text.y = element_blank(), axis.ticks.y = element_blank()) +
  646. coord_cartesian(xlim = c(0, 285)) +
  647. transition_time(Year) +
  648. ease_aes('linear')
  649. }
  650. return(p)
  651. }
  652. output$animationPlot <- renderImage({
  653. d <- active_simulation_data()
  654. plot_data <- d$plot_data
  655. repeat_len <- isolate(input$germlineHDRepeatLength)
  656. highlight <- isolate(input$highlightTransducedCells)
  657. max_life <- isolate(input$maxLifetime)
  658. model_title <- ifelse(input$modelMode == "handsaker",
  659. paste0("Handsaker CTMC model (p_exp = ", input$p_exp, ")"),
  660. "Latus event-sampling model")
  661. p <- make_repeat_animation(plot_data,
  662. title_prefix = model_title,
  663. repeat_len = repeat_len,
  664. highlight = highlight,
  665. max_life = max_life)
  666. anim_file <- tempfile(fileext = '.gif')
  667. anim_save(anim_file, animate(p, nframes = max_life, fps = 4))
  668. isolate({ renderState$animationDone <- TRUE })
  669. list(src = anim_file,
  670. contentType = 'image/gif',
  671. width = 800,
  672. height = 600,
  673. alt = "Repeat expansion animation")
  674. }, deleteFile = TRUE)
  675. ### Collect information about dead cells ### Collect information about dead cells (>300 CAGs) ###
  676. # Define function
  677. get_dead_cells_fun <- function(z){
  678. levels <- levels(as.factor(z$Year))
  679. barplot_data_pct_dead_cells <- sapply(levels, function(x) {
  680. y <- as.numeric(x)
  681. year_subset <- z[z$Year == y,]
  682. pctDead <- (sum(year_subset$RepeatLength >= 300, na.rm = TRUE) / length(year_subset$RepeatLength)) * 100
  683. return(c(y, pctDead))
  684. } )
  685. barplot_data_pct_dead_cells <- barplot_data_pct_dead_cells
  686. barplot_data_pct_dead_cells <- t(as.data.frame(barplot_data_pct_dead_cells))
  687. barplot_data_pct_dead_cells <- as.data.frame(barplot_data_pct_dead_cells)
  688. colnames(barplot_data_pct_dead_cells) <- c("Age","pct_Dead_Cells")
  689. return(barplot_data_pct_dead_cells)
  690. }
  691. # Render plot
  692. output$deadCellsPlot <- renderPlot({
  693. # Make plot_data dataframes generated in simulation dataframes availible ###
  694. plot_data <- simulation_data()[[1]]
  695. plot_data_untreated <- simulation_data()[[2]]
  696. # Run Function on dead cells
  697. barplot_data_pct_dead_cells <- get_dead_cells_fun(plot_data)
  698. #barplot_data_pct_dead_cells_untreated <- get_dead_cells_fun(plot_data_untreated)
  699. #barplot_data_pct_dead_cells_merged <- merge(barplot_data_pct_dead_cells,barplot_data_pct_dead_cells_untreated, by = "Age")
  700. #colnames(barplot_data_pct_dead_cells_merged) <- c("Age","DeadTreated","DeadUntreated")
  701. dead_plot <- ggplot(barplot_data_pct_dead_cells, aes(x = Age, y = pct_Dead_Cells)) +
  702. geom_bar(stat = "identity", fill = "steelblue") +
  703. theme_cowplot() +
  704. ylim(0, 100) +
  705. labs(title = "Percentage of Dead Cells", x = "Age", y = "% Dead Cells")
  706. return(dead_plot)
  707. })
  708. ### Collect information about healthy cells (<150 CAGs) ###
  709. # Define function
  710. get_healthy_cells_fun <- function(z, threshold = input$HealthyCellCAG_Threshold) {
  711. levels <- levels(as.factor(z$Year))
  712. barplot_data_pct_healthy_cells <- sapply(levels, function(x) {
  713. y <- as.numeric(x)
  714. year_subset <- z[z$Year == y,]
  715. pcthealthy <- (sum(year_subset$RepeatLength <= threshold, na.rm = TRUE) / length(year_subset$RepeatLength)) * 100
  716. return(c(y, pcthealthy))
  717. } )
  718. barplot_data_pct_healthy_cells <- t(as.data.frame(barplot_data_pct_healthy_cells))
  719. barplot_data_pct_healthy_cells <- as.data.frame(barplot_data_pct_healthy_cells)
  720. colnames(barplot_data_pct_healthy_cells) <- c("Age","pct_healthy_Cells")
  721. return(barplot_data_pct_healthy_cells)
  722. }
  723. ### Create histogram of healthy cells ###
  724. output$healthyCellsPlot <- renderPlot({
  725. d <- active_simulation_data()
  726. if (!is.null(d$barplot_data_pct_healthy_cells_merged)) {
  727. barplot_data_pct_healthy_cells <- d$barplot_data_pct_healthy_cells_merged[, c("Age", "HealthyTreated")]
  728. colnames(barplot_data_pct_healthy_cells) <- c("Age", "pct_healthy_Cells")
  729. } else if (!is.null(d$healthy_cells)) {
  730. barplot_data_pct_healthy_cells <- d$healthy_cells[, c("Age", "HealthyTreated")]
  731. colnames(barplot_data_pct_healthy_cells) <- c("Age", "pct_healthy_Cells")
  732. } else {
  733. barplot_data_pct_healthy_cells <- get_healthy_cells_fun(d$plot_data, input$HealthyCellCAG_Threshold)
  734. }
  735. healthy_plot <- ggplot(barplot_data_pct_healthy_cells, aes(x = Age, y = pct_healthy_Cells)) +
  736. geom_bar(stat = "identity", fill = "steelblue") +
  737. theme_cowplot() +
  738. ylim(0, 100) +
  739. labs(title = paste0("Percentage of Cells Under ", input$HealthyCellCAG_Threshold, " Repeats"),
  740. subtitle = ifelse(input$modelMode == "handsaker", "Handsaker CTMC model", "Latus event-sampling model"),
  741. x = "Age", y = paste0("% Cells Under ", input$HealthyCellCAG_Threshold, " Repeats"))
  742. isolate({ renderState$healthyDone <- TRUE })
  743. updateProgressBar(session, id = "pbRun", value = 80, title = "Rendering output plots...")
  744. stored_plots$healthyPlot <- healthy_plot
  745. return(healthy_plot)
  746. })
  747. ### Create a line plot of from both treated and untreated datasets ###
  748. output$linePlot <- renderPlot({
  749. d <- active_simulation_data()
  750. repeat_len <- isolate(input$germlineHDRepeatLength)
  751. intervention_age <- isolate(input$age_at_intervention)
  752. max_life <- isolate(input$maxLifetime)
  753. therapeuticBenefitYears <- ifelse(!is.null(d$therapeuticBenefitYears), d$therapeuticBenefitYears, NA)
  754. linePlot_data <- d$linePlot_data
  755. if (is.null(linePlot_data)) {
  756. hc <- get_healthy_cells_fun(d$plot_data, input$HealthyCellCAG_Threshold)
  757. hcu <- get_healthy_cells_fun(d$plot_data_untreated, input$HealthyCellCAG_Threshold)
  758. hcm <- merge(hc, hcu, by = "Age")
  759. colnames(hcm) <- c("Age", "HealthyTreated", "HealthyUntreated")
  760. dc <- get_dead_cells_fun(d$plot_data)
  761. dcu <- get_dead_cells_fun(d$plot_data_untreated)
  762. dcm <- merge(dc, dcu, by = "Age")
  763. colnames(dcm) <- c("Age", "DeadTreated", "DeadUntreated")
  764. linePlot_data <- merge(hcm, dcm, by = "Age")
  765. }
  766. Predicted_AgeOfOnset <- GetPredAgeOfOnset(repeat_len)
  767. convertToTreated_fun <- function(x){
  768. if (!is.finite(x) || x >= max_life) return(max_life)
  769. selected_line <- linePlot_data[linePlot_data$Age == x,]
  770. if (nrow(selected_line) == 0) return(max_life)
  771. pctHealthUntreatedCells <- selected_line[3]
  772. selected_line_2 <- linePlot_data[linePlot_data$HealthyTreated >= pctHealthUntreatedCells[1,1],]
  773. if (nrow(selected_line_2) == 0) return(max_life)
  774. age <- max(selected_line_2$Age) + 1
  775. return(age)
  776. }
  777. # Use existing stage shading if available; otherwise skip background rectangles
  778. background_data_treated <- NULL
  779. if (!is.null(d$stage1_df) && !is.null(d$stage2_df) && !is.null(d$stage3_df)) {
  780. s1_start <- safe_min_age(d$stage1_df, max_life)
  781. s2_start <- safe_min_age(d$stage2_df, max_life)
  782. s3_start <- safe_min_age(d$stage3_df, max_life)
  783. background_data_treated <- data.frame(
  784. xmin = as.numeric(c(-Inf, convertToTreated_fun(s1_start), convertToTreated_fun(s2_start), convertToTreated_fun(s3_start))),
  785. xmax = as.numeric(c(convertToTreated_fun(s1_start), convertToTreated_fun(s2_start), convertToTreated_fun(s3_start), max_life)),
  786. ymin = rep(-Inf, 4),
  787. ymax = rep(Inf, 4),
  788. fill = factor(c("white", "lightgrey", "grey", "darkgrey"), levels = c("white", "lightgrey", "grey", "darkgrey"))
  789. )
  790. }
  791. line_plot <- ggplot(linePlot_data, aes(x = Age))
  792. if (!is.null(background_data_treated)) {
  793. line_plot <- line_plot +
  794. geom_rect(data = background_data_treated, aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax, fill = fill),
  795. alpha = 0.2, inherit.aes = FALSE)
  796. }
  797. line_plot <- line_plot +
  798. geom_line(aes(y = HealthyTreated, color = "healthy_treated"), linewidth = 1.2) +
  799. geom_line(aes(y = HealthyUntreated, color = "healthy_untreated"), linewidth = 1.2) +
  800. geom_vline(xintercept = intervention_age, color = "purple", linetype = "dashed") +
  801. annotate("text", x = intervention_age, y = 50, label = "Gene Therapy Administered",
  802. vjust = -1, hjust = 1, color = "purple", angle = 90) +
  803. geom_vline(xintercept = convertToTreated_fun(Predicted_AgeOfOnset), color = "purple", linetype = "dashed") +
  804. annotate("text", x = convertToTreated_fun(Predicted_AgeOfOnset), y = 50, label = "Motor Symptom Onset (CAP=100)",
  805. vjust = -1, hjust = 0.3, color = "purple", angle = 90) +
  806. scale_color_manual(
  807. values = c("healthy_treated" = "purple", "healthy_untreated" = "grey50"),
  808. labels = c("healthy_treated" = paste0("Healthy Cells (<", input$HealthyCellCAG_Threshold, ") treated"),
  809. "healthy_untreated" = paste0("Healthy Cells (<", input$HealthyCellCAG_Threshold, ") untreated"))
  810. ) +
  811. scale_fill_manual(name="HD-ISS Stages", values=c("white"="white", "lightgrey"="lightpink",
  812. "grey"="violet", "darkgrey"="purple"),
  813. labels=c("Stage 0", "Stage 1", "Stage 2", "Stage 3")) +
  814. labs(title="Percentage of Cells Over Time",
  815. subtitle=paste0(ifelse(input$modelMode == "handsaker", "Handsaker CTMC model", "Latus event-sampling model"),
  816. ifelse(is.na(therapeuticBenefitYears), "", paste0(" | Therapeutic Benefit = ", therapeuticBenefitYears, " years"))),
  817. x="Age", y="Percentage of cells", color="Cell Type") +
  818. theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank(),
  819. panel.background = element_blank(), axis.line = element_line(colour = "black"))
  820. isolate({ renderState$linePlotDone <- TRUE })
  821. updateProgressBar(session, id = "pbRun", value = 90, title = "Rendering output plots...")
  822. stored_plots$linePlot <- line_plot
  823. return(line_plot)
  824. })
  825. # ============================================================================
  826. # Download Handlers
  827. # ============================================================================
  828. # --- Animation GIF ---
  829. output$download_animation_gif <- downloadHandler(
  830. filename = function() { paste0(get_file_prefix(), "_animation.gif") },
  831. content = function(file) {
  832. d <- active_simulation_data()
  833. plot_data <- d$plot_data
  834. repeat_len <- input$germlineHDRepeatLength
  835. highlight <- input$highlightTransducedCells
  836. max_life <- input$maxLifetime
  837. model_title <- ifelse(input$modelMode == "handsaker",
  838. paste0("Handsaker CTMC model (p_exp = ", input$p_exp, ")"),
  839. "Latus event-sampling model")
  840. p <- make_repeat_animation(plot_data, model_title, repeat_len, highlight, max_life)
  841. anim_save(file, animate(p, nframes = max_life, fps = 4))
  842. }
  843. )
  844. # --- Animation underlying data (CSV) ---
  845. output$download_animation_csv <- downloadHandler(
  846. filename = function() { paste0(get_file_prefix(), "_cell_data.csv") },
  847. content = function(file) {
  848. write.csv(active_simulation_data()$plot_data, file, row.names = FALSE)
  849. }
  850. )
  851. # --- Line Plot PNG ---
  852. output$download_linePlot_png <- downloadHandler(
  853. filename = function() { paste0(get_file_prefix(), "_linePlot.png") },
  854. content = function(file) {
  855. ggsave(file, stored_plots$linePlot, width = 10, height = 7, dpi = 300)
  856. }
  857. )
  858. # --- Line Plot data (CSV) ---
  859. output$download_linePlot_csv <- downloadHandler(
  860. filename = function() { paste0(get_file_prefix(), "_linePlot_data.csv") },
  861. content = function(file) {
  862. write.csv(active_simulation_data()$linePlot_data, file, row.names = FALSE)
  863. }
  864. )
  865. # --- Healthy Cells Plot PNG ---
  866. output$download_healthyPlot_png <- downloadHandler(
  867. filename = function() { paste0(get_file_prefix(), "_healthyCells.png") },
  868. content = function(file) {
  869. ggsave(file, stored_plots$healthyPlot, width = 10, height = 7, dpi = 300)
  870. }
  871. )
  872. # --- Healthy Cells data (CSV) ---
  873. output$download_healthyPlot_csv <- downloadHandler(
  874. filename = function() { paste0(get_file_prefix(), "_healthyCells_data.csv") },
  875. content = function(file) {
  876. write.csv(if (!is.null(active_simulation_data()$barplot_data_pct_healthy_cells_merged)) active_simulation_data()$barplot_data_pct_healthy_cells_merged else active_simulation_data()$healthy_cells, file, row.names = FALSE)
  877. }
  878. )
  879. # --- Run Configuration file ---
  880. output$download_config <- downloadHandler(
  881. filename = function() { paste0(get_file_prefix(), "_config.csv") },
  882. content = function(file) {
  883. config <- data.frame(
  884. Parameter = c(
  885. "Germline CAG Repeat Length",
  886. "Age at Intervention",
  887. "Max Lifetime",
  888. "MSH3 Knockdown Percentage",
  889. "Transduction Rate",
  890. "Number of MSNs Simulated",
  891. "Healthy Cell CAG Threshold",
  892. "Contraction Events per Cycle",
  893. "Expansion Events per Cycle",
  894. "Expansion:Contraction Ratio",
  895. "Phase 1 Threshold (T1)",
  896. "Phase 1 Rate (r1)",
  897. "Phase 2 Threshold (T2)",
  898. "Phase 2 Rate (r2)",
  899. "Cell Death Threshold (CAG)",
  900. "Timestamp"
  901. ),
  902. Value = c(
  903. input$germlineHDRepeatLength,
  904. input$age_at_intervention,
  905. input$maxLifetime,
  906. input$therapeuticModifier,
  907. input$transductionRate,
  908. input$nMSNs_CaudatePutamen,
  909. input$HealthyCellCAG_Threshold,
  910. input$n_contractions,
  911. input$n_expansions,
  912. paste0(input$n_expansions, ":", input$n_contractions),
  913. 33.5,
  914. 0.035,
  915. 55,
  916. 0.576,
  917. 300,
  918. format(Sys.time(), "%Y-%m-%d %H:%M:%S")
  919. )
  920. )
  921. write.csv(config, file, row.names = FALSE)
  922. }
  923. )
  924. }

server.r at commit b9de42a, no license · at the source

Overview

Authors: Bryan P Simpson1, Paul T Ranum1, David E Leib1, Luis Tecedor2, Ryan C Giovenco2, Icnelia Huerta-Ocampo2, Amiel A Hundley1, Noah Smith1, Christopher M Fluta1, Christopher P Cali1, Jamie Benoit1, Jenna C Soper1, John Connelly1, George J Yohrling1, P Peter Ghoroghchian1, Jang-Ho J Cha1, Beverly L Davidson2,3
  1. Latus Bio, Philadelphia, PA USA
  2. Raymond G. Perelman Center for Cellular and Molecular Therapeutics, The Children’s Hospital of Philadelphia, Philadelphia, PA USA
  3. Department of Pathology and Laboratory Medicine, University of Pennsylvania, Philadelphia, PA USA
Institutions: Children's Hospital of Philadelphia (United States); University of Pennsylvania (United States)
Journal: Nature communications, volume 17, issue 1, article 11036
Dates: received 17 March 2026; accepted 29 September 2026; published online 28 October 2026
Type: Research article · Language: English
License: CC BY-NC-ND
Identifiers: DOI 10.1038/s41467-026-78769-2 · PMID 42951910 · PMCID PMC13716904 · OpenAlex W7224083701
Open access: gold, a free copy (OpenAlex)
Status: code verified (how the code is found and verified)
Categories: computational modeling (no new data) (modality), human (organism), mouse (organism), non-human primate (organism), other condition (population), clinical / translational (subfield)
Methods: Statistics
Keywords: Diseases of the nervous system, Movement disorders, Huntington's disease
MeSH: Genetic Therapy*, Huntington Disease*, Animals, Computer Simulation, Dependovirus, Disease Models, Animal, Female, Genetic Vectors, Humans, Male, Medium Spiny Neurons, Mice, MicroRNAs, RNA, Messenger, Trinucleotide Repeat Expansion (* major topic)
Topic: Genetic Neurodegenerative Diseases (Cellular and Molecular Neuroscience, Neuroscience), according to OpenAlex
Funding: Hereditary Disease Foundation (HDF) (115873)
Citations: not cited yet (Europe PMC); 50 references in the paper

Abstract

The abstract is not reproduced here: the paper's license (CC BY-NC-ND) does not allow it. Read it in the paper, at the publisher or on Europe PMC.

Repositories

Its files are read in the Code ↔ Paper reader above, with 7 matches between paragraphs and lines of code.

LatusBio/HD_Model_NatComm_Publication

License: none: the authors keep all their rights
State: the link answers, verified on 30 October 2026
Evidence: files inventoried
Commit: b9de42ac6f634d137f68e57b059caf08f9ef4d05, 28 October 2026
Languages: R (4)
Size: 8 files, 4 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: tidyverse (2 files), cowplot (1 file), ggplot2 (1 file), reshape2 (1 file)
Availability: 1 check, the latest on 30 October 2026: the link answers
  • 30 October 2026: the link answers
5 files

Zenodo 22118021

License: CC-BY-4.0
State: the link answers, verified on 30 October 2026
Evidence: files inventoried
Size: 1 file
Software Heritage: not checked
Found in: “Code availability”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: tidyverse (2 files), cowplot (1 file), ggplot2 (1 file), reshape2 (1 file)
Availability: 1 check, the latest on 30 October 2026: the link answers (HTTP 200)
  • 30 October 2026: the link answers (HTTP 200)
5 files
At the source:

Zenodo 22118020

License: CC-BY-4.0
State: the link answers, verified on 30 October 2026
Evidence: files inventoried
Size: 1 file
Software Heritage: not checked
Found in: the references
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: tidyverse (2 files), cowplot (1 file), ggplot2 (1 file), reshape2 (1 file)
Availability: 1 check, the latest on 30 October 2026: the link answers (HTTP 200)
  • 30 October 2026: the link answers (HTTP 200)
5 files
At the source:

Code availability statement

The paper has a code availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:

Read it in the paper: doi.org/10.1038/s41467-026-78769-2.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 3 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 12 scripts, each with its path and the digest of its content;
  • 7 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

Datasets cited

Data availability statement

The paper has a data availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:

Read it in the paper: doi.org/10.1038/s41467-026-78769-2.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 30 October 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 17 authors, 3 keywords, 15 MeSH terms, 1 funder, 49 references.

Cite

This paper

Simpson, B. P., Ranum, P. T., Leib, D. E., Tecedor, L., Giovenco, R. C., Huerta-Ocampo, I., Hundley, A. A., Smith, N., Fluta, C. M., Cali, C. P., Benoit, J., Soper, J. C., Connelly, J., Yohrling, G. J., Ghoroghchian, P. P., Cha, J.-H. J., & Davidson, B. L. (2026). Computational modeling and preclinical evaluation support targeting somatic instability for Huntington's disease treatment. Nature communications, 17(1), 11036. https://doi.org/10.1038/s41467-026-78769-2

BibTeX

@article{simpson2026computational,
author = {Simpson, Bryan P and Ranum, Paul T and Leib, David E and Tecedor, Luis and Giovenco, Ryan C and Huerta-Ocampo, Icnelia and Hundley, Amiel A and Smith, Noah and Fluta, Christopher M and Cali, Christopher P and Benoit, Jamie and Soper, Jenna C and Connelly, John and Yohrling, George J and Ghoroghchian, P Peter and Cha, Jang-Ho J and Davidson, Beverly L},
title = {{Computational modeling and preclinical evaluation support targeting somatic instability for Huntington's disease treatment}},
journal = {Nature communications},
year = {2026},
month = oct,
volume = {17},
number = {1},
pages = {11036},
publisher = {Nature Publishing Group},
issn = {2041-1723},
doi = {10.1038/s41467-026-78769-2},
url = {https://doi.org/10.1038/s41467-026-78769-2},
pmid = {42951910},
pmcid = {PMC13716904}
}

RIS

TY - JOUR
AU - Simpson, Bryan P
AU - Ranum, Paul T
AU - Leib, David E
AU - Tecedor, Luis
AU - Giovenco, Ryan C
AU - Huerta-Ocampo, Icnelia
AU - Hundley, Amiel A
AU - Smith, Noah
AU - Fluta, Christopher M
AU - Cali, Christopher P
AU - Benoit, Jamie
AU - Soper, Jenna C
AU - Connelly, John
AU - Yohrling, George J
AU - Ghoroghchian, P Peter
AU - Cha, Jang-Ho J
AU - Davidson, Beverly L
TI - Computational modeling and preclinical evaluation support targeting somatic instability for Huntington's disease treatment
T2 - Nature communications
J2 - Nat Commun
PY - 2026
DA - 2026/10/28
VL - 17
IS - 1
SP - 11036
SN - 2041-1723
PB - Nature Publishing Group
DO - 10.1038/s41467-026-78769-2
UR - https://doi.org/10.1038/s41467-026-78769-2
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41467-026-78769-2",
"type": "article-journal",
"title": "Computational modeling and preclinical evaluation support targeting somatic instability for Huntington's disease treatment",
"container-title": "Nature communications",
"author": [
{
"family": "Simpson",
"given": "Bryan P"
},
{
"family": "Ranum",
"given": "Paul T"
},
{
"family": "Leib",
"given": "David E"
},
{
"family": "Tecedor",
"given": "Luis"
},
{
"family": "Giovenco",
"given": "Ryan C"
},
{
"family": "Huerta-Ocampo",
"given": "Icnelia"
},
{
"family": "Hundley",
"given": "Amiel A"
},
{
"family": "Smith",
"given": "Noah"
},
{
"family": "Fluta",
"given": "Christopher M"
},
{
"family": "Cali",
"given": "Christopher P"
},
{
"family": "Benoit",
"given": "Jamie"
},
{
"family": "Soper",
"given": "Jenna C"
},
{
"family": "Connelly",
"given": "John"
},
{
"family": "Yohrling",
"given": "George J"
},
{
"family": "Ghoroghchian",
"given": "P Peter"
},
{
"family": "Cha",
"given": "Jang-Ho J"
},
{
"family": "Davidson",
"given": "Beverly L"
}
],
"container-title-short": "Nat Commun",
"volume": "17",
"issue": "1",
"page": "11036",
"DOI": "10.1038/s41467-026-78769-2",
"PMID": "42951910",
"PMCID": "PMC13716904",
"ISSN": "2041-1723",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s41467-026-78769-2",
"language": "en",
"issued": {
"date-parts": [
[
2026,
10,
28
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1016/j.ajhg.2025.12.002 [code]
MSH3 is a genetic modifier of somatic repeat instability in X-linked dystonia parkinsonism.
Journal: American journal of human genetics
In common: ggplot2, tidyverse, other condition, 12 references
[2] doi:10.1016/j.ajhg.2024.04.015 [code]
Therapeutic validation of MMR-associated genetic modifiers in a human ex vivo model of Huntington disease.
Journal: American journal of human genetics
In common: other condition, 13 references
[3] doi:10.1073/pnas.2518854123
Huntington's disease LIG1 modifier variant increases ligase fidelity and suppresses somatic CAG repeat expansion.
Journal: Proceedings of the National Academy of Sciences of the United States of America
In common: other condition, mouse, 12 references
[4] doi:10.1016/j.celrep.2025.116685 [code]
A human CAGinSTEM platform for decoding HTT repeats' somatic instability links CAG interruption to HD pathology in neurons.
Journal: Cell reports
In common: ggplot2, tidyverse, other condition, 9 references
[5] doi:10.1177/18796397261443137 [code]
Towards AI-driven prediction of &lt;i&gt;HTT&lt;/i&gt; CAG size in super-expanded human spiny projection neurons from Huntington disease donors.
Journal: Journal of Huntington's disease
In common: other condition, 10 references
[6] doi:10.1038/s41467-025-60000-3 [code]
Optimized AAV capsids for basal ganglia diseases show robust potency and distribution.
Journal: Nature communications
In common: reshape2, ggplot2, tidyverse, non-human primate, other condition, mouse, 3 references, author Luis Tecedor
[7] doi:10.1242/dmm.052803
Molecular features of a Huntington's disease knock-in minipig.
Journal: Disease models & mechanisms
In common: other condition, 9 references
[8] doi:10.1038/s41467-025-64485-w [code]
Mechanism of trinucleotide repeat expansion by MutSβ-MutLγ and contraction by FAN1.
Journal: Nature communications
In common: other condition, 9 references
[9] doi:10.1093/braincomms/fcae418 [code]
Genomic characterization of Huntington's disease genetic modifiers informs drug target tractability.
Journal: Brain communications
In common: reshape2, ggplot2, tidyverse, other condition, 6 references
[10] doi:10.1038/s41588-024-01653-6
Cell-type-specific CAG repeat expansions and toxicity of mutant Huntingtin in human striatum and cerebellum.
Journal: Nature genetics
In common: other condition, 8 references

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Add the badge to the code's README

OSCR: the badge of this paper

If you are an author or a maintainer of this paper's code, you can add this badge. It says what OSCR knows of the paper (its code, its validated tracing map) and links the code to this page. Paste one of these snippets under the README's title. Keep the two marks around it, so that the badge can be replaced in place and is never added twice. OSCR changes nothing in your repository.

Markdown
<!-- oscr-badge:start -->
[![OSCR](https://openscicode.org/badge/doi/10.1038/s41467-026-78769-2.svg)](https://openscicode.org/badge/doi/10.1038/s41467-026-78769-2)
<!-- oscr-badge:end -->
reStructuredText
.. oscr-badge:start

.. image:: https://openscicode.org/badge/doi/10.1038/s41467-026-78769-2.svg
   :target: https://openscicode.org/badge/doi/10.1038/s41467-026-78769-2
   :alt: OSCR

.. oscr-badge:end
HTML
<!-- oscr-badge:start -->
<a href="https://openscicode.org/badge/doi/10.1038/s41467-026-78769-2"><img src="https://openscicode.org/badge/doi/10.1038/s41467-026-78769-2.svg" alt="OSCR"></a>
<!-- oscr-badge:end -->

On GitHub, open the README, choose Edit (the pencil), paste the snippet under the title, then "Commit changes".

If you choose to, you can have the badge kept up to date for you, with oscr badge --write from the command line or with the GitHub Action in your repository's workflows. Every other form and option (a card, AsciiDoc, LaTeX, shields.io) is on the badge page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: code errors and mismatches between the code and the paper about this paper and its code, from readers and its authors, beside the paper and its tracing map.

Report a mismatch between the code and the paper · Report a code error (signed in)

Reproductions: reports from readers who ran the authors' code and did not get the paper's result, what they ran, with which environment, commit and data.

Report a reproduction failure (signed in)

Versions of its code: the releases of its code tied to a version of this paper (the preprint, the accepted manuscript, the version of record…), each with the tracing map versioned with it.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.