## ----include = FALSE----------------------------------------------------------
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  cache = TRUE,
  cache.path = "advent-cache/",
  fig.width = 7,
  fig.height = 4
)
set.seed(46012244)

## ----setup, message=FALSE-----------------------------------------------------
library(goldilocks)

## ----trial-summary, echo=FALSE------------------------------------------------
trial_summary <- data.frame(
  Feature = c(
    "Population",
    "Treatment arm",
    "Control arm",
    "Randomization",
    "Follow-up",
    "Adaptive sample sizes",
    "Primary effectiveness endpoint",
    "Primary safety endpoint"
  ),
  `Reported ADVENT design` = c(
    "Drug-resistant paroxysmal atrial fibrillation",
    "Pulsed field ablation",
    "Thermal ablation by radiofrequency or cryoballoon ablation",
    "1:1 after nonrandomized roll-in subjects",
    "12 months",
    "350, 450, 550, 650, or 750 mITT subjects",
    paste(
      "Treatment success: acute procedural success and freedom from",
      "specified chronic failures through 12 months"
    ),
    paste(
      "Composite device- or procedure-related serious adverse events,",
      "including selected acute and chronic events"
    )
  ),
  check.names = FALSE
)

knitr::kable(trial_summary)

## ----trial-flowchart, echo=FALSE, message=FALSE, out.width='100%'-------------
DiagrammeR::grViz("
digraph advent_trial_flow {
  graph [rankdir = TB, bgcolor = transparent, ranksep = 0.55, nodesep = 0.35]
  node  [shape = box, style = rounded, fontname = Helvetica, fontsize = 14,
         margin = 0.12]
  edge  [fontname = Helvetica, fontsize = 12]

  screen [label = 'Eligible patients\\nwith drug-resistant PAF']
  rollin [label = 'Site roll-in\\n1 to 3 PFA subjects']
  random [label = 'Randomized comparison\\n1:1 allocation']
  pfa    [label = 'PFA arm']
  thermal [label = 'Thermal ablation arm\\nRF or cryoballoon']
  blank  [label = '90-day blanking period']
  follow [label = 'Follow to 12 months']
  endpoints [label = 'Co-primary endpoints\\neffectiveness + safety']

  screen -> rollin
  rollin -> random
  random -> pfa
  random -> thermal
  pfa -> blank
  thermal -> blank
  blank -> follow
  follow -> endpoints
}
", width = "100%", height = "620px")

## ----adaptive-flowchart, echo=FALSE, message=FALSE, out.width='100%'----------
DiagrammeR::grViz("
digraph advent_goldilocks {
  graph [rankdir = TB, bgcolor = transparent, ranksep = 0.55, nodesep = 0.35]
  node  [shape = box, style = rounded, fontname = Helvetica, fontsize = 14,
         margin = 0.12]
  edge  [fontname = Helvetica, fontsize = 12]

  start [label = 'Start adaptive review\\nN = 350']
  high  [shape = diamond, label = 'Predicted success\\nhigh for both endpoints?']
  success [label = 'Stop enrollment\\ncurrent N is adequate']
  low   [shape = diamond, label = 'Predicted success\\nlow for either endpoint\\nat max N = 750?']
  futile [label = 'Stop enrollment\\nfutility']
  maxn  [shape = diamond, label = 'Reached\\nN = 750?']
  maxstop [label = 'Stop enrollment\\nmaximum reached']
  accrue [label = 'Accrue 100 more\\nmITT subjects']
  follow [label = 'Complete 12-month\\nfollow-up']
  final [label = 'Final Bayesian\\nnoninferiority analyses']

  start -> high
  high -> success [label = 'Yes']
  high -> low [label = 'No']
  low -> futile [label = 'Yes']
  low -> maxn [label = 'No']
  maxn -> maxstop [label = 'Yes']
  maxn -> accrue [label = 'No']
  accrue -> high
  success -> follow
  futile -> follow
  maxstop -> follow
  follow -> final
}
", width = "100%", height = "760px")

## ----sample-size-arguments----------------------------------------------------
N_total <- 750
interim_look <- c(350, 450, 550, 650)

Sn <- c(0.95, 0.90, 0.85, 0.80)
Fn <- c(0.05, 0.10, 0.10, 0.10)

## ----endpoint-table, echo=FALSE-----------------------------------------------
endpoint_map <- data.frame(
  Endpoint = c("Effectiveness", "Safety"),
  `Published scale` = c(
    "Treatment success by 12 months",
    "Primary safety event by 12 months"
  ),
  `Code event` = c(
    "Failure to meet treatment success",
    "Primary safety event"
  ),
  `Target event probability` = c(0.35, 0.08),
  `Noninferiority margin` = c(0.15, 0.08),
  `Posterior threshold` = c(0.956, 0.966),
  check.names = FALSE
)

knitr::kable(endpoint_map, digits = 3)

## ----beta-prior---------------------------------------------------------------
prior_bin <- c(0.5, 0.5)

## ----imputation-prior---------------------------------------------------------
prior_surv_effectiveness <- rbind(
  shape = c(0.5, 0.5, 0.5, 0.5, 5),
  rate = c(0.001, 0.001, 0.001, 0.001, 10000)
)
prior_surv_default <- c(shape = 0.5, rate = 0.001)
prior_surv_final <- prior_surv_default

## ----hazards------------------------------------------------------------------
days_per_month <- 30L
follow_up_months <- 12L
end_of_study_day <- follow_up_months * days_per_month

eff_event_cutpoints_day <- c(90, 104, 150, 210)
eff_hazard_per_day <- c(
  0.000111670,
  0.002197976,
  0.003163208,
  0.002839089,
  0.000494053
)

safety_event_cutpoints_day <- 7
safety_hazard_per_day <- c(0.011137363, 1.53540e-5)

event_free_at_interval_end <- function(hazard, interval_end) {
  vapply(seq_along(hazard), function(j) {
    cutpoints <- if (j == 1L) NULL else interval_end[seq_len(j - 1L)]
    post <- array(hazard[seq_len(j)], dim = c(1L, j, 1L))

    1 - goldilocks:::haz_to_prop(
      post = post,
      cutpoints = cutpoints,
      end_of_study = interval_end[j],
      single_arm = TRUE
    )$p_treatment
  }, numeric(1))
}

implied_event_free_proportion <- c(
  event_free_at_interval_end(
    eff_hazard_per_day,
    c(eff_event_cutpoints_day, end_of_study_day)
  ),
  event_free_at_interval_end(
    safety_hazard_per_day,
    c(safety_event_cutpoints_day, end_of_study_day)
  )
)

hazard_table <- data.frame(
  Endpoint = c(rep("Effectiveness failure", 5), rep("Safety event", 2)),
  `Follow-up interval (days)` = c(
    "0--<90", "90--<104", "104--<150", "150--<210",
    "210--360", "0--<7", "7--360"
  ),
  `Hazard per patient-day` = c(
    eff_hazard_per_day,
    safety_hazard_per_day
  ),
  `Implied event-free proportion at interval end` =
    implied_event_free_proportion,
  check.names = FALSE
)

knitr::kable(hazard_table, digits = c(9, 3))

## ----probability-check--------------------------------------------------------
prob_check <- data.frame(
  Endpoint = c("Effectiveness failure", "Safety event"),
  `SAP target event probability at day 360` = c(0.35, 0.08),
  `Calculated event probability at day 360` = c(
    ppwe(
      hazard = matrix(eff_hazard_per_day, nrow = 1),
      end_of_study = end_of_study_day,
      cutpoints = eff_event_cutpoints_day
    ),
    ppwe(
      hazard = matrix(safety_hazard_per_day, nrow = 1),
      end_of_study = end_of_study_day,
      cutpoints = safety_event_cutpoints_day
    )
  ),
  check.names = FALSE
)

knitr::kable(prob_check, digits = 6)

## ----scaled-hazards-----------------------------------------------------------
scale_pwe_to_event_probability <- function(
  hazard_per_day,
  cutpoints_day,
  end_day,
  target_event_probability
) {
  interval_length_day <- diff(c(0, cutpoints_day, end_day))
  cumulative_hazard <- sum(hazard_per_day * interval_length_day)
  scale <- -log1p(-target_event_probability) / cumulative_hazard
  hazard_per_day * scale
}

eff_margin_hazard_per_day <- scale_pwe_to_event_probability(
  eff_hazard_per_day,
  eff_event_cutpoints_day,
  end_of_study_day,
  target_event_probability = 0.50
)

safety_margin_hazard_per_day <- scale_pwe_to_event_probability(
  safety_hazard_per_day,
  safety_event_cutpoints_day,
  end_of_study_day,
  target_event_probability = 0.16
)

## ----accrual-and-missingness--------------------------------------------------
enrollment_rate_per_month <- c(2, 5, 10, 15, 20, 25, 30, 33)
enrollment_rate_change_month <- 1:7

enrollment_rate_per_day <- enrollment_rate_per_month / days_per_month
enrollment_rate_change_day <-
  enrollment_rate_change_month * days_per_month

effectiveness_prop_loss <- 0.075
safety_prop_loss <- 0.05

enrollment_schedule <- data.frame(
  `Trial-calendar interval (months)` = c(
    paste("Month", 1:7),
    "Month 8 onward"
  ),
  `Trial-calendar interval (days since first patient in)` = c(
    paste0(
      (0:6) * days_per_month,
      "--<",
      (1:7) * days_per_month
    ),
    paste0(7 * days_per_month, " onward")
  ),
  `SAP rate (subjects/month)` = enrollment_rate_per_month,
  `Rate supplied to goldilocks (subjects/day)` = enrollment_rate_per_day,
  check.names = FALSE
)

knitr::kable(enrollment_schedule, digits = 4)

## ----effectiveness-fit--------------------------------------------------------
set.seed(4601)

# One simulated effectiveness-endpoint trial
advent_effectiveness <- survival_adapt(
  hazard_treatment = eff_hazard_per_day,
  hazard_control = eff_hazard_per_day,
  cutpoints = eff_event_cutpoints_day,
  N_total = N_total,
  lambda = enrollment_rate_per_day,
  lambda_time = enrollment_rate_change_day,
  interim_look = interim_look,
  end_of_study = end_of_study_day,
  prior_surv = prior_surv_effectiveness,
  prior_surv_final = prior_surv_final,
  prior_bin = prior_bin,
  bin_method = "quadrature",
  block = 2,
  rand_ratio = c(1, 1),
  prop_loss = effectiveness_prop_loss,
  alternative = "less",
  h0 = 0.15,
  Fn = Fn,
  Sn = Sn,
  prob_ha = 0.956,
  N_impute = 50,
  empty_interval = "prior",
  method = "bayes-bin",
  imputed_final = TRUE
)

advent_effectiveness

## ----safety-fit---------------------------------------------------------------
set.seed(4602)

# One simulated safety-endpoint trial
advent_safety <- survival_adapt(
  hazard_treatment = safety_hazard_per_day,
  hazard_control = safety_hazard_per_day,
  cutpoints = safety_event_cutpoints_day,
  N_total = N_total,
  lambda = enrollment_rate_per_day,
  lambda_time = enrollment_rate_change_day,
  interim_look = interim_look,
  end_of_study = end_of_study_day,
  prior_surv = prior_surv_default,
  prior_surv_final = prior_surv_final,
  prior_bin = prior_bin,
  bin_method = "quadrature",
  block = 2,
  rand_ratio = c(1, 1),
  prop_loss = safety_prop_loss,
  alternative = "less",
  h0 = 0.08,
  Fn = Fn,
  Sn = Sn,
  prob_ha = 0.966,
  N_impute = 50,
  empty_interval = "prior",
  method = "bayes-bin",
  imputed_final = TRUE
)

advent_safety

## ----common-design------------------------------------------------------------
advent_common <- list(
  N_total = N_total,
  lambda = enrollment_rate_per_day,
  lambda_time = enrollment_rate_change_day,
  interim_look = interim_look,
  end_of_study = end_of_study_day,
  prior_surv = prior_surv_default,
  prior_surv_final = prior_surv_final,
  prior_bin = prior_bin,
  bin_method = "quadrature",
  block = 2,
  rand_ratio = c(1, 1),
  alternative = "less",
  Fn = Fn,
  Sn = Sn,
  N_impute = 30,
  empty_interval = "prior",
  method = "bayes-bin",
  imputed_final = TRUE,
  ncores = 2
)

advent_effectiveness_args <- modifyList(advent_common, list(
  cutpoints = eff_event_cutpoints_day,
  prior_surv = prior_surv_effectiveness,
  prop_loss = effectiveness_prop_loss
))

advent_safety_args <- modifyList(advent_common, list(
  cutpoints = safety_event_cutpoints_day,
  prop_loss = safety_prop_loss
))

advent_effectiveness_args

## ----oc-small-----------------------------------------------------------------
set.seed(4610)

eff_target <- do.call(sim_trials, c(
  advent_effectiveness_args,
  list(
    N_trials = 500,
    hazard_treatment = eff_hazard_per_day,
    hazard_control = eff_hazard_per_day,
    h0 = 0.15,
    prob_ha = 0.956,
    return_trace = TRUE,
    seed = 4610
  )
))

eff_null_boundary <- do.call(sim_trials, c(
  advent_effectiveness_args,
  list(
    N_trials = 500,
    hazard_treatment = eff_margin_hazard_per_day,
    hazard_control = eff_hazard_per_day,
    h0 = 0.15,
    prob_ha = 0.956,
    seed = 4611
  )
))

oc_small <- summarise_sims(list(
  "target: equal 35% failure" = eff_target$sims,
  "margin: PFA failure 50%" = eff_null_boundary$sims
))

knitr::kable(oc_small, digits = 3)

## ----advent-oc-plot, fig.width=9, fig.height=4.5------------------------------
oc_small$true_pfa_event_probability <- c(0.35, 0.50)
plot_sim_ocs(
  oc_small,
  effect = "true_pfa_event_probability",
  xlab = "True 12-month PFA event probability"
)

## ----advent-stopping-plot, fig.width=7, fig.height=5--------------------------
plot_sim_stopping(eff_target)

## ----advent-decision-plot, fig.width=9, fig.height=8--------------------------
plot_sim_decisions(eff_target)

## ----full-calibration-template, eval=FALSE------------------------------------
# advent_effectiveness_full_args <- modifyList(advent_effectiveness_args, list(
#   N_impute = 5000,
#   ncores = 8
# ))
# 
# advent_safety_full_args <- modifyList(advent_safety_args, list(
#   N_impute = 5000,
#   ncores = 8
# ))
# 
# eff_target_full <- do.call(sim_trials, c(
#   advent_effectiveness_full_args,
#   list(
#     N_trials = 1000,
#     hazard_treatment = eff_hazard_per_day,
#     hazard_control = eff_hazard_per_day,
#     h0 = 0.15,
#     prob_ha = 0.956,
#     seed = 4620
#   )
# ))
# 
# eff_margin_full <- do.call(sim_trials, c(
#   advent_effectiveness_full_args,
#   list(
#     N_trials = 1000,
#     hazard_treatment = eff_margin_hazard_per_day,
#     hazard_control = eff_hazard_per_day,
#     h0 = 0.15,
#     prob_ha = 0.956,
#     seed = 4621
#   )
# ))
# 
# safety_target_full <- do.call(sim_trials, c(
#   advent_safety_full_args,
#   list(
#     N_trials = 1000,
#     hazard_treatment = safety_hazard_per_day,
#     hazard_control = safety_hazard_per_day,
#     h0 = 0.08,
#     prob_ha = 0.966,
#     seed = 4622
#   )
# ))
# 
# safety_margin_full <- do.call(sim_trials, c(
#   advent_safety_full_args,
#   list(
#     N_trials = 1000,
#     hazard_treatment = safety_margin_hazard_per_day,
#     hazard_control = safety_hazard_per_day,
#     h0 = 0.08,
#     prob_ha = 0.966,
#     seed = 4623
#   )
# ))
# 
# summarise_sims(list(
#   "effectiveness target" = eff_target_full$sims,
#   "effectiveness margin" = eff_margin_full$sims,
#   "safety target" = safety_target_full$sims,
#   "safety margin" = safety_margin_full$sims
# ))

## ----sap-reference-benchmarks, echo=FALSE-------------------------------------
sap_reference_benchmarks <- data.frame(
  Scenario = c(
    "Target: both endpoints at expected rates",
    "Effectiveness noninferiority boundary",
    "Safety noninferiority boundary"
  ),
  `Reported probability` = c(0.9635, 0.0487, 0.0528),
  `Probability meaning` = c(
    "Joint power",
    "Effectiveness type I error",
    "Safety type I error"
  ),
  `Mean mITT sample size` = c(504, 551, 457),
  check.names = FALSE
)

knitr::kable(sap_reference_benchmarks, digits = 4)

