Skip to content

Info objective - #91

Open
kaibagley wants to merge 14 commits into
biometryhub:mainfrom
kaibagley:info-objective
Open

Info objective#91
kaibagley wants to merge 14 commits into
biometryhub:mainfrom
kaibagley:info-objective

Conversation

@kaibagley

Copy link
Copy Markdown
Contributor

Fisher information based objective function, with A and D optimality criterion, and optimises spatially using a user-supplied covariance structure. It is calculated from treatment contrasts, adjusted for nuisance fixed effects like block and intercept.

$$ I = X_1^{\intercal} L X_1 $$

And L removes nuisance effects under a spatial correlation structure from user-supplied $n \times n$ covariance $\Sigma$ and nuisance effects $X_2$

$$ L = \Sigma^{-1} - \Sigma^{-1} X_2 (X_2^{\intercal} \Sigma^{-1} X_2)^{-1} X_2^{\intercal}\Sigma^{-1} $$

With A-optimality $\mathrm{tr}(I^-)$ or contrast-space D-optimality (pseudo determinant since I has rank $\nu - 1$) $-\log \left| I \right|$.

So basically the user specifies the correlation structure they want to use at analysis, such as $AR1 \times AR1$, and the design is optimised to be efficient under that spatial structure. So it should be directly targeting the Cramér-Rao lower bound rather than the adjacency proxy currently used in speed.

@wvjgsuhp

wvjgsuhp commented May 4, 2026

Copy link
Copy Markdown
Contributor

Thank you for additional objective function.
Please have a look at _pkgdown.yml and see if new functions could be populated where appropriate.
Also, I'd mark helper functions with #' @keywords internal instead of #' @export if they're not expected for users.

@kaibagley

kaibagley commented Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, i updated the yaml so the new public functions are in the index. The low level helpers are already @noRd, so they shouldn't be exported, and I left some of the functions like compute_L_projection and others because I thought they might be useful for users who might want to inspect the information objective workflow. But I'm happy to hide them if you want!

Comment thread R/objectives.R
Comment on lines +79 to +121
objective_function_info <- function(
layout_df,
swap,
spatial_cols,
criterion = c("A", "D"),
L_matrix = NULL,
block_column = "block",
...
) {
criterion <- match.arg(criterion)

ci <- .compute_info(layout_df, swap, L_matrix, block_column)
info <- ci$info
v <- ci$v

## get positive eigenvals from info matrix
eig <- eigen(info, symmetric = TRUE, only.values = TRUE)$values
max_eig <- max(eig)
if (max_eig <= 0) {
score <- 1e10
pos_eig <- numeric(0)
} else {
pos_eig <- eig[eig > max_eig * 1e-10]
## if design is disconnected, penalise greatly
## otherwise A or D
if (length(pos_eig) < v - 1) {
score <- 1e10
} else {
score <- switch(criterion,
A = sum(1 / pos_eig),
D = -sum(log(pos_eig))
)
if (!is.finite(score)) score <- 1e10
}
}

return(list(
score = score,
info_matrix = info,
eigenvalues = sort(pos_eig, decreasing = TRUE),
criterion = criterion
))
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be able to take advantage of current_score_obj and swapped_items passed to objective function calls from the 2nd iteration onwards, so only partial calculation or object assigning might be required each iteration (?).
Please see example usage in objective_function_piepho.

Comment thread R/objectives.R
#' @return \eqn{(n \times n)} numeric matrix.
#'
#' @export
compute_L_projection <- function(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Order matters here and speed calls order under the hood.
It might be better that the users do not have to call this themselves by calling this function in the 1st objective function call and pass around via current_score_obj.

Comment thread R/objectives.R

## L = I - X2 (X2t X2)^{-1} X2t
X2tX2_inv <- diag(1 / colSums(X2), nrow = b)
diag(n) - X2 %*% X2tX2_inv %*% t(X2)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add an explicit return statement.
There are other places as well.

Comment thread R/objectives.R
Comment on lines +215 to +225
if (nrow(Sigma) != n) {
stop(
"Sigma matrix must have dimension equal ",
"to dimension of layout_df dataframe")
}
if (ncol(Sigma) != n) {
stop(
"Sigma matrix must have dimension equal ",
"to dimension of layout_df dataframe"
)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These can be collapsed into 1 if.

Comment thread R/objectives.R
Comment on lines +329 to +341
#' Calculate concurrence matrix
#'
#' @param layout_df Data frame representing the spatial layout of the
#' experiment.
#' @param treatment_column Column name containing the design's treatments in
#' \code{layout_df}.
#' @param block_column Column name containing the designs block in
#' \code{layout_df}.
#'
#' @return Symmetric \eqn{N N^\intercal}{N Nᵀ} matrix.
#'
#' @keywords internal
calc_concurrence_matrix <- function(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be exported and live in metrics.R.
Maybe add brief description and use calculate_ to be consistent.

Comment thread R/objectives.R
Comment on lines +352 to +366
#' Calculate canonical efficiency factors
#'
#' @param layout_df Data frame representing the spatial layout of the
#' experiment.
#' @param treatment_column Column name containing the design's treatments in
#' \code{layout_df}.
#' @param L_matrix Precomputed projection matrix from
#' \code{compute_L_projection}.
#' @param block_column Column name containing the designs block in
#' \code{layout_df}.
#'
#' @return A list with \code{efficiency_factors}, \code{E}, \code{replication}.
#'
#' @keywords internal
calculate_efficiency_factors <- function(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be exported and live in metrics.R as well with brief description.

Comment thread R/objectives.R

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name should be objective_{something}.R, like objective_fisher.
You got the idea.
Also, I'll check the math later (wish me luck).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd move metric functions like efficiency and matrices out of this file.
Can even do 1 file per function like a bunch of existing test-calculate_*s.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants