
# Sample size recalculation based on the overall success rate in a randomized test-treatment trial with restricting randomization to discordant pairs

# Additional File 2

# Caroline Elzner*, Amra Pepić, Oke Gerke, Antonia Zapf

# *Correspondence: c.elzner@acomed-statistik.de
# ACOMED statistik, Fockestrasse 57, 04275 Leipzig, Germany.


#------------------------------------------------------------------------------------------------------------
# Program to simulate a paired design, consider an adaptive approach and calculate sample sizes, Biases, 
# Power and Type I error rates

# Version 1.0 - last edited 21JANUARY2024



############################################################################################################################################
################################### Load Packages ###############################################################################################

#ATTENTION: Do not change the order of the packages. 

library(tidyr)
library(rlist)
library(randomizr)
library(bindata)
library(DescTools)
library(dplyr)
library(foreach)
library(parallel)
library(doParallel) 


##########################################################################################################################################################
############################################## 1. STEP: DISCORDANT RATES CALCULATION ##########################################################################

### Discordant Rates Calculation

## Input parameters:
# prev: disease prevalence
# se.A, se.B: Sensitivity of test A or B
# sp.A, sp.B: Specificity of test A or B
# out: user's decision: 0= minimum discordant rates, 1= mean discordant rates, 2= maximum discordant rates

## Output parameters:
# discordant rates according to users decision (either min, mean or max discordant rates):
#   disc.rate.d: proportion of discordant test results for truly diseased subjects
#   disc.rate.nd: proportion of discordant test results for truly non-diseased subjects
#   disc.rate: overall proportion of discordant test results of truly diseased and truly non-diseased subjects


disc.rate.calc = function(prev, se.A, sp.A, se.B, sp.B, out) {
  
  #discordant rate for truly diseased subjects: 
  min.disc.rate.d=abs(se.A-se.B)
  max.disc.rate.d=se.A + se.B - 2*se.A*se.B
  mean.disc.rate.d=(min.disc.rate.d+max.disc.rate.d)/2
  
  #discordant rate for truly non-diseased subjects: 
  min.disc.rate.nd=abs(sp.A-sp.B)
  max.disc.rate.nd=sp.A + sp.B - 2*sp.A*sp.B
  mean.disc.rate.nd=(min.disc.rate.nd+max.disc.rate.nd)/2
  
  #overall min, max, mean discordant rate:
  min.disc.rate=prev*min.disc.rate.d + (1-prev)*min.disc.rate.nd
  max.disc.rate=prev*max.disc.rate.d + (1-prev)*max.disc.rate.nd
  mean.disc.rate=prev*mean.disc.rate.d + (1-prev)*mean.disc.rate.nd
  
  #depending on user's decision, either min, mean or max discordant rates will be returned:
  disc.rate <- ifelse(out==0, min.disc.rate, ifelse(out==1, mean.disc.rate, ifelse(out==2, max.disc.rate)))
  disc.rate.d <- ifelse(out==0, min.disc.rate.d, ifelse(out==1, mean.disc.rate.d, ifelse(out==2, max.disc.rate.d)))
  disc.rate.nd <- ifelse(out==0, min.disc.rate.nd, ifelse(out==1, mean.disc.rate.nd, ifelse(out==2, max.disc.rate.nd)))
  
  return(list(disc.rate=disc.rate, disc.rate.d=disc.rate.d, disc.rate.nd=disc.rate.nd))
  
}




##########################################################################################################################################################
############################################## 2. STEP: SAMPLE SIZE CALCULATION ##########################################################################

### Sample Size Calculation for two independent proportions (equal group allocation: n.A = n.B) 

# Kieser, M. (2020). Comparison of Two Groups for Binary Outcomes and Test for Difference or Superiority. 
# In: Methods and Applications of Sample Size Calculation and Recalculation in Clinical Trials. Springer Series in Pharmaceutical Statistics. Springer, Cham. https://doi.org/10.1007/978-3-030-49528-2_5

## Input parameters:
# alpha: Type I error
# beta: Type II error
# prev: disease prevalence
# se.A, se.B: Sensitivity of test A or B
# sp.A, sp.B: Specificity of test A or B
# mu10: expected outcome of management strategy I in non-diseased population (false positive)
# mu11: expected outcome of management strategy I in diseased population (true positive)
# mu20: expected outcome of management strategy II in non-diseased population (true negative)
# mu21: expected outcome of management strategy II in diseased population (false negative)
# omega: pre-specified shift between the initial assumed and true overall success rate  
# disc.rate.d: proportion of discordant test results for truly diseased subjects
# disc.rate.nd: proportion of discordant test results for truly non-diseased subjects
# disc.rate: overall proportion of discordant test results of truly diseased and truly non-diseased subjects
# H0: delta=0, H1: delta <>0
#   delta = difference between thetaA and thetaB: delta = 0 testing for equality (= Sample size calculation under Null hypothesis)
#   delta = difference between thetaA and thetaB: delta <> 0 testing for inequality (= Sample size calculation under Alternative hypothesis)

## Output:
# N : vector with total sample size for testing the inequality of two binomial proportions thetaA and thetaB with allocation of subjects to test A or B 
#     as well as the sample size for discordant cases and delta, the single success rates and the overall success rate

# The function sampleSizeParallel is based on the simulation by Amra Hot (Hot et al., 2022) and was adapted for the paired design.


sampleSizeParallel <- function(alpha, beta, prev, se.A, sp.A, se.B, sp.B, mu10, mu11, mu20, mu21, omega, disc.rate.d, disc.rate.nd, disc.rate) {
 
  #initialize:
  N.v = c()
  TPPR<- NULL
  TNNR<-NULL
  
  # True positive positive rate:
  TPPR=0.5*(se.A + se.B - disc.rate.d)
  
  # True negative negative rate:
  TNNR=0.5*(sp.B + sp.A - disc.rate.nd)
  
  # thetaA / thetaB: rate of favourable outcome for subjects randomized to the test i-based strategy (i = A or B) varied by 1/2 omega
  thetaA = (prev*((se.A-TPPR)*mu11 + (se.B-TPPR)*mu21) + (1-prev)*((sp.B-TNNR)*mu10 + (sp.A-TNNR)*mu20))/disc.rate - omega/2
  thetaB = (prev*((se.B-TPPR)*mu11 + (se.A-TPPR)*mu21) + (1-prev)*((sp.A-TNNR)*mu10 + (sp.B-TNNR)*mu20))/disc.rate - omega/2
  
  # Difference between thetaA und thetaB:
  delta=thetaA - thetaB
  
  # Overall success rate:
  theta.0 = (thetaA + thetaB)/2
  
  # Sample size of discordant cases for each group:
  n1 = (qnorm(1-alpha/2)*sqrt((2*theta.0*(1-theta.0)))+
          qnorm(1-beta)*sqrt(thetaA*(1-thetaA) + thetaB*(1-thetaB)))^2 /
    ((thetaA - thetaB)^2)
  n1=ceiling(n1)
  n2 = n1 
  
  # Sample size of discordant cases:
  n_disc=n1+n2 
  
  # Total sample size:
  n = n_disc/disc.rate
  #n = ifelse(n%%2<=1,ceiling(n+1),ceiling(n))
  n = ifelse(ceiling(n)%%2==0 | ceiling(n)%%2<1,ceiling(n),ceiling(n+1))  
  
  # return a vector with total sample size and sample size of discordant cases as well as delta, the single success rates and the overall success rate:
  N.v=c(n,n_disc,delta,thetaA,thetaB,theta.0)
  
  return(N.v)
  
}


################################################################################################
########## SAMPLE SIZE CALCULATION using estimated overall success rate and delta ##############

### Sample Size Calculation for two independent proportions (equal group allocation: n.A = n.B) using estimated overall success rate and delta

## Input parameters:
# alpha: Type I error
# beta: Type II error
# disc.rate: overall proportion of discordant test results of truly diseased and truly non-diseased subjects
# OSR: estimated overall success rate
# delta: difference between thetaA and thetaB
# H0: delta=0, H1: delta <>0
#   delta = difference between thetaA and thetaB: delta = 0 testing for equality (= Sample size calculation under Null hypothesis)
#   delta = difference between thetaA and thetaB: delta <> 0 testing for inequality (= Sample size calculation under Alternative hypothesis)

## Output: 
# N : vector with total sample size for testing the inequality of two binomial proportions thetaA and thetaB with allocation of subjects to test A or B 
#     as well as the sample size for discordant cases

