jigsawR / R /performance_utils.R
pjt222's picture
Upload folder using huggingface_hub
e232e39 verified
Raw
History Blame Contribute Delete
8.31 kB
# Performance Utilities for jigsawR
#
# Provides optimized functions using data.table and future/furrr when available.
# Falls back gracefully to base R implementations.
# =============================================================================
# Package Detection
# =============================================================================
#' Check if data.table is available
#' @return Logical
#' @keywords internal
has_data_table <- function() {
requireNamespace("data.table", quietly = TRUE)
}
#' Check if future/furrr are available
#' @return Logical
#' @keywords internal
has_parallel <- function() {
requireNamespace("future", quietly = TRUE) &&
requireNamespace("furrr", quietly = TRUE)
}
# =============================================================================
# data.table Configuration
# =============================================================================
#' Configure data.table for optimal performance
#'
#' Sets data.table threads based on available cores.
#' Should be called at package load or before intensive operations.
#'
#' @param threads Number of threads (NULL = auto-detect)
#' @param verbose Print configuration info
#' @return Invisible number of threads configured
#'
configure_data_table <- function(threads = NULL, verbose = FALSE) {
if (!has_data_table()) {
if (verbose) {
cli::cli_inform("data.table not available - using base R")
}
return(invisible(1L))
}
if (is.null(threads)) {
threads <- max(1L, parallel::detectCores() - 1L)
}
data.table::setDTthreads(threads)
if (verbose) {
cli::cli_inform("data.table configured with {threads} thread{?s}")
}
invisible(threads)
}
# =============================================================================
# High-Performance Neighbor Map Building
# =============================================================================
#' Build hexagonal neighbor map using data.table (if available)
#'
#' Uses data.table's fast set() operations for ~10x speedup over rbind.
#'
#' @param rings Number of rings in hexagonal puzzle
#' @return data.frame (or data.table if available) with piece_id, side, neighbor_id
#'
build_hex_neighbor_map_fast <- function(rings) {
num_pieces <- 3 * rings * (rings - 1) + 1
n_rows <- num_pieces * 6L
if (has_data_table()) {
# Use data.table with pre-allocation and set()
dt <- data.table::data.table(
piece_id = integer(n_rows),
side = integer(n_rows),
neighbor_id = integer(n_rows)
)
row_idx <- 1L
for (piece_id in seq_len(num_pieces)) {
for (side in 0:5) {
neighbor_id <- get_hex_neighbor(piece_id, side, rings)
data.table::set(dt, row_idx, "piece_id", as.integer(piece_id))
data.table::set(dt, row_idx, "side", as.integer(side))
data.table::set(dt, row_idx, "neighbor_id",
if (is.na(neighbor_id)) NA_integer_ else as.integer(neighbor_id))
row_idx <- row_idx + 1L
}
}
return(dt)
}
# Fallback to optimized base R
build_hex_neighbor_map(rings)
}
# =============================================================================
# Parallel Puzzle Generation
# =============================================================================
#' Generate multiple puzzles in parallel
#'
#' Uses future/furrr for parallel execution when available.
#' Falls back to sequential lapply otherwise.
#'
#' @param n_puzzles Number of puzzles to generate
#' @param type Puzzle type ("hexagonal", "rectangular", "concentric")
#' @param grid Grid specification
#' @param size Size specification
#' @param seeds Vector of seeds (length must equal n_puzzles)
#' @param n_workers Number of parallel workers (NULL = auto)
#' @param ... Additional arguments passed to generate_puzzle()
#'
#' @return List of puzzle results
#'
generate_puzzles_parallel <- function(n_puzzles, type, grid, size,
seeds = seq_len(n_puzzles),
n_workers = NULL, ...) {
if (length(seeds) != n_puzzles) {
stop("Length of seeds must equal n_puzzles")
}
if (has_parallel() && n_puzzles > 1) {
# Configure parallel backend
if (is.null(n_workers)) {
n_workers <- min(4L, parallel::detectCores() - 1L)
}
# Set up plan
old_plan <- future::plan()
on.exit(future::plan(old_plan), add = TRUE)
if (.Platform$OS.type == "windows") {
future::plan(future::multisession, workers = n_workers)
} else {
future::plan(future::multicore, workers = n_workers)
}
# Parallel generation
results <- furrr::future_map(seeds, function(seed) {
generate_puzzle(
type = type,
grid = grid,
size = size,
seed = seed,
save_files = FALSE,
...
)
}, .options = furrr::furrr_options(seed = TRUE))
} else {
# Sequential fallback
results <- lapply(seeds, function(seed) {
generate_puzzle(
type = type,
grid = grid,
size = size,
seed = seed,
save_files = FALSE,
...
)
})
}
results
}
# =============================================================================
# Vectorized String Operations
# =============================================================================
#' Vectorized coordinate formatting
#'
#' Faster than sprintf in loops for coordinate pairs.
#'
#' @param x Numeric vector of x coordinates
#' @param y Numeric vector of y coordinates
#' @param digits Number of decimal places
#' @return Character vector of formatted coordinate pairs
#'
#' @keywords internal
format_coords <- function(x, y, digits = 2) {
paste0(round(x, digits), ",", round(y, digits))
}
#' Build SVG path string efficiently
#'
#' Concatenates path commands without repeated paste0 calls.
#'
#' @param commands Character vector of SVG path commands
#' @return Single path string
#'
#' @keywords internal
build_svg_path <- function(commands) {
paste(commands, collapse = " ")
}
# =============================================================================
# Performance Information
# =============================================================================
#' Get performance configuration status
#'
#' Reports which performance optimizations are available.
#'
#' @return List with configuration details
#'
get_performance_config <- function() {
list(
data_table = list(
available = has_data_table(),
threads = if (has_data_table()) data.table::getDTthreads() else NA
),
parallel = list(
available = has_parallel(),
cores = parallel::detectCores()
),
r_version = R.version.string,
platform = .Platform$OS.type
)
}
#' Print performance configuration
#'
print_performance_config <- function() {
config <- get_performance_config()
if (requireNamespace("cli", quietly = TRUE)) {
cli::cli_h2("jigsawR Performance Configuration")
if (config$data_table$available) {
cli::cli_alert_success("data.table: {config$data_table$threads} threads")
} else {
cli::cli_alert_warning("data.table: not available")
}
if (config$parallel$available) {
cli::cli_alert_success("future/furrr: {config$parallel$cores} cores available")
} else {
cli::cli_alert_warning("future/furrr: not available")
}
cli::cli_alert_info("R: {config$r_version}")
cli::cli_alert_info("Platform: {config$platform}")
} else {
cat("jigsawR Performance Configuration\n")
cat("==================================\n")
cat(sprintf("data.table: %s\n",
if (config$data_table$available)
sprintf("%d threads", config$data_table$threads)
else "not available"))
cat(sprintf("future/furrr: %s\n",
if (config$parallel$available)
sprintf("%d cores", config$parallel$cores)
else "not available"))
cat(sprintf("R: %s\n", config$r_version))
cat(sprintf("Platform: %s\n", config$platform))
}
invisible(config)
}