Skip to content

Commit 9e50957

Browse files
committed
fix logs
1 parent 931a7f8 commit 9e50957

14 files changed

+271
-17
lines changed

R/adaptive_btl_refit.R

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2807,6 +2807,14 @@
28072807
as.integer(round_id_current)
28082808
}
28092809
controller <- .adaptive_controller_resolve(state)
2810+
max_pairs_after_stop <- as.integer(controller$max_pairs_after_stop %||% 0L)
2811+
if (!is.finite(max_pairs_after_stop) || is.na(max_pairs_after_stop) || max_pairs_after_stop < 0L) {
2812+
max_pairs_after_stop <- 0L
2813+
}
2814+
pairs_committed_after_stop <- as.integer(state$meta$pairs_committed_after_stop %||% 0L)
2815+
if (!is.finite(pairs_committed_after_stop) || is.na(pairs_committed_after_stop) || pairs_committed_after_stop < 0L) {
2816+
pairs_committed_after_stop <- 0L
2817+
}
28102818
round_summary <- state$refit_meta$last_completed_round_summary %||% list()
28112819
if (!is.na(round_id_at_refit) && !is.na(round_summary$round_id %||% NA_integer_) &&
28122820
as.integer(round_summary$round_id) == round_id_at_refit) {
@@ -2926,7 +2934,9 @@
29262934
mcmc_threads_per_chain = as.integer(mcmc_config_used$threads_per_chain %||% NA_integer_),
29272935
mcmc_cmdstanr_version = as.character(mcmc_config_used$cmdstanr_version %||% NA_character_),
29282936
stop_decision = as.logical(stop_decision),
2929-
stop_reason = if (isTRUE(stop_decision)) as.character(stop_reason) else NA_character_
2937+
stop_reason = if (isTRUE(stop_decision)) as.character(stop_reason) else NA_character_,
2938+
max_pairs_after_stop = as.integer(max_pairs_after_stop),
2939+
pairs_committed_after_stop = as.integer(pairs_committed_after_stop)
29302940
)
29312941

29322942
row

R/adaptive_logs.R

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,9 @@ schema_round_log <- c(
185185
mcmc_threads_per_chain = "integer",
186186
mcmc_cmdstanr_version = "character",
187187
stop_decision = "logical",
188-
stop_reason = "character"
188+
stop_reason = "character",
189+
max_pairs_after_stop = "integer",
190+
pairs_committed_after_stop = "integer"
189191
)
190192

