Handle boundary and line-search fixed-effect inference

This commit is contained in:
2026-08-14 16:54:57 +08:00
parent 48ab77f9ab
commit 9033201948
10 changed files with 220 additions and 41 deletions

View File

@@ -195,6 +195,30 @@ void test_ai_reml_fit_improves_likelihood() {
kr.hypothesis_tests.front().valid &&
kr.hypothesis_tests.front().numerator_df == 2,
"interior KR joint test is invalid");
auto line_search_flagged = fitted;
line_search_flagged.status =
spectra::reml::FitStatus::line_search_failed;
line_search_flagged.error = "synthetic retained line-search iterate";
const auto flagged_satterthwaite =
spectra::reml::infer_fixed_effects_spectral(
fixture.y, fixture.x, fixture.lambda, line_search_flagged,
spectra::reml::FixedEffectTestMethod::satterthwaite, {joint});
require(flagged_satterthwaite.status ==
spectra::reml::FixedEffectInferenceStatus::line_search_conditional,
"line-search Satterthwaite inference was not retained and flagged");
require(flagged_satterthwaite.hypothesis_tests.size() == 1 &&
flagged_satterthwaite.hypothesis_tests.front().valid,
"line-search Satterthwaite hypothesis test is invalid");
const auto flagged_kr = spectra::reml::infer_fixed_effects_spectral(
fixture.y, fixture.x, fixture.lambda, line_search_flagged,
spectra::reml::FixedEffectTestMethod::kenward_roger, {joint});
require(flagged_kr.status ==
spectra::reml::FixedEffectInferenceStatus::line_search_conditional,
"line-search KR inference was not retained and flagged");
require(flagged_kr.hypothesis_tests.size() == 1 &&
flagged_kr.hypothesis_tests.front().valid,
"line-search KR hypothesis test is invalid");
}
void test_probability_distributions_against_r() {
@@ -372,8 +396,11 @@ void test_residual_only_kkt_boundary() {
y, x, lambda, fitted,
spectra::reml::FixedEffectTestMethod::satterthwaite);
require(satterthwaite.status ==
spectra::reml::FixedEffectInferenceStatus::boundary_conditional,
"Satterthwaite boundary inference did not report conditional status");
spectra::reml::FixedEffectInferenceStatus::boundary_ols,
"Satterthwaite request at the boundary did not fall back to OLS");
require(satterthwaite.method ==
spectra::reml::FixedEffectTestMethod::ordinary_least_squares,
"boundary inference did not report ordinary least squares");
require(satterthwaite.coefficient_tests.size() == 1,
"Satterthwaite coefficient test is missing");
const auto& satt = satterthwaite.coefficient_tests.front();
@@ -389,8 +416,11 @@ void test_residual_only_kkt_boundary() {
y, x, lambda, fitted,
spectra::reml::FixedEffectTestMethod::kenward_roger);
require(kr.status ==
spectra::reml::FixedEffectInferenceStatus::boundary_conditional,
"KR boundary inference did not report conditional status");
spectra::reml::FixedEffectInferenceStatus::boundary_ols,
"KR request at the boundary did not fall back to OLS");
require(kr.method ==
spectra::reml::FixedEffectTestMethod::ordinary_least_squares,
"boundary KR request did not report ordinary least squares");
require(kr.coefficient_tests.size() == 1,
"KR coefficient test is missing");
const auto& kr_test = kr.coefficient_tests.front();
@@ -399,8 +429,8 @@ void test_residual_only_kkt_boundary() {
"KR did not recover OLS residual df");
require_near(kr_test.standard_error, expected_se, 2e-11,
"KR did not recover OLS standard error");
require_near(kr_test.statistic, expected_t * expected_t, 2e-10,
"KR did not recover OLS F statistic");
require_near(kr_test.statistic, expected_t, 2e-10,
"boundary fallback did not recover OLS t statistic");
}
} // namespace

View File

@@ -86,7 +86,7 @@ class SpectraRemlCliTests(unittest.TestCase):
"0\ttrait_a\tconverged\t2\t0\t0\t0\t1\t1\t0.5\t-1\t4\t8\t1e-8"
"\tsatterthwaite\tok\t0\t{}\t0\tnan\tnan\tnan\t\t\n"
"1\ttrait_b\tconverged_boundary\t3\t1\t2\t3\t0\t1\t0\t-2\t3\t6\t1e-9"
"\tsatterthwaite\tboundary_conditional\t2\t{}\t1\t8\t2\t0.2\t\t\n".format(
"\tordinary-least-squares\tboundary_ols\t2\t{}\t1\t8\t2\t0.2\t\t\n".format(
first_contrast_offset, second_contrast_offset
),
encoding="utf-8",