#pragma once #include "spectra_reml/types.hpp" #include #include #include namespace spectra::reml { struct SpectralGrm { std::vector eigenvalues; ColMajorMatrix eigenvectors; double minimum_eigenvalue = 0.0; double maximum_eigenvalue = 0.0; }; // GCTA stores the lower triangle, row by row, as IEEE-754 float32 values: // (0,0), (1,0), (1,1), (2,0), ... . The returned matrix is full symmetric, // double precision and column-major. [[nodiscard]] ColMajorMatrix read_gcta_grm_lower_triangle( const std::filesystem::path& grm_bin, std::size_t sample_count); // Reads and diagonalizes a GRM once. Eigenvalues are deliberately not // clipped: preserving the supplied GRM is required for numerical equivalence. // The REML evaluator rejects parameter trials for which sigma_g^2 lambda_i + // sigma_e^2 is not positive. [[nodiscard]] SpectralGrm read_and_diagonalize_gcta_grm( const std::filesystem::path& grm_bin, std::size_t sample_count); // Optional structural check for a GCTA .grm.id file. The Python driver owns // sample-ID alignment; this guard catches wrong dimensions at the CLI boundary. void validate_grm_id_count(const std::filesystem::path& grm_id, std::size_t expected_sample_count); } // namespace spectra::reml