···11+Package: R.NURS
22+Title: What the Package Does (One Line, Title Case)
33+Version: 0.0.0.9000
44+Authors@R:
55+ person("First", "Last", , "first.last@example.com", role = c("aut", "cre"))
66+Description: What the package does (one paragraph).
77+License: MIT + file LICENSE
88+Encoding: UTF-8
99+Roxygen: list(markdown = TRUE)
1010+RoxygenNote: 7.3.2
···11+# MIT License
22+33+Copyright (c) 2025 R.NURS authors
44+55+Permission is hereby granted, free of charge, to any person obtaining a copy
66+of this software and associated documentation files (the "Software"), to deal
77+in the Software without restriction, including without limitation the rights
88+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+copies of the Software, and to permit persons to whom the Software is
1010+furnished to do so, subject to the following conditions:
1111+1212+The above copyright notice and this permission notice shall be included in all
1313+copies or substantial portions of the Software.
1414+1515+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1616+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1717+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121+SOFTWARE.
+2
NAMESPACE
···11+# Generated by roxygen2: do not edit by hand
22+
···11+log_sum_exp <- function(log_vals) {
22+ if (requireNamespace("matrixStats", quietly = TRUE)) {
33+ matrixStats::logSumExp(log_vals)
44+ } else {
55+ m <- max(log_vals)
66+ m + log(sum(exp(log_vals - m)))
77+ }
88+}
99+1010+# No underrun condition
1111+NURS_stop <- function(log_vals, log_eps_h) {
1212+ max(log_vals[1], log_vals[length(log_vals)]) <=
1313+ log_eps_h + log_sum_exp(log_vals)
1414+}
1515+1616+# Recursive sub stopping
1717+NURS_sub_stop <- function(log_vals, log_eps_h) {
1818+ n <- length(log_vals)
1919+ if (n < 2) return(FALSE)
2020+ if (NURS_stop(log_vals, log_eps_h)) return(TRUE)
2121+ left_indices <- 1:(n / 2)
2222+ NURS_sub_stop(log_vals[left_indices], log_eps_h) ||
2323+ NURS_sub_stop(log_vals[-left_indices], log_eps_h)
2424+}
2525+2626+NURS_step <- function(logpdf, theta, epsilon, h, M) {
2727+ d <- length(theta)
2828+ # hit
2929+ z <- rnorm(d)
3030+ rho <- z / sqrt(sum(z^2))
3131+3232+ # run
3333+ s <- runif(1, -h / 2, h / 2)
3434+ log_cur <- logpdf(theta)
3535+ log_shifted <- logpdf(theta + s * rho)
3636+ theta0 <- if (
3737+ (log_shifted - log_cur >= 0) || (runif(1) < exp(log_shifted - log_cur))
3838+ )
3939+ theta + s * rho else theta
4040+4141+ # helpers
4242+ log_eps_h <- log(epsilon) + log(h)
4343+ orbit_points <- list(theta0)
4444+ log_vals <- logpdf(theta0)
4545+4646+ # bookkeeping to get ends
4747+ left <- right <- 1
4848+4949+ # Orbit selection procedure
5050+ B <- sample(c(FALSE, TRUE), M, replace = TRUE)
5151+ for (k in seq_len(M)) {
5252+ B_k <- B[k]
5353+ n_ext <- 2^(k - 1)
5454+ orbit_ext <- lapply(
5555+ seq_len(n_ext),
5656+ \(i)
5757+ (if (B_k) orbit_points[[right]] else orbit_points[[left]]) +
5858+ (2 * B_k - 1) * i * h * rho
5959+ )
6060+ log_ext <- sapply(orbit_ext, logpdf)
6161+6262+ if (NURS_sub_stop(log_ext, log_eps_h)) break
6363+6464+ if (B_k) {
6565+ orbit_points <- c(orbit_points, orbit_ext)
6666+ log_vals <- c(log_vals, log_ext)
6767+ } else {
6868+ orbit_points <- c(orbit_ext, orbit_points)
6969+ log_vals <- c(log_ext, log_vals)
7070+ left <- 1
7171+ }
7272+ right <- right + n_ext
7373+7474+ if (NURS_stop(log_vals, log_eps_h)) break
7575+ }
7676+7777+ orbit_points[[sample(
7878+ length(log_vals),
7979+ 1,
8080+ prob = exp(log_vals - log_sum_exp(log_vals))
8181+ )]]
8282+}
8383+8484+NURS <- function(logpdf, theta_init, n, epsilon, h, M) {
8585+ d <- length(theta_init)
8686+ draws <- matrix(NA_real_, n, d)
8787+ draws[1, ] <- theta_init
8888+ for (i in 2:n) {
8989+ draws[i, ] <- NURS_step(logpdf, draws[i - 1, ], epsilon, h, M)
9090+ }
9191+ draws
9292+}
+42
README.md
···11+22+# R.NURS
33+44+Base R implementation of the No-Underrun Sampler.
55+66+## Installation
77+88+You can install the development version of R.NURS like so:
99+1010+``` r
1111+# install.packages("pak")
1212+pak::pak("VisruthSK/R.NURS")
1313+```
1414+1515+## Example
1616+1717+This is a basic example which shows you how to solve a common problem:
1818+1919+``` r
2020+library(R.NURS)
2121+library(ggplot2)
2222+2323+set.seed(0)
2424+logpdf_funnel <- function(theta) {
2525+ y <- theta[1]
2626+ dnorm(y, 0, 3, log = TRUE) + sum(dnorm(theta[-1], 0, exp(y / 2), log = TRUE))
2727+}
2828+samples <- NURS(
2929+ logpdf_funnel,
3030+ theta_init = rep(0, 15),
3131+ n = 5000,
3232+ epsilon = 0.1,
3333+ h = 0.5,
3434+ M = 5
3535+)
3636+3737+data.frame(y = samples[, 1], x1 = samples[, 2]) |>
3838+ ggplot(aes(x = y, y = x1)) +
3939+ geom_point(alpha = 0.3) +
4040+ theme_minimal()
4141+```
4242+