Skip to content
Closed
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
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@
## 2026-07-14 - Matrix Cross Product Optimization
**Learning:** In R, matrix multiplication of the form `t(X) %*% Y` explicitly allocates memory for the transposed matrix. Using the optimized base function `crossprod(X, Y)` avoids this allocation.
**Action:** Always replace `t(X) %*% Y` with `crossprod(X, Y)` for faster and more memory-efficient cross-product calculations.
## 2026-07-16 - Optimize ifelse overhead in R
**Learning:** In R, the `ifelse()` function evaluates both the true and false branches entirely before subsetting, which causes unnecessary calculations and memory allocations, especially for computationally expensive operations like `dnbinom()` or vector logs.
**Action:** Replace `ifelse(cond, true_val, false_val)` with preallocation (`res <- numeric(n)`) and vectorized subsetting (`if (any(cond)) res[cond] <- ...`) to skip evaluating the expensive branch on irrelevant elements.
36 changes: 28 additions & 8 deletions R/llcont.R
Original file line number Diff line number Diff line change
Expand Up @@ -147,36 +147,56 @@ llcont.hurdle <- function(x, ...) {
linkinv <- x$linkinv

## individual LL functions
## Bolt: replaced ifelse() with vectorized subsetting for performance to avoid unnecessary calculations on all elements
zeroPoisson <- function(parms) {
mu <- as.vector(exp(Z %*% parms + offsetz))
loglik0 <- -mu
Y0 * weights * loglik0 + ifelse(Y1, weights * log(1 - exp(loglik0)), 0)
res <- Y0 * weights * loglik0
if (any(Y1)) {
w <- if(length(weights) == 1) weights else weights[Y1]
res[Y1] <- res[Y1] + w * log(1 - exp(loglik0[Y1]))
}
res
}

countPoisson <- function(parms) {
mu <- Y1 * as.vector(exp(X %*% parms + offsetx))
loglik0 <- -mu
loglik1 <- Y1 * dpois(Y, lambda = mu, log = TRUE)
Y1 * weights * loglik1 - ifelse(Y1, weights * log(1 - exp(loglik0)), 0)
res <- Y * 0
if (any(Y1)) {
w <- if(length(weights) == 1) weights else weights[Y1]
loglik1 <- dpois(Y[Y1], lambda = mu[Y1], log = TRUE)
res[Y1] <- w * loglik1 - w * log(1 - exp(loglik0[Y1]))
}
res
}

zeroNegBin <- function(parms) {
mu <- as.vector(exp(Z %*% parms[1:kz] + offsetz))
theta <- exp(parms[kz + 1])
loglik0 <- suppressWarnings(dnbinom(0, size = theta,
mu = mu, log = TRUE))
Y0 * weights * loglik0 +
ifelse(Y1, weights * log(1 - exp(loglik0)), 0)
res <- Y0 * weights * loglik0
if (any(Y1)) {
w <- if(length(weights) == 1) weights else weights[Y1]
res[Y1] <- res[Y1] + w * log(1 - exp(loglik0[Y1]))
}
res
}

countNegBin <- function(parms) {
mu <- as.vector(exp(X %*% parms[1:kx] + offsetx))
theta <- exp(parms[kx + 1])
loglik0 <- suppressWarnings(dnbinom(0, size = theta,
mu = mu, log = TRUE))
loglik1 <- suppressWarnings(dnbinom(Y, size = theta,
mu = mu, log = TRUE))
ifelse(Y1, weights * loglik1 - weights * log(1 - exp(loglik0)), 0)
res <- Y * 0
if (any(Y1)) {
w <- if(length(weights) == 1) weights else weights[Y1]
loglik1 <- suppressWarnings(dnbinom(Y[Y1], size = theta,
mu = mu[Y1], log = TRUE))
res[Y1] <- w * loglik1 - w * log(1 - exp(loglik0[Y1]))
}
res
}

zeroGeom <- function(parms) zeroNegBin(c(parms, 0))
Expand Down
Loading