Changes in version 0.3.0 New features - Added the synthetic geneexpr dataset: 100 genes, 20 conditions, a GO semantic similarity matrix and ground-truth biological process labels. Generated by data-raw/generate_geneexpr.R with a fixed seed. Use data(geneexpr) to load. - The introductory vignette vignette("moc-gapbk-intro") has been extended to cover the geneexpr dataset, side-by-side Pareto-front plots with ggplot2 + patchwork, and ground-truth validation via the Adjusted Rand Index. - The README now ships with a publication-quality Pareto-front plot on the geneexpr dataset. - ggplot2, patchwork, and usethis have been added to Suggests to support the new graphics and the data-raw build script. Removed - The Codecov integration introduced in 0.2.0 has been removed: codecov.yml, the test-coverage.yaml GitHub Actions workflow, and the Codecov badge from the README. Tests still run on every push through the R-CMD-check.yaml workflow. Major performance rewrite of the local search routines This release rewrites the Pareto Local Search (PLS) and Path-Relinking (PR) modules. The acceptance rules are now expressed as batched, vectorised operations rather than per-candidate sequential decisions. Equivalence note. Numerical results are no longer bit-identical to 0.2.x under the same set.seed(). The new acceptance rule is the textbook PLS rule (Dubois-Lacoste et al., 2015): the whole neighborhood of the current incumbent is evaluated jointly and a single non-dominated sort decides which solutions survive. Pareto-front quality is preserved on the internal benchmark. Pareto Local Search - The inner for (i in seq_len(num_neighborhood)) loop, which used to generate and evaluate one neighbor at a time, has been replaced by a batched construction: all candidate neighbors are built in one matrix allocation, all their Xie-Beni objectives are computed in a single vectorised pass and a single fastNonDominatedSorting() decides which ones survive. - merge() to locate the perturbed solution in the dominance table has been removed. The replacement uses rowSums(A != B) == 0L on integer matrices. - The explored flag is now tracked on a logical vector parallel to the archive matrix, eliminating the repeated as.data.frame()/subset()/cbind() round-trips in the original while-loop body. - generate.groups() and calculate.ranking.crowding() are no longer called from inside the neighborhood loop. They are called only at the end, on the final archive, to reshape the return value. - The outer while-loop now has a safety cap (10 * pop_size + 50) to avoid pathological oscillations when the archive churns. Path-Relinking - The cluster created via parallel::makeCluster() is now reused across all PR pairs in one generation, with the squared distance matrices exported once. - Symmetric differences between s_initial and s_guide are computed with setdiff() on integer vectors instead of data.frames. - The full calculate.ranking.crowding() call inside the while-loop has been replaced by .pr_pick_best(), which evaluates both Xie-Beni objectives on the batch of intermediate solutions in one vectorised pass and picks a rank-1 representative. The previous code ran calculate.ranking.crowding() (two ranking passes, one crowding distance, several data.frame coercions) for every step of the path. - Intermediate solutions are built in a single replicate-and-overwrite step (matrix allocation + per-row index swap) rather than chained rbind() calls on a growing data frame. Other routines reached by the local-search loop - calculate.objective.functions(): the inner sum and the medoid-to-medoid minimum are computed as sum() over a matrix slice and min(diag(sub) <- Inf) over a (k x k) submatrix, both C-level primitives. The cached dist_sq attribute makes the squaring step a one-time cost. - calculate.ranking.crowding(): crowding sums use .rowSums() directly. calculate.objectives.range() uses a single range() per column instead of two apply() scans. - singletons.repair(): when a single medoid is replaced, only that individual's group assignment is recomputed; the rest of the population is left untouched. - generate.crossover.k.points(): the per-column random draw is now a single runif(num_k) plus a vectorised ifelse() over both children. - generate.mutation.random.controller(): mutation decisions are made in one runif(pop_size) draw; only the surviving individuals enter the per-row body. - verify.feasibility(): duplicated medoids are replaced from the complement set (setdiff(seq_len(num_objects), row)) rather than by blind resampling, which converges in fewer iterations. - population.P is now allocated directly as matrix(0L, ...) instead of the t(sapply(..., function(u) array(rep(0, num_k)))) pattern. Removed - The apply(A, 1, function(x) all(x == B)) row-matching helper is superseded by the vectorised row_equals(), which uses rowSums() on a tiled comparison matrix. Changes in version 0.2.1 (2026-05-14) Performance This release rewrites the hot inner loops of the algorithm using vectorised R primitives. Numerical results are bit-identical to 0.2.0 under the same random seed; only the runtime changes. - generate.groups(): the original nested for-loop is replaced by a single max.col(-dmatrix[, medoids]) call, which runs in C. - calculate.objective.functions(): the original triple for-loop over individuals, clusters and objects is replaced by a single matrix slice plus sum(). The minimum medoid-to-medoid squared distance is now obtained from a (k x k) submatrix with diag(sub) <- Inf and min(sub), also vectorised. - singletons.delete() and singletons.repair() use tabulate() to count cluster sizes in C instead of which() + length() in R. - singletons.repair() no longer resets the column index to 1 on every replacement; it now retries on the same column up to num_objects attempts, eliminating an O(n^2) worst case. - moc.gapbk() precomputes the element-wise squared distance matrix for each input matrix once and caches it via an attribute, instead of squaring entries inside the inner loop on every call. - Path-Relinking caches the symmetric difference between the initial and guide solutions across each while iteration. - Path-Relinking builds the batch of intermediate solutions in a single replicate-and-overwrite step rather than chained rbind() calls on a growing data frame. - Pareto Local Search replaces apply(A, 1, function(x) all(x == B)) with vectorised rowSums(A != B) == 0L. Changes in version 0.2.0 GitHub infrastructure and developer experience - New README.Rmd (dynamic, generates README.md via devtools::build_readme()) with CRAN, R-CMD-check, pkgdown, Codecov, lifecycle and license badges. - Added GitHub Actions workflows under .github/workflows/: - R-CMD-check.yaml on Ubuntu (devel, release, oldrel-1), macOS and Windows. - pkgdown.yaml that deploys the documentation site to gh-pages. - test-coverage.yaml that pushes coverage to Codecov. - Added _pkgdown.yml (Bootstrap 5, cosmo theme, navbar, structured reference index, OpenGraph metadata, ORCID link). - Added codecov.yml with informational thresholds. - Added CODE_OF_CONDUCT.md (Contributor Covenant 2.1) and .github/CONTRIBUTING.md, .github/pull_request_template.md, .github/ISSUE_TEMPLATE/{bug_report,feature_request}.md. - Added a hex-style package logo at man/figures/logo.png. - Added the Language: en-US field to DESCRIPTION. - Updated .Rbuildignore and .gitignore. - Moved amap from Imports to Suggests. Breaking changes - The main function has been renamed from moc.gabk() to moc.gapbk() to match the package name. The previous name is preserved as a deprecated alias that emits a warning and forwards its arguments to moc.gapbk(). CRAN compliance - Removed doMPI from the dependency list. - nsga2R, foreach, doParallel, parallel, utils, stats and amap are now declared with explicit importFrom directives. - registerDoParallel() is now called with its namespace prefix. - The Author: field has been removed from DESCRIPTION in favor of a single Authors@R: declaration. - A copyright-holder role (cph) has been added for the maintainer. Code quality - The single 1100-line R/main.R has been split into thematic files. - Fixed a latent bug in singletons.repair(). - Added input validation with informative error messages. - parallel::makeCluster() now uses on.exit(parallel::stopCluster()). Documentation and tests - Added tests/testthat/ with smoke tests. - Added a vignettes/moc-gapbk-intro.Rmd introductory vignette. - Added README.md, NEWS.md and cran-comments.md. - Minimum R version raised from 3.2.5 to 3.5.0.