Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .github/workflows/linux-arm64-check.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Mirrors CRAN's linux-arm64 special check
# (https://github.com/r-devel/linux-arm64-checks): arm64 runner, rcheckserver
# container (CRAN Debian system libraries), Suggests such as 'minqa' built
# from source with that toolchain.
on:
push:
branches: [main, master]
pull_request:
workflow_dispatch:

name: linux-arm64-check.yaml

permissions: read-all

jobs:
linux-arm64:
name: ubuntu-24.04-arm (rcheckserver)
runs-on: ubuntu-24.04-arm
container:
image: ghcr.io/r-devel/rcheckserver/ubuntu:latest

steps:
- uses: actions/checkout@v4

- name: Install dependencies
timeout-minutes: 90
run: |
install.packages("remotes")
remotes::install_deps(dependencies = TRUE, upgrade = "never")
shell: Rscript {0}

- name: Build package
timeout-minutes: 30
run: R CMD build --no-build-vignettes --no-manual .
shell: bash

- name: Check package
timeout-minutes: 90
run: |
R CMD check RcppTrust_*.tar.gz --no-manual --ignore-vignettes || true
status=$(grep "^Status:" RcppTrust.Rcheck/00check.log)
echo "$status"
if [ -z "$status" ] || echo "$status" | grep -qE "WARNING|ERROR"; then
exit 1
fi
shell: bash
env:
_R_CHECK_FORCE_SUGGESTS_: false

