Condition: Stateful trigger and analysis unit
Condition.RdA Condition encapsulates a single trigger rule that is evaluated against
a data snapshot at each simulated timepoint. It combines three concerns:
Filtering - a
dplyr::filter()expression selects the rows relevant to this condition (e.g. "only enrolled subjects in arm A").Analysis - an optional function transforms the filtered snapshot into a result (e.g. a t-test, a subject count, a Go/No-Go decision).
Trigger bookkeeping - the condition fires only when the filtered data is non-empty, the cooldown period has elapsed since the last trigger, and the maximum trigger count has not been reached.
Condition objects are stored in trial$conditions and evaluated by
Trial$run() at each timepoint.
Details
Three-gate logic. A trigger fires only when all three gates pass:
The filtered snapshot contains at least one row.
current_time - last_trigger_time >= cooldown(or the condition has never fired before).trigger_count < max_triggers.
If any gate fails, check_conditions() returns an empty list and state
is not updated.
On a successful trigger, the condition calls
analysis(df, current_time, ...) and stores the result under
name (or 1L when no name is set). Any values in analysis_args are
appended as additional named arguments. If no analysis function is
provided, the filtered data frame is returned as-is with a warning.
Fields
wherelistof quosures (fromrlang::quos()) used asdplyr::filter()predicates, or atriggerobject (converted automatically).NULLor an empty list passes the full snapshot.analysisfunctionorNULL. Called asanalysis(df, current_time, ...)on a successful trigger, where...are any values fromanalysis_args. Should return adata.frameor named list. IfNULL, the filtered data frame is returned with a warning.analysis_argslistorNULL. Named list of extra arguments injected into every call toanalysis.namecharacterorNULL. Key used to label the result in the returned list. Falls back to1LwhenNULL.cooldownnumeric. Minimum time units that must elapse between consecutive triggers. Default0(no cooldown).max_triggersinteger. Maximum number of times this condition may fire. UseInffor unlimited. Default1L.trigger_countinteger. Number of successful triggers so far. Initialised to0L.last_trigger_timenumeric. Calendar time of the most recent successful trigger. Initialised toNA_real_.
Methods
$new(where, analysis, name, cooldown, max_triggers)Construct a new
Condition. All arguments exceptwhereare optional.cooldownmust be a single non-negative number;max_triggersmust be a single non-negative integer orInf.$check_conditions(locked_data, current_time)Evaluate the condition against
locked_dataatcurrent_time. Returns a namedlistcontaining the analysis result (or filtered data frame) if the condition fires, or an emptylistotherwise. On a successful trigger,trigger_countis incremented andlast_trigger_timeis updated.
See also
Timerfor managing trial timepointsTrialfor running the simulation and iterating over conditionsvalue_trigger(),count_trigger(),enroll_trigger(),calendar_trigger()for building trigger specificationscondition_calendar_time()andcondition_enrollment_fraction()for convenientConditionconstructors
Public fields
wherelistof quosures (rlang::quos()) used asdplyr::filter()predicates, or atriggerobject (converted automatically).NULLor empty list passes the full snapshot.analysisfunctionorNULL. Called asanalysis(df, current_time, ...)on a successful trigger, where...are any values fromanalysis_args.analysis_argslistorNULL. Named list of extra values injected into the analysis function call as additional named arguments.namecharacterorNULL. Key labelling the result in the output list. Falls back to1LwhenNULL.cooldownnumeric. Minimum time units between consecutive triggers. Default0.max_triggersintegerorInf. Maximum number of times this condition may fire. Default1L.trigger_countinteger. Number of successful triggers so far. Initialised to0L.last_trigger_timenumeric. Calendar time of the most recent successful trigger.NA_real_until first trigger.
Methods
Method new()
Create a new Condition instance.
Usage
Condition$new(
where = NULL,
analysis = NULL,
analysis_args = NULL,
name = NULL,
cooldown = 0,
max_triggers = 1L
)Arguments
wheretriggerobject (fromvalue_trigger(),count_trigger(),enroll_trigger(), orcalendar_trigger()), alistof quosures fromrlang::quos(), orNULLto use the full snapshot.analysisfunctionorNULL. Called asanalysis(df, current_time, ...)on a successful trigger, where...are the values fromanalysis_args.analysis_argslistorNULL. Named list of extra arguments passed to the analysis function afterdfandcurrent_time.namecharacterorNULL. Result key. Defaults to1L.cooldownnumeric. Minimum time between triggers. Default0.max_triggersinteger. Maximum trigger count. Default1L. UseInffor unlimited.
Method check_conditions()
Evaluate this condition against a data snapshot.
Applies the three-gate logic: non-empty filter result, cooldown
elapsed, and trigger count below max_triggers. Returns the analysis
result (or filtered data) on a successful trigger, or an empty list
otherwise.
Examples
# Build a snapshot data frame
snapshot <- data.frame(
arm = c("A", "A", "A", "B"),
status = c("active", "active", "active", "active"),
stringsAsFactors = FALSE
)
# Analysis function: count active subjects per arm
count_fn <- function(df, current_time) {
data.frame(n_active = nrow(df), fired_at = current_time)
}
# Condition fires once when 3+ subjects are active (max_triggers = 1)
cond <- Condition$new(
where = count_trigger("enroll_time", ">=", 3L),
analysis = count_fn,
name = "interim_A",
cooldown = 0,
max_triggers = 1L
)
# First call: fires and returns analysis result
res <- cond$check_conditions(snapshot, current_time = 5)
#> Error in dplyr::filter(locked_data, !!!self$where): ℹ In argument: `sum(!is.na(.data[["enroll_time"]])) >= 3L`.
#> Caused by error in `.data[["enroll_time"]]`:
#> ! Column `enroll_time` not found in `.data`.
res[["interim_A"]] # data.frame(n_active = 3, fired_at = 5)
#> Error: object 'res' not found
# Second call: does not fire (max_triggers already reached)
res2 <- cond$check_conditions(snapshot, current_time = 6)
#> Error in dplyr::filter(locked_data, !!!self$where): ℹ In argument: `sum(!is.na(.data[["enroll_time"]])) >= 3L`.
#> Caused by error in `.data[["enroll_time"]]`:
#> ! Column `enroll_time` not found in `.data`.
length(res2) # 0
#> Error: object 'res2' not found