Improve phenotype scaling and safeguarded line search

This commit is contained in:
2026-08-09 15:05:40 +08:00
parent da971a2ca7
commit 76dd8b1379
8 changed files with 226 additions and 20 deletions

View File

@@ -165,6 +165,49 @@ void test_ai_reml_fit_improves_likelihood() {
"reported sigma_g2 is inconsistent");
}
void test_internal_phenotype_scaling_restores_original_units() {
Fixture fixture;
constexpr double scale = 1.0e6;
std::vector<double> scaled_y = fixture.y;
for (double& value : scaled_y) {
value *= scale;
}
spectra::reml::RemlOptions options;
options.max_iterations = 150;
const auto original = spectra::reml::fit_ai_reml_spectral(
fixture.y, fixture.x, fixture.lambda, options);
const auto scaled = spectra::reml::fit_ai_reml_spectral(
scaled_y, fixture.x, fixture.lambda, options);
require(original.has_estimates() && scaled.has_estimates(),
"scale-invariance fixture returned no estimates");
require(original.status == scaled.status,
"phenotype scaling changed the fit status");
require_near(scaled.sigma_e2, original.sigma_e2 * scale * scale, 2e-10,
"sigma_e2 was not restored to phenotype units");
require_near(scaled.sigma_g2, original.sigma_g2 * scale * scale, 2e-10,
"sigma_g2 was not restored to phenotype units");
require_near(scaled.h2, original.h2, 2e-11,
"phenotype scaling changed h2");
for (std::size_t i = 0; i < original.beta.size(); ++i) {
require_near(scaled.beta[i], original.beta[i] * scale, 2e-10,
"beta was not restored to phenotype units");
}
for (std::size_t i = 0;
i < original.beta_covariance_packed_lower.size(); ++i) {
require_near(scaled.beta_covariance_packed_lower[i],
original.beta_covariance_packed_lower[i] * scale * scale,
3e-10,
"beta covariance was not restored to phenotype units");
}
const double expected_log_likelihood_shift =
-static_cast<double>(fixture.y.size() - fixture.x.cols()) *
std::log(scale);
require_near(scaled.log_likelihood,
original.log_likelihood + expected_log_likelihood_shift,
2e-11,
"REML likelihood was not restored to phenotype units");
}
void test_residual_only_kkt_boundary() {
constexpr std::size_t n = 14;
spectra::reml::ColMajorMatrix x(n, 1);
@@ -216,6 +259,7 @@ int main() {
test_score_matches_finite_difference();
test_signed_parameterization_and_row_permutation();
test_ai_reml_fit_improves_likelihood();
test_internal_phenotype_scaling_restores_original_units();
test_residual_only_kkt_boundary();
std::cout << "test_reml_synthetic: PASS\n";
return EXIT_SUCCESS;

View File

@@ -90,6 +90,8 @@ class SpectraRemlCliTests(unittest.TestCase):
"--extra-indices",
"--n-phenotype-rows",
"--n-extra-covariate-rows",
"--line-search-expansion",
"--zoom-safeguard",
):
self.assertEqual(command.count(option), 1)