# The function sampleSizeParallelOR is based on the simulation by Amra Hot (Hot et al., 2022) and was adapted for the paired design.


sampleSizeParallelOR <- function(alpha, beta, disc.rate, OSR, delta) {
  
  #initialize vector:
  N.v = c()
  
  # estimated overall success rate: 
  theta.0 = OSR
  
  # determine single success rates using delta (=difference between thetaA and thetaB):
  thetaA.new=theta.0 + delta/2
  thetaB.new=theta.0 - delta/2
  
  # Sample size of discordant cases for each group:
  n1 = (qnorm(1-alpha/2)*sqrt((2*theta.0*(1-theta.0)))+
          qnorm(1-beta)*sqrt(thetaA.new*(1-thetaA.new) + thetaB.new*(1-thetaB.new)))^2 /
    ((thetaA.new - thetaB.new)^2)
  n1=ceiling(n1)
  n2 = n1 
  
  # Sample size of discordant cases:
  n_disc=n1+n2 
  
  # Total sample size (using overall discordant rate):
  n = n_disc/disc.rate
  #n = ifelse(n%%2<=1,ceiling(n+1),ceiling(n))
  n = ifelse(ceiling(n)%%2==0 | ceiling(n)%%2<1,ceiling(n),ceiling(n+1))  
  
  # return a vector with total sample size and sample size of discordant cases:
  N.v=c(n,n_disc)
  
  return(N.v)
  
}



##########################################################################################################################################################
################################################### 3. STEP: DATA SIMULATION ###############################################################################

### Data simulation for randomized diagnostic studies with restricting randomization to discordant pairs 

# A randomized diagnostic test-treatment study with restricting randomization to discordant pairs
# A binary endpoint with a positive outcome, e.g. pregnancy rate is assumed.  

## Input parameters:
# N: sample size
# prev: disease prevalence
# se.A, se.B: Sensitivity of test A or B
# sp.A, sp.B: Specificity of test A or B
# mu10: expected outcome of management strategy I in non-diseased population (false positive)
# mu11: expected outcome of management strategy I in diseased population (true positive)
# mu20: expected outcome of management strategy II in non-diseased population (true negative)
# mu21: expected outcome of management strategy II in diseased population (false negative)
# disc.rate.d: proportion of discordant test results for truly diseased subjects
# disc.rate.nd: proportion of discordant test results for truly non-diseased subjects

## Output data set with: 
# ID: Subject ID / unique identifier of the data record
# rs: Reference standard (0 / 1 - truly non-diseased / truly diseased)
# resA: test result of Test A (0 / 1 - negative / positive)
# resB: test result of Test B (0 / 1 - negative / positive)
# flag.disc.cases: flag of data records with discordant test results (0 / 1 - concordant test result / discordant test result)
# Test: result of randomization "which Test to be followed" [only for discordant test results] (1 / 2 - follow Test A result / follow Test B result)
# Result: “final” test result for discordant test results: Result of randomized Test to be followed (0 / 1 - negative / positive)
#         “final” test result for concordant test results: Result of both Tests (0 / 1 - negative/positive)
# Management strategy: management strategy I if test result is positive / management strategy II if test result is negative (1 / 2 - management strategy I / II) 
# Outcome: Outcome as "result of the management strategy" (0 / 1 - no success / success)

# The function sim.parallel is based on the simulation by Amra Hot (Hot et al., 2022) and was adapted for the paired design, 
# whereby in particular the data generation process is based on the simulation by Maria Stark (Stark et al., 2022).


sim.parallel = function(N, prev, se.A, sp.A, se.B, sp.B, mu10, mu11, mu20, mu21, disc.rate.d, disc.rate.nd){
     
  # Subject ID:
  ID = seq(1:N)
  
  ## generate Test A and Test B results separately for truly non-diseased (rs=0) and truly diseased subjects (rs=1):
  
  # R Code from Maria Stark - START -
  
    #initialize vectors:
    res.0 <-  res.1 <- res.0I <- res.0II <- res.1I <-res.1II <- c()
    TPPR<- TNNR<-NULL
    prob.se <- prob.sp <-c()
    
    # True positive positive rate:
    TPPR <- 0.5*(se.A + se.B - disc.rate.d) 
  
    # True negative negative rate:
    TNNR <- 0.5*(sp.A + sp.B - disc.rate.nd)
    
    # convert TPPR and TNNR into useful format which is needed for matrix prob.se and prob.sp later on:
    TPPR <- ifelse(TPPR < (se.A + se.B - 1), TPPR+1e-9, ifelse(TPPR > min(se.A, se.B), TPPR-1e-9, TPPR))
    TNNR <- ifelse(TNNR < (sp.A + sp.B - 1), TNNR+1e-9, ifelse(TNNR > min(sp.A, sp.B), TNNR-1e-9, TNNR))

    # Probabilities as matrix for "commonprob" 
    # ("commonprob" = matrix of probabilities that components i and j are simultaneously 1 (i.e. == test positive)):
    prob.se <- cbind(c(se.A,TPPR),c(TPPR, se.B))
    prob.sp <- cbind(c(sp.A,TNNR),c(TNNR, sp.B))
    
    #Check if the matrix "commonprob" works without errors:
    #check.commonprob(prob.se)
    #check.commonprob(prob.sp)
    
    # Reference Standard (sorted vector with truly non-diseased and truly diseased subjects)
    rs <- sort(rbinom(N,1,prev)) 
   
    # Number of true diseased and true non-diseased subjects:
    n1 <- sum(rs)
    n0 <- N - n1
    
    # generate Test A and Test B results for truly diseased subjects:
    if(n1 != 0) {res.1 <- rmvbin(n=n1, commonprob=prob.se)}
    
    # generate Test A and Test B results for truly non-diseased subjects:
    if(n0 != 0) {res.0 <- 1-rmvbin(n=n0, commonprob=prob.sp)} 
    #--> commonprob: matrix of probabilities that components i and j are simultaneously 1 (i.e. == test positive) 
    #--> Since the results for the truly non-diseased subjects are generated here, 1-rmvbin (i.e. == test negative) is used.
    
    res <- rbind(res.0, res.1)
    colnames(res)<-c("resA", "resB")
    
  # R Code from Maria Stark - STOP -
  
  dat <- data.frame(ID, rs, res)
  
  #flag discordant cases:
  dat$flag.disc.cases = ifelse(dat$resA==dat$resB, 0, 1)
  
  # number of discordant cases:
  n_disc = ceiling(nrow(dat[which(dat$flag.disc.cases == 1),]))

  # Randomization ("which Test to be followed"): follow either Test A or Test B result [considering only discordant cases]:
  dat$Test = ifelse(dat$flag.disc.cases==1, complete_ra(N = n_disc, conditions = c("A", "B")),"-")

  # Assignment of "final" test result: Result of randomized "Test to be followed" for discordant cases and Test A=Test B result for concordant cases:
  dat$Result = ifelse(dat$Test=="1", dat$resA, ifelse(dat$Test=="2", dat$resB, dat$resA))
  
  # Assignment of management strategy I or II based on "final" test result:
  dat$Treatment =  ifelse(dat$Result == 1 ,"1", "2")
 
  ## management strategy effects in 4 different subgroups of subjects (considering all cases: concordant + discordant cases): 

  # number of true positives (based on reference standard and "final" test result):
  n.rp = ceiling(nrow(dat[which(dat$rs == 1 & dat$Result == 1),]))
  
  # number of false positives (based on reference standard and "final" test result):
  n.fp = ceiling(nrow(dat[which(dat$rs == 0 & dat$Result == 1),]))
  
  # number of false negatives (based on reference standard and "final" test result):
  n.fn = ceiling(nrow(dat[which(dat$rs == 1 & dat$Result == 0),]))
  
  # number of true negatives (based on reference standard and "final" test result):
  n.rn = ceiling(nrow(dat[which(dat$rs == 0 & dat$Result == 0),]))
  
  # expected outcome in the respective subgroups of diseased or non-diseased population and assigned management strategy:
  res.0I = rbinom(n.fp,1,mu10)
  res.0II = rbinom(n.rn,1,mu20)
  res.1I = rbinom(n.rp,1,mu11)
  res.1II = rbinom(n.fn,1,mu21)
  
  dat.new = dat[order(dat$rs,dat$Result),]
  
  dat.new$Outcome <-  NA
  dat.new$Outcome <-  c(res.0II, res.0I, res.1II, res.1I)
  
  dat.new = dat.new[order(dat.new$ID),]
  
  return (dat.new)
}



