read_stanbin <- function(path) {
if (!file.exists(path)) {
# Try adding extension if not present
if (!grepl("\\.stanbin$", path) && file.exists(paste0(path, ".stanbin"))) {
path <- paste0(path, ".stanbin")
} else {
stop("Stanbin file not found: ", path, call. = FALSE)
}
}
con <- file(path, "rb")
on.exit(close(con), add = TRUE)
# Read header (64 bytes)
magic <- readBin(con, "raw", n = 8)
expected_magic <- as.raw(c(0x53, 0x54, 0x41, 0x4e, 0x42, 0x49, 0x4e, 0x00))
if (!all(magic == expected_magic)) {
stop("Invalid stanbin file: magic bytes mismatch", call. = FALSE)
}
version <- readBin(con, "integer", n = 1, size = 4, endian = "little")
if (version > 1) {
stop(
"Unsupported stanbin version: ",
version,
". Please update reader.",
call. = FALSE
)
}
flags <- readBin(con, "integer", n = 1, size = 4, endian = "little")
# Read as raw bytes and convert to avoid signed integer issues with large values
rows_raw <- readBin(con, "raw", n = 8)
cols_raw <- readBin(con, "raw", n = 8)
# Convert little-endian raw bytes to numeric
rows <- sum(as.numeric(rows_raw) * 256^(0:7))
cols <- sum(as.numeric(cols_raw) * 256^(0:7))
header_size <- readBin(
con,
"integer",
n = 1,
size = 4,
endian = "little"
)
names_size <- readBin(
con,
"integer",
n = 1,
size = 4,
endian = "little"
)
# Skip reserved bytes (24 bytes)
readBin(con, "raw", n = 24)
# Read names section
names_raw <- readBin(con, "raw", n = names_size)
# Split by null bytes directly (0x00)
null_positions <- which(names_raw == as.raw(0x00))
if (length(null_positions) > 0) {
col_names <- list()
start <- 1
for (pos in null_positions) {
if (pos > start) {
col_names[[length(col_names) + 1]] <- rawToChar(names_raw[
start:(pos - 1)
])
}
start <- pos + 1
}
# Handle last segment
if (start <= length(names_raw)) {
col_names[[length(col_names) + 1]] <- rawToChar(names_raw[
start:length(names_raw)
])
}
col_names <- unlist(col_names)
} else {
col_names <- rawToChar(names_raw)
}
if (length(col_names) != cols) {
warning(sprintf(
"Column name count mismatch: %d names for %d columns",
length(col_names),
cols
))
}
if (rows == 0) {
warning("Stanbin file contains 0 rows")
mat <- matrix(numeric(0), nrow = 0, ncol = cols)
if (length(col_names) == cols) {
colnames(mat) <- col_names
}
return(mat)
}
# Read data section
data <- readBin(con, "double", n = rows * cols, size = 8, endian = "little")
if (length(data) != rows * cols) {
stop(
sprintf(
"Data size mismatch: expected %d values, got %d",
rows * cols,
length(data)
),
call. = FALSE
)
}
# Reshape to matrix (R is column-major, data is row-major)
mat <- matrix(data, nrow = rows, ncol = cols, byrow = TRUE)
if (length(col_names) == cols) {
colnames(mat) <- col_names
}
mat
}