@@ -867,6 +867,94 @@ initialize_model_pointer <- function(env, datafile_path, seed = 0) {
867867 invisible (NULL )
868868}
869869
870+ # Check if Stan-level parameter names (which may include tuple names like
871+ # "b_tuple") have a match among leaf-level variable names (which use ":"
872+ # to separate tuple elements, e.g., "b_tuple:1:1", "b_tuple:1:2").
873+ # A Stan-level name matches if it appears directly in leaf_names, or if
874+ # any leaf name starts with "<name>:" (tuple expansion).
875+ stan_param_has_leaf <- function (stan_names , leaf_names ) {
876+ vapply(stan_names , function (nm ) {
877+ nm %in% leaf_names || any(startsWith(leaf_names , paste0(nm , " :" )))
878+ }, logical (1 ), USE.NAMES = FALSE )
879+ }
880+
881+ # Check if a parameter's type info represents a tuple.
882+ # Tuples have $type as a list; non-tuples have $type as a string.
883+ is_tuple_type <- function (var_info ) {
884+ is.list(var_info $ type )
885+ }
886+
887+ # Reconstruct a tuple init value as a nested named list from flat leaf draws.
888+ # Also validates that no leaf values contain NA or Inf.
889+ #
890+ # @param path The accumulated `:` path (e.g., "b_tuple", "b_tuple:1")
891+ # @param var_info The type info at this level (from model_variables)
892+ # @param draws_rvar The draws_rvars object containing leaf entries
893+ # @param draw_iter Which draw iteration to extract
894+ # @return A list with two elements:
895+ # - `value`: nested named list suitable for CmdStan JSON
896+ # - `bad_leaves`: character vector of leaf names with NA/Inf values
897+ build_tuple_init_value <- function (path , var_info , draws_rvar , draw_iter ) {
898+ components <- var_info $ type
899+ result <- vector(" list" , length(components ))
900+ names(result ) <- as.character(seq_along(components ))
901+ bad_leaves <- character (0 )
902+ for (i in seq_along(components )) {
903+ child_path <- paste0(path , " :" , i )
904+ child_info <- components [[i ]]
905+ if (is_tuple_type(child_info )) {
906+ child <- build_tuple_init_value(
907+ child_path , child_info , draws_rvar , draw_iter
908+ )
909+ result [[i ]] <- child $ value
910+ bad_leaves <- c(bad_leaves , child $ bad_leaves )
911+ } else {
912+ x <- .extract_draw_value(child_path , draws_rvar , draw_iter )
913+ if (any(is.infinite(x )) || any(is.na(x ))) {
914+ bad_leaves <- c(bad_leaves , child_path )
915+ }
916+ if (child_info $ dimensions == 0 ) {
917+ result [[i ]] <- as.double(x )
918+ } else {
919+ result [[i ]] <- x
920+ }
921+ }
922+ }
923+ list (value = result , bad_leaves = bad_leaves )
924+ }
925+
926+ # Extract a single draw value from draws_rvar for a given variable name.
927+ # Handles the subset → draws_of → remove_leftmost_dim pipeline.
928+ .extract_draw_value <- function (var_name , draws_rvar , draw_iter ) {
929+ .remove_leftmost_dim(posterior :: draws_of(
930+ posterior :: subset_draws(draws_rvar [[var_name ]], draw = draw_iter )
931+ ))
932+ }
933+
934+ # Expand Stan-level parameter names to their leaf-level equivalents in
935+ # stan_variables. Non-tuple names pass through unchanged. Tuple names
936+ # (e.g., "b_tuple") are expanded to all matching leaf names
937+ # (e.g., "b_tuple:1:1", "b_tuple:1:2", "b_tuple:2").
938+ expand_stan_params_to_leaves <- function (stan_params , leaf_names ) {
939+ result <- character (0 )
940+ for (param in stan_params ) {
941+ if (param %in% leaf_names ) {
942+ result <- c(result , param )
943+ } else {
944+ # Find leaf-level names for this tuple parameter
945+ prefix <- paste0(param , " :" )
946+ leaves <- leaf_names [startsWith(leaf_names , prefix )]
947+ if (length(leaves ) > 0 ) {
948+ result <- c(result , leaves )
949+ } else {
950+ # No match found, include as-is (will be caught by subset_draws)
951+ result <- c(result , param )
952+ }
953+ }
954+ }
955+ result
956+ }
957+
870958create_skeleton <- function (param_metadata , model_variables ,
871959 transformed_parameters , generated_quantities ) {
872960 target_params <- names(model_variables $ parameters )
@@ -878,7 +966,25 @@ create_skeleton <- function(param_metadata, model_variables,
878966 target_params <- c(target_params ,
879967 names(model_variables $ generated_quantities ))
880968 }
881- lapply(param_metadata [target_params ], function (par_dims ) {
969+ # Expand target_params to match param_metadata leaf names.
970+ # For tuple parameters, the Stan-level name (e.g., "b_tuple") maps to
971+ # multiple leaf entries in param_metadata (e.g., "b_tuple.1.1",
972+ # "b_tuple.1.2", "b_tuple.2"). We expand by matching the prefix.
973+ meta_names <- names(param_metadata )
974+ expanded_params <- character (0 )
975+ for (param in target_params ) {
976+ if (param %in% meta_names ) {
977+ expanded_params <- c(expanded_params , param )
978+ } else {
979+ # Find leaf entries with this prefix (tuple expansion)
980+ prefix <- paste0(param , " ." )
981+ leaves <- meta_names [startsWith(meta_names , prefix )]
982+ if (length(leaves ) > 0 ) {
983+ expanded_params <- c(expanded_params , leaves )
984+ }
985+ }
986+ }
987+ lapply(param_metadata [expanded_params ], function (par_dims ) {
882988 if ((length(par_dims ) == 0 )) {
883989 array (0 , dim = 1 )
884990 } else {
0 commit comments