- uses: actions/upload-artifact@v4
if: always()
with:
name: linux-arm64-check
path: |
RcppTrust.Rcheck/*.log
RcppTrust.Rcheck/tests
5 changes: 5 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
`OMP_THREAD_LIMIT`, so CRAN no longer sees "OMP: Warning #96: Cannot
form a team" (#3).

* The `bobyqa()`/`newuoa()` vs. 'minqa' comparison tests compare the
permutation-invariant chebyquad solutions as a set on platforms without
bitwise agreement, fixing a test failure on CRAN's linux-arm64 check
where the FMA-contracted 'minqa' build reached the same minimum with two
coordinates swapped.
* The NEWUOA `ierr = 3` test is skipped when R lacks long double support,
since its rounding path depends on `sum()` accumulating in long double
(CRAN's noLD check).
Expand Down
47 changes: 32 additions & 15 deletions tests/testthat/test-minqa.R
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,12 @@ bitwise <- R.version$arch %in% c("x86_64", "amd64")

# `wellPosed = FALSE` marks runs (e.g. a bound range of 1e-7) whose end
# point is sensitive to rounding, so contracted and uncontracted builds
# legitimately stop at different points.
expectSameAsMinqa <- function(ours, theirs, wellPosed = TRUE) {
# legitimately stop at different points. `permInvariant = TRUE` marks
# objectives (e.g. chebyquad) unchanged by permuting `x`: differently
# rounded runs may reach the same minimum with coordinates swapped (seen
# on aarch64), so the parameters are compared as a set.
expectSameAsMinqa <- function(ours, theirs, wellPosed = TRUE,
permInvariant = FALSE) {
expect_identical(class(ours), class(theirs))
if (bitwise) {
expect_identical(ours$par, theirs$par)
Expand All @@ -41,7 +45,12 @@ expectSameAsMinqa <- function(ours, theirs, wellPosed = TRUE) {
expect_identical(ours$ierr, theirs$ierr)
expect_identical(ours$msg, theirs$msg)
} else if (wellPosed) {
expect_equal(ours$par, theirs$par, tolerance = 1e-4)
if (permInvariant) {
expect_equal(sort(ours$par), sort(theirs$par), tolerance = 1e-4)
expect_equal(ours$fval, theirs$fval, tolerance = 1e-6)
} else {
expect_equal(ours$par, theirs$par, tolerance = 1e-4)
}
expect_equal(ours$ierr, theirs$ierr)
}
}
Expand All @@ -54,7 +63,7 @@ test_that("bobyqa() matches minqa::bobyqa() exactly", {
list(par = rep(0.5, 6), fn = extRosen, lower = -2, upper = 2,
control = list(npt = 13)),
list(par = seq(0.1, 0.9, length.out = 6), fn = chebyquad, lower = 0,
upper = 1),
upper = 1, permInvariant = TRUE),
list(par = c(1, 2), fn = fr, lower = c(0, 0), upper = c(4, 4),
control = list(maxfun = 50)),
list(par = c(4, 4), fn = fr, lower = c(0, 3.9999999), upper = c(4, 4),
Expand All @@ -65,7 +74,8 @@ test_that("bobyqa() matches minqa::bobyqa() exactly", {
ours <- suppressWarnings(bobyqa(cs$par, cs$fn, cs$lower, cs$upper, ctrl))
theirs <- suppressWarnings(minqa::bobyqa(cs$par, cs$fn, cs$lower,
cs$upper, ctrl))
expectSameAsMinqa(ours, theirs, wellPosed = !isFALSE(cs$wellPosed))
expectSameAsMinqa(ours, theirs, wellPosed = !isFALSE(cs$wellPosed),
permInvariant = isTRUE(cs$permInvariant))
}
})

Expand All @@ -75,15 +85,15 @@ test_that("newuoa() matches minqa::newuoa() exactly", {
list(par = c(-1.2, 1), fn = fr),
list(par = rep(0.5, 6), fn = extRosen, control = list(npt = 13)),
list(par = seq(0.1, 0.9, length.out = 6), fn = chebyquad,
control = list(npt = 28)),
control = list(npt = 28), permInvariant = TRUE),
list(par = rep(pi, 4), fn = function(x) -(10 - sum(x * seq_along(x))^2),
control = list(maxfun = 25))
)
for (cs in cases) {
ctrl <- if (is.null(cs$control)) list() else cs$control
ours <- suppressWarnings(newuoa(cs$par, cs$fn, ctrl))
theirs <- suppressWarnings(minqa::newuoa(cs$par, cs$fn, ctrl))
expectSameAsMinqa(ours, theirs)
expectSameAsMinqa(ours, theirs, permInvariant = isTRUE(cs$permInvariant))
}
})

Expand Down Expand Up @@ -170,17 +180,24 @@ test_that("NEWUOA's BIGDEN safeguard path matches minqa", {
test_that("BOBYQA's RESCUE path runs deterministically", {
# A Hilbert-matrix quadratic with a tiny rhoend enters RESCUE, where minqa
# 1.2.8 itself evaluates uninitialized memory, so there is no minqa
# reference: the port must converge and repeat exactly.
# reference: the port must converge and repeat exactly. The objective
# avoids BLAS (`%*%`), whose CPU-dependent kernels changed which exit the
# run took between CI machines (and so the coverage of the
# cancellation exit). On x86_64, n = 7 ends at the "too much
# cancellation" exit and n = 8 takes the other RESCUE re-entries.
hilb <- function(x) {
n <- length(x)
drop(x %*% (1 / (outer(0:(n - 1), 0:(n - 1), "+") + 1)) %*% x)
sum(outer(x, x) / (outer(0:(n - 1), 0:(n - 1), "+") + 1))
}
for (n in 7:8) {
ctrl <- list(npt = (n + 1) * (n + 2) / 2, rhobeg = 0.5, rhoend = 1e-10,
maxfun = 20000)
a <- suppressWarnings(bobyqa(rep(1, n), hilb, -2, 2, control = ctrl))
b <- suppressWarnings(bobyqa(rep(1, n), hilb, -2, 2, control = ctrl))
expect_identical(a, b)
expect_true(a$ierr %in% c(0, 3, 5))
expect_lt(a$fval, 1e-4)
}
ctrl <- list(npt = 36, rhobeg = 0.5, rhoend = 1e-10, maxfun = 20000)
a <- suppressWarnings(bobyqa(rep(1, 7), hilb, -2, 2, control = ctrl))
b <- suppressWarnings(bobyqa(rep(1, 7), hilb, -2, 2, control = ctrl))
expect_identical(a, b)
expect_true(a$ierr %in% c(0, 3, 5))
expect_lt(a$fval, 1e-4)
})

test_that("minqa R wrapper argument handling", {
Expand Down