##########################################################################################################################################################
############################################# 4. STEP: DATA ANALYSIS  #####################################################################################

### Comparison of Outcomes for test A-based strategy versus test B-based strategy [considering only discordant cases]

## Data analysis using two-sided 95% Wald Confidence Interval [considering only discordant cases]

## Input: 
# dat: name of data set [considering only discordant cases]
# Test: Variable name of test (A or B)
# Outcome: Variable containing the result of the Outcome : Outcome = 1 "success" or Outcome = 0 "no success"
# H0: delta=0, H1: delta <>0
#   delta = difference between thetaA and thetaB: delta <> 0 testing for inequality
# alpha = type I error

## Output:
# est: estimate of proportion difference
# lwr.ci: lower CI limit
# upr.ci: upper CI limit

## use R Function: BinomDiffCI(x1, n1, x2, n2, conf.level = 0.95, sides = c("two.sided","left","right"),
#                         method = c("wald", "waldcc", "ac", "score", "scorecc", "mn", "mee", "blj", "ha", "beal")) 
# with: 
# x1: number of successes for the first group (Test A)
# n1: number of trials for the first group (Test A)
# x2: number of successes for the second group (Test B)
# n2: number of trials for the second group (Test B)
# conf.level: confidence level (default is 0.95)  
# sides: two-sided
# method: "wald"

# The function CIParallel is based on the simulation by Amra Hot (Hot et al., 2022) and was adapted for the paired design.


CIParallel <- function(datin, Test, Outcome, alpha){
  
  dat.in <- datin

  #number of trials:
  dat.A = dat.in[dat.in$Test==1,]
  dat.B = dat.in[dat.in$Test==2,]
  
  nA = nrow(dat.A)
  nB = nrow(dat.B)
  
  #number of successes:
  n1.A = nrow(dat.A[dat.A$Outcome == 1,])
  n1.B = nrow(dat.B[dat.B$Outcome == 1,])  
  
  #two-sided 95% Wald CI:
  WaldCI = BinomDiffCI(x1 = n1.A, n1 = nA, x2 = n1.B, n2 = nB, conf.level = 1-alpha, sides = "two.sided",
                       method = "wald")

  return(WaldCI)
  
}




########################################################################################################################################################
#################################################### 5. STEP: SIMULATION PROGRAM ########################################################################

### Adaptive Design: blinded sample size recalculation based on estimated overall success rate [considering only discordant cases] 


###################################################################################################################################################
#################################################### POWER ##################################################################################

## Input parameters:
# N: sample size
# se.A, se.B: Sensitivity of test A or B
# sp.A, sp.B: Specificity of test A or B
# mu10: expected outcome of management strategy I in non-diseased population (false positive)
# mu11: expected outcome of management strategy I in diseased population (true positive)
# mu20: expected outcome of management strategy II in non-diseased population (true negative)
# mu21: expected outcome of management strategy II in diseased population (false negative)
# omega: pre-specified shift between the initial assumed and true overall success rate  
# alpha: Type I error probability (alpha = 0.05)
# beta: Type II error probability (beta = 0.2)
# prev: disease prevalence
# out: 0=minimum discordant rates will be used for data generation and sample size calculation
#      1=mean discordant rates will be used for data generation and sample size calculation 
#      2=maximum discordant rates will be used for data generation and sample size calculation
# use.disc.rate.est: 0=no, use the initial calculated overall discordant rate to calculate the adjusted sample size
#                    1=yes, use the re-calculated overall discordant rate to calculate the adjusted sample size
# frac: Size of internal pilot study
# nsim: number of simulations runs
# Seed: control random numbers

## Output parameters: 
# PowerCI.fix / PowerCI.val: Calculated power based on confidence intervals for fixed / adaptive design
# Ntrue: true sample size based on true overall success rate
# NtrueDISC: true sample size of discordant cases
# Ninit: initial sample size based on assumed overall success rate
# NinitDISC: initial sample size of discordant cases
# N.part: pre-planned fractional proportion of initial planned sample size
# Nadapt: adjusted sample size based on re-estimated overall success rate
# NadaptDISC: adjusted sample size of discordant cases
# absBias.OSR / relBias.OSR: mean Bias calculated as absolute/relative difference between true and re-estimated overall success rate
# absBias.disc.rate / relBias.disc.rate: mean Bias calculated as absolute/relative difference between true and re-estimated discordant rate
# absBias.delta / relBias.delta: mean Bias calculated as absolute/relative difference between true and adaptive delta
# RatioN: ratio between mean adjusted sample size and true sample size
# RatioNdisc: ratio between mean adjusted sample size of discordant cases and true sample size of discordant cases
# RMSEval.N: Root mean squared error of adjusted sample size
# RMSEval.Ndisc: Root mean squared error of adjusted sample size of discordant cases
# RMSEval.OSR: Root mean squared error of re-estimated overall success rate
# RMSEval.delta: Root mean squared error of adaptive delta
# effectAfix / effectAadapt, effectBfix / effectBadapt: mean single success rates for the test A-based strategy and test B-based strategy for fixed/adaptive design
# delta.init: initial assumed delta
# delta.fix / delta.adapt: mean delta for fixed / adaptive design
# omega: pre-specified shift between true and initial assumed overall success rate
# thetaOtrue: true overall success rate
# effectOfix / effectOadapt: mean overall success rate for fixed / adaptive design
# effectOest: mean re-estimated overall success rate in interim analysis
# alpha: Type I error probability (alpha = 0.05)
# beta: Type II error probability (beta = 0.2)
# prev: disease prevalence
# se.A, se.B: Sensitivity of test A or B
# sp.A, sp.B: Specificity of test A or B
# mu10: expected outcome of management strategy I in non-diseased population (false positive)
# mu11: expected outcome of management strategy I in diseased population (true positive)
# mu20: expected outcome of management strategy II in non-diseased population (true negative)
# mu21: expected outcome of management strategy II in diseased population (false negative)
# out: 0=minimum discordant rates will be used for data generation and sample size calculation
#      1=mean discordant rates will be used for data generation and sample size calculation 
#      2=maximum discordant rates will be used for data generation and sample size calculation
# use.disc.rate.est: 0=no, use the initial calculated overall discordant rate to calculate the adjusted sample size
#                    1=yes, use the re-calculated overall discordant rate to calculate the adjusted sample size
# disc.rate, disc.rate.d, disc.rate.nd: discordant rates: overall, for truly diseased and truly non-diseased population
# frac: Size of internal pilot study
# N.exc.frac: Frequency of ratio between adjusted sample size and true sample size exceeding 1.2

# The function power.sc2 is based on the simulation by Amra Hot (Hot et al., 2022) and was adapted for the paired design.
 

######################
# # POWER calculation (calculated under alternative hypothesis H1: delta <> 0) ______________________________________________________________________________________________________________________

power.sc2 = function(se.A, sp.A, se.B, sp.B, mu10, mu11, mu20, mu21, omega, alpha, beta, prev, out, use.disc.rate.est, frac, nsim, Seed, dots = TRUE){
    
  start.time <- Sys.time()
  if(dots) cat("Simulations (",nsim,") \n----|--- 1 ---|--- 2 ---|--- 3 ---|--- 4 ---| --- 5 \n",sep="")
  
  ## First set parameters for the simulation:
  
  # initialize vector for nsim seeds:
  states <- rep(NA,nsim)
  
  # initialize: How often was H0 rejected:
  power.val2 <- power.fix2 <- 0 # based on CI

  # initialize: Point estimator for single success rates (test A-based strategy, test B-based strategy),  
  # overall success rate and delta for fixed and adaptive design:
  theta.A.fix <- theta.A.adapt <- theta.B.fix <- theta.B.adapt <- c()
  thetaO.fix.v <- thetaO.est.v <- thetaO.adapt.v <-c()  
  delta.fix.v <- delta.adapt.v <- c()
  disc.rate.est.v <- c()
  thetaO.true <- thetaO.init <- delta.init <- 0
  effectAfix <- effectBfix <- effectAval <- effectBval <- effectOfix <- effectOval <- OSRrecalc <- c()
 
  # initialize matrix with Wald-test results for fixed and adaptive design:
  Endresult.fix=matrix(data=NA, nrow=nsim, ncol=3)
  Endresult.val=matrix(data=NA, nrow=nsim, ncol=3)
  
  # initialize RMSE and Bias:
  RMSEval.N <- RMSEval.Ndisc <- RMSEval.OSR <- RMSEval.disc.rate <- 0
  absBias.OSR <- relBias.OSR <- absBias.disc.rate <- relBias.disc.rate <- 0
  
  # initialize: Root mean squared error for adjusted sample size per simulation run:
  RMSEval.sim = c()
  
  # initialize vectors:
  disc.rate.res <- c()
  N.true.v <- N.v <- N.pilot.v <- N.korr.v <- N.korr.disc.v <- c()
 
  # initialize: R-value per simulation run:
  R.sim = c()
  R.sim.init = c()
  
  # initialize: How often does R.sim exceed a factor of 1.2: 
  N.exc <- c()
  
  # initialize: Frequency of N.exc over all simulation runs:
  N.exc.frac <- c()

  # set seed for random-number generator: 
  set.seed(Seed)
  
  # generate a vector with nsim seeds:
  seed.states <- sample(seq(1000, 1000000), nsim)

  
  ## 1. step: calculate the discordant rates used for the data generation and the sample size calculation:
  disc.rate.res=disc.rate.calc(prev=prev, se.A=se.A, sp.A=sp.A, se.B=se.B, sp.B=sp.B, out=out)
  disc.rate=as.numeric(disc.rate.res[1])
  disc.rate.d=as.numeric(disc.rate.res[2])
  disc.rate.nd=as.numeric(disc.rate.res[3])
    
  
  ## 2. step:  INITIAL sample size calculation:  
    N.v = sampleSizeParallel(alpha=alpha, beta=beta, prev=prev, 
                             se.A=se.A, sp.A=sp.A, se.B=se.B, sp.B=sp.B, 
                             mu10=mu10, mu11=mu11, mu20=mu20, mu21=mu21, omega=omega,
                             disc.rate.d=disc.rate.d, disc.rate.nd=disc.rate.nd, disc.rate=disc.rate)
    # INITIAL sample size:  
    N=N.v[1]
    # INITIAL sample size for discordant cases:
    N.disc=N.v[2]
    # initial assumed difference between thetaA.init and thetaB.init:
    delta.init=(N.v[3])  
    # initial assumed success rate for test A-based strategy: 
    thetaA.init=(N.v[4])
    # initial assumed success rate for test B-based strategy:  
    thetaB.init=(N.v[5])
    # initial assumed overall success rate:
    thetaO.init=(N.v[6])
    
    
  ## 3. step: Calculation of true overall success rate (which is shifted by a pre-specified omega from the initial assumed overall success rate) 
    # and true single success rates by maintaining the initial assumed delta: 
    thetaO.true=thetaO.init + omega
    thetaA.true=thetaO.true + delta.init/2
    thetaB.true=thetaO.true - delta.init/2
    
  ## 4. step: TRUE sample size calculation using the true overall success rate and initial assumed delta
    N.true.v = sampleSizeParallelOR(alpha=alpha, beta=beta, disc.rate=disc.rate, OSR=thetaO.true, delta=delta.init)
    
    # TRUE sample size:  
    N.true=(N.true.v[1])
    # TRUE sample size for discordant cases: 
    N.disc.true=(N.true.v[2])
   
 
  ## beginning of the loop (=simulation runs): 
    
  i = 1

  while (i < nsim+1) {
      
    # Set seed for each simulation run:
    states[i] <- seed.states[i]
    set.seed(states[i])
    
    ## 5. step: data generation under H1:
    dat.f = sim.parallel(N = N, prev=prev, se.A=se.A, sp.A=sp.A, se.B=se.B, sp.B=sp.B, 
                         mu10=mu10, mu11=mu11, mu20=mu20, mu21=mu21, disc.rate.d=disc.rate.d, disc.rate.nd=disc.rate.nd)
      
    # pre-planned fractional proportion of initial planned sample size:
    N.part = ceiling(N*frac)
    
    ## 6. step: extract a random sample of N.part subjects: 
    dat.1=sample_n(dat.f,N.part)
    
    
    ## 7. step: internal pilot study (re-estimation of overall success rate using the data [considering only discordant cases] of the pre-planned fractional proportion of subjects):
    # extract only discordant cases:
    dat.1.disc = dat.1[which(dat.1$flag.disc.cases == 1),]
    # re-estimation of overall success rate and maintain the pre-specified shift of omega between initial assumed and true overall success rate:
    thetaO.est=mean(dat.1.disc$Outcome, na.rm = TRUE) + omega/2 
    thetaO.est.v[i] = thetaO.est
    # determine the discordant rate:
    disc.rate.est = mean(dat.1$flag.disc.cases, na.rm = TRUE)
    disc.rate.est.v[i]=disc.rate.est
    
    ## 8. step: re-calculate the sample size using the re-estimated overall success rate, the initial assumed delta and if requested, the re-calculated overall discordant rate: 
    if (use.disc.rate.est == 0){
      N.pilot.v = sampleSizeParallelOR(alpha=alpha, beta=beta, disc.rate=disc.rate, OSR=thetaO.est, delta=delta.init)
    } else {
      N.pilot.v = sampleSizeParallelOR(alpha=alpha, beta=beta, disc.rate=disc.rate.est.v[i], OSR=thetaO.est, delta=delta.init)
    }
    
    # ADJUSTED sample size:      
    N.korr=(N.pilot.v[1])
    N.korr.v[i] = N.korr
    # ADJUSTED sample size for discordant cases: 
    N.korr.disc.v[i]=(N.pilot.v[2])
    

    ## 9. step: How many subjects remain after interim analysis?
     if (N.korr < N.part | N.korr == N.part){
      # initial sample size overestimated -> the pre-planned fraction of subjects is sufficient
      dat = dat.1
    } else if (N.korr > N.part & N.korr < N){ 
      # initial sample size overestimated -> some subjects need to be recruited additionally to the pre-planned fraction of subjects
      N.left = ceiling(N.korr-N.part)
      dat.f.left = setdiff(dat.f, dat.1)
      dat.2 = sample_n(dat.f.left, N.left)
      dat = rbind(dat.1,dat.2)
    } else if (N.korr == N) {
      dat = dat.f
    } else {
      # initial sample size underestimated -> more subjects need to be recruited
      dat.f.left = setdiff(dat.f, dat.1) 
      N.add = ceiling(N.korr - N)
      #generate data for additional subjects:
      dat.add = sim.parallel(N = N.add, prev=prev, se.A=se.A, sp.A=sp.A, se.B=se.B, sp.B=sp.B, 
                             mu10=mu10, mu11=mu11, mu20=mu20, mu21=mu21, disc.rate.d=disc.rate.d, disc.rate.nd=disc.rate.nd)
      dat = rbind(dat.1,dat.f.left, dat.add)
      dat = sample_n(dat, nrow(dat))
      
    } 
    
    
    ## 10. step: data analysis [consider ONLY discordant cases] for fixed design (=dat.f) and for adaptive design (=dat): 
    
    ## 95%-Wald-CI per simulation run:
    CI.fix2 = CIParallel(datin=dat.f[which(dat.f$flag.disc.cases==1),], Test=Test, Outcome=Outcome, alpha=alpha)
    CI.val2 = CIParallel(datin=dat[which(dat$flag.disc.cases==1),], Test=Test, Outcome=Outcome, alpha=alpha)
   
    # save 95%-Wald-CI per simulation run:
    Endresult.fix[i,] <- cbind(CI.fix2)
    Endresult.val[i,] <- cbind(CI.val2)

    # count the number of cases in which the null hypothesis was rejected:
    power.fix2 = power.fix2 + ((0 < CI.fix2[2] && 0 < CI.fix2[3]) | (CI.fix2[2] < 0 && CI.fix2[3] < 0))
    power.val2 = power.val2 + ((0 < CI.val2[2] && 0 < CI.val2[3]) | (CI.val2[2] < 0 && CI.val2[3] < 0))
    
    ## estimated outcomes for fixed and adaptive design:
    
    ## fixed design:
    dat.f.disc=dat.f[which(dat.f$flag.disc.cases == 1),]
    dat.f.A = dat.f.disc[which(dat.f.disc$Test==1),]
    dat.f.B = dat.f.disc[which(dat.f.disc$Test==2),]
    # determine the single success rates and maintain the pre-specified shift of omega between initial assumed and true overall success rate:
    theta.A.fix[i] <- mean(dat.f.A$Outcome, na.rm = TRUE) + omega/2
    theta.B.fix[i] <- mean(dat.f.B$Outcome, na.rm = TRUE) + omega/2
    # calculate the overall success rate:
    thetaO.fix.v[i]= (theta.A.fix[i] + theta.B.fix[i])/2
    # calculate the difference between single success rates:
    delta.fix.v[i] = theta.A.fix[i] - theta.B.fix[i]
    
    ## adaptive design:
    dat.disc = dat[which(dat$flag.disc.cases == 1),]
    dat.A = dat.disc[which(dat.disc$Test==1),]
    dat.B = dat.disc[which(dat.disc$Test==2),]
    # determine the single success rates and maintain the pre-specified shift of omega between initial assumed and true overall success rate: 
    theta.A.adapt[i] <- mean(dat.A$Outcome, na.rm = TRUE) + omega/2
    theta.B.adapt[i] <- mean(dat.B$Outcome, na.rm = TRUE) + omega/2
    # calculate the overall success rate:
    thetaO.adapt.v[i]= (theta.A.adapt[i] + theta.B.adapt[i])/2
    # calculate the difference between single success rates:
    delta.adapt.v[i] = theta.A.adapt[i] -theta.B.adapt[i]
    
    # Calculate ratio between adjusted sample size and true sample size (should be close to 1):
    R.sim[i] <- N.korr.v[i]/N.true
    # Calculate ratio between adjusted sample size and initial sample size (should be close to 1):
    R.sim.init[i] <- N.korr.v[i]/N
    
    # Does R.sim exceeds a factor of 1.2? (1= yes, 0 = no) -> If yes, further investigations are required.:
    N.exc[i] = ifelse(R.sim[i] > 1.2 , 1, 0)
    
    #Root mean square error loss for each simulation run:
    RMSEval.sim[i] <- MLmetrics::RMSE(y_pred = N.korr.v[i], y_true = N.true)
    
    if(dots) cat(".",sep="")
    if(dots && i %% 50 == 0) cat(i,"\n")
    
    i = i + 1
  }  
  
  # End of loop (=simulation runs):
  if(dots) cat("\nSimulation Run Time:",round(difftime(Sys.time(), start.time,units="hours"),3)," Hours \n")
  
  ## aggregate the results over all simulation runs:
  
  # Calculate power method (Power):
  fix.power2 = power.fix2/nsim
  val.power2 = power.val2/nsim

  # Calculate mean adjusted sample size and mean adjusted sample size of discordant cases:
  Nkorr <- mean(N.korr.v)
  Nkorr.disc <- mean(N.korr.disc.v)
  
  # Calculate mean difference (delta) for fix and adaptive design:
  delta.fix <- mean(delta.fix.v)
  delta.adapt <- mean(delta.adapt.v)
  
  # Calculate mean discordant rate:
  disc.rate.recalc <- mean(disc.rate.est.v)
  
  # Calculate ratio between mean adjusted sample size and true sample size:
  RatioN <- Nkorr/N.true
  # Calculate ratio between mean adjusted sample size of discordant cases and true sample size of discordant cases:
  RatioNdisc <- Nkorr.disc/N.disc.true
  
  # Calculate mean Bias regarding delta: 
  #absBias.delta <- delta.adapt - delta.init 
  #relBias.delta <- (delta.adapt - delta.init)/delta.init 
  
  # Calculate mean Bias regarding overall success rate:
  absBias.OSR <- mean(thetaO.est.v) - thetaO.true
  relBias.OSR <- (mean(thetaO.est.v) - thetaO.true)/thetaO.true
  
  # Calculate mean Bias regarding discordant rate:
  absBias.disc.rate <- disc.rate.recalc - disc.rate
  relBias.disc.rate <- (disc.rate.recalc - disc.rate)/disc.rate
  
  # Calculate mean single success rates for the test A-based strategy and test B-based strategy for fixed and adaptive design:
  effectAfix <- mean(theta.A.fix)
  effectBfix <- mean(theta.B.fix)
  effectAval <- mean(theta.A.adapt)
  effectBval <- mean(theta.B.adapt)
  
  #Calculate mean overall success rate:
  effectOfix <- mean(thetaO.fix.v) #fixed design OSR
  OSRrecalc <- mean(thetaO.est.v) #interim analysis: re-estimated OSR
  effectOval <- mean(thetaO.adapt.v) #adapted design OSR
  
  # Calculate Root Mean Square Error Loss (RMSE) regarding overall success rate:
  RMSEval.OSR <- MLmetrics::RMSE(y_pred = OSRrecalc, y_true = thetaO.true)
  #Mean Square Error Loss:
  #MSEval.OSR <- MLmetrics::MSE(y_pred = OSRrecalc, y_true = thetaO.true)
  
  # Calculate Root Mean Square Error Loss (RMSE) regarding discordant rate:
  RMSEval.disc.rate <- MLmetrics::RMSE(y_pred = disc.rate.recalc, y_true = disc.rate)

  # Calculate Root Mean Square Error Loss (RMSE) regarding delta:
  #RMSEval.delta <- MLmetrics::RMSE(y_pred = delta.adapt, y_true = delta.init)
  
  # Calculate Root Mean Square Error Loss (RMSE) regarding sample size:
  RMSEval.N <- MLmetrics::RMSE(y_pred = Nkorr, y_true = N.true)

  # Calculate Root Mean Square Error Loss (RMSE) regarding sample size of discordant cases:
  RMSEval.Ndisc <- MLmetrics::RMSE(y_pred = Nkorr.disc, y_true = N.disc.true)
  
  # Frequency of ratio between adjusted sample size and true sample size exceeding 1.2:
  N.exc.frac <- mean(N.exc)
  
  # Rounding the mean adjusted sample size and mean adjusted sample size of discordant cases for output:
  #Nkorr <- ceiling(Nkorr)
  #Nkorr.disc <- ceiling(Nkorr.disc)
  
  ## Return the results over all simulation runs as data.frame:

   Result <- data.frame(PowerCI.fix = fix.power2, PowerCI.val = val.power2,
                        Ntrue=N.true, NtrueDISC=N.disc.true, Ninit = N, NinitDISC = N.disc, N.part, Nadapt = Nkorr, NadaptDISC = Nkorr.disc,
                        absBias.OSR, relBias.OSR, absBias.disc.rate, relBias.disc.rate,
                        RatioN, RatioNdisc, RMSEval.N, RMSEval.Ndisc, RMSEval.OSR, RMSEval.disc.rate,
                        effectAfix, effectBfix, effectAadapt=effectAval, effectBadapt=effectBval,
                        delta.init, delta.fix, delta.adapt, omega,
                        thetaO.true, effectOfix, effectOest=OSRrecalc, effectOadapt=effectOval,
                        alpha=alpha, beta=beta, prev, se.A = se.A, sp.A = sp.A, se.B = se.B, sp.B = sp.B,
                        mu10, mu11, mu20, mu21,
                        out, disc.rate.recalc, disc.rate, disc.rate.d, disc.rate.nd,
                        frac = frac, N.exc.frac)


   # return only "Result":
   # return(Result)
   
  ## Return the results of the single simulation runs as data.frame:
   
   Endresult.fix.out <- data.frame(Endresult.fix)
   names(Endresult.fix.out) <- c("est.fix", "lwr.ci.fix", "upr.ci.fix")
   Endresult.val.out <- data.frame(Endresult.val)
   names(Endresult.val.out) <- c("est.val", "lwr.ci.val", "upr.ci.val")

   Sim.Result <- cbind.data.frame(states.seed = states, Endresult.fix.out, Endresult.val.out,
                                  thetaA.init, thetaB.init, thetaO.init, delta.init, omega,
                                  thetaO.true, thetaA.true, thetaB.true,
                                  theta.A.fix, theta.B.fix, thetaO.fix=thetaO.fix.v, delta.fix=delta.fix.v,
                                  thetaO.est=thetaO.est.v, theta.A.adapt=theta.A.adapt, theta.B.adapt=theta.B.adapt, thetaO.adapt=thetaO.adapt.v, delta.adapt=delta.adapt.v,
                                  Ntrue=N.true, NtrueDISC=N.disc.true, Ninit = N, NinitDISC = N.disc, N.part, Nadapt = N.korr.v, NadaptDISC = N.korr.disc.v,
                                  prev, se.A, sp.A, se.B, sp.B, mu10, mu11, mu20, mu21,
                                  frac = frac, RMSEval.sim, R.sim, R.sim.init, N.exc,
                                  out=out, use.disc.rate.est, disc.rate.est=disc.rate.est.v, disc.rate, disc.rate.d, disc.rate.nd)

   # return only "Sim.Result":
   # return(Sim.Result)

   # return both "Result" and "Sim.Result":
   return(list(Sim.Result = Sim.Result, Result = Result))  
  
}




