From 7f74499ceb20adc964424e63921e870f6a576854 Mon Sep 17 00:00:00 2001 From: James Yang Date: Wed, 8 Nov 2023 11:42:04 -0800 Subject: [PATCH] Finish SNP Unphased --- adelie/__init__.py | 1 + adelie/data.py | 123 ++++++- adelie/io.py | 154 +++++++++ adelie/matrix.py | 90 +++-- adelie/solver.py | 17 +- adelie/src/adelie_core.cpp | 3 + adelie/src/decl.hpp | 3 +- adelie/src/include/adelie_core/bcd.hpp | 26 +- .../adelie_core/io/io_snp_unphased.hpp | 263 +++++++++++++++ .../matrix/matrix_naive_snp_unphased.hpp | 259 ++++++++++++-- .../src/include/adelie_core/matrix/utils.hpp | 25 ++ .../adelie_core/solver/solve_basil_naive.hpp | 2 - adelie/src/io.cpp | 33 ++ adelie/src/matrix.cpp | 22 ++ adelie/state.py | 7 +- docs/sphinx/api_reference.rst | 21 +- docs/sphinx/notebooks/edpp_study.ipynb | 319 ------------------ docs/sphinx/notebooks/snp_io.ipynb | 176 ++++++++++ docs/sphinx/user_guide.rst | 1 - docs/tex/pivot_rule/main.pdf | Bin 1711390 -> 1711365 bytes tests/test_io.py | 60 ++++ tests/test_matrix.py | 280 ++++++++++----- tests/test_solver.py | 10 +- tests/test_state.py | 6 +- 24 files changed, 1379 insertions(+), 522 deletions(-) create mode 100644 adelie/io.py create mode 100644 adelie/src/io.cpp delete mode 100644 docs/sphinx/notebooks/edpp_study.ipynb create mode 100644 docs/sphinx/notebooks/snp_io.ipynb create mode 100644 tests/test_io.py diff --git a/adelie/__init__.py b/adelie/__init__.py index 6f040455..f417b9e4 100644 --- a/adelie/__init__.py +++ b/adelie/__init__.py @@ -2,6 +2,7 @@ from . import bcd from . import data from . import diagnostic +from . import io from . import matrix from . import optimization from . import research diff --git a/adelie/data.py b/adelie/data.py index 2fb7193f..c26c00ca 100644 --- a/adelie/data.py +++ b/adelie/data.py @@ -1,7 +1,7 @@ import numpy as np -def create_test_data_basil( +def create_dense( n: int, p: int, G: int, @@ -13,7 +13,7 @@ def create_test_data_basil( snr: float = 1, seed: int =0, ): - """Creates a test dataset for BASIL method. + """Creates a dense dataset. The groups and group sizes are generated randomly such that ``G`` groups are created and the sum of the group sizes is ``p``. @@ -71,6 +71,7 @@ def create_test_data_basil( assert rho >= -1 / (p-1) assert (0 <= sparsity) & (sparsity <= 1) assert (0 <= zero_penalty) & (zero_penalty <= 1) + assert snr > 0 assert seed >= 0 np.random.seed(seed) @@ -99,13 +100,125 @@ def create_test_data_basil( X = np.asfortranarray(X) beta = np.random.normal(0, 1, p) - beta[np.random.choice(p, int(sparsity * p), replace=False)] = 0 + beta_zero_indices = np.random.choice(p, int(sparsity * p), replace=False) + beta_nnz_indices = np.array(list(set(np.arange(p)) - set(beta_zero_indices))) + X_sub = X[:, beta_nnz_indices] + beta_sub = beta[beta_nnz_indices] noise_scale = np.sqrt( - (rho * np.sum(beta) ** 2 + (1-rho) * np.sum(beta ** 2)) + (rho * np.sum(beta_sub) ** 2 + (1-rho) * np.sum(beta_sub ** 2)) / snr ) - y = X @ beta + noise_scale * np.random.normal(0, 1, n) + y = X_sub @ beta_sub + noise_scale * np.random.normal(0, 1, n) + + return { + "X": X, + "y": y, + "groups": groups, + "group_sizes": group_sizes, + "penalty": penalty, + } + + +def create_snp_unphased( + n: int, + p: int, + *, + sparsity: float =0.95, + one_ratio: float =0.25, + two_ratio: float =0.05, + zero_penalty: float =0, + snr: float =1, + seed: int =0, +): + """Creates a SNP Unphased dataset. + + This dataset is only used for lasso, so ``groups`` is simply each individual feature + and ``group_sizes`` is a vector of ones. + The data matrix ``X`` has sparsity ratio ``1 - one_ratio - two_ratio`` + where ``one_ratio`` of the entries are randomly set to ``1`` + and ``two_ratio`` are randomly set to ``2``. + The response ``y`` is generated from a linear model :math:`y = X\\beta + \\epsilon` + with :math:`\\epsilon \\sim N(0, \\sigma^2 I_n)` + and :math:`\\beta` such that ``sparsity`` proportion of the entries are set to :math:`0`. + We compute :math:`\\sigma^2` such that the signal-to-noise ratio is given by ``snr``. + The penalty factors are by default set to ``np.sqrt(group_sizes)``, + however if ``zero_penalty > 0``, a random set of penalties will be set to zero, + in which case, ``penalty`` is rescaled such that the :math:`\\ell_2` norm squared is ``p``. + + Parameters + ---------- + n : int + Number of data points. + p : int + Number of features. + sparsity : float, optional + Proportion of :math:`\\beta` entries to be zeroed out. + Default is ``0.95``. + one_ratio : float, optional + Proportion of the entries of ``X`` that is set to ``1``. + Default is ``0.25``. + two_ratio : float, optional + Proportion of the entries of ``X`` that is set to ``2``. + Default is ``0.05``. + zero_penalty : float, optional + Proportion of ``penalty`` entries to be zeroed out. + Default is ``0``. + snr : float, optional + Signal-to-noise ratio. + Default is ``1``. + seed : int, optional + Random seed. + Default is ``0``. + + Returns + ------- + data : dict + A dictionary containing the generated data: + + - ``"X"``: feature matrix. + - ``"y"``: response vector. + - ``"groups"``: mapping of group index to the starting column index of ``X``. + - ``"group_sizes"``: mapping of group index to the group size. + - ``"penalty"``: penalty factor for each group index. + """ + assert n >= 1 + assert p >= 1 + assert sparsity >= 0 and sparsity <= 1 + assert one_ratio >= 0 and one_ratio <= 1 + assert two_ratio >= 0 and two_ratio <= 1 + assert zero_penalty >= 0 and zero_penalty <= 1 + assert snr > 0 + assert seed >= 0 + + np.random.seed(seed) + + nnz_ratio = one_ratio + two_ratio + one_ratio = one_ratio / nnz_ratio + two_ratio = two_ratio / nnz_ratio + nnz = int(nnz_ratio * n * p) + nnz_indices = np.random.choice(n * p, nnz, replace=False) + one_indices = np.random.choice(nnz_indices, int(one_ratio * nnz), replace=False) + two_indices = np.array(list(set(nnz_indices) - set(one_indices))) + X = np.zeros((n, p), dtype=np.int8) + X.ravel()[one_indices] = 1 + X.ravel()[two_indices] = 2 + + groups = np.arange(p) + group_sizes = np.ones(p) + + penalty = np.sqrt(group_sizes) + penalty[np.random.choice(p, int(zero_penalty * p), replace=False)] = 0 + penalty /= np.linalg.norm(penalty) / np.sqrt(p) + + beta = np.random.normal(0, 1, p) + beta_nnz_indices = np.random.choice(p, int((1-sparsity) * p), replace=False) + X_sub = X[:, beta_nnz_indices] + beta_sub = beta[beta_nnz_indices] + + signal_var = np.dot(beta_sub, np.dot(np.cov(X_sub.T), beta_sub)) + noise_scale = np.sqrt(signal_var / snr) + y = X_sub @ beta_sub + noise_scale * np.random.normal(0, 1, n) return { "X": X, diff --git a/adelie/io.py b/adelie/io.py new file mode 100644 index 00000000..827e931a --- /dev/null +++ b/adelie/io.py @@ -0,0 +1,154 @@ +import numpy as np +from .adelie_core import io as core_io + + +class snp_unphased: + """IO handler for SNP Unphased data. + + Parameters + ---------- + filename : str + File name to either read from or write to related to the SNP data. + """ + def __init__( + self, + filename, + ): + self._core = core_io.IOSNPUnphased(filename) + + def endian(self): + """Gets the endianness used in the file. + + Returns + ------- + endian : str + ``"big-endian"`` if big-endian and ``"little-endian"`` if little-endian. + """ + return ( + "big-endian" + if self._core.endian() else + "little-endian" + ) + + def rows(self): + """Gets the number of rows of the matrix. + + Returns + ------- + rows : int + Number of rows. + """ + return self._core.rows() + + def cols(self): + """Gets the number of columns of the matrix. + + Returns + ------- + cols : int + Number of columns. + """ + return self._core.cols() + + def outer(self): + """Gets the outer indexing vector. + + Returns + ------- + outer : (p+1,) np.ndarray + Outer indexing vector. + """ + return self._core.outer() + + def nnz(self, j: int): + """Gets the number of non-zero entries at a column. + + Parameters + ---------- + j : int + Column index. + + Returns + ------- + nnz : int + Number of non-zero entries column ``j``. + """ + return self._core.nnz(j) + + def inner(self, j: int): + """Gets the inner indexing vector at a column. + + Parameters + ---------- + j : int + Column index. + + Returns + ------- + inner : np.ndarray + Inner indexing vector at column ``j``. + """ + return self._core.inner(j) + + def value(self, j: int): + """Gets the value vector at a column. + + Parameters + ---------- + j : int + Column index. + + Returns + ------- + v : np.ndarray + Value vector at column ``j``. + """ + return self._core.value(j) + + def to_dense(self, n_threads: int =1): + """Creates a dense matrix. + + Parameters + ---------- + n_threads : int, optional + Number of threads. + Default is ``1``. + + Returns + ------- + dense : (n, p) np.ndarray + Dense matrix. + """ + return self._core.to_dense(n_threads) + + def read(self): + """Read and load the matrix from file. + + Returns + ------- + total_bytes : int + Number of bytes read. + """ + return self._core.read() + + def write( + self, + calldata: np.ndarray, + n_threads: int =1, + ): + """Write dense array to the file in special format. + + Parameters + ---------- + calldata : (n, p) np.ndarray + SNP unphased calldata in dense format. + n_threads : int, optional + Number of threads. + Default is ``1``. + + Returns + ------- + total_bytes : int + Number of bytes written. + """ + return self._core.write(calldata, n_threads) diff --git a/adelie/matrix.py b/adelie/matrix.py index 9e69be30..178abea9 100644 --- a/adelie/matrix.py +++ b/adelie/matrix.py @@ -8,17 +8,24 @@ import numpy as np -def naive_dense( +def dense( mat: np.ndarray, *, + method: str, n_threads: int =1, ): - """Creates a viewer of a dense matrix for naive method. + """Creates a viewer of a dense matrix. Parameters ---------- mat : np.ndarray The matrix to view. + method : str + Method type. It must be one of the following: + + - ``"naive"``: naive method. + - ``"cov"``: covariance method. + n_threads : int, optional Number of threads. Default is ``1``. @@ -31,7 +38,7 @@ def naive_dense( if n_threads < 1: raise ValueError("Number of threads must be >= 1.") - dispatcher = { + naive_dispatcher = { np.dtype("float64"): { "C": core.matrix.MatrixNaiveDense64C, "F": core.matrix.MatrixNaiveDense64F, @@ -41,15 +48,32 @@ def naive_dense( "F": core.matrix.MatrixNaiveDense32F, }, } + + cov_dispatcher = { + np.dtype("float64"): { + "C": core.matrix.MatrixCovDense64C, + "F": core.matrix.MatrixCovDense64F, + }, + np.dtype("float32"): { + "C": core.matrix.MatrixCovDense32C, + "F": core.matrix.MatrixCovDense32F, + }, + } + + dispatcher = { + "naive" : naive_dispatcher, + "cov" : cov_dispatcher, + } + dtype = mat.dtype order = ( "C" if mat.flags.c_contiguous else "F" ) - core_base = dispatcher[dtype][order] + core_base = dispatcher[method][dtype][order] - class _naive_dense(core_base): + class _dense(core_base): def __init__( self, mat: np.ndarray, @@ -58,23 +82,30 @@ def __init__( self.mat = mat core_base.__init__(self, self.mat, n_threads) - return _naive_dense(mat, n_threads) + return _dense(mat, n_threads) -def cov_dense( - mat: np.ndarray, +def snp_unphased( + filenames: list, *, n_threads: int =1, + dtype: np.float32 | np.float64 =np.float64, ): - """Creates a viewer of a dense matrix for covariance method. + """Creates a SNP unphased matrix. + + .. note:: + This matrix only works for naive method! Parameters ---------- - mat : np.ndarray - The matrix to view. + filenames : list + List of file names that contain column-block slices of the matrix. n_threads : int, optional Number of threads. Default is ``1``. + dtype : Union[np.float32, np.float64], optional + Underlying value type. + Default is ``np.float64``. Returns ------- @@ -85,34 +116,20 @@ def cov_dense( raise ValueError("Number of threads must be >= 1.") dispatcher = { - np.dtype("float64"): { - "C": core.matrix.MatrixCovDense64C, - "F": core.matrix.MatrixCovDense64F, - }, - np.dtype("float32"): { - "C": core.matrix.MatrixCovDense32C, - "F": core.matrix.MatrixCovDense32F, - }, + np.float64: core.matrix.MatrixNaiveSNPUnphased64, + np.float32: core.matrix.MatrixNaiveSNPUnphased32, } + core_base = dispatcher[dtype] - dtype = mat.dtype - order = ( - "C" - if mat.flags.c_contiguous else - "F" - ) - core_base = dispatcher[dtype][order] - - class _cov_dense(core_base): + class _snp_unphased(core_base): def __init__( self, - mat: np.ndarray, - n_threads: int =1, + filenames: list, + n_threads: int, ): - self.mat = mat - core_base.__init__(self, self.mat, n_threads) + core_base.__init__(self, filenames, n_threads) - return _cov_dense(mat, n_threads) + return _snp_unphased(filenames, n_threads) def cov_lazy( @@ -120,11 +137,14 @@ def cov_lazy( *, n_threads: int =1, ): - """Creates a viewer of a lazy matrix for covariance method. + """Creates a viewer of a lazy covariance matrix. + .. note:: + This matrix only works for covariance method! + Parameters ---------- - mat : np.ndarray + mat : (n, p) np.ndarray The data matrix from which to lazily compute the covariance. n_threads : int, optional Number of threads. diff --git a/adelie/solver.py b/adelie/solver.py index 3d870a30..0499aa33 100644 --- a/adelie/solver.py +++ b/adelie/solver.py @@ -76,10 +76,7 @@ def objective( ) -def solve_pin( - state, - logger=logger.logger, -): +def solve_pin(state): """Solves the pinned group elastic net problem. The pinned group elastic net problem is given by @@ -119,7 +116,7 @@ def solve_pin( # raise any errors if out["error"] != "": - logger.warning(RuntimeError(out["error"])) + logger.logger.warning(RuntimeError(out["error"])) # return a subsetted Python result object core_state = out["state"] @@ -128,10 +125,7 @@ def solve_pin( return state -def solve_basil( - state, - logger=logger.logger, -): +def solve_basil(state): """Solves the group elastic net problem using BASIL. Parameters @@ -162,7 +156,7 @@ def solve_basil( # raise any errors if out["error"] != "": - logger.error(RuntimeError(out["error"])) + logger.logger.warning(RuntimeError(out["error"])) # return a subsetted Python result object core_state = out["state"] @@ -311,8 +305,7 @@ def grpnet( The type is the same as that of ``state``. """ if isinstance(X, np.ndarray): - X_raw = X - X = ad.matrix.naive_dense(X_raw, n_threads=n_threads) + X = ad.matrix.dense(X, method="naive", n_threads=n_threads) assert ( isinstance(X, matrix.MatrixNaiveBase64) or diff --git a/adelie/src/adelie_core.cpp b/adelie/src/adelie_core.cpp index 0eaf0726..68790fd3 100644 --- a/adelie/src/adelie_core.cpp +++ b/adelie/src/adelie_core.cpp @@ -10,6 +10,9 @@ PYBIND11_MODULE(adelie_core, m) { auto m_bcd = m.def_submodule("bcd", "BCD submodule."); register_bcd(m_bcd); + + auto m_io = m.def_submodule("io", "IO submodule."); + register_io(m_io); auto m_matrix = m.def_submodule("matrix", "Matrix submodule."); register_matrix(m_matrix); diff --git a/adelie/src/decl.hpp b/adelie/src/decl.hpp index 46a64573..9df8076f 100644 --- a/adelie/src/decl.hpp +++ b/adelie/src/decl.hpp @@ -14,4 +14,5 @@ void register_bcd(py::module_&); void register_matrix(py::module_&); void register_optimization(py::module_&); void register_state(py::module_&); -void register_solver(py::module_&); \ No newline at end of file +void register_solver(py::module_&); +void register_io(py::module_&); \ No newline at end of file diff --git a/adelie/src/include/adelie_core/bcd.hpp b/adelie/src/include/adelie_core/bcd.hpp index 6075e844..35eb0e7a 100644 --- a/adelie/src/include/adelie_core/bcd.hpp +++ b/adelie/src/include/adelie_core/bcd.hpp @@ -201,9 +201,9 @@ auto root_lower_bound( * @param vbuffer1 vector containing L + l2. * @param v any vector of same length as vbuffer1. * @param zero_tol if a float is <= zero_tol, it is considered to be 0. - * @return (h_max, vbuffer1_min_nzn) + * @return (h_max, vbuffer1_min_nnz) * h_max: the upper bound - * vbuffer1_min_nzn: smallest value in vbuffer1 among non-zero values based on zero_tol. + * vbuffer1_min_nnz: smallest value in vbuffer1 among non-zero values based on zero_tol. */ template inline @@ -217,7 +217,7 @@ auto root_upper_bound( const value_t vbuffer1_min = vbuffer1.minCoeff(); - value_t vbuffer1_min_nzn = std::numeric_limits::infinity(); + value_t vbuffer1_min_nnz = std::numeric_limits::infinity(); value_t h_max = 0; // If L+l2 have entries <= threshold, @@ -230,15 +230,15 @@ auto root_upper_bound( const bool is_nonzero = vbuffer1[i] > zero_tol; const auto vi2 = v[i] * v[i]; h_max += is_nonzero ? vi2 / (vbuffer1[i] * vbuffer1[i]) : 0; - vbuffer1_min_nzn = is_nonzero ? std::min(vbuffer1_min_nzn, vbuffer1[i]) : vbuffer1_min_nzn; + vbuffer1_min_nnz = is_nonzero ? std::min(vbuffer1_min_nnz, vbuffer1[i]) : vbuffer1_min_nnz; } h_max = std::sqrt(h_max); } else { - vbuffer1_min_nzn = vbuffer1_min; + vbuffer1_min_nnz = vbuffer1_min; h_max = (v / vbuffer1).matrix().norm(); } - return std::make_pair(h_max, vbuffer1_min_nzn); + return std::make_pair(h_max, vbuffer1_min_nnz); } template @@ -453,7 +453,7 @@ void newton_abs_solver( const value_t h_min = root_lower_bound(vbuffer1, v, l1); const auto h_max_out = root_upper_bound(vbuffer1, v, l1); const value_t h_max = std::get<0>(h_max_out); - const value_t vbuffer1_min_nzn = std::get<1>(h_max_out); + const value_t vbuffer1_min_nnz = std::get<1>(h_max_out); value_t h; @@ -471,7 +471,7 @@ void newton_abs_solver( const auto ada_bisect = [&]() { // enforce some movement towards h_min for safety. - w = std::max(l1 / (vbuffer1_min_nzn * h_cand + l1), 0.05); + w = std::max(l1 / (vbuffer1_min_nnz * h_cand + l1), 0.05); h_cand = w * h_min + (1-w) * h_cand; fh = root_function(h_cand, vbuffer1, v, l1); }; @@ -567,7 +567,7 @@ void newton_abs_debug_solver( // compute h_max const value_t vbuffer1_min = vbuffer1.minCoeff(); - value_t vbuffer1_min_nzn = vbuffer1_min; + value_t vbuffer1_min_nnz = vbuffer1_min; // If L+l2 have entries <= threshold, // find h_max with more numerically-stable routine. @@ -575,13 +575,13 @@ void newton_abs_debug_solver( // but we will use this to bisect and find an h where f(h) >= 0, // so we don't necessarily need h_max to be f(h_max) <= 0. if (vbuffer1_min <= 1e-10) { - vbuffer1_min_nzn = std::numeric_limits::infinity(); + vbuffer1_min_nnz = std::numeric_limits::infinity(); h_max = 0; for (int i = 0; i < vbuffer1.size(); ++i) { const bool is_nonzero = vbuffer1[i] > 1e-10; const auto vi2 = v[i] * v[i]; h_max += is_nonzero ? vi2 / (vbuffer1[i] * vbuffer1[i]) : 0; - vbuffer1_min_nzn = is_nonzero ? std::min(vbuffer1_min_nzn, vbuffer1[i]) : vbuffer1_min_nzn; + vbuffer1_min_nnz = is_nonzero ? std::min(vbuffer1_min_nnz, vbuffer1[i]) : vbuffer1_min_nnz; } h_max = std::sqrt(h_max); } else { @@ -598,14 +598,14 @@ void newton_abs_debug_solver( //// NEW METHOD: bisection // Adaptive method enforces some movement towards h_min for safety. - value_t w = std::max(l1 / (vbuffer1_min_nzn * h_max + l1), 0.05); + value_t w = std::max(l1 / (vbuffer1_min_nnz * h_max + l1), 0.05); h = w * h_min + (1-w) * h_max; value_t fh = (v / (vbuffer1 * h + l1)).matrix().squaredNorm() - 1; smart_iters.push_back(h); while ((fh < 0) && std::abs(fh) > tol) { - w = std::max(l1 / (vbuffer1_min_nzn * h + l1), 0.05); + w = std::max(l1 / (vbuffer1_min_nnz * h + l1), 0.05); h = w * h_min + (1-w) * h; fh = (v / (vbuffer1 * h + l1)).matrix().squaredNorm() - 1; smart_iters.push_back(h); diff --git a/adelie/src/include/adelie_core/io/io_snp_unphased.hpp b/adelie/src/include/adelie_core/io/io_snp_unphased.hpp index e69de29b..118ca080 100644 --- a/adelie/src/include/adelie_core/io/io_snp_unphased.hpp +++ b/adelie/src/include/adelie_core/io/io_snp_unphased.hpp @@ -0,0 +1,263 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace adelie_core { +namespace io { + +class IOSNPUnphased +{ +public: + using string_t = std::string; + using file_unique_ptr_t = std::unique_ptr< + std::FILE, + std::function + >; + using bool_t = bool; + using outer_t = uint64_t; + using inner_t = uint32_t; + using value_t = int8_t; + using vec_outer_t = util::rowvec_type; + using vec_inner_t = util::rowvec_type; + using vec_value_t = util::rowvec_type; + using buffer_t = util::rowvec_type; + using rowarr_value_t = util::rowarr_type; + +protected: + static constexpr size_t _multiplier = ( + sizeof(inner_t) + + sizeof(value_t) + ); + + const string_t _filename; + buffer_t _buffer; + bool_t _is_read; + + static void throw_no_read() + { + throw std::runtime_error( + "File is not read yet. Call read() first." + ); + } + + static auto fopen_safe( + const char* filename, + const char* mode + ) + { + file_unique_ptr_t file_ptr( + std::fopen(filename, mode), + [](std::FILE* fp) { std::fclose(fp); } + ); + auto fp = file_ptr.get(); + if (!fp) { + throw std::runtime_error("Cannot open file " + std::string(filename)); + } + return file_ptr; + } + + static bool is_big_endian() + { + union { + uint32_t i; + char c[4]; + } _bint = {0x01020304}; + + return _bint.c[0] == 1; + } + +public: + IOSNPUnphased( + const string_t& filename + ): + _filename(filename), + _buffer(), + _is_read(false) + {} + + bool_t endian() const { + if (!_is_read) throw_no_read(); + return reinterpret_cast(_buffer[0]); + } + + inner_t rows() const { + if (!_is_read) throw_no_read(); + return reinterpret_cast(_buffer[sizeof(bool_t)]); + } + + inner_t cols() const + { + if (!_is_read) throw_no_read(); + return reinterpret_cast(_buffer[sizeof(bool_t) + sizeof(inner_t)]); + } + + Eigen::Ref outer() const + { + if (!_is_read) throw_no_read(); + return Eigen::Map( + reinterpret_cast(&_buffer[sizeof(bool_t) + 2 * sizeof(inner_t)]), + cols() + 1 + ); + } + + inner_t nnz(int j) const + { + if (!_is_read) throw_no_read(); + const auto _outer = outer(); + return (_outer[j+1] - _outer[j]) / _multiplier; + } + + Eigen::Ref inner(int j) const + { + if (!_is_read) throw_no_read(); + const auto _outer = outer(); + return Eigen::Map( + reinterpret_cast(&_buffer[_outer[j]]), + nnz(j) + ); + } + + Eigen::Ref value(int j) const + { + if (!_is_read) throw_no_read(); + const auto _outer = outer(); + const auto _nnz = nnz(j); + return Eigen::Map( + reinterpret_cast(&_buffer[_outer[j] + sizeof(inner_t) * _nnz]), + _nnz + ); + } + + rowarr_value_t to_dense( + size_t n_threads + ) const + { + if (!_is_read) throw_no_read(); + const auto n = rows(); + const auto p = cols(); + rowarr_value_t dense(n, p); + + n_threads = std::min(n_threads, p); + #pragma omp parallel for schedule(auto) num_threads(n_threads) + for (inner_t j = 0; j < p; ++j) { + const auto _inner = inner(j); + const auto _value = value(j); + auto dense_j = dense.col(j); + dense_j.setZero(); + for (inner_t i = 0; i < _inner.size(); ++i) { + dense_j[_inner[i]] = _value[i]; + } + } + + return dense; + } + + size_t read() + { + _is_read = true; + + auto file_ptr = fopen_safe(_filename.c_str(), "rb"); + auto fp = file_ptr.get(); + std::fseek(fp, 0, SEEK_END); + const size_t total_bytes = std::ftell(fp); + + _buffer.resize(total_bytes); + std::fseek(fp, 0, SEEK_SET); + const size_t read = std::fread(_buffer.data(), sizeof(char), _buffer.size(), fp); + if (read != _buffer.size()) { + throw std::runtime_error( + "Could not read the whole file into buffer." + ); + } + + bool endian = _buffer[0]; + if (endian != is_big_endian()) { + throw std::runtime_error( + "Endianness is inconsistent! " + "Regenerate the file on a machine with the same endianness." + ); + } + + return total_bytes; + } + + size_t write( + const Eigen::Ref& calldata, + size_t n_threads + ) + { + const bool_t endian = is_big_endian(); + const inner_t n = calldata.rows(); + const inner_t p = calldata.cols(); + + // outer[i] = number of bytes to jump from beginning of file + // to start reading column i information. + // outer[i+1] - outer[i] = total number of bytes for column i. + vec_outer_t outer(p+1); + outer[0] = 0; + outer.tail(p) = (calldata != 0).colwise().count().template cast(); + for (int i = 1; i < outer.size(); ++i) { + outer[i] += outer[i-1]; + } + outer *= _multiplier; + outer += ( + sizeof(bool_t) + + 2 * sizeof(inner_t) + + outer.size() * sizeof(outer_t) + ); + + auto& buffer = _buffer; + buffer.resize(outer[p]); + + size_t idx = 0; + reinterpret_cast(buffer[idx]) = endian; idx += sizeof(bool_t); + reinterpret_cast(buffer[idx]) = n; idx += sizeof(inner_t); + reinterpret_cast(buffer[idx]) = p; idx += sizeof(inner_t); + Eigen::Map( + reinterpret_cast(&buffer[idx]), + outer.size() + ) = outer; + + n_threads = std::min(n_threads, p); + #pragma omp parallel for schedule(auto) num_threads(n_threads) + for (inner_t j = 0; j < p; ++j) { + const auto col_j = calldata.col(j); + const auto nnz_bytes = outer[j+1] - outer[j]; + const auto nnz = nnz_bytes / _multiplier; + Eigen::Map inner( + reinterpret_cast(&buffer[outer[j]]), + nnz + ); + Eigen::Map value( + reinterpret_cast(&buffer[outer[j] + sizeof(inner_t) * nnz]), + nnz + ); + + size_t count = 0; + for (int i = 0; i < n; ++i) { + if (col_j[i] == 0) continue; + inner[count] = i; + value[count] = col_j[i]; + ++count; + } + } + + auto file_ptr = fopen_safe(_filename.c_str(), "wb"); + auto fp = file_ptr.get(); + auto total_bytes = std::fwrite(buffer.data(), sizeof(char), buffer.size(), fp); + if (total_bytes != buffer.size()) { + throw std::runtime_error( + "Could not write the full buffer." + ); + } + + return total_bytes; + } +}; + +} // namespace io +} // namespace adelie_core \ No newline at end of file diff --git a/adelie/src/include/adelie_core/matrix/matrix_naive_snp_unphased.hpp b/adelie/src/include/adelie_core/matrix/matrix_naive_snp_unphased.hpp index af0a540d..4f3cdaff 100644 --- a/adelie/src/include/adelie_core/matrix/matrix_naive_snp_unphased.hpp +++ b/adelie/src/include/adelie_core/matrix/matrix_naive_snp_unphased.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace adelie_core { namespace matrix { @@ -19,40 +20,78 @@ class MatrixNaiveSNPUnphased : public MatrixNaiveBase using typename base_t::colmat_value_t; using typename base_t::rowmat_value_t; using typename base_t::sp_mat_value_t; + using io_t = io::IOSNPUnphased; + using string_t = std::string; using vec_vec_index_t = util::rowvec_type; - using dyn_vec_string_t = std::vector; + using dyn_vec_string_t = std::vector; + using dyn_vec_io_t = std::vector; protected: const dyn_vec_string_t _filenames; // (F,) array of file names - const vec_index_t _file_begins; // (F+1,) array of file begin indices. - // _file_begins[i] == starting feature index for file i. - const size_t _n_threads; + const dyn_vec_io_t _ios; // (F,) array of IO handlers + const size_t _p; // total number of feature across all slices + const vec_index_t _io_slice_map; // (p,) array mapping to matrix slice + const vec_index_t _io_index_map; // (p,) array mapping to (relative) index of the slice + const size_t _n_threads; // number of threads - auto init_file_begins( + static auto init_ios( const dyn_vec_string_t& filenames ) { - vec_index_t file_begins(filenames.size() + 1); - file_begins[0] = 0; - for (int i = 1; i < file_begins.size(); ++i) { - const auto& name = filenames[i-1]; - FILE* fp = fopen(name.c_str(), "rb"); - if (!fp) { - throw std::runtime_error("Cannot open file " + name); - } - int32_t n_cols; - size_t status = fread(&n_cols, sizeof(int32_t), 1, fp); - if (status < 1) { - throw std::runtime_error("Could not read the first byte of file " + name); - } - file_begins[i] = n_cols; - fclose(fp); + dyn_vec_io_t ios; + ios.reserve(filenames.size()); + for (int i = 0; i < filenames.size(); ++i) { + ios.emplace_back(filenames[i]); + ios.back().read(); } + return ios; + } - for (int i = 1; i < file_begins.size(); ++i) { - file_begins[i] += file_begins[i-1]; + static auto init_p( + const dyn_vec_io_t& ios + ) + { + size_t p = 0; + for (const auto& io : ios) { + p += io.cols(); } - return file_begins; + return p; + } + + static auto init_io_slice_map( + const dyn_vec_io_t& ios, + size_t p + ) + { + vec_index_t io_slice_map(p); + size_t begin = 0; + for (int i = 0; i < ios.size(); ++i) { + const auto& io = ios[i]; + const auto pi = io.cols(); + for (int j = 0; j < pi; ++j) { + io_slice_map[begin + j] = i; + } + begin += pi; + } + return io_slice_map; + } + + static auto init_io_index_map( + const dyn_vec_io_t& ios, + size_t p + ) + { + vec_index_t io_index_map(p); + size_t begin = 0; + for (int i = 0; i < ios.size(); ++i) { + const auto& io = ios[i]; + const auto pi = io.cols(); + for (int j = 0; j < pi; ++j) { + io_index_map[begin + j] = j; + } + begin += pi; + } + return io_index_map; } public: @@ -61,28 +100,192 @@ class MatrixNaiveSNPUnphased : public MatrixNaiveBase size_t n_threads ): _filenames(filenames), - _file_begins(init_file_begins(filenames)), + _ios(init_ios(filenames)), + _p(init_p(_ios)), + _io_slice_map(init_io_slice_map(_ios, _p)), + _io_index_map(init_io_index_map(_ios, _p)), _n_threads(n_threads) - {} + { + if (filenames.size() == 0) { + throw std::runtime_error( + "filenames must be non-empty!" + ); + } + + // make sure every file has the same number of rows. + const size_t rows = _ios[0].rows(); + for (const auto& io : _ios) { + if (io.rows() != rows) { + throw std::runtime_error( + "Every slice must have same number of rows." + ); + } + } + } value_t cmul( int j, const Eigen::Ref& v ) const override { - if (is_cached(j)) { + const auto slice = _io_slice_map[j]; + const auto& io = _ios[slice]; + const auto index = _io_index_map[j]; + const auto inner = io.inner(index); + const auto value = io.value(index); + value_t sum = 0; + #pragma omp simd reduction(+:sum) + for (int i = 0; i < inner.size(); ++i) { + sum += v[inner[i]] * value[i]; } + + return sum; } - bool is_cached(int j) const + void ctmul( + int j, + value_t v, + const Eigen::Ref& weights, + Eigen::Ref out + ) const override { + const auto slice = _io_slice_map[j]; + const auto& io = _ios[slice]; + const auto index = _io_index_map[j]; + const auto inner = io.inner(index); + const auto value = io.value(index); + dvzero(out, _n_threads); + + for (int i = 0; i < inner.size(); ++i) { + out[inner[i]] = v * value[i] * weights[inner[i]]; + } } - void cache() + void bmul( + int j, int q, + const Eigen::Ref& v, + Eigen::Ref out + ) override { + const auto n_threads = std::min(_n_threads, q); + #pragma omp parallel for schedule(static) num_threads(n_threads) + for (int t = 0; t < q; ++t) + { + const auto slice = _io_slice_map[j+t]; + const auto& io = _ios[slice]; + const auto index = _io_index_map[j+t]; + const auto inner = io.inner(index); + const auto value = io.value(index); + value_t sum = 0; + #pragma omp simd reduction(+:sum) + for (int i = 0; i < inner.size(); ++i) { + sum += v[inner[i]] * value[i]; + } + out[t] = sum; + } + } + + void btmul( + int j, int q, + const Eigen::Ref& v, + const Eigen::Ref& weights, + Eigen::Ref out + ) override + { + dvzero(out, _n_threads); + for (int t = 0; t < q; ++t) + { + const auto slice = _io_slice_map[j+t]; + const auto& io = _ios[slice]; + const auto index = _io_index_map[j+t]; + const auto inner = io.inner(index); + const auto value = io.value(index); + for (int i = 0; i < inner.size(); ++i) { + out[inner[i]] += value[i] * weights[inner[i]] * v[t]; + } + } + } + + void mul( + const Eigen::Ref& v, + Eigen::Ref out + ) override + { + bmul(0, cols(), v, out); + } + + int rows() const override { return _ios[0].rows(); } + int cols() const override { return _p; } + + void sp_btmul( + int j, int q, + const sp_mat_value_t& v, + const Eigen::Ref& weights, + Eigen::Ref out + ) const override + { + const auto n_threads = std::min(_n_threads, v.outerSize()); + #pragma omp parallel for schedule(static) num_threads(n_threads) + for (int k = 0; k < v.outerSize(); ++k) { + typename sp_mat_value_t::InnerIterator it(v, k); + auto out_k = out.row(k); + out_k.setZero(); + for (; it; ++it) + { + const auto t = it.index(); + const auto slice = _io_slice_map[j+t]; + const auto& io = _ios[slice]; + const auto index = _io_index_map[j+t]; + const auto inner = io.inner(index); + const auto value = io.value(index); + for (int i = 0; i < inner.size(); ++i) { + out_k[inner[i]] += value[i] * weights[inner[i]] * it.value(); + } + } + } + } + + void to_dense( + int j, int q, + Eigen::Ref out + ) const override + { + auto begin = 0; + while (begin < q) { + const auto slice = _io_slice_map[j+begin]; + const auto& io = _ios[slice]; + const auto index = _io_index_map[j+begin]; + const auto size = std::min(q - begin, io.cols() - index); + out.middleCols(begin, size) = io.to_dense(1).middleCols(index, size).template cast(); + begin += size; + } + } + + void means( + const Eigen::Ref& weights, + Eigen::Ref out + ) const override + { + const auto p = cols(); + const auto n_threads = std::min(_n_threads, p); + #pragma omp parallel for schedule(static) num_threads(n_threads) + for (int j = 0; j < p; ++j) + { + const auto slice = _io_slice_map[j]; + const auto& io = _ios[slice]; + const auto index = _io_index_map[j]; + const auto inner = io.inner(index); + const auto value = io.value(index); + value_t sum = 0; + #pragma omp simd reduction(+:sum) + for (int i = 0; i < inner.size(); ++i) { + sum += weights[inner[i]] * value[i]; + } + out[j] = sum; + } } }; diff --git a/adelie/src/include/adelie_core/matrix/utils.hpp b/adelie/src/include/adelie_core/matrix/utils.hpp index 98648200..2d01a338 100644 --- a/adelie/src/include/adelie_core/matrix/utils.hpp +++ b/adelie/src/include/adelie_core/matrix/utils.hpp @@ -184,5 +184,30 @@ void dgemv( } } +template +ADELIE_CORE_STRONG_INLINE +void dvzero( + OutType& out, + size_t n_threads +) +{ + assert(n_threads > 0); + const size_t n = out.size(); + const int n_blocks = std::min(n_threads, n); + const int block_size = n / n_blocks; + const int remainder = n % n_blocks; + + #pragma omp parallel for schedule(static) num_threads(n_blocks) + for (int t = 0; t < n_blocks; ++t) + { + const auto begin = ( + std::min(t, remainder) * (block_size + 1) + + std::max(t-remainder, 0) * block_size + ); + const auto size = block_size + (t < remainder); + out.segment(begin, size).setZero(); + } +} + } // namespace matrix } // namespace adelie_core \ No newline at end of file diff --git a/adelie/src/include/adelie_core/solver/solve_basil_naive.hpp b/adelie/src/include/adelie_core/solver/solve_basil_naive.hpp index f736c2f2..6351bcf9 100644 --- a/adelie/src/include/adelie_core/solver/solve_basil_naive.hpp +++ b/adelie/src/include/adelie_core/solver/solve_basil_naive.hpp @@ -281,7 +281,6 @@ size_t kkt( const auto& groups = state.groups; const auto alpha = state.alpha; const auto& penalty = state.penalty; - const auto& weights = state.weights; const auto intercept = state.intercept; const auto n_threads = state.n_threads; const auto& screen_hashset = state.screen_hashset; @@ -330,7 +329,6 @@ inline void solve_basil( const auto& X_means = state.X_means; const auto alpha = state.alpha; const auto& penalty = state.penalty; - const auto& weights = state.weights; const auto& screen_set = state.screen_set; const auto& rsqs = state.rsqs; const auto early_exit = state.early_exit; diff --git a/adelie/src/io.cpp b/adelie/src/io.cpp new file mode 100644 index 00000000..4f9a94d8 --- /dev/null +++ b/adelie/src/io.cpp @@ -0,0 +1,33 @@ +#include "decl.hpp" +#include + +namespace py = pybind11; +namespace ad = adelie_core; + +void io_snp_unphased(py::module_& m) +{ + using io_t = ad::io::IOSNPUnphased; + py::class_(m, "IOSNPUnphased") + .def(py::init(), + py::arg("filename") + ) + .def("endian", &io_t::endian) + .def("rows", &io_t::rows) + .def("cols", &io_t::cols) + .def("outer", &io_t::outer) + .def("nnz", &io_t::nnz) + .def("inner", &io_t::inner) + .def("value", &io_t::value) + .def("to_dense", &io_t::to_dense) + .def("read", &io_t::read) + .def("write", &io_t::write, + py::arg("calldata").noconvert(), + py::arg("n_threads") + ) + ; +} + +void register_io(py::module_& m) +{ + io_snp_unphased(m); +} \ No newline at end of file diff --git a/adelie/src/matrix.cpp b/adelie/src/matrix.cpp index 40d403da..38a95c92 100644 --- a/adelie/src/matrix.cpp +++ b/adelie/src/matrix.cpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace py = pybind11; namespace ad = adelie_core; @@ -422,6 +423,24 @@ void matrix_naive_dense(py::module_& m, const char* name) ; } +template +void matrix_naive_snp_unphased(py::module_& m, const char* name) +{ + using internal_t = ad::matrix::MatrixNaiveSNPUnphased; + using base_t = typename internal_t::base_t; + using dyn_vec_string_t = typename internal_t::dyn_vec_string_t; + py::class_(m, name) + .def( + py::init< + const dyn_vec_string_t&, + size_t + >(), + py::arg("filenames").noconvert(), + py::arg("n_threads") + ) + ; +} + template void matrix_cov_dense(py::module_& m, const char* name) { @@ -469,6 +488,9 @@ void register_matrix(py::module_& m) matrix_naive_dense>(m, "MatrixNaiveDense32C"); matrix_naive_dense>(m, "MatrixNaiveDense32F"); + matrix_naive_snp_unphased(m, "MatrixNaiveSNPUnphased64"); + matrix_naive_snp_unphased(m, "MatrixNaiveSNPUnphased32"); + /* cov matrices */ matrix_cov_dense>(m, "MatrixCovDense64C"); matrix_cov_dense>(m, "MatrixCovDense64F"); diff --git a/adelie/state.py b/adelie/state.py index 93f9b56c..ffc2220e 100644 --- a/adelie/state.py +++ b/adelie/state.py @@ -125,9 +125,6 @@ def create_from_core( class pin_base(base): - def __init__(self): - self.solver = "pin" - def check( self, method: str =None, @@ -1487,7 +1484,7 @@ def check( # This one is tricky! Since we keep track of ever-active set, # some coefficients may have once been active but now zero'ed out. # We can only check that if the non-zero coefficient blocks are active. - nzn_idxs = np.array([ + nnz_idxs = np.array([ i for i, sb, gs in zip( np.arange(len(self.screen_set)), @@ -1497,7 +1494,7 @@ def check( if np.any(self.screen_beta[sb:sb+gs] != 0) ], dtype=int) self._check( - np.all(self.screen_is_active[nzn_idxs]), + np.all(self.screen_is_active[nnz_idxs]), "check screen_is_active is only active on non-zeros of screen_beta", method, logger, ) diff --git a/docs/sphinx/api_reference.rst b/docs/sphinx/api_reference.rst index f5961a66..a834752a 100644 --- a/docs/sphinx/api_reference.rst +++ b/docs/sphinx/api_reference.rst @@ -32,7 +32,8 @@ adelie.data :toctree: generated/ - create_test_data_basil + create_dense + create_snp_unphased adelie.diagnostic @@ -59,6 +60,20 @@ adelie.diagnostic Diagnostic +adelie.io +--------- + + +.. currentmodule:: adelie.io + + +.. autosummary:: + :toctree: generated/ + + + snp_unphased + + adelie.matrix ------------- @@ -70,9 +85,9 @@ adelie.matrix :toctree: generated/ - naive_dense - cov_dense + dense cov_lazy + snp_unphased adelie.state diff --git a/docs/sphinx/notebooks/edpp_study.ipynb b/docs/sphinx/notebooks/edpp_study.ipynb deleted file mode 100644 index dfd63e9b..00000000 --- a/docs/sphinx/notebooks/edpp_study.ipynb +++ /dev/null @@ -1,319 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# __Effectiveness of EDPP Safe Rule__" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this notebook, we study the effectiveness of EDPP rule to construct a safe set.\n", - "While the strong rule is much more aggressive in discarding predictors than any safe rule,\n", - "it too can suffer from not discarding enough.\n", - "In addition, since strong rules do not have the same guarantees as safe rules\n", - "in that the discarded predictors truly have zero coefficients, it may be overzealous.\n", - "\n", - "We summarize our findings with EDPP rule:\n", - "\n", - "- EDPP rule and strong rule behave similarly for large $\\lambda$ when $X$ is fairly uncorrelated,\n", - " both discarding a large amount of variables and very tight to the true active set.\n", - "- EDPP rule discards more than strong rule and is more robust for all $\\lambda$ the more $X$ is correlated.\n", - " In fact, strong rule may fail to discard a large portion of the predictors early on in this case.\n", - "- Strong rule applied on the EDPP safe set is very robust and achieves best of both worlds.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "First, we load our library and other tools." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "%load_ext autoreload\n", - "%autoreload 2" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "import adelie as ad\n", - "import numpy as np" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will create a fictitious dataset for this example.\n", - "The following defines the configuration for our dataset.\n", - "See the documentation for `ad.data.create_test_data_basil` for more information on how the data is generated." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "n = 1000 # number of data points\n", - "p = 100000 # number of features\n", - "G = 10000 # number of groups\n", - "rho = 0.6 # equi-correlation across features\n", - "snr = 1 # signal-to-noise ratio" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We now create the dataset." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "data = ad.data.create_test_data_basil(\n", - " n=n,\n", - " p=p,\n", - " G=G,\n", - " rho=rho,\n", - " snr=snr,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will first run our group elastic net solver using the default setting,\n", - "which will use EDPP rule to create a safe set and then further apply strong rule within the safe set\n", - "to discard more predictors.\n", - "We only fit until the training $R^2$ reaches $0.4$ simply to save time." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "state = ad.grpnet(\n", - " **data,\n", - " n_threads=8,\n", - " rsq_tol=0.4,\n", - " use_edpp=True,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We plot the ever-active set size, strong set size, and EDPP safe set size.\n", - "Notice that the safe set and strong set behave similarly for large $\\lambda$.\n", - "However, for smaller $\\lambda$, the strong set is much tighter to the ever-active set\n", - "and the safe set begins to diverge from the strong set.\n", - "This shows that the strong rule remains effective even when EDPP rule is applied.\n", - "Hence, combining EDPP and strong rules yields a more effective rule than EDPP alone." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "_ = ad.diagnostic.plot_set_sizes(state)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, we run the same solver but turn off EDPP rule.\n", - "This will effectively include all groups into the safe set\n", - "and the strong rule will once again further discard predictors from this (trivial) safe set." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "state = ad.grpnet(\n", - " **data,\n", - " n_threads=8,\n", - " rsq_tol=0.4,\n", - " use_edpp=False,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The same plot below shows that turning off EDPP rule makes the strong rule a lot less effective!\n", - "We see that the ever-active set remains extremely small,\n", - "however, the strong rule quickly fails to discard a large proportion of the predictors.\n", - "In comparison, the previous plot shows that the strong set size proportion is much smaller with the EDPP rule.\n", - "This shows that EDPP rule is also important and strong rule _alone_ does not suffice." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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i21eiH+Iqpapqnd+fMd0fmTZSM3bO08WWTxiDHnJS96Sl4W87h9D3hsnr7OyvLHvDbWu3FoWi0XuYDEM9x3bPH/+vA4dOiRJatq0qWbNmqUOHTqofPnyqlatmsaNG6c//vhDS5YskZR965yGDRvqscce00MPPaSvvvpKI0aM0Jo1a9SlSxe7tpmamqrg4GClpKQoKCjIqc8nMytT3Vd116n0U6ZjvCxeshpW0/nhgeGSVOA6inIMeclL3pKXhbzuH0PeGyevM7JElI7Qmt5r5OvtazqmMOztRG49srhz50516NDB9vOoUaMkSQMGDNCiRYt08uRJHT161Da/Ro0aWrNmjUaOHKnZs2eratWq+n//7//ZXRRdzdfbV+93f19/XvrTdMwV6xWV8jLf7eX9y0tSgesoyjHkJS95S14W8rp/DHlvnLzOyuLsougItx5ZdAdXHlkEAAAoLuztRCX+amgAAAAUHmURAAAApiiLAAAAMEVZBAAAgCnKIgAAAExRFgEAAGCKsggAAABTlEUAAACYoiwCAADAFGURAAAApiiLAAAAMEVZBAAAgCnKIgAAAExRFgEAAGCKsggAAABTlEUAAACYoiwCAADAFGURAAAApiiLAAAAMEVZBAAAgCnKIgAAAExRFgEAAGCKsggAAABTlEUAAACYoiwCAADAFGURAAAApiiLAAAAMEVZBAAAgCnKIgAAAExRFgEAAGCKsggAAABTlEUAAACYoiwCAADAFGURAAAApiiLAAAAMEVZBAAAgCnKIgAAAExRFgEAAGCKsggAAABTlEUAAACYoiwCAADAFGURAAAApiiLAAAAMEVZBAAAgCnKIgAAAExRFgEAAGCKsggAAABTlEUAAACYoiwCAADAFGURAAAApiiLAAAAMEVZBAAAgCnKIgAAAExRFgEAAGCKsggAAABTlEUAAACYoiwCAADAFGURAAAApiiLAAAAMEVZBAAAgCnKIgAAAExRFgEAAGDK7WVx3rx5ioqKkr+/v1q1aqXvv/++wPGxsbGqU6eOAgICFBkZqZEjR+rSpUtFlBYAAODG4tayuHz5co0aNUqTJk3S7t271bhxY3Xp0kWnT5/Od/yyZcs0duxYTZo0Sfv379c777yj5cuX6z//+U8RJwcAALgxuLUszpo1S0OGDNGgQYNUv359LViwQIGBgXr33XfzHb99+3a1adNGDzzwgKKionTHHXeob9++1zwaCQAAgMJxW1nMzMzUrl271KlTp7/CeHmpU6dOiouLy3eZ1q1ba9euXbZy+Ntvv2nt2rXq3r276XYyMjKUmpqa6wEAAAD7lHLXhs+cOaOsrCyFh4fnmh4eHq4DBw7ku8wDDzygM2fO6NZbb5VhGLpy5YqGDRtW4MfQ06ZN0+TJk52aHQAA4Ebh9gtcHLF582ZNnTpVb7zxhnbv3q1Vq1ZpzZo1evHFF02XGTdunFJSUmyPY8eOFWFiAACA4s1tRxYrVqwob29vnTp1Ktf0U6dOKSIiIt9lJk6cqAcffFCDBw+WJEVHR+vChQsaOnSoxo8fLy+vvN3Xz89Pfn5+zn8CAAAANwC3HVn09fVV8+bNtXHjRts0q9WqjRs3KiYmJt9l0tPT8xRCb29vSZJhGK4LCwAAcINy25FFSRo1apQGDBigFi1aqGXLloqNjdWFCxc0aNAgSVL//v1VpUoVTZs2TZLUs2dPzZo1S02bNlWrVq106NAhTZw4UT179rSVRgAAADiPW8vifffdp6SkJD333HNKTExUkyZNtG7dOttFL0ePHs11JHHChAmyWCyaMGGC/vjjD4WGhqpnz56aMmWKu54CAABAiWYxbrDPb1NTUxUcHKyUlBQFBQW5Ow4AAIBb2NuJitXV0AAAAChalEUAAACYoiwCAADAFGURAAAApiiLAAAAMEVZBAAAgCnKIgAAAExRFgEAAGCKsggAAABTlEUAAACYoiwCAADAFGURAAAApiiLAAAAMEVZBAAAgCnKIgAAAExRFgEAAGCKsggAAABTlEUAAACYoiwCAADAFGURAAAApiiLAAAAMEVZBAAAgCnKIgAAAExRFgEAAGCKsggAAABTlEUAAACYoiwCAADAFGURAAAApiiLAAAAMEVZBAAAgCnKIgAAAExRFgEAAGCKsggAAABTDpfFxYsXa82aNbafn3nmGYWEhKh169b6/fffnRoOAAAA7uVwWZw6daoCAgIkSXFxcZo3b55mzJihihUrauTIkU4PCAAAAPcp5egCx44dU61atSRJq1evVp8+fTR06FC1adNG7du3d3Y+AAAAuJHDRxbLlCmjs2fPSpI2bNigzp07S5L8/f118eJF56YDAACAWzl8ZLFz584aPHiwmjZtql9//VXdu3eXJP3888+Kiopydj4AAAC4kcNHFufNm6eYmBglJSVp5cqVqlChgiRp165d6tu3r9MDAgAAwH0shmEY7g5RlFJTUxUcHKyUlBQFBQW5Ow4AAIBb2NuJHP4YWpLOnTund955R/v375ck1atXTw899JDKly9fuLQAAADwSA5/DP31118rKipKr7/+us6dO6dz585pzpw5qlGjhr7++mtXZAQAAICbOPwxdHR0tGJiYjR//nx5e3tLkrKysvToo49q+/bt+vHHH10S1Fn4GBoAAMD+TuTwkcVDhw5p9OjRtqIoSd7e3ho1apQOHTpUuLQAAADwSA6XxWbNmtnOVbza/v371bhxY6eEAgAAgGdw+AKXESNG6Mknn9ShQ4f0j3/8Q5L07bffat68eZo+fbp++OEH29hGjRo5LykAAACKnMPnLHp5FXww0mKxyDAMWSwWZWVlXVc4V+CcRQAAABfeOichIeG6ggEAAKD4cLgsVq9e3RU5AAAA4IEcLotLliwpcH7//v0LHQYAAACexeFzFsuVK5fr58uXLys9PV2+vr4KDAzUn3/+6dSAzsY5iwAAAC68z2LOt7bkPM6fP6/4+Hjdeuut+uCDD64rNAAAADyLw2UxP7Vr19b06dP15JNPOmN1AAAA8BBOKYuSVKpUKZ04ccJZqwMAAIAHcPgCl88++yzXz4Zh6OTJk5o7d67atGnjtGAAAABwP4fLYq9evXL9bLFYFBoaqttvv10zZ850Vi4AAAB4AIfLotVqdUUOAAAAeKDrOmfRMAw5eOcdAAAAFCOFKotLlixRdHS0AgICFBAQoEaNGum9995zdjYAAAC4mcMfQ8+aNUsTJ07U448/brugZevWrRo2bJjOnDmjkSNHOj0kAAAA3MPhb3CpUaOGJk+enOdr/RYvXqznn39eCQkJTg3obHyDCwAAgAu/weXkyZNq3bp1numtW7fWyZMnHV0dAAAAPJjDZbFWrVr66KOP8kxfvny5ateu7XCAefPmKSoqSv7+/mrVqpW+//77AscnJyfrscceU6VKleTn56ebb75Za9eudXi7AAAAuDaHz1mcPHmy7rvvPn399de2cxa3bdumjRs35lsiC7J8+XKNGjVKCxYsUKtWrRQbG6suXbooPj5eYWFhecZnZmaqc+fOCgsL04oVK1SlShX9/vvvCgkJcfRpAAAAwA4On7MoSbt379asWbO0f/9+SVK9evU0evRoNW3a1KH1tGrVSrfccovmzp0rKfsejpGRkXriiSc0duzYPOMXLFigV155RQcOHJCPj4+jsSVxziIAAIBkfydyqCxevnxZjzzyiCZOnKgaNWpcV8DMzEwFBgZqxYoVub4VZsCAAUpOTtann36aZ5nu3burfPnyCgwM1KeffqrQ0FA98MADevbZZ+Xt7Z3vdjIyMpSRkWH7OTU1VZGRkZRFAABwQ3PJBS4+Pj5auXLldYeTpDNnzigrK0vh4eG5poeHhysxMTHfZX777TetWLFCWVlZWrt2rSZOnKiZM2fqpZdeMt3OtGnTFBwcbHtERkY6JT8AAMCNwOELXHr16qXVq1e7IMq1Wa1WhYWF6a233lLz5s113333afz48VqwYIHpMuPGjVNKSortcezYsSJMDAAAULw5fIFL7dq19cILL2jbtm1q3ry5SpcunWv+iBEj7FpPxYoV5e3trVOnTuWafurUKUVEROS7TKVKleTj45PrI+d69eopMTFRmZmZ8vX1zbOMn5+f/Pz87MoEAABcKysrS5cvX3Z3jBvC3ztTYTlcFt955x2FhIRo165d2rVrV655FovF7rLo6+ur5s2ba+PGjbZzFq1WqzZu3KjHH38832XatGmjZcuWyWq1yssr+6Dor7/+qkqVKuVbFAEAgGcwDEOJiYlKTk52d5QbSkhIiCIiImSxWAq9DofLojO/oWXUqFEaMGCAWrRooZYtWyo2NlYXLlzQoEGDJEn9+/dXlSpVNG3aNEnS8OHDNXfuXD355JN64okndPDgQU2dOtXuggoAANwjpyiGhYUpMDDwusoLrs0wDKWnp+v06dOSsj+dLSyHy6Iz3XfffUpKStJzzz2nxMRENWnSROvWrbNd9HL06FHbEURJioyM1Pr16zVy5Eg1atRIVapU0ZNPPqlnn33WXU8BAABcQ1ZWlq0oVqhQwd1xbhgBAQGSpNOnTyssLKzQH0nbfeuc5ORkffDBBxo+fLgkqV+/frp48aJtvre3t95++22Pv0E291kEAKBoXbp0SQkJCYqKirIVGBSNixcv6siRI6pRo4b8/f1zzXP6rXPefvttbd261fbzZ599Ji8vL9staX788UfFxsY6/iwAAMANgY+ei54z9rndZXHFihW2cwlzzJgxQwsXLtTChQs1bdq0fG+kDQAAgOLL7rL422+/qU6dOraf69Spk+sK5MaNG+vgwYPOTQcAAFACWSwWt9232lF2X+By4cIFpaSk2L4BZefOnXnmW61W56YDAAC4SpbV0PcJf+p02iWFlfVXyxrl5e3luR9vP//881q9erX27t2ba/rJkydVrlw594RykN1l8aabbtLu3bvVsGHDfOfv3Lnzur8vGgAAwMy6n07qpTX7dfzcXxfYVi0XoAk96qlrw8LfGsYdzL6AxBPZ/TF07969NWHChDzfuCJl3ztp0qRJ6t27t1PDAQAASNlFcfjS3aobUVarHm2tnyd30apHW6tuRFkNX7pb63466bptr1unW2+9VSEhIapQoYL++c9/6vDhw7b5x48fV9++fVW+fHmVLl1aLVq00HfffadFixZp8uTJ2rdvnywWiywWixYtWiQp98fQrVu3znMbwKSkJPn4+Ojrr7+WJGVkZGjMmDGqUqWKSpcurVatWmnz5s0ue85Xs/vI4jPPPKOVK1eqdu3aevDBB3XzzTdLkuLj4/X++++rSpUq3O8QAAA4XZbV0Etr9qtj3TC99WALef3fx87NqpXTWw+20ND3dmrK2v3qXD/CJR9JX7hwQaNGjVKjRo10/vx5Pffcc+rdu7f27t2r9PR0tWvXTlWqVNFnn32miIgI7d69W1arVffdd59++uknrVu3Tl9++aUkKTg4OM/6+/XrpxkzZmj69Om2q5eXL1+uypUr67bbbpMkPf744/rll1/04YcfqnLlyvrkk0/UtWtX/fjjj6pdu7bTn/PV7C6LZcuW1bZt2zRu3Dh98MEHtq/rCQkJ0QMPPKCpU6eqbNmyrsoJAABuUN8n/Knj5y7q9b5NbUUxh5eXRcPb11Kf+dv1fcKfiqnp/Jt+9+nTJ9fP7777rkJDQ/XLL79o+/btSkpK0o4dO1S+fHlJUq1atWxjy5Qpo1KlShX4sfO9996rp556Slu3brWVw2XLlqlv376yWCw6evSoFi5cqKNHj6py5cqSpDFjxmjdunVauHChpk6d6uynnItD3+BSrlw5LViwQPPnz1dSUpIkKTQ0lPsmAQAAlzmddkmSVCc8/4NSdSLK5hrnbAcPHtRzzz2n7777TmfOnLFd0Hv06FHt3btXTZs2tRXFwggNDdUdd9yhpUuX6rbbblNCQoLi4uL05ptvSpJ+/PFHZWVl2T7VzZGRkVEk34hTqK/7s1gsCgsLc3YWAACAPMLKZn/zSPypNDWrlvcK4vjEtFzjnK1nz56qXr263n77bVWuXFlWq1UNGzZUZmam076Rpl+/fhoxYoTmzJmjZcuWKTo6WtHR0ZKk8+fPy9vbW7t27crzlX1lypRxyvYLYvcFLgAAAO7QskZ5VS0XoDc2HZLVmvtbiq1WQ/M3H1Jk+QC1rFH4o3tmzp49q/j4eE2YMEEdO3ZUvXr1dO7cOdv8Ro0aae/evfrzzz/zXd7X11dZWVnX3M5dd92lS5cuad26dVq2bJn69etnm9e0aVNlZWXp9OnTqlWrVq5HUVxVTVkEAAAezdvLogk96mnjgdMa+t5O7fr9nM5nXNGu389p6Hs7tfHAaY3vXs8lF7eUK1dOFSpU0FtvvaVDhw7pq6++0qhRo2zz+/btq4iICPXq1Uvbtm3Tb7/9ppUrVyouLk6SFBUVpYSEBO3du1dnzpxRRkZGvtspXbq0evXqpYkTJ2r//v3q27evbd7NN9+sfv36qX///lq1apUSEhL0/fffa9q0aVqzZo3Tn/PfURYBAIDH69qwkub3a6YDiWnqM3+7Gk5arz7ztyv+VJrm92vmsvssenl56cMPP9SuXbvUsGFDjRw5Uq+88optvq+vrzZs2KCwsDB1795d0dHRmj59uu3j4j59+qhr167q0KGDQkND9cEHH5huq1+/ftq3b59uu+02VatWLde8hQsXqn///ho9erTq1KmjXr16aceOHXnGuYLFMAzjWoPKly+vX3/9VRUrVtRDDz2k2bNnF9srn1NTUxUcHKyUlBQFBQW5Ow4AACXepUuXlJCQoBo1asjf//rOKyxu3+DibgXte3s7kV1HFjMzM5WamipJWrx4sS5dcs3VRgAAAAXx9rIopmYF3dWkimJqVqAoFgG7roaOiYlRr1691Lx5cxmGoREjRphe/fPuu+86NSAAAADcx66y+P777+u1117T4cOHZbFYlJKSwtFFAACAG4BdZTE8PFzTp0+XJNWoUUPvvfdekdwEEgAAAO7l8E25ExISXJEDAAAAHqhQt87ZsmWLevbsabsh5J133qlvvvnG2dkAAADgZg6Xxffff1+dOnVSYGCgRowYYbvYpWPHjlq2bJkrMgIAAMBNHP4YesqUKZoxY4ZGjhxpmzZixAjNmjVLL774oh544AGnBgQAAID7OHxk8bffflPPnj3zTL/zzjs5nxEAAKCEcbgsRkZGauPGjXmmf/nll4qMjHRKKAAAAHgGhz+GHj16tEaMGKG9e/eqdevWkqRt27Zp0aJFmj17ttMDAgAAKOW4dOGM+fzSoVJwlaLLI2ngwIFKTk7W6tWri3S7Rc3hsjh8+HBFRERo5syZ+uijjyRJ9erV0/Lly3XXXXc5PSAAALjBXcmQ3rlDSv3DfExQVWnEbqmUX9HlstPly5fl4+Pj7hiFVqhb5/Tu3Vtbt27V2bNndfbsWW3dupWiCAAAXMPbVwqqLJWrIQ3dLA3dctVjc/b0oErZ41xgxYoVio6OVkBAgCpUqKBOnTrp6aef1uLFi/Xpp5/KYrHIYrFo8+bNOnLkiCwWi5YvX6527drJ399fS5culdVq1QsvvKCqVavKz89PTZo00bp162zbyFlu1apV6tChgwIDA9W4cWPFxcXlyvL2228rMjJSgYGB6t27t2bNmqWQkBCXPO8chSqLAAAARcZikdqPlc4lSOlnpcpN/nqkn82e3n5s9jgnO3nypPr27auHHnpI+/fv1+bNm3X33Xdr0qRJuvfee9W1a1edPHlSJ0+etJ2eJ0ljx47Vk08+qf3796tLly6aPXu2Zs6cqVdffVU//PCDunTpojvvvFMHDx7Mtb3x48drzJgx2rt3r26++Wb17dtXV65ckZR92t+wYcP05JNPau/evercubOmTJni9Of8dw5/DA0AAFDkanaUqt4ibZ6e/f8Wi2QY2T9XvSV7mgucPHlSV65c0d13363q1atLkqKjoyVJAQEBysjIUERERJ7lnnrqKd199922n1999VU9++yzuv/++yVJL7/8sjZt2qTY2FjNmzfPNm7MmDHq0aOHJGny5Mlq0KCBDh06pLp162rOnDnq1q2bxowZI0m6+eabtX37dn3++ecuee45OLIIAAA8X87RxeM7pMP/d1eWwxuzf3bRUUVJaty4sTp27Kjo6Gj961//0ttvv61z585dc7kWLVrY/j81NVUnTpxQmzZtco1p06aN9u/fn2tao0aNbP9fqVIlSdLp06clSfHx8WrZsmWu8X//2RUoiwAAoHi4+uhiERxVlCRvb2/973//0xdffKH69etrzpw5qlOnzjXvLV26dOlCbe/qC2Es/1eArVZrodblLJRFAABQPFx9dHH9eJcfVfxrsxa1adNGkydP1p49e+Tr66tPPvlEvr6+ysrKuubyQUFBqly5srZt25Zr+rZt21S/fn27c9SpU0c7duzINe3vP7uCw+csZmVladGiRdq4caNOnz6dp+1+9dVXTgsHAACQS87RxW/nufyooiR999132rhxo+644w6FhYXpu+++U1JSkurVq6dLly5p/fr1io+PV4UKFRQcHGy6nqefflqTJk1SzZo11aRJEy1cuFB79+7V0qVL7c7yxBNPqG3btpo1a5Z69uypr776Sl988YXtCKSrOFwWn3zySS1atEg9evRQw4YNXR4QAADAxmKROoyXNkzM/q+Le0hQUJC+/vprxcbGKjU1VdWrV9fMmTPVrVs3tWjRQps3b1aLFi10/vx5bdq0SVFRUfmuZ8SIEUpJSdHo0aN1+vRp1a9fX5999plq165td5Y2bdpowYIFmjx5siZMmKAuXbpo5MiRmjt3rpOebf4shmEYjixQsWJFLVmyRN27d3dVJpdKTU1VcHCwUlJSFBQU5O44AACUeJcuXVJCQoJq1Kghf39/d8cpUYYMGaIDBw7om2++yXd+Qfve3k7k8JFFX19f1apVy9HFAAAAcJ1effVVde7cWaVLl9YXX3yhxYsX64033nDpNh2+wGX06NGaPXu2HDwgCQAAgOv0/fffq3PnzoqOjtaCBQv0+uuva/DgwS7dpsNHFrdu3apNmzbpiy++UIMGDfJ81+GqVaucFg4AAAB/+eijj4p8mw6XxZCQEPXu3dsVWQAAAOBhHC6LCxcudEUOAAAAeKBCfzd0UlKS4uPjJWXfJDI0NNRpoQAAAOAZHL7A5cKFC3rooYdUqVIltW3bVm3btlXlypX18MMPKz093RUZAQAA4CYOl8VRo0Zpy5Yt+u9//6vk5GQlJyfr008/1ZYtWzR69GhXZAQAAICbOPwx9MqVK7VixQq1b9/eNq179+4KCAjQvffeq/nz5zszHwAAANzI4SOL6enpCg8PzzM9LCyMj6EBAACukpiYaLuJdkhIiLvjFIrDZTEmJkaTJk3SpUuXbNMuXryoyZMnKyYmxqnhAAAAirPXXntNJ0+e1N69e/Xrr7+6O06hOPwx9OzZs9WlSxdVrVpVjRs3liTt27dP/v7+Wr9+vdMDAgAAXC3uRJxm7pyp0S1GK6ayZx+oOnz4sJo3b67atWu7O0qhOXxksWHDhjp48KCmTZumJk2aqEmTJpo+fboOHjyoBg0auCIjAACAJMkwDM3dO1fx5+I1d+/cIvn64RUrVig6OloBAQGqUKGCOnXqpAsXLmjHjh3q3LmzKlasqODgYLVr1067d++2LRcVFaWVK1dqyZIlslgsGjhwoCQpOTlZgwcPVmhoqIKCgnT77bdr3759Ln8ehVWo+ywGBgZqyJAhzs4CAABQoO0ntuuHpB/0YP0H9d4v72n7ie1qU6WNy7Z38uRJ9e3bVzNmzFDv3r2Vlpamb775RoZhKC0tTQMGDNCcOXNkGIZmzpyp7t276+DBgypbtqx27Nih/v37KygoSLNnz1ZAQIAk6V//+pcCAgL0xRdfKDg4WG+++aY6duyoX3/9VeXLl3fZcyksu8riZ599pm7dusnHx0efffZZgWPvvPNOpwQDAAC4mmEYemPfG2oU2khPt3ha+5L26Y19b6h15dayWCwu2ebJkyd15coV3X333apevbokKTo6WpJ0++235xr71ltvKSQkRFu2bNE///lPhYaGys/PTwEBAYqIiJAkbd26Vd9//71Onz4tPz8/SdKrr76q1atXa8WKFRo6dKhLnsf1sKss9urVS4mJiQoLC1OvXr1Mx1ksFmVlZTkrGwAAgE3OUcUFnRbIYrHo0caPatiXw1x6dLFx48bq2LGjoqOj1aVLF91xxx265557VK5cOZ06dUoTJkzQ5s2bdfr0aWVlZSk9PV1Hjx41Xd++fft0/vx5VahQIdf0ixcv6vDhwy55DtfLrrJotVrz/X8AAICicPVRxdaVW0uSWldurUahjVx6dNHb21v/+9//tH37dm3YsEFz5szR+PHj9d1332n48OE6e/asZs+ererVq8vPz08xMTHKzMw0Xd/58+dVqVIlbd68Oc88T721jsMXuCxZskQZGRl5pmdmZmrJkiVOCQUAAHC1nKOKjzZ+1FYKc44u/pD0g7af2O6ybVssFrVp00aTJ0/Wnj175Ovrq08++UTbtm3TiBEj1L17dzVo0EB+fn46c+ZMgetq1qyZEhMTVapUKdWqVSvXo2LFii57DtfD4bI4aNAgpaSk5JmelpamQYMGOSUUAABAjpyjipFlIxXiH6Jfzv5ie4T4hyiybKTe2PeGS66M/u677zR16lTt3LlTR48e1apVq5SUlKR69eqpdu3aeu+997R//35999136tevn+0iFjOdOnVSTEyMevXqpQ0bNujI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8wIAir9iUxYlae7cubabcjdp0kSvv/66WrXKvhikffv2ioqK0qJFi2zjP/74Y02YMMF2U+4ZM2Z4xk25AQAAioliVRaLEmURAACgGN2UGwAAAJ6LsggAAABTlEUAAACYoiwCAADAFGURAAAApiiLAAAAMEVZBAAAgCnKIgAAAExRFgEAAGCKsggAAABTpdwdoKjlfLthamqqm5MAAAC4T04XutY3P99wZTEtLU2SFBkZ6eYkAAAA7peWlqbg4GDT+RbjWnWyhLFarTpx4oTKli0ri8Xi7jimUlNTFRkZqWPHjhX45d5wPva9e7Df3Yd97x7sd/dh32czDENpaWmqXLmyvLzMz0y84Y4senl5qWrVqu6OYbegoKAb+oXsTux792C/uw/73j3Y7+7DvleBRxRzcIELAAAATFEWAQAAYIqy6KH8/Pw0adIk+fn5uTvKDYd97x7sd/dh37sH+9192PeOueEucAEAAID9OLIIAAAAU5RFAAAAmKIsAgAAwBRlEQAAAKYoix4oIyNDTZo0kcVi0d69ewsce+nSJT322GOqUKGCypQpoz59+ujUqVNFE7QEufPOO1WtWjX5+/urUqVKevDBB3XixIkCl2nfvr0sFkuux7Bhw4oocclQmP3Oa/76HTlyRA8//LBq1KihgIAA1axZU5MmTVJmZmaBy/Gavz6F3e+85p1jypQpat26tQIDAxUSEmLXMgMHDszzmu/atatrg3ogyqIHeuaZZ1S5cmW7xo4cOVL//e9/9fHHH2vLli06ceKE7r77bhcnLHk6dOigjz76SPHx8Vq5cqUOHz6se+6555rLDRkyRCdPnrQ9ZsyYUQRpS47C7Hde89fvwIEDslqtevPNN/Xzzz/rtdde04IFC/Sf//znmsvymi+8wu53XvPOkZmZqX/9618aPny4Q8t17do112v+gw8+cFFCD2bAo6xdu9aoW7eu8fPPPxuSjD179piOTU5ONnx8fIyPP/7YNm3//v2GJCMuLq4I0pZcn376qWGxWIzMzEzTMe3atTOefPLJogt1A7jWfuc17zozZswwatSoUeAYXvPOd639zmve+RYuXGgEBwfbNXbAgAHGXXfd5dI8xQFHFj3IqVOnNGTIEL333nsKDAy85vhdu3bp8uXL6tSpk21a3bp1Va1aNcXFxbkyaon2559/aunSpWrdurV8fHwKHLt06VJVrFhRDRs21Lhx45Senl5EKUsee/Y7r3nXSUlJUfny5a85jte8c11rv/Oad7/NmzcrLCxMderU0fDhw3X27Fl3RypylEUPYRiGBg4cqGHDhqlFixZ2LZOYmChfX988516Eh4crMTHRBSlLtmeffValS5dWhQoVdPToUX366acFjn/ggQf0/vvva9OmTRo3bpzee+89/fvf/y6itCWHI/ud17xrHDp0SHPmzNEjjzxS4Dhe885lz37nNe9eXbt21ZIlS7Rx40a9/PLL2rJli7p166asrCx3RytSlEUXGzt2bJ6TY//+OHDggObMmaO0tDSNGzfO3ZFLDHv3fY6nn35ae/bs0YYNG+Tt7a3+/fvLKOALjoYOHaouXbooOjpa/fr105IlS/TJJ5/o8OHDRfH0PJar9zvMObrvJemPP/5Q165d9a9//UtDhgwpcP285vPn6v0Oc4XZ9464//77deeddyo6Olq9evXS559/rh07dmjz5s3OexLFQCl3ByjpRo8erYEDBxY45qabbtJXX32luLi4PN9T2aJFC/Xr10+LFy/Os1xERIQyMzOVnJyc61+dp06dUkREhDPiF2v27vscFStWVMWKFXXzzTerXr16ioyM1LfffquYmBi7tteqVStJ2UcLatasWejcxZ0r9zuv+YI5uu9PnDihDh06qHXr1nrrrbcc3h6v+Wyu3O+85gvm6L6/XjfddJMqVqyoQ4cOqWPHjk5br6ejLLpYaGioQkNDrznu9ddf10svvWT7+cSJE+rSpYuWL19ue0P+u+bNm8vHx0cbN25Unz59JEnx8fE6evSo3QWnJLN33+fHarVKyr6Nkb1ybnNUqVKlQm2zpHDlfuc1XzBH9v0ff/yhDh06qHnz5lq4cKG8vBz/oInXfDZX7nde8wW7nvebwjh+/LjOnj17473m3XyBDUwkJCTkuRr6+PHjRp06dYzvvvvONm3YsGFGtWrVjK+++srYuXOnERMTY8TExLghcfH17bffGnPmzDH27NljHDlyxNi4caPRunVro2bNmsalS5cMw8i77w8dOmS88MILxs6dO42EhATj008/NW666Sajbdu27nwqxUph9rth8Jp3huPHjxu1atUyOnbsaBw/ftw4efKk7XH1GF7zzlWY/W4YvOad5ffffzf27NljTJ482ShTpoyxZ88eY8+ePUZaWpptTJ06dYxVq1YZhmEYaWlpxpgxY4y4uDgjISHB+PLLL41mzZoZtWvXtr1H3Sgoix4qv7KYM23Tpk22aRcvXjQeffRRo1y5ckZgYKDRu3fvXG88uLYffvjB6NChg1G+fHnDz8/PiIqKMoYNG2YcP37cNubv+/7o0aNG27ZtbcvUqlXLePrpp42UlBQ3PYvipzD73TB4zTvDwoULDUn5PnLwmne+wux3w+A17ywDBgzId99fva8lGQsXLjQMwzDS09ONO+64wwgNDTV8fHyM6tWrG0OGDDESExPd8wTcyGIYnEkOAACA/HE1NAAAAExRFgEAAGCKsggAAABTlEUAAACYoiwCAADAFGURAAAApiiLAAAAMEVZBAAAgCnKIgAAAExRFgEAAGCKsggA16l9+/Z66qmnnL7es2fPKiwsTEeOHMk1fezYsfLz89MDDzyQZ5n7779fM2fOdHoWADcuyiIAeKgpU6borrvuUlRUVK7p48aN08yZM/XBBx/o0KFDueZNmDBBU6ZMUUpKShEmBVCSURYBwAOlp6frnXfe0cMPP5xnXnBwsB5++GF5eXnpxx9/zDWvYcOGqlmzpt5///2iigqghKMsAoCTZWRkaMSIEQoLC5O/v79uvfVW7dixwzY/LS1N/fr1U+nSpVWpUiW99tpreT7KXrt2rfz8/PSPf/wj321cuXJFgYGB+umnn/LM69mzpz788EOnPy8ANybKIgA42TPPPKOVK1dq8eLF2r17t2rVqqUuXbrozz//lCSNGjVK27Zt02effab//e9/+uabb7R79+5c6/jmm2/UvHlz021MmDBB58+fz7cstmzZUt9//70yMjKc+8QA3JAoiwDgRBcuXND8+fP1yiuvqFu3bqpfv77efvttBQQE6J133lFaWpoWL16sV199VR07dlTDhg21cOFCZWVl5VrP77//rsqVK+e7jV27dmnBggXq0aNHvmWxcuXKyszMVGJiokueI4AbC2URAPIxduxYWSyWAh8HDhzIs9zhw4d1+fJltWnTxjbNx8dHLVu21P79+/Xbb7/p8uXLatmypW1+cHCw6tSpk2s9Fy9elL+/f571W61WPfLII3r88cfVv39/HTx4UJcvX841JiAgQFL2eY8AcL1KuTsAAHii0aNHa+DAgQWOuemmm1y2/YoVK+rcuXN5ps+ZM0dnzpzRCy+8oKNHj+ry5cs6cOCAoqOjbWNyPu4ODQ11WT4ANw7KIgDkIzQ0tFBlq2bNmvL19dW2bdtUvXp1SdLly5e1Y8cOPfXUU7rpppvk4+OjHTt2qFq1apKklJQU/frrr2rbtq1tPU2bNs1zRfMff/yhiRMn6oMPPlDp0qVVu3Zt+fn56aeffspVFn/66SdVrVpVFStWLMxTB4Bc+BgaAJyodOnSGj58uJ5++mmtW7dOv/zyi4YMGaL09HQ9/PDDKlu2rAYMGKCnn35amzZt0s8//2y7DY7FYrGtp0uXLvr5559zHV0cMWKEunXrph49ekiSSpUqpXr16uU5b/Gbb77RHXfcUTRPGECJx5FFAHCy6dOny2q16sEHH1RaWppatGih9evXq1y5cpKkWbNmadiwYfrnP/+poKAgPfPMMzp27FiucxSjo6PVrFkzffTRR3rkkUf0+eef66uvvtL+/ftzbSs6OjpXWbx06ZJWr16tdevWFc2TBVDiWQzDMNwdAgBuZBcuXFCVKlU0c+bMXDfhXrNmjZ5++mn99NNP8vKy74Og+fPn65NPPtGGDRtcFRfADYYjiwBQxPbs2aMDBw6oZcuWSklJ0QsvvCBJuuuuu3KN69Gjhw4ePKg//vhDkZGRdq3bx8dHc+bMcXpmADcujiwCQBHbs2ePBg8erPj4ePn6+qp58+aaNWtWrotUAMBTUBYBAABgiquhAQAAYIqyCAAAAFOURQAAAJiiLAIAAMAUZREAAACmKIsAAAAwRVkEAACAKcoiAAAATFEWAQAAYIqyCAAAAFP/H7klsayzDaURAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "_ = ad.diagnostic.plot_set_sizes(state)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This problem is exacerbated the more correlated $X$ is.\n", - "We increase $\\rho$ to $0.8$ and notice that the strong set proportion is significantly increased without the EDPP rule." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "rho = 0.8\n", - "data = ad.data.create_test_data_basil(\n", - " n=n,\n", - " p=p,\n", - " G=G,\n", - " rho=rho,\n", - " snr=snr,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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gYEBMjMz1VWOiIiIiERA5QtcDh06JPdeEARkZWUhOjoaPXr0UFtjRERERKR9KofFsLAwufcSiQR2dnZ46aWXsHLlSnX1RUREREQioHJYlEqlmuiDiIiIiESoQecsCoIAFe+8Q0RERETPkHqFxa+//hre3t4wNTWFqakpfHx88M0336i7NyIiIiLSMpW/hl61ahUWL16MadOmyS5o+f333zF58mTk5+dj9uzZam+SiIiIiLRD5Se4uLu7Y+nSpTUe67d9+3Z89NFHyMjIUGuD2sAnuBAREZGu09gTXLKyshAUFFRjeVBQELKyslQtR0REREQipnJY9PDwwN69e2ss37NnDzw9PdXSFBERERGJg8rnLC5duhTDhg3Dr7/+Kjtn8dSpU4iLi6s1RBIRERHRs0vlmcUhQ4bg7NmzsLW1xcGDB3Hw4EHY2tri7NmzGDRokCZ6JCIiIiItUWlmsaKiApMmTcLixYuxY8cOTfVERERERCKh0syioaEhvvvuO031QkREREQio/LX0GFhYTh48KAGWiEiIiIisVH5AhdPT098/PHHOHXqFLp27Qpzc3O59TNmzFBbc0RERESkXfW6KbfCYhIJ/v777wY3pW28KTcRERHpOmXzjsozi7rwhBYiIiIiUo7K5ywSERERUdOhdFh88OABNmzYIHs/YsQIDB48WPZ688038eDBA5UbWL9+Pdzc3GBiYoKAgACcPXu2zvH79u2Dl5cXTExM4O3tjaNHj8qtf/ToEaZNmwYXFxeYmpqiQ4cO2Lhxo8p9EREREZEKYXHz5s34/fffZe8PHToEPT09WFlZwcrKCn/88QeioqJU2vmePXsQERGBJUuWIDk5Gb6+vggNDUVubm6t4xMSEhAeHo5x48bhwoULCAsLQ1hYGC5evCgbExERgdjYWOzYsQOXL1/GrFmzMG3aNBw6dEil3oiIiIhIhQtcAgICsGzZMoSEhAAALCwskJqaijZt2gAADhw4gI8//hgXLlxQeucBAQHo1q0boqOjAQBSqRSurq6YPn065s+fX2P8sGHDUFxcjMOHD8uWPf/88/Dz85PNHnbq1AnDhg3D4sWLZWO6du2K/v3749NPP1WqL17gQkRERLpO2byj9Mzi33//jXbt2snet2vXDkZGRrL3vr6+uHr1qtINlpeXIykpSRY+AUBPTw8hISFITEysdZvExES5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YKxcuRIDBw7E7t27cf78eWzatAkA0KJFC7Ro0UJuH4aGhnB0dES7du0a98MRERERPeO0HhaHDRuGvLw8fPjhh8jOzoafnx9iY2NlF7HcunULenr/mwANCgrCrl27sGjRInzwwQfw9PTEwYMH0alTJ219BCIiIiKdJYonuIgNn+BCREREuu6ZeYILEREREYkXwyIRERERKcSwSEREREQKMSwSERERkUIMi0RERESkEMMiERERESnEsEhERERECjEsEhEREZFCDItEREREpBDDIhEREREpxLBIRERERAoxLBIRERGRQgyLRERERKQQwyIRERERKcSwSEREREQKMSwSERERkUIMi0RERESkEMMiERERESnEsEhERERECjEsEhEREZFCDItEREREpBDDIhEREREpxLBIRERERAoxLBIRERGRQgyLRERERKQQwyIRERERKcSwSEREREQKMSwSERERkUIMi0RERESkEMMiERERESnEsEhERERECjEsEhEREZFCDItEREREpBDDIhEREREpxLBIRERERAoZaLsBIq0rvAMU5yteb24HWDlrbntdq0FERDqFYZGatidlwFd9gaK7AIBEE2OstGmOOQX3EVhaVjXG0gWYkQwYGKt/e12rQUREOodfQ1PTpm8EWDoBzd0hTIhHtFcPpBsbIdqrB4QJ8UBzd8CyZdU4TWyvazWIiEjnMCxS0yaRAL3mA/czkJCZgLTCaxjZYSTSCq8hITMBuJ9RtV4i0cz2ulaDiIh0jkQQBEHbTYhNUVERrKysUFhYCEtLS223Q5omCBC+CsHb+vcB+/bYMWAH3j76NpB7GTsqm0My7ue6A1JDt9e1GkRE9ExQNu9wZpFIIkGC7yCk6VVgin0gJBIJptgHIk2vAgm+g54ejhq6va7VICIinSKKsLh+/Xq4ubnBxMQEAQEBOHv2bJ3j9+3bBy8vL5iYmMDb2xtHjx6VrauoqMD7778Pb29vmJubw8nJCaNGjUJmZqamPwY9owRBQExuInykhghKPQAIAoJSD8BHaoiY3EQ8bfK9odvrWg0iItItWg+Le/bsQUREBJYsWYLk5GT4+voiNDQUubm5tY5PSEhAeHg4xo0bhwsXLiAsLAxhYWG4ePEiAKCkpATJyclYvHgxkpOTsX//fqSnp+O1115rzI9Fz5CEzASk5adhSqexkNw5DxxbCMmd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apkqx+fBgweCoaGhsG/fPtmyy5cvCwCExMTERuhWu77//ntBIpEI5eXlCseYm5sLX3/9tdwyGxsbYfPmzZpuT+uUOT4BAQHCokWLGrEr8VDm+AiCIFy4cEFwdnYWsrKyBADCgQMHGqdBLVP2+PyTrv98/qenHZ+m/vO5Lrr3v6KkVjk5OZgwYQK++eYbmJmZ1atGYWEhLC0tYWCge48iV/X4JCUloaKiAiEhIbJlXl5eaNWqFRITEzXZqtYVFBRg586dCAoKgqGhocJxQUFB2LNnDwoKCiCVSrF7926UlpaiV69ejdesFihzfHJzc3HmzBnY29sjKCgIDg4OCA4Oxu+//97I3TY+Zf/+lJSU4K233sL69evh6OjYiB1ql7LH5990+efzPylzfJryz+enYVgkhQRBwDvvvIPJkyfD39+/XjXy8/PxySefYOLEiWruTvvqc3yys7NhZGQEa2trueUODg7Izs7WQJfa9/7778Pc3BwtWrTArVu38P3339c5fu/evaioqECLFi1gbGyMSZMm4cCBA/Dw8GikjhuXKsfn77//BgB89NFHmDBhAmJjY9GlSxf06dMHV69ebayWG5Wqf39mz56NoKAgvP76643UoXapenz+SZd/PldT5fg0xZ/PymJYbILmz58PiURS5+vKlStYt24dHj58iAULFtRrP0VFRRg4cCA6dOiAjz76SL0fQoMa6/g8q5Q9PtXmzZuHCxcu4KeffoK+vj5GjRoFoY4HRy1evBgPHjzAzz//jPPnzyMiIgJDhw7FH3/80Rgfr8E0eXykUikAYNKkSRgzZgw6d+6M1atXo127dtiyZUujfL6G0uTxOXToEH755RdERUU10qdRP03/91VN138+V6vv8SF5fNxfE5SXl4d79+7VOaZNmzYYOnQofvjhB7mTxCsrK6Gvr48RI0Zg+/btCrd/+PAhQkNDYWZmhsOHD8PExERt/WuaJo/PL7/8gj59+uD+/fty//faunVrzJo1C7Nnz1bb59AUZY+PkZFRjeV37tyBq6srEhISEBgYWGP99evX4eHhgYsXL6Jjx46y5SEhIfDw8MDGjRsb/gE0TJPHJyMjA23atME333yDt99+W7Z82LBhMDAwwM6dOxv+ATRMk8dn1qxZWLt2rdzFPpWVldDT08OLL76IEydONLh/TdPk8anWFH4+1+f46MLPZ03R7ZMUqFZ2dnaws7N76ri1a9fi008/lb3PzMxEaGgo9uzZg4CAAIXbFRUVITQ0FMbGxjh06NAz9YMI0Ozx6dq1KwwNDREXF4chQ4YAANLT03Hr1q06f7iLibLHpzbVM2NlZWW1ri8pKQGAGlf26uvry7YVO00eHzc3Nzg5OSE9PV1u+V9//YX+/fvXa5+NTZPHZ/78+Rg/frzcMm9vb6xevRqvvvpqvfbZ2DR5fICm8/O5Nk87Prrw81ljtHZpDT1zMjIyalzte+fOHaFdu3bCmTNnBEEQhMLCQiEgIEDw9vYWrl27JmRlZcleT5480VLnjUOZ4yMIgjB58mShVatWwi+//CKcP39eCAwMFAIDA7XQsWadPn1aWLdunXDhwgXhxo0bQlxcnBAUFCS0bdtWKC0tFQSh5vEpLy8XPDw8hBdffFE4c+aMcO3aNWHFihWCRCIRjhw5os2Po3b1OT6CIAirV68WLC0thX379glXr14VFi1aJJiYmAjXrl3T1kfRiPoen3+Djl4NXZ/j05R+Ptf3709T+fmsKoZFUlptYah6WXx8vCAIghAfHy8AqPWVkZGhlb4bizLHRxAE4fHjx8KUKVOE5s2bC2ZmZsKgQYOErKysxm9Yw9LS0oTevXsLNjY2grGxseDm5iZMnjxZuHPnjmxMbcfnr7/+EgYPHizY29sLZmZmgo+PT41b6eiC+h4fQRCEyMhIwcXFRTAzMxMCAwOF3377rZG717yGHJ9/0tWwWJ/j05R+Ptf3709T+fmsKp6zSEREREQK8WpoIiIiIlKIYZGIiIiIFGJYJCIiIiKFGBaJiIiISCGGRSIiIiJSiGGRiIiIiBRiWCQiIiIihRgWiYiIiEghhkUiIiIiUohhkYiIiIgUYlgkItKAXr16YdasWWqve+/ePdjb2+PGjRtyy+fPnw9jY2O89dZbNbYZPnw4Vq5cqfZeiKhpYFgkInqGLFu2DK+//jrc3Nzkli9YsAArV67Ef//7X1y7dk1u3aJFi7Bs2TIUFhY2YqdEpCsYFomInhElJSX46quvMG7cuBrrrKysMG7cOOjp6eGPP/6QW9epUye0bdsWO3bsaKxWiUiHMCwSETWCsrIyzJgxA/b29jAxMcELL7yAc+fOydY/fPgQI0aMgLm5OVq2bInVq1fX+Cr76NGjMDY2xvPPP1/rPp48eQIzMzNcvHixxrpXX30Vu3fvVvvnIiLdx7BIRNQI3nvvPXz33XfYvn07kpOT4eHhgdDQUBQUFAAAIiIicOrUKRw6dAjHjx/Hb7/9huTkZLkav/32G7p27apwH4sWLcKjR49qDYvdu3fH2bNnUVZWpt4PRkQ6j2GRiEjDiouLsWHDBnz55Zfo378/OnTogM2bN8PU1BRfffUVHj58iO3bt2PFihXo06cPOnXqhK1bt6KyslKuzs2bN+Hk5FTrPpKSkrBx40YMHDiw1rDo5OSE8vJyZGdna+QzEpHuYlgkIlLS/PnzIZFI6nxduXKlxnbXr19HRUUFevToIVtmaGiI7t274/Lly/j7779RUVGB7t27y9ZbWVmhXbt2cnUeP34MExOTGvWlUikmTZqEadOmYdSoUbh69SoqKirkxpiamgKoOu+RiEgVBtpugIjoWTFnzhy88847dY5p06aNxvZva2uL+/fv11i+bt065Ofn4+OPP8atW7dQUVGBK1euwNvbWzam+utuOzs7jfVHRLqJYZGISEl2dnb1Cltt27aFkZERTp06hdatWwMAKioqcO7cOcyaNQtt2rSBoaEhzp07h1atWgEACgsL8ddff6Fnz56yOp07d65xRfPdu3exePFi/Pe//4W5uTk8PT1hbGyMixcvyoXFixcvwsXFBba2tvX56ETUhPFraCIiDTM3N8e7776LefPmITY2FpcuXcKECRNQUlKCcePGwcLCAqNHj8a8efMQHx+PP//8U3YbHIlEIqsTGhqKP//8U252ccaMGejfvz8GDhwIADAwMED79u1rnLf422+/oW/fvo3zgYlIp3BmkYioESxfvhxSqRQjR47Ew4cP4e/vj2PHjqF58+YAgFWrVmHy5Ml45ZVXYGlpiffeew+3b9+WO0fR29sbXbp0wd69ezFp0iQcPnwYv/zyCy5fviy3L29vb7mwWFpaioMHDyI2NrZxPiwR6RSJIAiCtpsgIiJ5xcXFcHZ2xsqVK+Vuwn3kyBHMmzcPFy9ehJ6ecl8ObdiwAQcOHMBPP/2kqXaJSIdxZpGISAQuXLiAK1euoHv37igsLMTHH38MAHj99dflxg0cOBBXr17F3bt34erqqlRtQ0NDrFu3Tu09E1HTwJlFIiIRuHDhAsaPH4/09HQYGRmha9euWLVqldxFKkRE2sCwSEREREQK8WpoIiIiIlKIYZGIiIiIFGJYJCIiIiKFGBaJiIiISCGGRSIiIiJSiGGRiIiIiBRiWCQiIiIihRgWiYiIiEghhkUiIiIiUohhkYiIiIgU+v9DkhOr2dWcYQAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "state = ad.grpnet(\n", - " **data,\n", - " n_threads=8,\n", - " rsq_tol=0.4,\n", - " use_edpp=True,\n", - ")\n", - "_ = ad.diagnostic.plot_set_sizes(state)" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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ytvbW927d9fs2bMd2R8AAABcyO6RxZSUFAUHB2ebHhQUxGFoAACAv4iPj7feRDsgIMDV7eSJ3WExIiJC48aN07Vr16zTrl69qvHjxysiIsKhzQEAANzJ3nvvPZ0+fVoxMTH67bffXN1Onth9GHr69Olq3769ypcvr7p160qS9u3bJy8vL61bt87hDQIAAPxV9KloTd01VSMajVBE2YI9UHX06FE1bNhQVatWdXUreWb3yGLt2rV1+PBhTZw4UfXq1VO9evU0adIkHT58WLVq1XJGjwAAAJIkwzA0M2amYi/EambMzHx5/PDSpUsVHh4ub29vlSpVSm3atNGVK1e0c+dOtW3bVqVLl5a/v79atmypPXv2WNcLCwvTsmXL9Mknn8hisahv376SpIsXL2rAgAEKDAyUn5+fHnzwQe3bt8/pnyOv8nSfRR8fHz399NOO7gUAAOCmtp3app8Tf9aTNZ/Up79+qm2ntql5ueZO297p06fVs2dPTZ48WV27dtWlS5e0efNmGYahS5cuqU+fPpoxY4YMw9DUqVPVsWNHHT58WMWLF9fOnTvVu3dv+fn5afr06fL29pYk/d///Z+8vb317bffyt/fXx9++KFat26t3377TSVLlnTaZ8mrXIXFVatWqUOHDnJ3d9eqVatuuuzDDz/skMYAAAD+yjAMfbDvA9UJrKOXG72sfYn79MG+D9SsbDNZLBanbPP06dO6fv26Hn30UVWsWFGSFB4eLkl68MEHbZb9z3/+o4CAAG3atEn//Oc/FRgYKE9PT3l7eyskJESStGXLFu3YsUNnzpyRp6enJGnKlClauXKlli5dqoEDBzrlc9yOXIXFLl26KD4+XkFBQerSpYvpchaLRRkZGY7qDQAAwCprVHFOmzmyWCx6tu6zGvz9YKeOLtatW1etW7dWeHi42rdvr3bt2umxxx5TiRIllJCQoDFjxmjjxo06c+aMMjIylJKSouPHj5vW27dvny5fvqxSpUrZTL969aqOHj3qlM9wu3IVFjMzM3P8MwAAQH7466his7LNJEnNyjZTncA6Th1ddHNz03fffadt27Zp/fr1mjFjhkaPHq3t27frmWee0blz5zR9+nRVrFhRnp6eioiIUFpammm9y5cvq0yZMtq4cWO2eQX11jp2X+DyySefKDU1Ndv0tLQ0ffLJJw5pCgAA4K+yRhWfrfus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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "state = ad.grpnet(\n", - " **data,\n", - " n_threads=8,\n", - " rsq_tol=0.4,\n", - " use_edpp=False,\n", - ")\n", - "_ = ad.diagnostic.plot_set_sizes(state)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This behavior is intuitive because the more correlated $X$ is,\n", - "the more similar the gradient $X^\\top (y - X\\beta)$ looks for each group,\n", - "which is one of the driving quantities used to pull groups into the strong set and EDPP safe set.\n", - "EDPP safe set adds a correction term that suffers less from this problem \n", - "while strong set purely uses the gradient information." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "adelie", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.12" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/docs/sphinx/notebooks/snp_io.ipynb b/docs/sphinx/notebooks/snp_io.ipynb new file mode 100644 index 00000000..3d6138b1 --- /dev/null +++ b/docs/sphinx/notebooks/snp_io.ipynb @@ -0,0 +1,176 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# __SNP IO Examples__" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import adelie as ad\n", + "import numpy as np\n", + "import os" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## __SNP Unphased__" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "n = 400000\n", + "ps = [10, 20, 30]\n", + "seed = 0\n", + "\n", + "filenames = [\n", + " f\"/tmp/dummy_{i}.snpdat\"\n", + " for i in range(len(ps))\n", + "]\n", + "\n", + "np.random.seed(seed)\n", + "calldatas = [\n", + " ad.data.create_snp_unphased(n, pi, seed=i)[\"X\"]\n", + " for i, pi in enumerate(ps)\n", + "]\n", + "calldata = np.concatenate(calldatas, axis=-1)\n", + "\n", + "for name, dat in zip(filenames, calldatas):\n", + " handler = ad.io.snp_unphased(name)\n", + " handler.write(dat, n_threads=4)" + ] + }, + { + "cell_type": "code", + "execution_count": 114, + "metadata": {}, + "outputs": [], + "source": [ + "mat = ad.matrix.snp_unphased(filenames, n_threads=8)" + ] + }, + { + "cell_type": "code", + "execution_count": 115, + "metadata": {}, + "outputs": [], + "source": [ + "w = np.random.uniform(1, 2, n)\n", + "w /= np.sum(w)" + ] + }, + { + "cell_type": "code", + "execution_count": 127, + "metadata": {}, + "outputs": [], + "source": [ + "j = 0\n", + "q = 1\n", + "v = np.random.normal(0, 1, q)\n", + "out = np.empty(n)" + ] + }, + { + "cell_type": "code", + "execution_count": 132, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 374 µs, sys: 0 ns, total: 374 µs\n", + "Wall time: 381 µs\n" + ] + } + ], + "source": [ + "%%time\n", + "mat.btmul(j, q, v, w, out)" + ] + }, + { + "cell_type": "code", + "execution_count": 126, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 29.6 ms, sys: 0 ns, total: 29.6 ms\n", + "Wall time: 12.2 ms\n" + ] + } + ], + "source": [ + "%%time\n", + "expected = (v.T @ (w[:, None] * calldata[:, j:j+q]).T)" + ] + }, + { + "cell_type": "code", + "execution_count": 101, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 101, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.allclose(out, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "adelie", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/sphinx/user_guide.rst b/docs/sphinx/user_guide.rst index e9fed97c..74243cac 100644 --- a/docs/sphinx/user_guide.rst +++ b/docs/sphinx/user_guide.rst @@ -6,5 +6,4 @@ User Guide :maxdepth: 1 - notebooks/edpp_study notebooks/pivot_rule \ No newline at end of file diff --git a/docs/tex/pivot_rule/main.pdf b/docs/tex/pivot_rule/main.pdf index dc90291bfaa215efae7e3d66d20596d81e680832..041362ec89b31c0f72810d6d1c811634a0dab70d 100644 GIT binary patch delta 22176 zcmW*RLv)}`vjE`OoY=N)+Y{TiHNhK9Cbn%S6WbHpwr%@<|6M$rKDDf>u0CBk?N%Y} zR<#D8psNjrpc3#9fj7*YiSdMJ!5s*p9SETR0s9a5e<1z?`5&nNK>r8kKd}FS`w#qo z{uhtx#AGH6V{|Dngm@OR$SW`vFY;a8S<_?x_R;G!Rs+V$Pm)S3fuMy_^}YuF$D}>dEcS zZ3=YwOnpyp@TJS#buv!BD<@XkFh^#t_CSxW1nZ}*YOg?mB*>m!;Dc~-Dq?bSB8l7B zD9~Z)&KR1A+t|k?HY5vZ?HEEOC=%z5SIWbk+}V_xAp=mBWWc6K00 zoSa|Z*lT>SAVmQV8WtcFm;pfo*auP44A$2>r)K8Hr*|1gLI9{+$peUozrR6=&<5z9 z2Qd|$Dlv8*gj*fNYL^8&7C8Fp+%+`DT2XsuBZzeapj#vepouNAGJIQt>v!fL}TR1>7usWFr=HlaQ z7mgF02h<=Doc#a;lopBjCU+z*3cp z4jRi6(~@p|o1y*->7Lizx;DM2E1Ll3VlV2YLy(4!D(>`rML_#|qY80li{RoDi@60X zGyU7)-^5~|I=tQC30y+z+xUU{@0ZvKumi9i3sckm!(*@jd{AO!Rk@|@T@C>PVSsuS z03r?fZXP{qJweiXI6?24SfRVVh=1{cZwiB8QEA52JojLHN29E+f?8;lFa|sm!_zVE z3C$V~65k8mp5J9x@q<9w)ohqT*M5F|d`_Pv8(2IU=}rR^-z!X&Rn1um5rq4>1mD$( zakbANUTm%|plLjkyEgmL+%Rbd;f_xdO63pasRYI^fxHP>RR(HbRJR%MBVKxIm?ChBK z&=|N`iOr#j%`27mSCZ|gAgeANv-IND>e+K8n3090={wQpxq13pXE**-P1J`NCMc%VcT%jSUdTr)5=kaPN;kV-Py{m94`?$eXnsrrwP$ z*xP4GZyt~;mmUd#lo6!zpAZm++*|R%wdb>F{T2KITJ;Mc^EUvYyCHfwd;W49cszT4 zLs|JOv;+=W=uRA8bhyrQRX>LV(0XldnMM$en%=N{C+(k+%`CxdgT5gRgV(>|yGuO( zJ4HPFV0y`T&+V*vFmtbS1N*FOUS46m`bK(Q%Ul=g>LO)|o6mjw2G%0WD--9>#~+3d zI~gy9UScg#AnriYNg0_|GU1?fm!Um3mLt&G*shgerd6)bi)Pt;nE2e=1q=R_&Mccj zI`D2rltw)i)LhEWZ-!OJeclN=*#Abhl~b|(KD~cn6!H7jL*Q%;=jl{`qSa{}01=p` zt$L9;%_K#134(W`0gNz0p7Bd*3aTxFRrx&oYsdZxyj!fB3Gr)khMutu#e*V1|?Zx=U(LNr2Jyx2<#;irF)X!IYLjU4n3+=a{9X?;Iru zsnj5qM1J!JRdFD~#xQ{-So*qfFs?zXBg+GNaEmdwS-W6+%;v(sSQ}D>xwX?C(|Gl3 zLZ`;RGJ^F-01Lg4XZ{Wc+LcG+xKX5^zkPUj_cS)e*PoGP*6Qy&k`US|31dCiqbh@ zUHo}Hgm=T;bQ*963WsJndZ#8I02ZXfiISLYz{*IoAX@*E<6auZED%njB zrRXN9rE1g@e;K1&fS==4zKoi~;g3z1*PlUFC55h3 z0ty}DedcCkRJ@meNi5D;IkC0ny*<7kpQz?WV#AVMEWBCQy?8gL9(eD<1^B)lJ?3&AR6z(J1MrkY%eCXW#`na&GCqR>eK5fI7^2P$$ow`>`?0wX(ihbMO|c1R}5?CtO5r!_x~=1i<|fp4+Nh4}IK>Ls!mEblf{`?SOpC1ohr`bnSM zm)CXccCb_1)to*?mK6^(gj$6o=o+z^Q`rOI{IP23?V-P-if+MPjDAz%IudCjLSTA6 z6dq9F8sfP(Oj^?o*uGbI_@M{)n2&nZ9Vv4386?dy%_>zd!fUY;y6NVRzFIW%0OK9g z9wCBhh}!yrEvDJo%!|txF3JE#(3!v*N~w$~dgQoWMJ zo*t2PtHx~Psn?@LAIX_?`DNBN;PGQR${Y&03KVSk&SGB8fuAP*D**(a#isYVi3BGsWeKwaG|Fw>k7pf{=4l>=7Xq1GEk6_9D$A;E*8JF(Uq&JbN`UE< zRfV_>eofN{0m*-tT-vu2t`zNV4HU7*fGRAVs6IQ|sJj9eIW4rhsfP`s0LKNW4 znwW(nrxQAst}hvj-r}skJ@ad}j0Is8QW1gXN8p0+{Y2CY7Li5m&lW8|yZaTs1;93cn2ooNRiTmYr?#_LNAL(W? zX?B8q-7A;6^U0ITZ@ZT@VAmExb<8{TKr0b0vrT1D^WbK^tw(M0t)Jn-mSdNmbrj`2 zimeJ#0$R*eT@q~ciGcLyZEzAtTq(kcb%jygUidR~*@y4Z^+j2}I6EJey-mwDK2O`9 zg|l#)K(b7IDmVEu3E0_%7wNy7<~$72AGwfxaT5`{I!ks^m#9OqfG&?e36-_I;byDH z{Y8-sqqs5}(uwufOogEoGOEtB@WC2WZ6bAYupdV5J~qizB7|B_rq;0h)j$xEg{!6M zvoo=iWUGz|9vwQ0axg_);n1IQ+J}JqCKqm)Jy?HCyVg6KQ3y>@ydbA?p-tc4irzcN zW~t8udM5bpVucdTfYcuT!EHsE?WweT&8CWE&+S`1*PYm!M%Z$ve)&@<`995%*Zpmw zvI;V<>CnDLw*9|HKEf=zwOEK@)ht^|PK1n++|!EjD3`YxuhzxXG{j#gMU{#J7$ZE{ zkpJbw9_02i+;E)nWA1opaW-CZY2hWVLg^TjmX-#QcwGN4px=_IL42Y8pOsFyAdnS(3Eb0CRsA7vxg}Tn*@}3W>o;_?*3XW4;&s|l1{K0w;GEDq z=nd(8=1@k95&1f8A(GG5x!Pxu2!tA{2h#V?@+rugQF6nX@y0PQd6Nlo)vH)_Fbrox=Qy z`F3f;bnH(xG=!%nJqVN*A$S>dZ=w< zWL&>TKg*TZE@eREJ(7syUCR5z%%#N8OA@VLf>2L*nee-XpFjExl!lEyvsm!oz_VIY z>>8F>{^1ZKQJXJAn+PnP%h;~sr;GaCpgis54S%EVAjEnib~7ZRS$=w0A5;Dq?e?uE zwLkz03{Xg$!Qa=0NI}l7P49#qc+rq2Bk2$){K3NH97e%`8SP zLB;OC|G1I91yDP*Le)~~mpya)`6JBVip8K%HXHpAw2b|I>#=T~%u(?p_4HPF&1&7u zzb`Rol~O7EF4LLbNU&h`9 zcX_k# zPe4~8)KvIG9sf=$R8O8lVeRPNV};f9{rt1?wUzF1;1tR#_DBCJ25a0&JgjYPf@O$Q zA5budR9CkyEEN?b-JcP9-S}X*?aYwqd+S%3YS_PG)=X?-*Pc%>$QCwASuJ$(kNpE4 zFEXHe?hF(60Qx71ZCS?TA(W{vY5iW}j=#kVCwA6Z9pn`{*?IgIEJg7f>0AnQ!M~Iq zZ2=YsP$p0C_=hnGr>lE-JL;B-O$i_TH=r5Ma47N3-@b$0d_SSeBeBO6+HK+~lV{Nf zODLU$>WY#ZtyrQB-lkWDvnHPQPbB`2H}&ASCk~m1fhNw+bU?=1f7wtN z5Y9PmxN_Wrldr#1Hs*;t@&sx?7mxUfL9a1-y5d+iq_>j#jGd9x8*hG3sz!7-@qQ6* zEzm?)J-Tp+gvJppDrK3VRp~N&^ld?`PGNv4!g$He6_SodRni+YGfc!<_A&TYm|n05 zN8v;G0slfnuPU>Ax`>JBIB>JF(lBS$ox(3TcWKMH3UPAvqrGpafQRt6DDMx5#nC%P%72*SKxgcg znJU}0vAtZYMA(OnWUY5fIymX_%I$8LLPGkFDD5!l{TElbjS-#U(9Kz(R%R4iSEB$U zmR4IGE1ui|SXX0x@?(eNP)%~O&v#YABg_Mx7^h{<%bxU`4Ku#)6HI!*ZoH;#R*886 z=t}5QF#V#kJuD68ds@Cw2I`b`7i^n$lBW3(@njYB0uE7m1g6v{@Z86qn_k&hdKm3m z1J;lFJ9}L%o0B@gB|`EZC91h6Ofmlb4*%1>Ybk&YEbyk_cpfrz8YZ~Ia=zziVGwcZ z9a6`hy!@m}_QnHou*$5u^D2_Y3<{O8lRf&qB^NGa&MkwIU=qZ>aoCI0t>SK1dJ{Xm zoWtcKd|yEPJxA}XsC>Gvh3WpF2dg%su<{w8rZq3aK$JYLGNi%qqe8OOdiSt4?8S!E z>7!-{0Cz~NOA;-qwqI&atJjW{9Wn&TARj<;%)3jO*ESnCxLYfx`(O?ETAmXILJ&LNh8vrD36Mcr$IY z66Yy-CS~s*(O>}%uFp~n?~GE+e@iWIZW`qLO=)wz z=#;+N`6TH-tgbDJ6#WEIzsry5D6Crw;8SJjrCK5-<*)k>o*ke2Ui0K+JIl75jZYIg zuZ=Es9d~A7ZXtUf!{Oat;$G)1d6)gf9Aw+5g;Baw0VRzU46tZeSELEbfU)3mtb!Np zN0IETw6wWMSQe>gt@&98(V!(#kbHTBxlgJ3*)NvA2_I|p(+(pj^9)HK#cpyQxay`A zJO5(ruJ#UXMf^|{EEyRQbr|tLOTV_PyYWn%eC%MVHP_@_VTUe$ zs7*pfi~MTBnS~d^xxX!w+~eui?vi6l9C>mH=3yh@OyI|5Wi{u2Ku(KsfOq(94uk7w zRk5W>YGQi{(=~3$j!)$Or-6tZ(Cv!os<)k&q&KBqVn1mOqSG?~fy$jl;vW3#tL>13 zVliRLsp81>S&GpnRPaI+vgP4mq zX~vm?sS5_8Y!~-_Q$yG;XhEUkmq9ah01EWiA97bNicCWrP1R zcw{C0rNi+MiJ@1aJM}@Lp#F(5<_`gG<$UWuFYBki99*OR!K{v36_{>P7L$madYn!x z&=HLpw+f%Vg(q$%@LDtqOveBv8ESiK_uj8-((|RjS~A0?t(+8%%D!+bry%SboIFIE zA~w5e;%nUN2GKnfJ669N_Ph|FL}}1UoY-YHKk~M9WAR+OrQrX0wGN}k_QS0k_Xz4p zowG=7bK|)(m-Bgw2%cU-&@$~e`2m>en40aWpq6T-MrboaY*-#Z!Z~`jQbG+SA1PXF z8G6Z)E#%uCqOC+-cpBd5))km{N{$hVP%IjY6qR1+A9cvZp(arhS6sLvOt?Y=ytvP! zxBG$d&20u}vj!vWVt|R^XJ?K{$2T4;Ed+JV{ZRYcMTV80EphjM`Qh zy5wA>EhMPQ0(J`EPj4LOp(1L|lCA#CJ*E!8hMAnc24xLlQ^|dNN~j7(vPFATv!2&7 zdL3#lma)Dw6Rm@Q5yozm8L8hznMPkNB|&2SYbZqi^Em^Pb2yCqO&V}CX6VilKSzBs z5M7invuIiIqD-+)!LMf8w)fw5fRzd4&yNu)YmZScB(woqv1GNZqqa3u2aMFSUEh_n zf|YqjF49bf5k>z%i@Z_J?nFT{045rS+B`oa!pvHp115eDQMJ#6BHVvm2Eshcqr-D; zu3t)JX`@QM%n(cd^_cdqQ^`VHxmckIet#XO51$3{+$AZopOo<2Eqj(%C#aZyZc%$^ z4S_y=8p*N8p4Ud}Lbptl%E}G?S6976vb?Z*3kLB<7t`5g`Ta0hS0d#2Cf8Bs=j^B= z`*+@nlq)he3>@&Bm2LcJmlk+xQfH1SrY2g*7VK{)B9+eZh(IFxzgcJn#?Rh{?R6mO zjtVF?nUJTS?8OYVO*%c(5~lAe|Nf!SttHhG65#uz>0IDuVpxrSL9%jRe`?+%Sn>zq zccu9b`#j@KSPX8sq?Y(gC`yzlrefif$s#$iP#$V1ygQyy*ez7*Ru}4+&f=?Zj0NEpWW2F zr)$DD;{9AOs_%n2%gUpPLKY240-*wkxNkxqQ7e_*(-^F~@9ZeHTVF*h;%&PZx{gOR)Qy&ok-Y&J^{BuW z)lf8XipY$|2VujAyGQr_?##o-I`;iDdqnm+#Z{U^flHA4g+)DAd3qrT_(eN@kqyql zB@RInwG6r!cWt8|t2g16XC~3Hd>SL;SZQ#N;v_pae`p4=IWs6k`ElCCU@V2wNQjD5 z!T3Z`o|bE6$7Mum;_qoL-0*-!26*atnq2Ii0<6W`@EM@w{>KfW+Hhcz_$CKhO>_~g zQP?umY&D}FHF^i(v9<_uVGECH2^NV?`^A-awnUY#K;{T`?2d9BZdIi`6|NG3$O3xB zlzCzth>o2=gZkJWGCodr+~%hPchd2fyg3=fqCLXW%RU&*cJK`)Q!6mG-B{&1dYXgXcv%KJao|{b~3$%Hv8nqw*rJow_vHXnz)bzew1+d6c*+!BNee(|B_=NfO>L)$bK-DJI!^iR~4&C(gtL;_lK&Qh7kny09HCgQza z%soWyXv9hCS#4z~>J=Z(;P>AHu5!9H%((94EXSzJ*?Nie`?>MrU|3G&VqerHK#vo?e)xx0 z4VYYq!^Ytju@XN;8Y z#Y13Y8pAZ6SYpSQscch~4E$lFSl>o3)i-;+4%I*NxCI1=!WD#N&uo;{McjHOW4~rw z6Mg!kZ^BSSS*qr`5fn7be`y&Wl!jNXN*2$aTEoX!J4CyaJJVFP2g;RtFKg$ z@oS~BQ4V9Q8f}yCoI^VQcw}MpfsN*4LQ?)Dzi=B)Ib5u!l=m60M@7u67M6*uz-DWc z)zCa8jL9B(h+%fJNFa*lzzP;6i|G`Ot|&4;rUy9h7RoLkeT2^~d11DEr@-J|v(Q> z+6JnB{nLp3?Cqse_t*(dc$O_5KedX}qIg&-)H%~Hh4&-2OQDP{vy3>8l`6ZYPJ1IU zz)uxjcI&^zkfWJ@CwTZRe}{pVZ*|s2=HM2GN#bQ`k{aQrl)EDF5ty7!S`aob&m&`Z zGu%i~;QXVG8>RVl(o9o$4t(8BL~OR5O(=*EJb%4&;^bnu78_up5c9L0Fb1)EcHe)v109>bM!eRglq$ncPDb-;`7Ks;sUTnMKvJILw4D{&QQ@mI0~mv zd5WC3mpg@fc!pF|7Yc67W5vTA1O86OYFGEpf~UPP`;`*)&`k4hDCtNQVbz2zhgG{{jGE(yF}wU-DgtQQIdEXVNM2?bn(@k#KSc3d zPfS!s`On;#UYn*j0}_&L)#h!JU@#!F^6vx}l;wwLE>YIGGBiJ;GHwT(GP(a}6n9lC9p9Z+K zc?-g;I-f*fX>sdT%z{r%B8uTz z8ciWs39HVW|IRbZWP<;Do&9{pwAeH}ODdsgU<$~2PVY9wA(w1Li9M@zpDQ9vLYsGw zbhfLTqUlQ~!VL0eBb!>a<^M?nl?^$IrsV;!mjI3K|-zj(fq6`U^ znG7@^4H+q#Dev*QC-kKZVB!mDBgE&OKXs2|GTaWO9kYFif9~>~45bP?b$OXZ09>JW zf)%g(c)w3d@86BX*P4C_NIO$X%QL(R(q^qW86yCpCJC=28{H$-%;X)!#Oky4_FRUZ zY;l9w+iC*f%_amPSX;kV_;`A~gFc~)PXTBn80CqY&%Wt0!u^VT7`b9^5rR!lsf2!EB4=zA`-rYc)30fxce{0D*#LLcpC zHz?kJcgWn^(=>xLB95f2<8#w3+yAxkmW7%EwJ0GnDY_1eWSCf8J0vS%UXKOzEx?F; zkXa9IA0HeTY^vswi9?buTCL5jML1RFvtA-HJ`S7g7l0=4=OP04l%|PIEheB>zh%?a zpXfvzmKdjS>RgIw-3YE|*nZ5-Wz)iDhnELjxqrq-%Z;0J`u+gtU`CW`3uy3k=0w~p z{?Z05hS!PYsh%}t;6>!n>)%J%0Su#v^_YJ(!&yJN=2tVVDenP)$8rw8)3)~e-~VAW z;<)2I4f6W^j&spjIpu`UC)q^OUP0g58OutWn7Is z4ZKI1ngUMu??mu;>DJJ3v^3X|Ot_{a_SFp%7i_ELH%C2O5c=v?o+Co1Qe`Y&(JFc8 z{F(Z%J&(c_m^%U!-48IL0lA-}11tT-&QBQNpefR4UPySaon}xK)N0HqLne^Y<&6Q6 z=eWhCg1c1ovazy6mOP`3z>iC2ICyxJCB$5U*+5S|{|*XEwyNLg;U(2wZ)|&WYdEdu zHojQrCc9o_E5Eyfas-0A#V7s})`%z0}h6 zGx3vx;ceXo_mPRbc{J_y4tLWfH)qU)^vd%)AU$w86Sdh=2lEGvyB;V_A$BnR#nIk3-OIDK32b>)D{C_x!6y&QxT$mDg8LB#<$ zw7r+i!jnRLjJjn6K<;LdRTY6w9k$+UlXRg<{(pZU7s(!b{B;%J%aKBewij(eQU7SI zHvh=;KKbt~pb#@qV$e3Ezspp=ZVev4*h0&mKAh^ygUAPxty2m)uMq}g)5=I@K&0EpEyzeuVv3^qpS$_Cbduv=C(aNLjpZP)eI zdjIE|oqZm68+jAxiV{V24NF%}pLz{KbX{k5GF?KMDYL1Xo=wAGbZS|}gWLUT95G@T z_FrO-W@S-q>Z)yJw3W2xda4o%Jv16?8M3P-5j0=N#Pg(5mn0ud%nOGj)Oeu1GN|R^HBod7O8@v|_;l7-bmEXy+GA8HHHK3D zNLc1SsyrdpK*7L`gWW_&E38DTJyt-_=W5LA_${4S8=e0;M(qX*diVnzU~ZHj?42<2 zutiIwkq<}8C^y=3ukW0gV->sDVPs&7YO6<@u<=^J$s2X*qbW_J9X^0!9-d`g{{6?} zzHwr8VBlB#;g6So+b$LCV)LyL2piuW49=fu%z@^Z2rNm-0qN@28t564T%=0yTh}y_ z^yCRRuH2jmMfslve_BtO0Ni0uuPAfziUMgH_S~Q0b;|0bFf1&C^1n!DHKWw$mab^F zSaDK7PBUbu;_e{9;`t+kG%C?0U>4*8VXbL5sh1igRh91TRJb>5%MfjC_scA0qTTnK zw(o~jl^#-)47gB(NE*vBwRX$5 z3wEDBREc8=w5p-nMqc$)$xHmJiIP@1_*xiVE=9T#R8mW3Iaw!|lN1wyIGcs`i(554 zSBuY;v2I})1gFdDpI4uu5#-oxK(@c0;7d0@w@ysTzHIO1BC;jc_}Q{yueD4N+c2>QKz$&Z^P!KKYs-s^AXSCJl z>*20#jhy9^MfUi`gZWIE@Kn}%Jxaet8adi~7E@UyFZwd+t#r1oSVYJAIqRmG^ViN3 z<77;qi6TQXsBKx+c2P~;&->}n#s8`8ZADpkn+V9viZUeH_#7Y>qI49*V7172J}a!8 zmeae-Tdm9mVy9jWX8tAh&$47&BHSf{|Wn z2v_)Xr!%EIcgGwhEBHJ%C_CSJxU_$?qYOhs{&I3=<;&K~3Wpy+QL=b}zx|_KC&=4D zRGF~iL328LFrLanLuvXvTk^|HwbYc1GwFg~mz#42!1-H5F}USvrp*z^*}TeyVJuSp zaCjwT4XNl5yrtyLg!SLnm4;-((tCS^;H-3%N3LBv?1tlu!%h6LS59IWYPresVKiwg zy3kKNgc@U}3@Y-?i^_;B*m#=Hq76a(H$gs!NirxHBU_2k){Zz%E#3$Qrl?X#3Paho z5l^;B0OYdDd`fnk&H&>=u>4fYrO3^_A%&i0yvzzMzND`|P8TOU{8gkoF%3nm{{U?_ zKO!O=G5%Cq;}*t3rxI4Rh9TDW4_cP70cZIsrTGj<{QE33UC?>+C|)7OcC#QleK6XF zT*jXV^LEe-ftb@rn!mva8m@ObI4u_2hp2E=Kn8zt7Ne@2nw9BQ^7C)P2N^p?a*IZ5 z81g~oDqlFM5s`th8T~&Z;$Hn@*;?LuvfNNw>t1E_N5dmzyq66)hyy?5+NJ%n^30tus1{VcVU z0a2?N)}}seK_CU$6D6aZ!p~-u%A_QJ%AO!FSoA-PI4BFb%N ztzaj0qLrCLuR=L-#?P+?x7v7-$|lE|TrlNnEaf0KD7ztx(GVZq+Q^!a92s^V7Q;w~ zKt!Ta0hMHwV@#Qttg5B3i>X0=6y6)}4yLWM3p?xtCP3#3ZXWoREJoNX$BFO`2vDN# z&~rV2olnbdi6rwHxes5hW9vO#=zUd_S>63-Zm5YxzCdMQQp* zmexosV_iH{j|97s=KBm`jg4c@=`a}UQ3S|ow#I#*`e46WA~eTS^mZncIf zJB`5*W%YvB#LB4TNuF`(R;m%qx0R?hwW@u~33L<+tp8YpH`*&Ft{tlM`U^>U;(sn> z1u+Gbs#Dcf36$tx3RD4UmN9kXcVU#PHs3GP)B~P#F0&jm(na=`snl&P&j$(xZB*1Ur_l`@mu3d^>4@u$S2qmb0B z6~>FJhdt^(sZl+Q`&aG8|Li_;8c-ECn}XbnJ=+*X4t<%JylDrd8|OzaeB0KAR7_K_iP6v(H*%ZZ{Hd zt&FN(UR+{hh2|`*?Tsj9ZJF5`^o(RP!4hN~z>#Vi|IKV*GLNS`GkjA&ShC(J^@gW5 z>m|p|Yxaxcqy$Tl+(k4h){J&w@gwvQrKh32t)Ks*)K~Tp{MwL30WcK1`&`d zbd!+-a2pK-HgW%{J=Z>25}Dl%oe`;Dg=o~01YuLWx&QLo98eW{wIyTp8$bBLLYNu+ zk*Iix*C7E5vzqil%*D_V4a`@?;3~oso%JDMI~LplW>E!EoL;Idb}uxjeyDV=!fluZ zw>>lMHzebw+HbaMo1J>I)xVJdoZHu`f9XaCP}$H&4fj@n9s*xS^-RtSQPE$So7-%{&= z1JKoiAl;uc3Y@iY>8vp9GYZfW@;3s6$%zB;iCnx=*9;IWcY>#ZWp6?St#c^}-&FRn zuIoU&Qyw{aC@b_-D|}f-Uuz-#Mev^haM~u`<7B%B*Ko`&d`@U^(jurRNti*2hu(Oi zGYZmxuq3K~=aR63wQ|g5&7_*gys2tZ|7*Us+*_gX?j!#3w=Vgq<^`yD1t}n!WG=^1 zyuv~&lsF4=1FWJ%Rg;G_g28&KjUrLp4*(6#0VUKQ7)Un?DNsvJiB=8hL^TtA7Xxw= ze7bLYna(74r9DI>LLD4bZD{_U5oj$^rjZ${M1s6=HiZS4#Gh<)dKh%Q29e={xX398zw7*Vn$YsRU^@_0o{*3%doy`^80{Fzu@Bip z?;JOVe`OL7d>?7hzgDo#VDC_*Z(AMZ{Pg~X(z7H+VpC6wvf-M6@KD7S!2;o};*C2l z=^BV{JhDu4^{ikRG`OP?oFf^#HFs%0cnzt@&niZh&@^o7ROnYN7U?XLMV%xv!hIlr zaj8$KNQ^hqG=yqX8OOzi)Q6Pm|CJBtwlcs}5uqX&pe9TF;!N@=F3W5YlDOP@fKP5D7q*Cn)LLIlO%lz-PkyKq=MgvB3WuZV@Kb)-ikUI5( zg9+&B!cu{54+6S-i{I7I}ap-pqnEwD3!`9b@Q(!@+R=&pq3ly zb7@}bhPIC1B|3Lxg!ud;IW44!o)7E?8PFDKp?o81L@54*A1SOJPGo9i{ShT`upmpV zwE}{?$@R~_(*&$#dioww!v^0w!Hsdc@y5*jPxZYV34>jZId`U&ujlTLr3S>aPbEMH zWqQ~Z`?vHK-_@Onj`=7lbZ`gyz}ucn?h2xiI}P)Z^9t)GX|$aa17C4)6Dkgducjs`UTQj-iQTov_lP=Jv2?uH3IX z=;EZx9T_;qbFlt-u^yioZSVVoqrNU}S(Ar;T;3vAtK%&-2HW4$UHu)`YGPa^g$)ws z<6L|G&fRYk4g33^TBC=4Yn9jZiZ_yG1?kU{sj)+e4K;2!p9YibH(iG4z_xEEz5*4M zC*xGfenc-3!|<`=%E|TO^&RP*_}vp;kM?uTo1$M2;UZPT=|zEHbc_`DeReMQk`x?m61tP}<9F$XBj!;nsx>(vk1y``!6V zkk%hapEcsi=7C)u77sy@Z(*Lwx2ps`CajCu%EjX0yrWWbb7y_d_?N5 zq}GpF?K_0}={TBiM+mg)r{cG#p)mC#`-gh+e$^*!;j>}wI}Wr1g9ko+xrk%tp2ky& z$5KqlTR}Y5@UA94IdcpwQX$n7-YaLwifY3Jq9HGC?sgfD2cwIWe_yiIj!qkfuVcgL zkef4b?86zt4W*l73~y%Xx>rifnN0>Xua>e1$l8A7&o%BvaA0JD!pO7 z!d1*|+SVN0#oqn^5>pQsUwpiqI_+xAPU}6}G9u+NHfU%`mOH5b3VLd4El%3ijLA*t z>t*N0rX;Qoo|-xH5PWvw5+kHMa@h=Fm6kSCPR~30G^H_%E&nF;M16}yj=k>klN1&5 z?Nm51^E#ks=lt5CPdlIb$8@10ek05F&RVNvY+@J(c>76(nzDC&$V!=YlJ|%m z|Hke=K5${_3y(L1)O6$A%e1_|wc^Q{?|bdaiJ210jX68@sw*q~2hQ4;ef!yv17yd8 z8puys?V9lF_)?hCQ@*^nqc-#L)Ny$t8TR}FKYEMqRIh02)7boBTev>9hNhLU9sV%r z!fr@rM|FJ%d_CS4E-Q&>bq=ZY22>n1Cs7&{vO^a!YYR-tmK&Pc$P|egUmdW=7<9LP zKVY*vI^(uKmMJ*3NHlE9-h;+KSzRfw51|__X9aGK;@}IuTrNU;K-ILQL-N9@iLne- z{?io=+UXNYf2VrnN4FxWF!@|{autW}Z%yjUlq|<*VH0P0tn2&in1%p@=n|g0ch>S- zA)o8hKiD_V_>eiVibsv35*`=!;;l4Lr12)p^lRg%i$N1hxtjZs9nfbJDf{!&s`R!@ zW)xCDO62XUIE>C9qRA1aW@@S{XJr3QB9h!^4EYGb?Z(_X{ir->x;#1|NP2!Dhi zLkh@%0wN3`C?O0T14uY9fOLnn)Zus5`QMy#aW3||_r-I!*V_BD_I{rykOu9pTy#D8 zr~LclObCp`21x?eNUbweB2hG~Er?1yb2T=hQMTUUL zhO=#ovLXLH)*=o!THM6GTuEyoUTvOHt%^+An9sTSh96?llLVCm;bM8GsHM;V$^C`z zz|9p&_ZQSmM!o(@XZ9H6pL2%TrAD`aiLn4VIp!yBIbP3pS5)^DAs2tU^zI{vJ2HrL zu9yi>`zHxOtW_ZeNlII0&KlrKy8FPv#bl7&*ZdnLqv*q{jF_JO{>dOEf+K~C0xGJ# z2Z}A~;d1kZ&8<^N&oI4<#B`OEG1TqxiYC7u{|A-(CQo| z*qn4e)ao1=HlLC@tM9)EI=Rn9acx$zuUjKxpp(p7e<2(AD%0>#;UdfOEPY{K;e7P> z7`CgLN>01zqDvHG)phGv&8PfVD#Ace*;iQk?Or!LqrdhtHdfdIvI`~Ts%R}mF%^X} zDb{a}2^ST1$`B+Q(sVCn;;#TwFkc$Pm43|fFJH#2fr5Md-xW-@smuwr-vyUhfi1N? z%-PFjI9x)sJa=_aIJ`0rW~NI&H!_Te3G;XMWUH0fnHK3H*}oYrIm;}r_;get|6Jiz zHl&#)nWk{-df@3shGtE{dbjzWBVo#4DYWBQ4=U}0*cK(x{>qneJGwx`IQ*D@FKxp5 zdr;JbYA-X)J6xRAPq%fs!D95mB3(^yWiM}ef-H*FY8Y9yj8hSvd^TCB7=rOt)!9uO zw&u0VPw27)Is|Kyn5C}7$Kgw4W~Nf!sv z3eF1-lN*svE4)<(a-^9KA$X7Hp9GQ{>8gCr`7Y7aN%;(*6*BJtq_;eh*SYW zkLDQyNL|X?gPjMjfa&~5qxSJ4JR*!EO9!DbSN(6<+G|OEkD_D2IvFX|WoT;7bdtGFs zbL}Ov7My+CbkaNy)4v}I3Jl+}SVd~=UXT^4V7k4r-FopqiaybVNc~Z)+EPxOiEP}L zKgTp2WDVp4_bV;rn!QvWHxsU)CIfUyjyMH%vqu@Xbtd!5-s!~7PwYa$qsKJDYQU<-1lM*xE0o%$xHj#W2*&bJxy*3ybb7)T57{!H z!PwQ0(^=(FR23^Qcp=$9@Va|LnlI%eKl)MRW?a0+J2LMLcae_i)J3W-RyjGh77_GfNul!|5*n!R22?lcPNAnXC8fkOZH*Z;AG^@wNYrPswS*XNeV%6_u5c z5|a}amE{!`<>e=RqVMbc3>@Ub&i71STvS{_Oic8DBuy>^EH;ED63?X7HPxj=WyLkc zq-4dU#HA%PBt@k))iorfo=VGUDzhv8zY@Fu106~dyh>k7 z5?FNIOU~XH(GcGaZFc-ktEseLyPfYfCvKK^24^#TR?-;GL>CvS(;r(ujE%E`t1lu{ z9s}R(*Fhsn>qA3FiV``jFqE_;FUrKgm!J|Y&(ENj^ls)VTP%%mVyf?-XfEpD58l)j z!tC!F?_pSE^`qZ5=$Co}92M2<*^SH%!izsu5ER}L9v{~wDb-Adgz6h6{XIL!rqUWO z^5frV+Js@&{PQT88-k0g-%fz~-d7WOOXa(&Q+g|Tzz01my!W*->PzA>D2#JC1x z7>-$V%+)>MD37oW)mc$YmE10IL9qm#D&qzzwpigfs?dn|d$Ek`adevz{&lemc~rD0 zjb^7GEi5jwkx)&nRt#6yK{h2}%!tqlObUt<6D8&GhjJ_`Y=oJORI21@J|R-W4T>E> z6I?B(#EXeC^mIb_qDhQMT|}v`9`kn59TL`LppAwjnc*; zXzg6$=o*LE!rRt3Np%c)vgi@`Ws%Hy!t`fI3@_>%Kg(UCK8nc{uYjGI2K|sw zLiV|;yq26Qjn*sGEzJv6kmhcbWNP1F;t)(6^b&=Nyr(^pxB{eTk09`ZB^1j_!!v+VbH!l= z3VV>!*O~YrMY}!m7rUoiZ}G5{M>$(G-{T$m`&2Fcw|*5Ew1`0GCd1yDWWqK&B~j?!t*1 z>AyivfTsQef-hIzIC(SY^gjp|JB3dtAw#{% zqMO*dv~i}e=SP&H`J3}AOATze@q(q0tcK7KJ%tVGy-vraa9Y=GcSgu(eWB|YLScGB z3!D}Qec7)ZCzl;h0IO#8{N|_mpIR7HG+ab(a!eqG`LEyaTa=TuY@K%MtdcI4{cye4 zJ-Wzspk-S8K)^u&lwiygi9z0oM9wbHYIZR(c`b!LnTGCx{r2=}&no32+o`DQDOE>%es3?9$>k>3fXuLh4(l-$e?PK#w{JD9VIyg4=$pW!M|_ zcB*5yNS3QmQA;s61AERy#%xH+sm2-34h|6G= zY<7MZ*mDHDD2Ik=B~L^4ykeKz>QOhEhw@D-esK^9S_rfWf|(6EV5=rPgbposYN6VD zE+>oU@JE5Tyu$s5Da^Cwwn7uu;C-{tnCZ-SUvTrjFr2WZ>OG{D@M&Hfua4B8`v`Ao zlIjKz-g~=|+wN0HlE8MLE<;;Y%;MVlG8eB+S~w~WjdKK!Pimjt@D*iT^Pd`_l5;ql144>IW^ z0!-x-|9C>w&G@!rDwClO7~t8ehvVN+N7rbCHK%Lp{dznxH~A~ep9mU4q*t7){v6lZ z88rC40f8jq)=UUQSlzFto@rH9t%pXCQ!uP2(q`dy(wNsfcu>`)j0ml(4buB{>Q=9J z3QSHo1ZT_Ep+mjyW#uih#JT(Jav22xgwu9hHI{QkGS27Q0}h-Z6V}kL#?f?hWg1G& z7oVHs+OR(m*VnZEvjj^a+>U;|%Uas7SN+o>C(BDt z?mT8{QbRGncxEbGq-WYe-wgcN7AGmc3C-II%{xB5)j9vB-ofTPvS4mbIsaHSZ-j9_ zl>X5U&7-#+Dkv>XFX5vF0p+iNRro-G%EUNz_y}(sf-xa?P+cIUv)vpXQ>)pj?4P@# z_{p44HlXCZ<@t`+ep1DoLXDYKC^NIN}?l0`k1n_GX5) z&Ts_uwNg;nC}DTSR>r ziuY2jcbK4OkXDC>!k?39J(Di#^T4&%d0DeIyk?wDtEgzt;V>(3^RiX${r5(eMkwJ_ zh%SABQ}K1Q@VSH_L(GDzh<^x!K&_Z}Ol~1jke91HM9b`G&NE`+Oa0Wn zf_J4%O@TAh#@_D482ldx*w)ckKNkdp-HuXgS|4m*wb9hQan^^Mz3 z>$je;XF9at%Sg9FwY+u_+zT`VMlEckSPzmzg|z;aPSh2+MBDP0-@7OiYLECkqs}#v zV_mvq*$%sP*ZHjVCCV^dKP&vY z-eU7Pa8cV~7df`%0GR%2Xr~PZ+>M)tV*`#%+q6#5sOFj0n;_+&I3F}tw&HhCNfknG zn`pp1r=}r*x%S-fKfrh_CSv+(?~8QePOU~-#>{F}REr|!4>H})oBl~n!P5?;rHdaM zvaH1*CuyWAsV2%WXc*rGu{}j0i3Ms}r>v*dXK|*FsEiTVmhJL;tFN~OlebH)0wd_X zYP8K!C#I>v*T|X{+_^{x76iJ*Q5fInb4EYK*!#VBi!Sg&^}n#10xvcWEEo7%1Ns;b zNAw3~23!SSgK@}X)r`oY!AYK<;GY&lS0AgDtP6Oe%rF8-Xz0W%EHegp+AKiNEc20o zuL7GNzDO&TS}d^T$vlGb?GCTbs7X8gSVSoB)~AGXN~$a>rbk#_P0}-Sk-_WQPmige z8&m$Z{48LvSbgrA7IuH3x`Sj=Gj;=&P%4Q8})bRhnoJjlS9{*3$L+; z7?PG-kIUbYJkpWOWCHL#%LsG7b_oZ-NG7izK)Y76l#FGt@Kn;o3)4x0|LFchbEK3< zVs7O9!)Q_r#L7ONtp$#MAw2YQzZOnV52hT7V?j*bZGGG?0dcSa37_uM5PNV;WB6UX zn#K0h8H0NI5F<7!1i_%V`ZjfaOahM;pkOlkM$R-E) zoD*^j_VrHxp`3jBxmGuDrk2Rvlf|SS|N=?bale`J4I~Dl7D1WOl(@tc z^YhG`L$ZU@Jr>22Kdd)0l~eEM!@_qq=GAtq&YF2WjtkI5-Ifok5#Llq1@%RN`eI)8Mcej4@mHX?3!1lh?`{E&p$wXsa5smrx-Xl9@fFYvQ#WzK#x{dHb`dhMxJ z9M5`s*tPMK%-QQu;OFd$h3#tk-`u%j-pkT9p0lok8${nqx%7EyBv0*n?G2*6-%Anu zh(h!goa|~$HFNrS{hILIw`E}R1~J1GKzcpqkm-&%(NYI3?F9jnKe?v(QM9aC$Rzy% zboX-JN;j?bI~p5g08gXWJk1rag=6otI#U;D(P3HX^3*xmdE9MpHyT4KleaZ zN;O2^S(NpMB5CUvx_4y6{@2npt=}+Gbu`s5=~_i!PVPA8_yG$C$z2l)H@AkNCh30x D3GM{W delta 22210 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 diff --git a/tests/test_io.py b/tests/test_io.py new file mode 100644 index 00000000..15f25469 --- /dev/null +++ b/tests/test_io.py @@ -0,0 +1,60 @@ +import adelie as ad +import numpy as np +import os + + +def create_calldata( + n, p, seed +): + np.random.seed(seed) + calldata = np.zeros((n, p), dtype=np.int8) + calldata.ravel()[ + np.random.choice(np.arange(n * p), int(0.25 * n * p), replace=False) + ] = 1 + calldata.ravel()[ + np.random.choice(np.arange(n * p), int(0.05 * n * p), replace=False) + ] = 2 + return calldata + + +def test_io_snp_unphased(): + def _test(n, p, seed=0): + calldata = create_calldata(n, p, seed) + + filename = "/tmp/dummy_snp_unphased.snpdat" + handler = ad.io.snp_unphased(filename) + w_bytes = handler.write(calldata) + r_bytes = handler.read() + os.remove(filename) + + total_bytes_exp = ( + 1 + 2 * 4 + 8 * (p + 1) + 5 * np.sum(calldata != 0) + ) + assert w_bytes == total_bytes_exp + assert w_bytes == r_bytes + assert handler.rows() == n + assert handler.cols() == p + + outer = handler.outer() + nnzs = np.array([handler.nnz(j) for j in range(p)]) + inners = [handler.inner(j) for j in range(p)] + values = [handler.value(j) for j in range(p)] + + assert np.allclose((outer[1:] - outer[:-1]) / 5, nnzs) + for j in range(p): + assert np.allclose( + np.arange(n)[calldata[:, j] != 0], + inners[j], + ) + assert np.allclose( + calldata[:, j][calldata[:, j] != 0], + values[j], + ) + + dense = handler.to_dense() + assert np.allclose(dense, calldata) + + _test(1, 1) + _test(200, 32) + _test(2000, 3000) + _test(1421, 927) diff --git a/tests/test_matrix.py b/tests/test_matrix.py index 884460a1..a12239b7 100644 --- a/tests/test_matrix.py +++ b/tests/test_matrix.py @@ -1,6 +1,8 @@ +import adelie as ad import adelie.matrix as mod import numpy as np import scipy +import os def test_naive_dense(): @@ -8,64 +10,69 @@ def _test(n, p, dtype, order, seed=0): np.random.seed(seed) X = np.random.normal(0, 1, (n, p)) - for dtype in dtypes: - atol = 1e-6 if dtype == np.float32 else 1e-14 - - X = np.array(X, dtype=dtype, order=order) - cX = mod.naive_dense(X, n_threads=1) - w = np.random.uniform(1, 2, n).astype(dtype) - w = w / np.sum(w) - - # test cmul - v = np.random.normal(0, 1, n).astype(dtype) - out = cX.cmul(p//2, v) - assert np.allclose(v @ X[:, p//2], out, atol=atol) - - # test ctmul - v = np.random.normal(0, 1) - out = np.empty(n, dtype=dtype) - cX.ctmul(p//2, v, w, out) - assert np.allclose(v * (w * X[:, p//2]), out, atol=atol) - - # test bmul - v = np.random.normal(0, 1, n).astype(dtype) - out = np.empty(p // 2, dtype=dtype) - cX.bmul(0, p // 2, v, out) - assert np.allclose(v.T @ X[:, :p//2], out, atol=atol) - - # test btmul - v = np.random.normal(0, 1, p//2).astype(dtype) - out = np.empty(n, dtype=dtype) - cX.btmul(0, p // 2, v, w, out) - assert np.allclose(v.T @ (w[:, None] * X[:, :p//2]).T, out, atol=atol) - - # test mul - v = np.random.normal(0, 1, n).astype(dtype) - out = np.empty(p, dtype=dtype) - cX.mul(v, out) - assert np.allclose(v.T @ X, out, atol=atol) - - # test sp_btmul - v = np.random.normal(0, 1, (2, p // 2)).astype(dtype) - v[:, :p//4] = 0 - expected = v @ (w[:, None] * X[:, :p//2]).T + atol = 1e-6 if dtype == np.float32 else 1e-14 + + X = np.array(X, dtype=dtype, order=order) + cX = mod.dense(X, method="naive", n_threads=1) + w = np.random.uniform(1, 2, n).astype(dtype) + w = w / np.sum(w) + + # test cmul + v = np.random.normal(0, 1, n).astype(dtype) + for i in range(p): + out = cX.cmul(i, v) + assert np.allclose(v @ X[:, i], out, atol=atol) + + # test ctmul + v = np.random.normal(0, 1) + out = np.empty(n, dtype=dtype) + for i in range(p): + cX.ctmul(i, v, w, out) + assert np.allclose(v * (w * X[:, i]), out, atol=atol) + + # test bmul + v = np.random.normal(0, 1, n).astype(dtype) + for i in range(1, p+1): + out = np.empty(i, dtype=dtype) + cX.bmul(0, i, v, out) + assert np.allclose(v.T @ X[:, :i], out, atol=atol) + + # test btmul + out = np.empty(n, dtype=dtype) + for i in range(1, p+1): + v = np.random.normal(0, 1, i).astype(dtype) + cX.btmul(0, i, v, w, out) + assert np.allclose(v.T @ (w[:, None] * X[:, :i]).T, out, atol=atol) + + # test mul + v = np.random.normal(0, 1, n).astype(dtype) + out = np.empty(p, dtype=dtype) + cX.mul(v, out) + assert np.allclose(v.T @ X, out, atol=atol) + + # test sp_btmul + out = np.empty((2, n), dtype=dtype) + for i in range(1, p+1): + v = np.random.normal(0, 1, (2, i)).astype(dtype) + v[:, :i//2] = 0 + expected = v @ (w[:, None] * X[:, :i]).T v = scipy.sparse.csr_matrix(v) - out = np.empty((2, n), dtype=dtype) - cX.sp_btmul(0, p // 2, v, w, out) + cX.sp_btmul(0, i, v, w, out) assert np.allclose(expected, out, atol=atol) - # test to_dense - out = np.empty((n, p // 2), dtype=dtype, order="F") - cX.to_dense(0, p // 2, out) - assert np.allclose(X[:, :p//2], out) + # test to_dense + for i in range(1, p+1): + out = np.empty((n, i), dtype=dtype, order="F") + cX.to_dense(0, i, out) + assert np.allclose(X[:, :i], out) - # test means - X_means = np.empty(p, dtype=dtype) - cX.means(w, X_means) - assert np.allclose(np.sum(w[:, None] * X, axis=0), X_means) + # test means + X_means = np.empty(p, dtype=dtype) + cX.means(w, X_means) + assert np.allclose(np.sum(w[:, None] * X, axis=0), X_means) - assert cX.rows() == n - assert cX.cols() == p + assert cX.rows() == n + assert cX.cols() == p dtypes = [np.float32, np.float64] orders = ["C", "F"] @@ -82,25 +89,24 @@ def _test(n, p, dtype, order, seed=0): X = np.random.normal(0, 1, (n, p)) A = X.T @ X / n - for dtype in dtypes: - atol = 1e-6 if dtype == np.float32 else 1e-14 + atol = 1e-6 if dtype == np.float32 else 1e-14 - A = np.array(A, dtype=dtype, order=order) - cA = mod.cov_dense(A, n_threads=4) + A = np.array(A, dtype=dtype, order=order) + cA = mod.dense(A, method="cov", n_threads=4) - # test bmul - v = np.random.normal(0, 1, p // 2).astype(dtype) - out = np.empty(p, dtype=dtype) - cA.bmul(0, 0, p // 2, p, v, out) - assert np.allclose(v.T @ A[:p//2, :], out, atol=atol) + # test bmul + v = np.random.normal(0, 1, p // 2).astype(dtype) + out = np.empty(p, dtype=dtype) + cA.bmul(0, 0, p // 2, p, v, out) + assert np.allclose(v.T @ A[:p//2, :], out, atol=atol) - # test to_dense - out = np.empty((p // 2, p // 2), dtype=dtype, order="F") - cA.to_dense(0, 0, p//2, p//2, out) - assert np.allclose(A[:p//2, :p//2], out, atol=atol) + # test to_dense + out = np.empty((p // 2, p // 2), dtype=dtype, order="F") + cA.to_dense(0, 0, p//2, p//2, out) + assert np.allclose(A[:p//2, :p//2], out, atol=atol) - assert cA.rows() == p - assert cA.cols() == p + assert cA.rows() == p + assert cA.cols() == p dtypes = [np.float32, np.float64] orders = ["C", "F"] @@ -118,33 +124,32 @@ def _test(n, p, dtype, order, seed=0): X /= np.sqrt(n) A = X.T @ X - for dtype in dtypes: - atol = 1e-6 if dtype == np.float32 else 1e-7 + atol = 1e-6 if dtype == np.float32 else 1e-7 - X = np.array(X, dtype=dtype, order=order) - A = np.array(A, dtype=dtype, order=order) - cA = mod.cov_lazy(X, n_threads=4) + X = np.array(X, dtype=dtype, order=order) + A = np.array(A, dtype=dtype, order=order) + cA = mod.cov_lazy(X, n_threads=4) - # test bmul - v = np.random.normal(0, 1, p // 2).astype(dtype) - out = np.empty(p, dtype=dtype) - cA.bmul(0, 0, p // 2, p, v, out) - assert np.allclose(v.T @ A[:p//2, :], out, atol=atol) + # test bmul + v = np.random.normal(0, 1, p // 2).astype(dtype) + out = np.empty(p, dtype=dtype) + cA.bmul(0, 0, p // 2, p, v, out) + assert np.allclose(v.T @ A[:p//2, :], out, atol=atol) - # check that cache occured properly - if p > 2: - v = np.random.normal(0, 1, p // 2 - 1).astype(dtype) - out = np.empty(p // 2, dtype=dtype) - cA.bmul(1, 0, p // 2-1, p // 2, v, out) - assert np.allclose(v.T @ A[1:p//2, :p//2], out, atol=atol) + # check that cache occured properly + if p > 2: + v = np.random.normal(0, 1, p // 2 - 1).astype(dtype) + out = np.empty(p // 2, dtype=dtype) + cA.bmul(1, 0, p // 2-1, p // 2, v, out) + assert np.allclose(v.T @ A[1:p//2, :p//2], out, atol=atol) - # test to_dense - out = np.empty((p // 2, p // 2), dtype=dtype, order="F") - cA.to_dense(0, 0, p//2, p//2, out) - assert np.allclose(A[:p//2, :p//2], out, atol=atol) + # test to_dense + out = np.empty((p // 2, p // 2), dtype=dtype, order="F") + cA.to_dense(0, 0, p//2, p//2, out) + assert np.allclose(A[:p//2, :p//2], out, atol=atol) - assert cA.rows() == p - assert cA.cols() == p + assert cA.rows() == p + assert cA.cols() == p dtypes = [np.float32, np.float64] orders = ["C", "F"] @@ -153,3 +158,98 @@ def _test(n, p, dtype, order, seed=0): _test(2, 2, dtype, order) _test(100, 20, dtype, order) _test(20, 100, dtype, order) + + +def test_snp_unphased(): + def _test(n, p, n_files, dtype, seed=0): + np.random.seed(seed) + datas = [ + ad.data.create_snp_unphased(n, p, seed=seed+i) + for i in range(n_files) + ] + filenames = [ + f"/tmp/test_snp_unphased_{i}.snpdat" + for i in range(n_files) + ] + for i in range(n_files): + handler = ad.io.snp_unphased(filenames[i]) + handler.write(datas[i]["X"]) + cX = mod.snp_unphased( + filenames=filenames, + dtype=dtype, + ) + for f in filenames: + os.remove(f) + + X = np.concatenate([data["X"] for data in datas], axis=-1, dtype=np.int8) + + atol = 1e-6 if dtype == np.float32 else 1e-14 + + # total number of features + p_total = p * n_files + + # test cmul + v = np.random.normal(0, 1, n).astype(dtype) + for idx in range(p_total): + actual = cX.cmul(idx, v) + expected = X[:, idx] @ v + assert np.allclose(actual, expected, atol=atol) + + # test ctmul + v = np.random.normal(0, 1) + w = np.random.uniform(0, 1, n).astype(dtype) + actual = np.empty(n, dtype=dtype) + for idx in range(p_total): + cX.ctmul(idx, v, w, actual) + expected = v * w * X[:, idx] + assert np.allclose(actual, expected, atol=atol) + + # test bmul + v = np.random.normal(0, 1, n).astype(dtype) + for i in range(1, p+1): + actual = np.empty(i, dtype=dtype) + cX.bmul(0, i, v, actual) + expected = v.T @ X[:, :i] + assert np.allclose(actual, expected, atol=atol) + + # test btmul + actual = np.empty(n, dtype=dtype) + for i in range(1, p+1): + v = np.random.normal(0, 1, i).astype(dtype) + cX.btmul(0, i, v, w, actual) + expected = (v.T @ X[:, :i].T) * w[None] + assert np.allclose(actual, expected, atol=atol) + + # test sp_btmul + actual = np.empty((2, n), dtype=dtype) + for i in range(1, p+1): + v = np.random.normal(0, 1, (2, i)).astype(dtype) + v = scipy.sparse.csr_matrix(v) + cX.sp_btmul(0, i, v, w, actual) + expected = (v @ X[:, :i].T) * w[None] + assert np.allclose(actual, expected, atol=atol) + + # test to_dense + for i in range(1, p+1): + actual = np.empty((n, i), dtype=dtype, order="F") + cX.to_dense(0, i, actual) + expected = X[:, :i] + assert np.allclose(actual, expected, atol=atol) + + # test means + actual = np.empty(p_total, dtype=dtype) + cX.means(w, actual) + expected = np.concatenate([ + np.sum(data["X"] * w[:, None], axis=0) + for data in datas + ], dtype=dtype) + assert np.allclose(actual, expected, atol=atol) + + assert cX.rows() == n + assert cX.cols() == p_total + + dtypes = [np.float64, np.float32] + for dtype in dtypes: + _test(10, 20, 3, dtype) + _test(1, 13, 3, dtype) + _test(144, 1, 3, dtype) \ No newline at end of file diff --git a/tests/test_solver.py b/tests/test_solver.py index 805f8bc6..9157a63a 100644 --- a/tests/test_solver.py +++ b/tests/test_solver.py @@ -195,7 +195,7 @@ def _test(n, p, G, S, intercept=True, alpha=1, sparsity=0.95, seed=0): resid = weights * (y - intercept * y_mean) y_var = np.sum(resid ** 2 / weights) Xs = [ - ad.matrix.naive_dense(X, n_threads=2) + ad.matrix.dense(X, method="naive", n_threads=2) ] for Xpy in Xs: state = ad.state.pin_naive( @@ -272,7 +272,7 @@ def _test(n, p, G, S, alpha=1, sparsity=0.95, seed=0): # list of different types of cov matrices to test As = [ - ad.matrix.cov_dense(A, n_threads=3), + ad.matrix.dense(A, method="cov", n_threads=3), ad.matrix.cov_lazy(WsqrtX, n_threads=3), ] @@ -320,7 +320,7 @@ def _test(n, p, G, S, alpha=1, sparsity=0.95, seed=0): # ======================================================================== -def create_test_data_basil( +def create_dense( n, p, G, intercept=True, alpha=1, @@ -457,13 +457,13 @@ def run_solve_basil(state, X, y): def test_solve_basil(): def _test(n, p, G, intercept=True, alpha=1, sparsity=0.95, seed=0): - test_data = create_test_data_basil( + test_data = create_dense( n, p, G, intercept, alpha, sparsity, seed, ) X, y = test_data["X"], test_data["y"] test_data.pop("y") Xs = [ - ad.matrix.naive_dense(X, n_threads=2) + ad.matrix.dense(X, method="naive", n_threads=2) ] for Xpy in Xs: test_data["X"] = Xpy diff --git a/tests/test_state.py b/tests/test_state.py index 3553f523..cdd06350 100644 --- a/tests/test_state.py +++ b/tests/test_state.py @@ -12,7 +12,7 @@ def test_state_pin_naive(): p = 100 G = 2 - X = matrix.naive_dense(np.random.normal(0, 1, (n, p)), n_threads=4) + X = matrix.dense(np.random.normal(0, 1, (n, p)), method="naive", n_threads=4) groups = np.array([0, 1]) group_sizes = np.array([1, p-1]) alpha = 1.0 @@ -66,7 +66,7 @@ def test_state_pin_cov(): G = 2 X = np.random.normal(0, 1, (n, p)) - A = matrix.cov_dense(X.T @ X / n, n_threads=4) + A = matrix.dense(X.T @ X / n, method="cov", n_threads=4) groups = np.array([0, 1]) group_sizes = np.array([1, p-1]) alpha = 1.0 @@ -114,7 +114,7 @@ def test_state_basil_naive(): G = 2 _X = np.random.normal(0, 1, (n, p)) - X = matrix.naive_dense(_X, n_threads=4) + X = matrix.dense(_X, method="naive", n_threads=4) X_means = np.mean(_X, axis=0) groups = np.array([0, 1]) group_sizes = np.array([1, p-1])