The bHIVE package has two interfaces. The functional
bHIVE() runs a whole analysis in one call, and the
introductory vignette,
vignette("bHIVE", package = "bHIVE"), uses it on mass
cytometry data. The R6 class AINet exposes the same engine
as a model object. This vignette covers AINet and the nine
immune modules it accepts, each of which changes one stage of the
algorithm. We use the small iris data so each example runs
in a second.
Each module is an independent R6 class that encapsulates a single
immunological mechanism. You create modules separately and pass them to
AINet$new(), allowing you to mix and match any
combination.
The SHMEngine controls how antibodies mutate after
cloning. Five strategies are available, each grounded in a different
aspect of SHM biology:
| Method | Biological Analogy | When to Use |
|---|---|---|
"uniform" |
Random point mutations | Baseline; simple and robust |
"airs" |
Affinity-proportional mutation (AIRS) | When high-affinity antibodies should explore less |
"hotspot" |
AID targets WRCY motifs | When some features matter more than others |
"energy" |
E_SHM ~ N_Mut^2 budget constraint | When you want bounded total perturbation |
"adaptive" |
Per-feature Adam-like moment tracking | Best for complex landscapes; most novel |
The adaptive method is the most distinctive in which somatic hypermutation and the Adam optimizer function the same. Each feature dimension maintains running mean and variance of past gradients.
data(iris)
X <- as.matrix(iris[, 1:4])
y <- iris$Species
set.seed(3)
methods <- c("uniform", "airs", "adaptive")
results <- lapply(methods, function(m) {
shm <- SHMEngine$new(method = m)
model <- AINet$new(nAntibodies = 15,
maxIter = 15,
shm = shm,
verbose = FALSE)
model$fit(X, y, task = "classification")
data.frame(
method = m,
accuracy = mean(model$result$assignments == as.character(y)),
n_ab = nrow(model$repertoire$as_matrix())
)
})
do.call(rbind, results)## method accuracy n_ab
## 1 uniform 0.8000000 12
## 2 airs 0.7266667 10
## 3 adaptive 0.9466667 15
In classical AIS, similar antibodies are suppressed via a simple
distance threshold (epsilon). The IdiotypicNetwork module
replaces this with network dynamics inspired by Varela and Coutinho’s
(1991) second-generation immune network theory.
The key idea is a bell-shaped activation function. Too little stimulation from other antibodies leads to death, moderate stimulation to activation, and excessive stimulation to suppression.
The module is experimental. It applies the activation to each pair of antibodies, so it works as intended on a small, tight repertoire. The example below builds one: four antibodies at pairwise affinity 0.25, in the middle of the default activation window, and one far from the rest.
a <- sqrt(log(4) / 2)
A <- rbind(cbind(diag(4) * a, 0), c(0, 0, 0, 0, 10))
rep <- ImmuneRepertoire$new(A)
idi <- IdiotypicNetwork$new(theta_low = 0.01, theta_high = 0.5)
idi$regulate(rep, "gaussian", list(alpha = 1))
idi## <IdiotypicNetwork>
## Activation window: [0.010, 0.500]
## Dynamics: 20 steps (dt=0.100)
## Source: 0.500 Decay: 0.100 Survival: 0.500
## Last run: 4/5 antibodies survived
## [1] 1.73 1.73 1.73 1.73 0.12
The four antibodies that stimulate each other survive. The isolated one is under-stimulated and dies.
On a full repertoire most pairs of antibodies are far apart. Their
affinity is near zero, below theta_low, so each pair counts
as suppression and the dynamics remove every antibody.
AINet then keeps the top 10% by population and reports
it:
set.seed(1)
model_idi <- AINet$new(nAntibodies = 25, maxIter = 15,
idiotypic = IdiotypicNetwork$new(),
verbose = FALSE)
model_idi$fit(X, task = "clustering")## IdiotypicNetwork removed every antibody in 1 of 15 iterations; the top 10% by population were kept each time. See ?IdiotypicNetwork for when the module applies.
## [1] 2
Check that antibodies survive on your data before relying on this module. A field-based form, in which the activation acts on the total stimulation each antibody receives, is planned.
The GerminalCenter module models the competition for T
follicular helper (Tfh) cell help that occurs in real germinal centers.
Only antibodies that receive Tfh help survive – this implements
quality-based selection pressure.
The quality metric is task-aware:
gc <- GerminalCenter$new(
nTfh = 20, # number of Tfh selectors
selectionPressure = 0.2, # 0 = no selection, 1 = very strict
rounds = 1
)
set.seed(4)
model_gc <- AINet$new(
nAntibodies = 30,
maxIter = 15,
germinalCenter = gc,
verbose = FALSE
)
model_gc$fit(X, y, task = "classification")
c(antibodies = model_gc$repertoire$size(),
accuracy = mean(model_gc$predict(X) == y))## antibodies accuracy
## 17.0000000 0.8533333
Selection pressure trades repertoire size for quality. Strong pressure trims the repertoire hard and, on a small dataset like iris, can cost accuracy. Tune it against a model without the module.
The Microenvironment module classifies the local context
around each antibody into three zones and adapts mutation rates
accordingly:
me <- Microenvironment$new(
high_density_threshold = 0.75,
low_density_threshold = 0.25,
stabilization_factor = 0.3, # reduce mutation to 30% in stable zones
exploration_factor = 2.0 # double mutation in exploration zones
)
rep <- ImmuneRepertoire$new(X[sample(150, 15), ])
env <- me$assess(rep, X)
table(env$zones)##
## boundary explore stable
## 7 4 4
Instead of random sampling, VDJLibrary generates
antibodies by combinatorial assembly of gene segments, mimicking V(D)J
recombination. The feature space is split into three segments (V, D, J),
each discretized into alleles, and new antibodies are built by combining
one allele from each segment.
vdj <- VDJLibrary$new(nV = 5,
nD = 3,
nJ = 3,
method = "pca")
A <- vdj$generate(20, X)
dim(A) # 20 antibodies x 4 features## [1] 20 4
## <VDJLibrary> method='pca' V=5 D=3 J=3
## Combinatorial space: 45 antibodies
Three decomposition methods are available:
"pca": PCA components define gene segments (best for
correlated features)"cluster": k-means within dimension groups creates
alleles"random_partition": random feature grouping
(simplest)In real immunity, B cells require two signals to activate: antigen
recognition (Signal 1) and costimulatory context (Signal 2).
The ActivationGate module prevents spurious cloning on
isolated outliers.
Signal 2 options:
"density": local data density around the antibody"danger": user-provided danger score per data
point"entropy": local label entropy (classification
only)gate <- ActivationGate$new(
signal2_type = "density",
threshold1 = 0.1, # minimum affinity (Signal 1)
threshold2 = 0.3 # density percentile (Signal 2)
)
A <- X[sample(150, 10), ]
rep <- ImmuneRepertoire$new(A)
aff <- rep$affinity_matrix(X, "gaussian")
activated <- gate$evaluate(aff, X, A)
c(activated = sum(activated), total = length(activated))## activated total
## 413 1500
The MemoryPool archives high-performing antibodies and
recalls them when relevant to current data. This is useful for streaming
or non-stationary data where distribution shifts may occur.
mp <- MemoryPool$new(
archive_threshold = 0.01, # minimum average affinity to archive
max_memory = 50,
recall_threshold = 0.01
)
rep <- ImmuneRepertoire$new(X[sample(150, 10), ])
mp$archive(rep, X)## [1] 10
## <MemoryPool> 10 cells (max 50)
## Archive threshold: 0.010
## Recall threshold: 0.010
## [1] 4
The ClassSwitcher module changes the effective kernel
width of antibodies based on their microenvironment zone, analogous to
real B cell isotype switching:
| Isotype | Kernel Width | Matching Style |
|---|---|---|
| IgM | alpha = 0.1 | Broad (exploration) |
| IgG | alpha = 5.0 | Specific (discrimination) |
| IgA | alpha = 1.0 | Intermediate (boundary patrol) |
cs <- ClassSwitcher$new(alpha_IgM = 0.1, alpha_IgG = 5.0, alpha_IgA = 1.0)
rep <- ImmuneRepertoire$new(X[sample(150, 10), ])
zones <- sample(c("stable", "explore", "boundary"), 10, replace = TRUE)
alphas <- cs$switch_isotypes(rep, zones)
data.frame(zone = zones, isotype = rep$metadata$isotype, alpha = alphas)## zone isotype alpha
## 1 explore IgM 0.1
## 2 stable IgG 5.0
## 3 boundary IgA 1.0
## 4 stable IgG 5.0
## 5 stable IgG 5.0
## 6 boundary IgA 1.0
## 7 stable IgG 5.0
## 8 boundary IgA 1.0
## 9 stable IgG 5.0
## 10 explore IgM 0.1
The ConvergentSelector identifies “public antibodies” –
antibodies that appear across multiple independent bHIVE runs. This
implements the immunological concept of public clonotypes as a
biologically-motivated ensemble method.
# Run 3 independent bHIVE analyses
set.seed(42)
results <- lapply(1:3, function(i) {
m <- AINet$new(nAntibodies = 15, maxIter = 10, verbose = FALSE)
m$fit(X, task = "clustering")
m$result
})
conv <- ConvergentSelector$new(tolerance = 1.0, min_appearances = 2)
public <- conv$from_results(results)
nrow(public)## [1] 12
Here is a full example combining multiple modules for a classification task:
set.seed(2)
model <- AINet$new(
nAntibodies = 25,
maxIter = 20,
k = 3,
beta = 5,
shm = SHMEngine$new(method = "adaptive", base_rate = 0.1),
germinalCenter = GerminalCenter$new(nTfh = 8, selectionPressure = 0.4),
microenvironment = Microenvironment$new(),
classSwitcher = ClassSwitcher$new(),
verbose = FALSE
)
model$fit(X, y, task = "classification")
model## <AINet>
## Repertoire: 9 antibodies x 4 features
## Task: classification
## Iterations: 20
## [1] 0.82
Not every module combination improves every dataset. Compare a
combination against the plain AINet before keeping it, on
held-out data or with cross-validation.
caret::train(method = bHIVEmodel) passes extra arguments,
modules included, on to bHIVE().
## R version 4.6.1 (2026-06-24)
## Platform: x86_64-pc-linux-gnu
## Running under: Ubuntu 26.04.1 LTS
##
## Matrix products: default
## BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
## LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.32.so; LAPACK version 3.12.0
##
## locale:
## [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
## [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
## [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
## [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
## [9] LC_ADDRESS=C LC_TELEPHONE=C
## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
##
## time zone: Etc/UTC
## tzcode source: system (glibc)
##
## attached base packages:
## [1] stats4 stats graphics grDevices utils datasets methods
## [8] base
##
## other attached packages:
## [1] ggplot2_4.0.3 bluster_1.23.1
## [3] HDCytoData_1.33.1 flowCore_2.25.1
## [5] ExperimentHub_3.3.2 AnnotationHub_4.3.2
## [7] BiocFileCache_3.3.0 dbplyr_2.6.0
## [9] SingleCellExperiment_1.35.2 SummarizedExperiment_1.43.0
## [11] Biobase_2.73.2 GenomicRanges_1.65.4
## [13] Seqinfo_1.3.2 IRanges_2.47.5
## [15] S4Vectors_0.51.10 BiocGenerics_0.59.12
## [17] generics_0.1.4 MatrixGenerics_1.25.0
## [19] matrixStats_1.5.0 bHIVE_0.99.8
## [21] BiocStyle_2.41.0
##
## loaded via a namespace (and not attached):
## [1] DBI_1.3.0 httr2_1.3.0
## [3] rlang_1.3.0 magrittr_2.0.5
## [5] otel_0.2.0 compiler_4.6.1
## [7] RSQLite_3.53.3 png_0.1-9
## [9] vctrs_0.7.3 pkgconfig_2.0.3
## [11] crayon_1.5.3 fastmap_1.2.0
## [13] backports_1.5.1 XVector_0.53.0
## [15] labeling_0.4.3 rmarkdown_2.32
## [17] purrr_1.2.2 bit_4.6.0
## [19] xfun_0.61 cachem_1.1.0
## [21] jsonlite_2.0.0 blob_1.3.0
## [23] DelayedArray_0.39.8 tweenr_2.0.3
## [25] BiocParallel_1.47.0 broom_1.0.13
## [27] clusterCrit_1.3.0 parallel_4.6.1
## [29] cluster_2.1.8.3 R6_2.6.1
## [31] bslib_0.12.0 RColorBrewer_1.1-3
## [33] car_3.1-5 jquerylib_0.1.4
## [35] Rcpp_1.1.2 knitr_1.52
## [37] BiocBaseUtils_1.15.1 Matrix_1.7-6
## [39] igraph_2.3.4 tidyselect_1.2.1
## [41] abind_1.4-8 yaml_2.3.12
## [43] codetools_0.2-20 curl_8.1.0
## [45] lattice_0.23-1 tibble_3.3.1
## [47] withr_3.0.3 KEGGREST_1.53.6
## [49] S7_0.2.2 evaluate_1.0.5
## [51] Rtsne_0.17 polyclip_1.10-7
## [53] ConsensusClusterPlus_1.77.0 Biostrings_2.81.9
## [55] ggpubr_1.0.0 pillar_1.11.1
## [57] BiocManager_1.30.27 filelock_1.0.3
## [59] carData_3.0-6 colorRamps_2.3.4
## [61] BiocVersion_3.24.0 scales_1.4.0
## [63] glue_1.8.1 maketools_1.3.2
## [65] tools_4.6.1 ggnewscale_0.5.2
## [67] BiocNeighbors_2.7.3 sys_3.4.3
## [69] ggsignif_0.6.4 buildtools_1.0.0
## [71] XML_3.99-0.25 grid_4.6.1
## [73] tidyr_1.3.2 RProtoBufLib_2.25.0
## [75] AnnotationDbi_1.75.2 ggforce_0.5.0
## [77] Formula_1.2-6 cli_3.6.6
## [79] rappdirs_0.3.4 cytolib_2.25.0
## [81] S4Arrays_1.13.2 viridisLite_0.4.3
## [83] dplyr_1.2.1 gtable_0.3.6
## [85] rstatix_1.1.0 sass_0.4.10
## [87] digest_0.6.39 FlowSOM_2.21.0
## [89] SparseArray_1.13.4 farver_2.1.2
## [91] memoise_2.0.1 htmltools_0.5.9
## [93] lifecycle_1.0.5 httr_1.4.9
## [95] MASS_7.3-66 bit64_4.8.6