191193
schema_link_stage_log <- c(

R/adaptive_persist.R

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,21 @@ read_log <- function(path) {
149149
out
150150
}
151151

152+
.adaptive_align_round_log_post_stop_columns <- function(round_log) {
153+
out <- tibble::as_tibble(round_log)
154+
n <- nrow(out)
155+
defaults <- c(
156+
max_pairs_after_stop = 0L,
157+
pairs_committed_after_stop = 0L
158+
)
159+
for (col in names(defaults)) {
160+
if (!col %in% names(out)) {
161+
out[[col]] <- rep.int(as.integer(defaults[[col]]), n)
162+
}
163+
}
164+
out
165+
}
166+
152167
.adaptive_item_log_current_schema <- function() {
153168
cols <- .adaptive_item_log_columns()
154169
int_cols <- c(
@@ -344,6 +359,7 @@ validate_session_dir <- function(session_dir) {
344359

345360
step_log <- read_log(paths$step_log)
346361
round_log <- read_log(paths$round_log)
362+
round_log <- .adaptive_align_round_log_post_stop_columns(round_log)
347363
link_stage_log <- if (file.exists(paths$link_stage_log)) {
348364
read_log(paths$link_stage_log)
349365
} else {
@@ -493,6 +509,7 @@ load_adaptive_session <- function(session_dir) {
493509

494510
step_log <- read_log(paths$step_log)
495511
round_log <- read_log(paths$round_log)
512+
round_log <- .adaptive_align_round_log_post_stop_columns(round_log)
496513
link_stage_log <- if (file.exists(paths$link_stage_log)) {
497514
read_log(paths$link_stage_log)
498515
} else {

R/adaptive_print.R

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,8 @@ adaptive_step_log <- function(state) {
584584
#' \code{mcmc_parallel_chains}, \code{mcmc_core_fraction},
585585
#' \code{mcmc_cores_detected_physical}, \code{mcmc_cores_detected_logical},
586586
#' \code{mcmc_threads_per_chain}, \code{mcmc_cmdstanr_version}.
587-
#' \item Stop output: \code{stop_decision}, \code{stop_reason}.
587+
#' \item Stop output: \code{stop_decision}, \code{stop_reason},
588+
#' \code{max_pairs_after_stop}, \code{pairs_committed_after_stop}.
588589
#' }
589590
#'
590591
#' @param state Adaptive state.

R/adaptive_run.R

Lines changed: 105 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,57 @@
308308
TRUE
309309
}
310310

311+
#' @keywords internal
312+
#' @noRd
313+
.adaptive_stop_boundary_bootstrap <- function(state) {
314+
out <- state
315+
out$meta <- out$meta %||% list()
316+
out$meta$stop_boundary_refit_id <- as.integer(out$meta$stop_boundary_refit_id %||% NA_integer_)
317+
out$meta$stop_boundary_step_id <- as.integer(out$meta$stop_boundary_step_id %||% NA_integer_)
318+
out$meta$pairs_committed_after_stop <- as.integer(out$meta$pairs_committed_after_stop %||% 0L)
319+
if (!is.finite(out$meta$pairs_committed_after_stop) ||
320+
is.na(out$meta$pairs_committed_after_stop) ||
321+
out$meta$pairs_committed_after_stop < 0L) {
322+
out$meta$pairs_committed_after_stop <- 0L
323+
}
324+
325+
boundary_step <- as.integer(out$meta$stop_boundary_step_id %||% NA_integer_)
326+
if (!is.na(boundary_step)) {
327+
step_log <- tibble::as_tibble(out$step_log %||% tibble::tibble())
328+
if (nrow(step_log) > 0L && all(c("step_id", "pair_id") %in% names(step_log))) {
329+
after_stop <- as.integer(step_log$step_id) > boundary_step & !is.na(step_log$pair_id)
330+
out$meta$pairs_committed_after_stop <- as.integer(sum(after_stop, na.rm = TRUE))
331+
} else {
332+
out$meta$pairs_committed_after_stop <- 0L
333+
}
334+
}
335+
336+
out
337+
}
338+
339+
#' @keywords internal
340+
#' @noRd
341+
.adaptive_stop_boundary_budget_status <- function(state, controller = NULL) {
342+
controller <- controller %||% .adaptive_controller_resolve(state)
343+
max_pairs_after_stop <- as.integer(controller$max_pairs_after_stop %||% 0L)
344+
if (!is.finite(max_pairs_after_stop) || is.na(max_pairs_after_stop) || max_pairs_after_stop < 0L) {
345+
max_pairs_after_stop <- 0L
346+
}
347+
boundary_step <- as.integer(state$meta$stop_boundary_step_id %||% NA_integer_)
348+
pairs_after_stop <- as.integer(state$meta$pairs_committed_after_stop %||% 0L)
349+
if (!is.finite(pairs_after_stop) || is.na(pairs_after_stop) || pairs_after_stop < 0L) {
350+
pairs_after_stop <- 0L
351+
}
352+
active <- !is.na(boundary_step) && !isTRUE(state$meta$stop_decision %||% FALSE)
353+
exhausted <- isTRUE(active) && pairs_after_stop >= max_pairs_after_stop
354+
list(
355+
active = isTRUE(active),
356+
exhausted = isTRUE(exhausted),
357+
max_pairs_after_stop = as.integer(max_pairs_after_stop),
358+
pairs_after_stop = as.integer(pairs_after_stop)
359+
)
360+
}
361+
311362
#' @keywords internal
312363
#' @noRd
313364
.adaptive_link_stage_progress <- function(state, spoke_id, stage_quotas, stage_order, refit_id = NULL) {
@@ -710,7 +761,8 @@
710761
#' \item{`link_identified_reliability_min`, `link_stop_reliability_min`,
711762
#' `link_rank_corr_min`, `delta_sd_max`, `delta_change_max`,
712763
#' `log_alpha_sd_max`, `log_alpha_change_max`, `cross_set_ppc_brier_max`,
713-
#' `ppc_calibration_id`, `probe_pairs_per_refit_per_spoke`,
764+
#' `ppc_calibration_id`, `max_pairs_after_stop`,
765+
#' `probe_pairs_per_refit_per_spoke`,
714766
#' `link_transform_escalation_refits_required`,
715767
#' `link_transform_escalation_is_one_way`,
716768
#' `spoke_quantile_coverage_bins`,
@@ -856,6 +908,11 @@ adaptive_rank_start <- function(items,
856908
#' \code{item_log}. Controller behavior can change after refits via
857909
#' identifiability-gated settings in \code{adaptive_config}; those controls
858910
#' affect pair routing and quotas, while BTL remains inference-only.
911+
#' If \code{adaptive_config$max_pairs_after_stop > 0}, the run records a stop
912+
#' boundary at the first refit with \code{stop_decision = TRUE} and allows at
913+
#' most that many additional committed comparisons before deterministic
914+
#' termination. Round logs record
915+
#' \code{max_pairs_after_stop} and \code{pairs_committed_after_stop}.
859916
#'
860917
#' @param state An adaptive state object created by [adaptive_rank_start()].
861918
#' @param judge A function called as `judge(A, B, state, ...)` that returns a
@@ -880,7 +937,8 @@ adaptive_rank_start <- function(items,
880937
#' `link_identified_reliability_min`, `link_stop_reliability_min`,
881938
#' `link_rank_corr_min`, `delta_sd_max`, `delta_change_max`,
882939
#' `log_alpha_sd_max`, `log_alpha_change_max`, `cross_set_ppc_brier_max`,
883-
#' `ppc_calibration_id`, `probe_pairs_per_refit_per_spoke`,
940+
#' `ppc_calibration_id`, `max_pairs_after_stop`,
941+
#' `probe_pairs_per_refit_per_spoke`,
884942
#' `link_transform_escalation_refits_required`,
885943
#' `link_transform_escalation_is_one_way`,
886944
#' `spoke_quantile_coverage_bins`,
@@ -1160,6 +1218,7 @@ adaptive_rank_run_live <- function(state,
11601218
state$config$persist_item_log <- isTRUE(persist_item_log)
11611219
}
11621220
state <- .adaptive_apply_controller_config(state, adaptive_config = adaptive_config)
1221+
state <- .adaptive_stop_boundary_bootstrap(state)
11631222
state$controller <- .adaptive_controller_with_phase_scope(state, controller = .adaptive_controller_resolve(state))
11641223
state <- .adaptive_phase_a_prepare(state)
11651224
state <- .adaptive_phase_a_finalize_if_ready(state)
@@ -1183,6 +1242,7 @@ adaptive_rank_run_live <- function(state,
11831242

11841243
remaining <- n_steps
11851244
while (remaining > 0L) {
1245+
state <- .adaptive_stop_boundary_bootstrap(state)
11861246
state <- .adaptive_phase_a_prepare(state)
11871247
state <- .adaptive_phase_a_finalize_if_ready(state)
11881248
.adaptive_phase_a_gate_or_abort(state)
@@ -1194,6 +1254,15 @@ adaptive_rank_run_live <- function(state,
11941254
}
11951255
return(state)
11961256
}
1257+
budget_status <- .adaptive_stop_boundary_budget_status(state)
1258+
if (isTRUE(budget_status$active) && isTRUE(budget_status$exhausted)) {
1259+
state$meta$stop_decision <- TRUE
1260+
state$meta$stop_reason <- "max_pairs_after_stop_exhausted"
1261+
if (!is.null(state$config$session_dir)) {
1262+
save_adaptive_session(state, session_dir = state$config$session_dir, overwrite = TRUE)
1263+
}
1264+
return(state)
1265+
}
11971266
state <- .adaptive_link_sync_warm_start(state)
11981267
state <- .adaptive_round_activate_if_ready(state)
11991268
state <- run_one_step(state, judge, ...)
@@ -1208,6 +1277,16 @@ adaptive_rank_run_live <- function(state,
12081277
} else {
12091278
state <- .adaptive_round_commit(state, step_row)
12101279
}
1280+
budget_status <- .adaptive_stop_boundary_budget_status(state)
1281+
if (isTRUE(budget_status$active)) {
1282+
state$meta$pairs_committed_after_stop <- as.integer(
1283+
state$meta$pairs_committed_after_stop %||% 0L
1284+
) + 1L
1285+
budget_status <- .adaptive_stop_boundary_budget_status(state)
1286+
if (isTRUE(budget_status$exhausted)) {
1287+
state$meta$stop_reason <- "max_pairs_after_stop_exhausted"
1288+
}
1289+
}
12111290
} else if (isTRUE(step_row$candidate_starved[[1L]]) &&
12121291
!identical(step_row$round_stage[[1L]], "warm_start")) {
12131292
starve <- .adaptive_round_starvation(state, step_row)
@@ -1313,12 +1392,31 @@ adaptive_rank_run_live <- function(state,
13131392
}
13141393
}
13151394
if (isTRUE(stop_decision)) {
1316-
state$meta$stop_decision <- TRUE
1317-
state$meta$stop_reason <- stop_reason
1318-
if (!is.null(state$config$session_dir)) {
1319-
save_adaptive_session(state, session_dir = state$config$session_dir, overwrite = TRUE)
1395+
round_row_tbl <- tibble::as_tibble(round_row)
1396+
boundary_refit_id <- if ("refit_id" %in% names(round_row_tbl)) {
1397+
as.integer(round_row_tbl$refit_id[[1L]] %||% NA_integer_)
1398+
} else {
1399+
NA_integer_
1400+
}
1401+
boundary_step_id <- if ("step_id_at_refit" %in% names(round_row_tbl)) {
1402+
as.integer(round_row_tbl$step_id_at_refit[[1L]] %||% NA_integer_)
1403+
} else {
1404+
NA_integer_
1405+
}
1406+
if (is.na(as.integer(state$meta$stop_boundary_step_id %||% NA_integer_))) {
1407+
state$meta$stop_boundary_refit_id <- boundary_refit_id
1408+
state$meta$stop_boundary_step_id <- boundary_step_id
1409+
state$meta$pairs_committed_after_stop <- 0L
1410+
}
1411+
budget_status <- .adaptive_stop_boundary_budget_status(state)
1412+
if (budget_status$max_pairs_after_stop <= 0L) {
1413+
state$meta$stop_decision <- TRUE
1414+
state$meta$stop_reason <- stop_reason
1415+
if (!is.null(state$config$session_dir)) {
1416+
save_adaptive_session(state, session_dir = state$config$session_dir, overwrite = TRUE)
1417+
}
1418+
return(state)
13201419
}
1321-
return(state)
13221420
}
13231421
if (isTRUE(.adaptive_link_all_spokes_stopped(state))) {
13241422
state$meta$stop_decision <- TRUE

R/adaptive_state.R

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
ppc_calibration_id = as.character(calibration_defaults$ppc_calibration_id %||% "default_p95_brier_active"),
108108
link_transform_escalation_refits_required = 2L,
109109
link_transform_escalation_is_one_way = TRUE,
110+
max_pairs_after_stop = 0L,
110111
probe_pairs_per_refit_per_spoke = 2L,
111112
spoke_quantile_coverage_bins = 3L,
112113
spoke_quantile_coverage_min_per_bin_per_refit = 1L,
@@ -182,6 +183,7 @@
182183
"ppc_calibration_id",
183184
"link_transform_escalation_refits_required",
184185
"link_transform_escalation_is_one_way",
186+
"max_pairs_after_stop",
185187
"probe_pairs_per_refit_per_spoke",
186188
"spoke_quantile_coverage_bins",
187189
"spoke_quantile_coverage_min_per_bin_per_refit",
@@ -343,6 +345,7 @@
343345
Inf
344346
)
345347
out$link_transform_escalation_is_one_way <- read_logical("link_transform_escalation_is_one_way")
348+
out$max_pairs_after_stop <- read_integer("max_pairs_after_stop", 0L, Inf)
346349
out$probe_pairs_per_refit_per_spoke <- read_integer("probe_pairs_per_refit_per_spoke", 0L, Inf)
347350
out$spoke_quantile_coverage_bins <- read_integer("spoke_quantile_coverage_bins", 1L, Inf)
348351
out$spoke_quantile_coverage_min_per_bin_per_refit <- read_integer(
@@ -790,7 +793,10 @@ new_adaptive_state <- function(items, now_fn = function() Sys.time()) {
790793
now_fn = now_fn,
791794
seed = 1L,
792795
stop_decision = FALSE,
793-
stop_reason = NA_character_
796+
stop_reason = NA_character_,
797+
stop_boundary_refit_id = NA_integer_,
798+
stop_boundary_step_id = NA_integer_,
799+
pairs_committed_after_stop = 0L
794800
)
795801
),
796802
class = "adaptive_state"

man/adaptive_rank_run_live.Rd

Lines changed: 7 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/adaptive_rank_start.Rd

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/adaptive_round_log.Rd

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)