1313setup_phen_data_small <- function (n = 3 , seed = 42 ) {
1414 set.seed(seed )
1515 # Construct PD matrices via Cholesky
16- L <- matrix (c(2 , 0 , 0 ,
17- 1 , 2 , 0 ,
18- 0.5 , 1 , 1.5 ), nrow = n , byrow = TRUE )
16+ L <- matrix (c(
17+ 2 , 0 , 0 ,
18+ 1 , 2 , 0 ,
19+ 0.5 , 1 , 1.5
20+ ), nrow = n , byrow = TRUE )
1921 P <- t(L ) %*% L
2022 colnames(P ) <- rownames(P ) <- paste0(" t" , seq_len(n ))
2123
22- L2 <- matrix (c(1 , 0 , 0 ,
23- 0.6 , 1 , 0 ,
24- 0.2 , 0.4 , 0.8 ), nrow = n , byrow = TRUE )
24+ L2 <- matrix (c(
25+ 1 , 0 , 0 ,
26+ 0.6 , 1 , 0 ,
27+ 0.2 , 0.4 , 0.8
28+ ), nrow = n , byrow = TRUE )
2529 G <- t(L2 ) %*% L2
2630 colnames(G ) <- rownames(G ) <- paste0(" t" , seq_len(n ))
2731
@@ -34,9 +38,11 @@ setup_phen_data_real <- function() {
3438 data(" seldata" , package = " selection.index" , envir = environment())
3539 pmat <- phen_varcov(seldata [, 3 : 9 ], seldata [, 2 ], seldata [, 1 ])
3640 gmat <- gen_varcov(seldata [, 3 : 9 ], seldata [, 2 ], seldata [, 1 ])
37- w <- as.numeric(c(10 , 8 , 6 , 4 , 2 , 1 , 1 )) # 7-trait weights
38- list (pmat = pmat , gmat = gmat , w = w , n = 7 ,
39- trait_names = colnames(pmat ))
41+ w <- as.numeric(c(10 , 8 , 6 , 4 , 2 , 1 , 1 )) # 7-trait weights
42+ list (
43+ pmat = pmat , gmat = gmat , w = w , n = 7 ,
44+ trait_names = colnames(pmat )
45+ )
4046}
4147
4248# ==============================================================================
@@ -60,7 +66,7 @@ test_that(".index_metrics NA branches fire when G = zero matrix", {
6066
6167test_that(" .index_metrics PRE_constant uses GAY when provided" , {
6268 d <- setup_phen_data_small()
63- res_no_GAY <- smith_hazel(d $ P , d $ G , d $ w )
69+ res_no_GAY <- smith_hazel(d $ P , d $ G , d $ w )
6470 res_with_GAY <- smith_hazel(d $ P , d $ G , d $ w , GAY = 10 )
6571 # Without GAY: PRE = GA * 100; with GAY: PRE = GA * 100/10 = GA * 10
6672 expect_equal(res_with_GAY $ PRE , res_no_GAY $ PRE / 10 , tolerance = 1e-6 )
@@ -87,8 +93,10 @@ test_that("smith_hazel returns correct structure with small synthetic data", {
8793
8894 expect_s3_class(res , " smith_hazel" )
8995 expect_s3_class(res , " selection_index" )
90- expect_named(res , c(" b" , " w" , " Delta_G" , " sigma_I" , " GA" , " PRE" ,
91- " hI2" , " rHI" , " selection_intensity" , " summary" ))
96+ expect_named(res , c(
97+ " b" , " w" , " Delta_G" , " sigma_I" , " GA" , " PRE" ,
98+ " hI2" , " rHI" , " selection_intensity" , " summary"
99+ ))
92100 expect_length(res $ b , d $ n )
93101 expect_length(res $ w , d $ n )
94102 expect_length(res $ Delta_G , d $ n )
@@ -124,10 +132,10 @@ test_that("smith_hazel handles matrix wmat with wcol selection", {
124132test_that(" smith_hazel respects custom selection_intensity" , {
125133 d <- setup_phen_data_small()
126134 res_default <- smith_hazel(d $ P , d $ G , d $ w )
127- res_custom <- smith_hazel(d $ P , d $ G , d $ w , selection_intensity = 1.755 )
135+ res_custom <- smith_hazel(d $ P , d $ G , d $ w , selection_intensity = 1.755 )
128136
129137 expect_equal(res_custom $ selection_intensity , 1.755 )
130- expect_equal(res_custom $ b , res_default $ b ) # b doesn't change with intensity
138+ expect_equal(res_custom $ b , res_default $ b ) # b doesn't change with intensity
131139 expect_equal(res_custom $ sigma_I , res_default $ sigma_I )
132140 expect_equal(res_custom $ GA / res_default $ GA , 1.755 / 2.063 , tolerance = 1e-6 )
133141})
@@ -163,7 +171,7 @@ test_that("smith_hazel stops when pmat and gmat have different dimensions", {
163171test_that(" smith_hazel stops when wmat has wrong number of rows" , {
164172 d <- setup_phen_data_small()
165173 expect_error(
166- smith_hazel(d $ P , d $ G , wmat = c(1 , 2 )), # 2 instead of 3
174+ smith_hazel(d $ P , d $ G , wmat = c(1 , 2 )), # 2 instead of 3
167175 " Number of rows in wmat"
168176 )
169177})
@@ -204,9 +212,11 @@ test_that("base_index returns correct structure with small synthetic data", {
204212
205213 expect_s3_class(res , " base_index" )
206214 expect_s3_class(res , " selection_index" )
207- expect_named(res , c(" b" , " w" , " Delta_G" , " sigma_I" , " GA" , " PRE" ,
208- " hI2" , " rHI" , " selection_intensity" , " summary" ,
209- " lpsi_comparison" ))
215+ expect_named(res , c(
216+ " b" , " w" , " Delta_G" , " sigma_I" , " GA" , " PRE" ,
217+ " hI2" , " rHI" , " selection_intensity" , " summary" ,
218+ " lpsi_comparison"
219+ ))
210220 # In Base Index b = w
211221 expect_equal(res $ b , round(d $ w , 4 ))
212222 expect_true(res $ sigma_I > 0 )
@@ -323,11 +333,11 @@ test_that("lpsi returns a data frame with expected columns", {
323333 res <- lpsi(ncomb = 3 , pmat = d $ pmat , gmat = d $ gmat , wmat = wmat , wcol = 1 )
324334
325335 expect_true(is.data.frame(res ))
326- expect_true(" ID" %in% names(res ))
327- expect_true(" GA" %in% names(res ))
328- expect_true(" PRE" %in% names(res ))
329- expect_true(" rHI" %in% names(res ))
330- expect_true(" hI2" %in% names(res ))
336+ expect_true(" ID" %in% names(res ))
337+ expect_true(" GA" %in% names(res ))
338+ expect_true(" PRE" %in% names(res ))
339+ expect_true(" rHI" %in% names(res ))
340+ expect_true(" hI2" %in% names(res ))
331341 expect_true(" Rank" %in% names(res ))
332342 expect_equal(nrow(res ), choose(d $ n , 3 ))
333343})
@@ -336,7 +346,7 @@ test_that("lpsi with GAY produces correct PRE_constant", {
336346 d <- setup_phen_data_real()
337347 data(" weight" , package = " selection.index" , envir = environment())
338348 wmat <- weight_mat(weight )
339- res_no_GAY <- lpsi(3 , d $ pmat , d $ gmat , wmat , wcol = 1 )
349+ res_no_GAY <- lpsi(3 , d $ pmat , d $ gmat , wmat , wcol = 1 )
340350 res_with_GAY <- lpsi(3 , d $ pmat , d $ gmat , wmat , wcol = 1 , GAY = 5 )
341351
342352 # With GAY: PRE = GA * 100/5 = GA * 20; without GAY: PRE = GA * 100
@@ -362,8 +372,10 @@ test_that("lpsi excluding_trait as character vector filters correctly", {
362372 data(" weight" , package = " selection.index" , envir = environment())
363373 wmat <- weight_mat(weight )
364374 # Exclude by trait name
365- res <- lpsi(3 , d $ pmat , d $ gmat , wmat , wcol = 1 ,
366- excluding_trait = c(" sypp" , " dtf" ))
375+ res <- lpsi(3 , d $ pmat , d $ gmat , wmat ,
376+ wcol = 1 ,
377+ excluding_trait = c(" sypp" , " dtf" )
378+ )
367379
368380 expect_true(is.data.frame(res ))
369381 expect_lt(nrow(res ), choose(d $ n , 3 ))
@@ -417,7 +429,8 @@ test_that("lpsi excluding_trait data.frame warns when no column names match", {
417429
418430test_that(" lpsi excluding_trait data.frame stops when pmat has no colnames" , {
419431 d <- setup_phen_data_small()
420- P_nonames <- d $ P ; colnames(P_nonames ) <- NULL
432+ P_nonames <- d $ P
433+ colnames(P_nonames ) <- NULL
421434 wmat <- matrix (d $ w , ncol = 1 )
422435 excl_df <- data.frame (t1 = 1 )
423436 expect_error(
@@ -477,7 +490,7 @@ test_that("lpsi excluding_trait matrix without colnames triggers stop (via data.
477490 # Verify numeric matrix IS treated as numeric index (no error):
478491 d <- setup_phen_data_small()
479492 wmat <- matrix (d $ w , ncol = 1 )
480- excl_mat <- matrix (1L , nrow = 1 , ncol = 1 ) # numeric matrix: treated as index c(1)
493+ excl_mat <- matrix (1L , nrow = 1 , ncol = 1 ) # numeric matrix: treated as index c(1)
481494 res <- lpsi(2 , d $ P , d $ G , wmat , excluding_trait = excl_mat )
482495 # Trait 1 excluded from 2-trait combos: only (2,3) remains
483496 expect_equal(nrow(res ), 1L )
@@ -619,15 +632,15 @@ test_that("print.base_index shows efficiency_ratio < 0.9 message", {
619632 w = c(1 , 2 , 3 ),
620633 Delta_G = c(0.5 , 1.0 , 1.5 ),
621634 sigma_I = 2.0 ,
622- GA = 0.85 ,
635+ GA = 0.85 ,
623636 PRE = 85 ,
624637 hI2 = 0.7 ,
625638 rHI = 0.84 ,
626639 selection_intensity = 2.063 ,
627640 summary = data.frame (),
628641 lpsi_comparison = list (
629642 b_lpsi = c(0.5 , 1.5 , 2.5 ),
630- GA_lpsi = 1.0 ,
643+ GA_lpsi = 1.0 ,
631644 PRE_lpsi = 100 ,
632645 hI2_lpsi = 0.8 ,
633646 rHI_lpsi = 0.89 ,
@@ -678,17 +691,17 @@ test_that("summary.base_index shows low-correlation warning when cor < 0.8", {
678691 list (
679692 b = c(1 , 2 , 3 ),
680693 w = setNames(c(1 , 2 , 3 ), c(" t1" , " t2" , " t3" )),
681- Delta_G = setNames(c(1 , - 1 , 1 ), c(" t1" , " t2" , " t3" )),
694+ Delta_G = setNames(c(1 , - 1 , 1 ), c(" t1" , " t2" , " t3" )),
682695 sigma_I = 1 ,
683- GA = 0.5 ,
696+ GA = 0.5 ,
684697 PRE = 50 ,
685698 hI2 = 0.6 ,
686699 rHI = 0.77 ,
687700 selection_intensity = 2.063 ,
688701 summary = data.frame (),
689702 lpsi_comparison = list (
690703 b_lpsi = c(2 , 2 , 2 ),
691- GA_lpsi = 0.6 ,
704+ GA_lpsi = 0.6 ,
692705 PRE_lpsi = 60 ,
693706 hI2_lpsi = 0.7 ,
694707 rHI_lpsi = 0.84 ,
@@ -716,8 +729,8 @@ test_that("summary.base_index returns invisible(object)", {
716729
717730test_that(" smith_hazel GA >= base_index GA (LPSI is optimal)" , {
718731 d <- setup_phen_data_real()
719- res_sh <- smith_hazel(d $ pmat , d $ gmat , d $ w )
720- res_bi <- base_index(d $ pmat , d $ gmat , d $ w , compare_to_lpsi = FALSE )
732+ res_sh <- smith_hazel(d $ pmat , d $ gmat , d $ w )
733+ res_bi <- base_index(d $ pmat , d $ gmat , d $ w , compare_to_lpsi = FALSE )
721734
722735 # LPSI maximises GA → smith_hazel GA should be >= base_index GA
723736 expect_gte(res_sh $ GA , res_bi $ GA - 1e-8 )
@@ -738,9 +751,31 @@ test_that("lpsi(ncomb=n) top PRE matches smith_hazel PRE", {
738751 data(" weight" , package = " selection.index" , envir = environment())
739752 wmat <- weight_mat(weight )
740753 res_lpsi <- lpsi(d $ n , d $ pmat , d $ gmat , wmat , wcol = 1 )
741- res_sh <- smith_hazel(d $ pmat , d $ gmat , wmat [, 1 ], GAY = NULL )
754+ res_sh <- smith_hazel(d $ pmat , d $ gmat , wmat [, 1 ], GAY = NULL )
742755
743756 top_PRE <- res_lpsi $ PRE [res_lpsi $ Rank == 1 ]
744757 # PRE from lpsi uses PRE_constant = 100; smith_hazel also uses 100 when GAY is NULL
745758 expect_equal(top_PRE , round(res_sh $ PRE , 4 ), tolerance = 0.01 )
746759})
760+
761+ # ==============================================================================
762+ # NEW COVERAGE TESTS — targeting previously uncovered lines
763+ # ==============================================================================
764+
765+ test_that(" smith_hazel stops when b coefficients are not finite (line 222)" , {
766+ d <- setup_phen_data_small()
767+
768+ # Mock cpp_symmetric_solve to return NAs to simulate poorly conditioned matrices
769+ testthat :: with_mocked_bindings(
770+ cpp_symmetric_solve = function (A , B ) {
771+ rep(NA_real_ , length(d $ w ))
772+ },
773+ .package = " selection.index" ,
774+ code = {
775+ expect_error(
776+ smith_hazel(d $ P , d $ G , d $ w ),
777+ " Failed to compute index coefficients. Check matrix conditioning."
778+ )
779+ }
780+ )
781+ })
0 commit comments