######################################
### Define Parameter Variations / Scenarios to be considered:

start.time <- Sys.time()

NumberOfCluster <- detectCores()
# Number of cores to be used:
cl <- makeCluster(NumberOfCluster-1)
registerDoParallel(cl)

# starting parallel computing / specify values for the considered parameters:
result.power <- foreach(frac = 0.5, .combine = "rbind", .errorhandling = "remove") %:%
  # expected outcome in specific subgroup of subjects (FP=false positives):
  foreach(mu10 = 0.5, .combine = "rbind", .errorhandling = "remove") %:%
  # expected outcome in specific subgroup of subjects (TP=true positives):
  foreach(mu11 = 0.2, .combine = "rbind", .errorhandling = "remove") %:%
  # expected outcome in specific subgroup of subjects (TN=true negatives):
  foreach(mu20 = 0.6, .combine = "rbind", .errorhandling = "remove") %:% 
  # expected outcome in specific subgroup of subjects (FN=false negatives):
  foreach(mu21 = 0.1, .combine = "rbind", .errorhandling = "remove") %:%
  # prevalence:
  foreach(prev = c(0.1, 0.2, 0.3), .combine = "rbind", .errorhandling = "remove") %:%
  # pre-specified shift between initial assumed OSR and true OSR:
  foreach(omega = c(0.05, 0.10, 0.15), .combine = "rbind", .errorhandling = "remove") %:%
  # discordant rates (0=minimum, 1=mean, 2=maximum):
  foreach(out = c(0, 1), .combine = "rbind", .errorhandling = "remove") %:%
  # use the re-calculated overall discordant rate to calculate the adjusted sample size (0=no, use the initial calculated overall discordant rate; 1=yes, use the re-calculated overall discordant rate):
  foreach(use.disc.rate.est = c(0, 1), .combine = "rbind", .errorhandling = "remove") %:%
  # sensitivity of test A:
  foreach(se.A = c(0.8, 0.9), .combine = "rbind", .errorhandling = "remove") %:%
  # specificity of test A:
  foreach(sp.A = c(0.8, 0.9), .combine = "rbind", .errorhandling = "remove") %:%
  # sensitivity of test B:
  foreach(se.B = c(se.A-0.1, se.A-0.2), .combine = "rbind", .errorhandling = "remove") %:%
  # specificity of test B:
  foreach(sp.B = c(sp.A-0.1, sp.A-0.2), .combine = "rbind", .errorhandling = "remove") %dopar% {
  #ATTENTION: Do not change the order of the packages. 
    library(tidyr)
    library(rlist)
    library(randomizr)
    library(bindata)
    library(DescTools)
    library(dplyr)
    library(foreach)
    library(parallel)
    library(doParallel) 
    
    res = power.sc2(alpha=0.05, beta=0.2, prev=prev, se.A = se.A, sp.A = sp.A, se.B = se.B, sp.B = sp.B,
                    mu10 = mu10, mu11 = mu11, mu20 = mu20, mu21 = mu21, 
                    omega=omega, out=out, use.disc.rate.est = use.disc.rate.est, frac = frac, nsim = 10000, Seed = 03122022)

  }


# end.time <- Sys.time()
# time.taken <- end.time - start.time
# time.taken

# save the results:
saveRDS(result.power, file = "result.power.RData")



###################################################################################################################################################
#################################################### TYPE I ERROR ##################################################################################

# # Type I error (calculated under null hypothesis H0: se.A=se.B & sp.A=sp.B) ______________________________________________________________________________________________________________________
 
# The function alpha.sc2 is based on the simulation by Amra Hot (Hot et al., 2022) and was adapted for the paired design.

  
alpha.sc2 = function(se.A, sp.A, se.B, sp.B, mu10, mu11, mu20, mu21, omega, alpha, beta, prev, out, use.disc.rate.est, frac, nsim, Seed, dots = TRUE){
  
  start.time <- Sys.time()
  if(dots) cat("Simulations (",nsim,") \n----|--- 1 ---|--- 2 ---|--- 3 ---|--- 4 ---| --- 5 \n",sep="")
  
  ## First set parameters for the simulation:
  
  # initialize vector for nsim seeds:
  states <- rep(NA,nsim)
  
  # initialize: How often was H0 rejected:
  alpha.val2 <- alpha.fix2 <- 0  # based on CI
  
  # initialize: Point estimator for single success rates (test A-based strategy, test B-based strategy),  
  # overall success rate and delta for fixed and adaptive design:
  theta.A.fix <- theta.A.adapt <- theta.B.fix <- theta.B.adapt <- c()
  thetaO.fix.v <- thetaO.est.v <- thetaO.adapt.v <-c() 
  disc.rate.est.v <- c()
  delta.fix.v <- delta.adapt.v <- c()
  thetaO.true <- thetaO.init <- delta.init <- 0
  effectAfix <- effectBfix <- effectAval <- effectBval <- effectOfix <- effectOval <- OSRrecalc <- c()
  
  # initialize matrix with Wald-test results for fixed and adaptive design:
  Endresult.fix=matrix(data=NA, nrow=nsim, ncol=3)
  Endresult.val=matrix(data=NA, nrow=nsim, ncol=3)
  
  # initialize RMSE and Bias:
  RMSEval.N <- RMSEval.Ndisc <- RMSEval.OSR <- RMSEval.delta <- RMSEval.disc.rate <- 0
  absBias.OSR <- relBias.OSR <- absBias.delta <- absBias.disc.rate <- relBias.disc.rate <- 0
  
  # initialize vectors:
  disc.rate.res <- c()
  N.true.v <- N.v <- N.pilot.v <- N.korr.v <- N.korr.disc.v <- c()

  # set seed for random-number generator: 
  set.seed(Seed)
  
  # generate a vector with nsim seeds:
  seed.states <- sample(seq(1000, 1000000), nsim)
  
  
  ## 1. step: calculate the discordant rates used for the data generation and the sample size calculation:
  disc.rate.res=disc.rate.calc(prev=prev, se.A=se.A, sp.A=sp.A, se.B=se.B, sp.B=sp.B, out=out)
  disc.rate=as.numeric(disc.rate.res[1])
  disc.rate.d=as.numeric(disc.rate.res[2])
  disc.rate.nd=as.numeric(disc.rate.res[3])
  
  
  ## 2. step:  INITIAL sample size calculation:  
    N.v = sampleSizeParallel(alpha=alpha, beta=beta, prev=prev, 
                           se.A=se.A, sp.A=sp.A, se.B=se.B, sp.B=sp.B, 
                           mu10=mu10, mu11=mu11, mu20=mu20, mu21=mu21, omega=omega,
                           disc.rate.d=disc.rate.d, disc.rate.nd=disc.rate.nd, disc.rate=disc.rate)
    #INITIAL sample size:  
    N=(N.v[1])
    #INITIAL sample size for discordant cases:
    N.disc=(N.v[2])
    # initial assumed difference between thetaA.init and thetaB.init:
    delta.init=(N.v[3])  
    # initial assumed success rate for test A-based strategy: 
    thetaA.init=(N.v[4])
    # initial assumed success rate for test B-based strategy:  
    thetaB.init=(N.v[5])
    # initial assumed overall success rate:
    thetaO.init=(N.v[6])
    
    
    ## 3. step: Calculation of true overall success rate (which is shifted by a pre-specified omega from the initial assumed overall success rate) 
    # and true single success rates by maintaining the initial assumed delta: 
    thetaO.true=thetaO.init + omega
    thetaA.true=thetaO.true + delta.init/2
    thetaB.true=thetaO.true - delta.init/2
    
    ## 4. step: TRUE sample size calculation using the true overall success rate and initial assumed delta
    N.true.v = sampleSizeParallelOR(alpha=alpha, beta=beta, disc.rate=disc.rate, OSR=thetaO.true, delta=delta.init)
    
    # TRUE sample size:  
    N.true=(N.true.v[1])
    # TRUE sample size for discordant cases: 
    N.disc.true=(N.true.v[2])
    
   
  ## beginning of the loop (=simulation runs):  
  
  i = 1
  
  while (i < nsim+1) {
      
    # Set seed for each simulation run:
    states[i] <- seed.states[i]
    set.seed(states[i])
    
    ## 5. step: data generation under H0: thetaA = thetaB <=> se.A = se.B and sp.A = sp.B:
    dat.f = sim.parallel(N = N, prev=prev, se.A=se.A, sp.A=sp.A, se.B=se.A, sp.B=sp.A, 
                         mu10=mu10, mu11=mu11, mu20=mu20, mu21=mu21, disc.rate.d=disc.rate.d, disc.rate.nd=disc.rate.nd)
    
    # pre-planned fractional proportion of initial planned sample size:
    N.part = ceiling(N*frac)
    
    ## 6. step: extract a random sample of N.part subjects: 
    dat.1=sample_n(dat.f,N.part)
    
 
    ## 7. step: internal pilot study (re-estimation of overall success rate using the data [considering only discordant cases] of the pre-planned fractional proportion of subjects):
    # extract only discordant cases:
    dat.1.disc = dat.1[which(dat.1$flag.disc.cases == 1),]
    # re-estimation of overall success rate and maintain the pre-specified shift of omega between initial assumed and true overall success rate:
    thetaO.est=mean(dat.1.disc$Outcome, na.rm = TRUE) + omega/2 
    thetaO.est.v[i] = thetaO.est
    # determine the discordant rate:
    disc.rate.est = mean(dat.1$flag.disc.cases, na.rm = TRUE)
    disc.rate.est.v[i]=disc.rate.est
    
    ## 8. step: re-calculate the sample size using the re-estimated overall success rate, the initial assumed delta and if requested, the re-calculated overall discordant rate: 
    if (use.disc.rate.est == 0){
      N.pilot.v = sampleSizeParallelOR(alpha=alpha, beta=beta, disc.rate=disc.rate, OSR=thetaO.est, delta=delta.init)
    } else {
      N.pilot.v = sampleSizeParallelOR(alpha=alpha, beta=beta, disc.rate=disc.rate.est.v[i], OSR=thetaO.est, delta=delta.init)
    }
    
    # ADJUSTED sample size:      
    N.korr=(N.pilot.v[1])
    N.korr.v[i] = N.korr
    # ADJUSTED sample size for discordant cases: 
    N.korr.disc.v[i]=(N.pilot.v[2])
    
    
    ## 9. step: How many subjects remain after interim analysis?
    if (N.korr < N.part | N.korr == N.part){
      # initial sample size overestimated -> the pre-planned fraction of subjects is sufficient
      dat = dat.1
    } else if (N.korr > N.part & N.korr < N){ 
      # initial sample size overestimated -> some subjects need to be recruited additionally to the pre-planned fraction of subjects
      N.left = ceiling(N.korr-N.part)
      dat.f.left = setdiff(dat.f, dat.1)
      dat.2 = sample_n(dat.f.left, N.left)
      dat = rbind(dat.1,dat.2)
    } else if (N.korr == N) {
      dat = dat.f
    } else {
      # initial sample size underestimated -> more subjects need to be recruited
      dat.f.left = setdiff(dat.f, dat.1) 
      N.add = ceiling(N.korr - N)
      #generate data for additional subjects:
      dat.add = sim.parallel(N = N.add, prev=prev, se.A=se.A, sp.A=sp.A, se.B=se.A, sp.B=sp.A, 
                             mu10=mu10, mu11=mu11, mu20=mu20, mu21=mu21, disc.rate.d=disc.rate.d, disc.rate.nd=disc.rate.nd)
      dat = rbind(dat.1,dat.f.left, dat.add)
      dat = sample_n(dat, nrow(dat))
      
    } 
    

    ## 10. step: data analysis [consider ONLY discordant cases] for fixed design (=dat.f) and for adaptive design (=dat):  
    
    ## 95%-Wald-CI per simulation run:
    CI.fix2 = CIParallel(datin=dat.f[which(dat.f$flag.disc.cases==1),], Test=Test, Outcome=Outcome, alpha=alpha)
    CI.val2 = CIParallel(datin=dat[which(dat$flag.disc.cases==1),], Test=Test, Outcome=Outcome, alpha=alpha)
    
    # save 95%-Wald-CI per simulation run:
    Endresult.fix[i,] <- cbind(CI.fix2)
    Endresult.val[i,] <- cbind(CI.val2)
    
    # count the number of cases in which the null hypothesis was rejected:
    alpha.fix2 = alpha.fix2 + ((0 < CI.fix2[2] && 0 < CI.fix2[3]) | (CI.fix2[2] < 0 && CI.fix2[3] < 0))
    alpha.val2 = alpha.val2 + ((0 < CI.val2[2] && 0 < CI.val2[3]) | (CI.val2[2] < 0 && CI.val2[3] < 0))
    
    ## estimated outcomes for fixed and adaptive design:
    
    ## fixed design:
    dat.f.disc=dat.f[which(dat.f$flag.disc.cases == 1),]
    dat.f.A = dat.f.disc[which(dat.f.disc$Test==1),]
    dat.f.B = dat.f.disc[which(dat.f.disc$Test==2),]
    # determine the single success rates and maintain the pre-specified shift of omega between initial assumed and true overall success rate:
    theta.A.fix[i] <- mean(dat.f.A$Outcome, na.rm = TRUE) + omega/2
    theta.B.fix[i] <- mean(dat.f.B$Outcome, na.rm = TRUE) + omega/2
    # calculate the overall success rate:
    thetaO.fix.v[i]= (theta.A.fix[i] + theta.B.fix[i])/2
    # calculate the difference between single success rates:
    delta.fix.v[i] = theta.A.fix[i] - theta.B.fix[i]
    
    ## adaptive design:
    dat.disc = dat[which(dat$flag.disc.cases == 1),]
    dat.A = dat.disc[which(dat.disc$Test==1),]
    dat.B = dat.disc[which(dat.disc$Test==2),]
    # determine the single success rates and maintain the pre-specified shift of omega between initial assumed and true overall success rate: 
    theta.A.adapt[i] <- mean(dat.A$Outcome, na.rm = TRUE) + omega/2
    theta.B.adapt[i] <- mean(dat.B$Outcome, na.rm = TRUE) + omega/2
    # calculate the overall success rate:
    thetaO.adapt.v[i]= (theta.A.adapt[i] + theta.B.adapt[i])/2
    # calculate the difference between single success rates:
    delta.adapt.v[i] = theta.A.adapt[i] -theta.B.adapt[i]
    
    if(dots) cat(".",sep="")
    if(dots && i %% 50 == 0) cat(i,"\n")
    
    i = i + 1
  }
  
  # End of loop (=simulation runs):
  if(dots) cat("\nSimulation Run Time:",round(difftime(Sys.time(),start.time,units="hours"),3)," Hours \n")
  
  ## aggregate the results over all simulation runs:
  
  # Calculate alpha method (Type I error):
  fix.alpha2 = alpha.fix2/nsim
  val.alpha2 = alpha.val2/nsim
    
  # Calculate mean adjusted sample size and mean adjusted sample size of discordant cases:
  Nkorr <- mean(N.korr.v)
  Nkorr.disc <- mean(N.korr.disc.v)

  # Calculate mean difference (delta) for fix and adaptive design:
  delta.fix <- mean(delta.fix.v)
  delta.adapt <- mean(delta.adapt.v)
  
  # Calculate mean discordant rate:
  disc.rate.recalc <- mean(disc.rate.est.v)

  # Calculate ratio between mean adjusted sample size and true sample size:
  RatioN <- Nkorr/N.true
  # Calculate ratio between mean adjusted sample size of discordant cases and true sample size of discordant cases:
  RatioNdisc <- Nkorr.disc/N.disc.true
  
  # Calculate mean Bias regarding delta: 
  absBias.delta <- delta.adapt - 0 
  #relBias.delta <- (delta.adapt - 0)/0 -> not defined because dividing by 0
  
  # Calculate mean Bias regarding overall success rate:
  absBias.OSR <- mean(thetaO.est.v) - thetaO.true
  relBias.OSR <- (mean(thetaO.est.v) - thetaO.true)/thetaO.true
  
  # Calculate mean Bias regarding discordant rate:
  absBias.disc.rate <- disc.rate.recalc - disc.rate
  relBias.disc.rate <- (disc.rate.recalc - disc.rate)/disc.rate

  # Calculate mean single success rates for the test A-based strategy and test B-based strategy for fixed and adaptive design:
  effectAfix <- mean(theta.A.fix)
  effectBfix <- mean(theta.B.fix)
  effectAval <- mean(theta.A.adapt)
  effectBval <- mean(theta.B.adapt)
  
  #Calculate mean overall success rate:
  effectOfix <- mean(thetaO.fix.v) #fixed design OSR
  OSRrecalc <- mean(thetaO.est.v) #interim analysis: re-estimated OSR
  effectOval <- mean(thetaO.adapt.v) #adapted design OSR

  # Calculate Root Mean Square Error Loss (RMSE) regarding overall success rate:
  RMSEval.OSR <- MLmetrics::RMSE(y_pred = OSRrecalc, y_true = thetaO.true)
  #Mean Square Error Loss:
  #MSEval.OSR <- MLmetrics::MSE(y_pred = OSRrecalc, y_true = thetaO.true)
  
  # Calculate Root Mean Square Error Loss (RMSE) regarding discordant rate:
  RMSEval.disc.rate <- MLmetrics::RMSE(y_pred = disc.rate.recalc, y_true = disc.rate)

  # Calculate Root Mean Square Error Loss (RMSE) regarding delta:
  RMSEval.delta <- MLmetrics::RMSE(y_pred = delta.adapt, y_true = delta.init)
  
  # Calculate Root Mean Square Error Loss (RMSE) regarding sample size:
  RMSEval.N <- MLmetrics::RMSE(y_pred = Nkorr, y_true = N.true)
  
  # Calculate Root Mean Square Error Loss (RMSE) regarding sample size of discordant cases:
  RMSEval.Ndisc <- MLmetrics::RMSE(y_pred = Nkorr.disc, y_true = N.disc.true)
  
  # Rounding the mean adjusted sample size and mean adjusted sample size of discordant cases for output:
  #Nkorr <- ceiling(Nkorr)
  #Nkorr.disc <- ceiling(Nkorr.disc)
  
  
  ## Return the results over all simulation runs as data.frame:
  
  Result <- data.frame(alphaCI.fix = fix.alpha2, alphaCI.val = val.alpha2,
                       Ntrue=N.true, NtrueDISC=N.disc.true, Ninit = N, NinitDISC = N.disc, N.part, Nadapt = Nkorr, NadaptDISC = Nkorr.disc,
                       absBias.OSR, relBias.OSR, absBias.disc.rate, relBias.disc.rate, absBias.delta,
                       RatioN, RatioNdisc, RMSEval.N, RMSEval.Ndisc, RMSEval.OSR, RMSEval.disc.rate, RMSEval.delta, 
                       effectAfix, effectBfix, effectAadapt=effectAval, effectBadapt=effectBval,
                       delta.init, delta.fix, delta.adapt, 
                       thetaO.true, effectOfix, effectOest=OSRrecalc, effectOadapt=effectOval,
                       alpha=alpha, beta=beta, prev, se.A = se.A, sp.A = sp.A, se.B = se.B, sp.B = sp.B,
                       mu10, mu11, mu20, mu21, omega,
                       out, disc.rate.recalc, disc.rate, disc.rate.d, disc.rate.nd,
                       frac = frac)

   # return only "Result":
   # return(Result)
  
  ## Return the results of the single simulation runs as data.frame:
  
  Endresult.fix.out <- data.frame(Endresult.fix)
  names(Endresult.fix.out) <- c("est.fix", "lwr.ci.fix", "upr.ci.fix")
  Endresult.val.out <- data.frame(Endresult.val)
  names(Endresult.val.out) <- c("est.val", "lwr.ci.val", "upr.ci.val")

  Sim.Result <- cbind.data.frame(states.seed = states, Endresult.fix.out, Endresult.val.out,
                                 thetaA.init, thetaB.init, thetaO.init, delta.init, omega,
                                 thetaO.true, thetaA.true, thetaB.true,
                                 theta.A.fix, theta.B.fix, thetaO.fix=thetaO.fix.v, delta.fix=delta.fix.v,
                                 thetaO.est=thetaO.est.v, theta.A.adapt=theta.A.adapt, theta.B.adapt=theta.B.adapt, thetaO.adapt=thetaO.adapt.v, delta.adapt=delta.adapt.v,
                                 Ntrue=N.true, NtrueDISC=N.disc.true, Ninit = N, NinitDISC = N.disc, N.part, Nadapt = N.korr.v, NadaptDISC = N.korr.disc.v,
                                 prev, se.A, sp.A, se.B, sp.B, mu10, mu11, mu20, mu21,
                                 frac = frac, out=out, use.disc.rate.est=use.disc.rate.est, disc.rate.est=disc.rate.est.v, disc.rate, disc.rate.d, disc.rate.nd)

   # return only "Sim.Result":
   # return(Sim.Result)
  
   # return both "Result" and "Sim.Result":
   return(list(Sim.Result = Sim.Result, Result = Result))  
  
}


