---
title: "Research-package workflow boundaries with thisutils"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Research-package workflow boundaries with thisutils}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
has_neighbors <- requireNamespace("BiocNeighbors", quietly = TRUE)
```

`thisutils` provides a small common layer for recurring research-package
tasks. This vignette follows four boundaries: data representation,
neighborhood and metric evaluation, repeated execution, and optional runtime
dependencies.

## Preserve matrix representation and meaning

```{r representation}
library(Matrix)
library(thisutils)

x <- Matrix(
  c(-3, 0, 2, -1, 4, 0),
  nrow = 3,
  sparse = TRUE,
  dimnames = list(paste0("r", 1:3), paste0("c", 1:2))
)

compact <- matrix_to_table(x, keep_zero = FALSE)
roundtrip <- table_to_matrix(compact, return_sparse = TRUE)
collapsed <- collapse_sparse_rows(x, c("g1", "g1", "g2"))

c(
  sparsity = check_sparsity(x),
  stored_coordinates = nrow(compact),
  collapsed_rows = nrow(collapsed),
  roundtrip_equal = isTRUE(all.equal(as.matrix(roundtrip), as.matrix(x)))
)
```

Numeric matrices and sparse graphs give unstored entries different meanings.
The two top-k interfaces make that choice visible:

```{r topk}
run_sparse_topk(x, k = 2, by = "col")$value
run_sparse_topk_stored(x, k = 2, by = "col")$value
```

## Combine neighborhood and classification evaluation

The next example creates three synthetic labels in two batches. It is small
enough to run while building the vignette.

```{r embedding-data}
set.seed(20260810)
cell_type <- rep(c("T", "B", "Mono"), each = 40)
batch <- rep(rep(c("A", "B"), each = 20), 3)
metadata <- data.frame(batch = batch, cell_type = cell_type)
centers <- rbind(
  T = c(-3, 0, 0),
  B = c(3, 0, 0),
  Mono = c(0, 3, 0)
)
embedding <- centers[cell_type, , drop = FALSE] +
  matrix(stats::rnorm(120 * 3, sd = 0.7), ncol = 3)
```

`compute_lisi()` evaluates local label diversity, while
`run_biocneighbors_knn()` provides a standardized neighbor result that can be
fed into `classification_metrics_compute()`.

```{r evaluation, eval=has_neighbors}
mixing <- compute_lisi(
  embedding,
  metadata,
  c("batch", "cell_type"),
  perplexity = 10,
  n_threads = 1,
  max_dense_bytes = 16 * 1024^2
)

neighbors <- run_biocneighbors_knn(
  embedding,
  k = 7,
  exclude_self = TRUE,
  n_threads = 1
)
predicted <- apply(neighbors$idx, 1L, function(index) {
  votes <- sort(table(metadata$cell_type[index]), decreasing = TRUE)
  names(votes)[[1L]]
})
metrics <- classification_metrics_compute(predicted, metadata$cell_type)

c(
  median_batch_lisi = median(mixing$batch),
  median_cell_type_lisi = median(mixing$cell_type),
  accuracy = metrics$accuracy,
  macro_f1 = metrics$macro_f1
)
```

## Repeat work under an explicit execution contract

The same function can run serially or with PSOCK workers. A seed defines one
random-number stream per input, while `progress = FALSE` keeps stable lifecycle
messages without an elapsed-time display.

```{r execution}
draw <- function(cores, verbose = FALSE) {
  parallelize_fun(
    setNames(1:4, letters[1:4]),
    function(i) stats::rnorm(2),
    cores = cores,
    backend = "psock",
    seed = 42,
    verbose = verbose,
    progress = FALSE,
    timestamp_format = ""
  )
}

serial <- draw(1)
psock <- draw(2, verbose = TRUE)
identical(serial, psock)
```

PSOCK tasks that reference an object from the function environment should
include that object in each task or name it in `export_fun`.

## Diagnose optional dependencies without modifying a library

```{r dependencies}
status <- check_r(
  c("Matrix", "BiocNeighbors"),
  install = FALSE,
  verbose = FALSE
)
backend <- if (isTRUE(status[["BiocNeighbors"]])) {
  get_namespace_fun("BiocNeighbors", "findKNN")
} else {
  NULL
}

c(unlist(status), backend_is_function = is.function(backend))
```

`install = FALSE` is useful inside package workflows because availability
checking remains read-only. Installation, when intentionally enabled, can be
bounded with the `timeout` argument.