######################################
### Define Parameter Variations / Scenarios to be considered:
 
# start.time <- Sys.time()

# specify values for the considered parameters: 
result.alpha <- foreach(frac = 0.5, .combine = "rbind", .errorhandling = "remove") %:%
  # expected outcome in specific subgroup of subjects FP (=false positives):
  foreach(mu10 = 0.5, .combine = "rbind", .errorhandling = "remove") %:%
  # expected outcome in specific subgroup of subjects TP (=true positives):
  foreach(mu11 = 0.2, .combine = "rbind", .errorhandling = "remove") %:%
  # expected outcome in specific subgroup of subjects TN (=true negatives):
  foreach(mu20 = 0.6, .combine = "rbind", .errorhandling = "remove") %:% 
  # expected outcome in specific subgroup of subjects FN (=false negatives):
  foreach(mu21 = 0.1, .combine = "rbind", .errorhandling = "remove") %:%
  # prevalence:
  foreach(prev = c(0.1, 0.2, 0.3), .combine = "rbind", .errorhandling = "remove") %:%
  # pre-specified shift between initial assumed OSR and true OSR:
  foreach(omega = c(0.05, 0.10, 0.15), .combine = "rbind", .errorhandling = "remove") %:%
  # discordant rates (0=minimum, 1=mean, 2=maximum):
  foreach(out = c(0, 1), .combine = "rbind", .errorhandling = "remove") %:%
  # use the re-calculated overall discordant rate to calculate the adjusted sample size (0=no, use the initial calculated overall discordant rate; 1=yes, use the re-calculated overall discordant rate):
  foreach(use.disc.rate.est = c(0, 1), .combine = "rbind", .errorhandling = "remove") %:%
  # sensitivity of test A:
  foreach(se.A = c(0.8, 0.9), .combine = "rbind", .errorhandling = "remove") %:%
  # specificity of test A:
  foreach(sp.A = c(0.8, 0.9), .combine = "rbind", .errorhandling = "remove") %:%
  # sensitivity of test B:
  foreach(se.B = c(se.A-0.1, se.A-0.2), .combine = "rbind", .errorhandling = "remove") %:%
  # specificity of test B:
  foreach(sp.B = c(sp.A-0.1, sp.A-0.2), .combine = "rbind", .errorhandling = "remove") %dopar% {
  #ATTENTION: Do not change the order of the packages. 
    library(tidyr)
    library(rlist)
    library(randomizr)
    library(bindata)
    library(DescTools)
    library(dplyr)
    library(foreach)
    library(parallel)
    library(doParallel) 
    
    res = alpha.sc2(alpha=0.05, beta=0.2, prev=prev, se.A = se.A, sp.A = sp.A, se.B = se.B, sp.B = sp.B,
                     mu10 = mu10, mu11 = mu11, mu20 = mu20, mu21 = mu21,
                     omega=omega, out=out, use.disc.rate.est = use.disc.rate.est, frac = frac, nsim = 10000, Seed = 03122022)
    
  }


end.time <- Sys.time()
time.taken <- end.time - start.time
time.taken

# save the results:
saveRDS(result.alpha, file = "result.alpha.RData")

stopCluster(cl)

