def test_blockdiag_approx_on_blockdiag(self): # Check that blockdiag approx yields same solution # for block-diagonal matrix. p = 250 rho = 0.3 gamma = 0 groups = np.arange(1, p + 1, 1) dgprocess = dgp.DGP() _, _, _, _, V = dgprocess.sample_data(p=p, method="blockequi", gamma=gamma, rho=rho) for method in ["mvr", "maxent", "sdp"]: # Check that S_approx is valid S_approx = smatrix.compute_smatrix(V, method=method, max_block=10) self.check_S_properties(V, S_approx, groups) # In this case, should be the same as S_exact S_exact = smatrix.compute_smatrix(V, method=method) np.testing.assert_array_almost_equal( S_approx, S_exact, 2, err_msg= "Blockdiag approximation yields incorrect answer for blockdiag Sigma", )
def test_agreement_on_factor_models(self): # Random factor models np.random.seed(110) ks = [1, 10, 20, 50] p = 150 for k in ks: for method in ['mvr', 'maxent', 'sdp']: D = np.random.uniform(low=0.01, high=1, size=(p, )) U = np.random.randn(p, k) / np.sqrt(k) # Rescale to correlation matrix diag_Sigma = D + (np.power(U, 2)).sum(axis=1) D = D / diag_Sigma U = U / np.sqrt(diag_Sigma).reshape(-1, 1) Sigma = np.diag(D) + np.dot(U, U.T) # Check the factor method agrees with reg. method S1 = smatrix.compute_smatrix(Sigma=None, D=D, U=U, method=method, how_approx='factor') S2 = smatrix.compute_smatrix(Sigma=Sigma, method=method, solver='cd', tol=0) np.testing.assert_array_almost_equal( S1, S2, decimal=2, err_msg= f"factored/non-factored versions for method={method}, k={k} do not agree" )
def test_sdp_tolerance(self): # Get graph np.random.seed(110) Q = dgp.ErdosRenyi(p=50, tol=1e-1) V = utilities.cov2corr(utilities.chol2inv(Q)) groups = np.concatenate([np.zeros(10) + j for j in range(5)]) + 1 groups = groups.astype("int32") # Solve SDP for tol in [1e-3, 0.01, 0.02]: S = smatrix.compute_smatrix( Sigma=V, groups=groups, method="sdp", objective="pnorm", num_iter=10, tol=tol, ) G = np.hstack([np.vstack([V, V - S]), np.vstack([V - S, V])]) mineig = np.linalg.eig(G)[0].min() self.assertTrue( tol - mineig > -1 * tol / 10, f"sdp solver fails to control minimum eigenvalues: tol is {tol}, val is {mineig}", ) self.check_S_properties(V, S, groups)
def test_blockdiag_approx_on_ar1(self): # Check that blockdiag approx yields a good soln on AR1 p = 150 a = 1 b = 1 # Create trivial and nontrivial groups triv_groups = np.arange(1, p + 1, 1) nontriv_groups = np.around(triv_groups / 2 + 0.01) nontriv_groups = utilities.preprocess_groups(nontriv_groups) # Sample data np.random.seed(110) dgprocess = dgp.DGP() _, _, _, _, V = dgprocess.sample_data(p=p, method="ar1", a=a, b=b) for groups in [triv_groups, nontriv_groups]: for method in ["mvr", "maxent", "mmi", "sdp"]: # Note we can't fit group mvr/maxent without pytorch nontriv_group_flag = np.all(groups == nontriv_groups) if not TORCH_AVAILABLE and nontriv_group_flag: continue # Check that S_approx is valid S_approx = smatrix.compute_smatrix(V, method=method, groups=groups, max_block=50) self.check_S_properties(V, S_approx, groups) # For exponentially decaying offdiags, should be quite similar # This seems to not be true for groups, so skip that for now... # For now, skip grouped mvr/mmi if nontriv_group_flag: continue S_exact = smatrix.compute_smatrix(V, method=method, groups=groups) diff = np.abs(S_approx - S_exact) mean_diff = diff[S_exact != 0].mean() expected = 1e-2 if CHOLDATE_AVAILABLE else 5e-2 identifier = f"for method={method}, groups={groups}" self.assertTrue( mean_diff < expected, msg= f"Blockdiag apprx is {mean_diff} > {expected} on avg. away from exact soln {identifier}", )
def test_warnings_raised(self): print( f"torch={TORCH_AVAILABLE}, dsdp={DSDP_AVAILABLE}, choldate={CHOLDATE_AVAILABLE}" ) # Sigma p = 50 rho = 0.8 Sigma = rho * np.ones((p, p)) + (1 - rho) * np.eye(p) for method, dependency_flag, dependency_name, suppress_kwargs in zip( ['mvr', 'maxent', 'sdp'], [CHOLDATE_AVAILABLE, CHOLDATE_AVAILABLE, DSDP_AVAILABLE], ["choldate", "choldate", "dsdp"], [{ "choldate_warning": False }, { "choldate_warning": False }, { "dsdp_warning": False }]): # Check default setting with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always') smatrix.compute_smatrix(Sigma, method=method) if not dependency_flag: self.assertTrue(dependency_name in str(w[-1].message)) else: self.assertTrue(len(w) == 0) # Check that it does not trigger with correct flag with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always') smatrix.compute_smatrix(Sigma, method=method, **suppress_kwargs) self.assertTrue( len(w) == 0, "warning still triggers with choldate_warning=False")
def test_cd_SDP(self): """ Test that CD / SDP solvers get similar answers in a hard problem """ np.random.seed(110) V = utilities.cov2corr(dgp.ErdosRenyi(p=100, tol=1e-1)) S_cd = smatrix.compute_smatrix(Sigma=V, method="sdp", solver="cd", verbose=True, mu=0.95, num_iter=100) mac_cd = np.diag(S_cd).mean() S_sdp = smatrix.compute_smatrix(Sigma=V, method="sdp", solver="sdp") mac_sdp = np.diag(S_sdp).mean() np.testing.assert_almost_equal( mac_sdp, mac_cd, decimal=2, err_msg= f"compute_smatrix mac differs between SDP/CD solvers ({mac_sdp}, {mac_cd}, respectively" )
def test_maxent(self): """ Both maxent/mmi work properly """ # Sample data dgprocess = dgp.DGP() dgprocess.sample_data(p=50, method='ar1', a=3) # Check solve_maxent/solve_mmi np.random.seed(110) S_ME = smatrix.compute_smatrix(dgprocess.Sigma, method='maxent') np.random.seed(110) S_MMI = smatrix.compute_smatrix(dgprocess.Sigma, method='mmi') np.testing.assert_array_almost_equal( S_ME, S_MMI, decimal=3, err_msg=f"compute_smatrix yields diff answers for mmi, maxent") # Check solve_maxent/solve_mmi np.random.seed(110) S_ME = mrc.solve_maxent(dgprocess.Sigma) np.random.seed(110) S_MMI = mrc.solve_mmi(dgprocess.Sigma) np.testing.assert_array_almost_equal( S_ME, S_MMI, decimal=3, err_msg=f"solve_maxent and solve_mmi yield different answers") # Check maxent_loss/mmi_loss L_ME = mrc.maxent_loss(dgprocess.Sigma, S_ME) L_MMI = mrc.mmi_loss(dgprocess.Sigma, S_MMI) np.testing.assert_almost_equal( L_ME, L_MMI, decimal=3, err_msg=f"maxent_loss and mmi_loss yield different answers")
def test_equicorr_SDP(self): # Test non-group SDP on equicorrelated cov matrix p = 100 rhos = [0.6, 0.8, 0.9] solvers = ['sdp', 'cd'] scales = [1, 5] for scale in scales: for rho in rhos: V = scale * rho * np.ones( (p, p)) + scale * (1 - rho) * np.eye(p) for solver in solvers: S = smatrix.compute_smatrix(Sigma=V, method="sdp", solver=solver, verbose=True) expected = (2 - 2 * rho) * np.eye(p) np.testing.assert_almost_equal( S / scale, expected, decimal=2, err_msg= f"compute_smatrix produces incorrect S_SDP for equicorr, rho={rho}, scale={scale}, solver={solver}", )
def test_easy_sdp(self): # Test non-group SDP first n = 200 p = 50 X, _, _, _, corr_matrix, groups = dgp.block_equi_graph(n=n, p=p, gamma=0.3) # S matrix trivial_groups = np.arange(0, p, 1) + 1 S_triv = smatrix.compute_smatrix( Sigma=corr_matrix, groups=trivial_groups, method="sdp", verbose=True, ) np.testing.assert_array_almost_equal( S_triv, np.eye(p), decimal=2, err_msg= "solve_group_SDP does not produce optimal S matrix (blockequi graphs)", ) self.check_S_properties(corr_matrix, S_triv, trivial_groups) # Repeat for gaussian_knockoffs method ksampler = knockoffs.GaussianSampler( X=X, Sigma=corr_matrix, groups=trivial_groups, verbose=False, method="sdp", ) S_triv2 = ksampler.fetch_S() np.testing.assert_array_almost_equal( S_triv2, np.eye(p), decimal=2, err_msg= "solve_group_SDP does not produce optimal S matrix (blockequi graphs)", ) self.check_S_properties(corr_matrix, S_triv2, trivial_groups) # Test slightly harder case _, _, _, _, expected_out, _ = dgp.block_equi_graph(n=n, p=p, gamma=0) ksampler = knockoffs.GaussianSampler(X=X, Sigma=corr_matrix, groups=groups, verbose=False, method="sdp") S_harder = ksampler.fetch_S() np.testing.assert_almost_equal( S_harder, expected_out, decimal=2, err_msg= "solve_group_SDP does not produce optimal S matrix (blockequi graphs)", ) self.check_S_properties(corr_matrix, S_harder, groups) # Repeat for ASDP ksampler = knockoffs.GaussianSampler( X=X, Sigma=corr_matrix, groups=groups, method="ASDP", verbose=False, max_block=10, ) S_harder_ASDP = ksampler.fetch_S() np.testing.assert_almost_equal( S_harder_ASDP, expected_out, decimal=2, err_msg= "solve_group_ASDP does not produce optimal S matrix (blockequi graphs)", ) self.check_S_properties(corr_matrix, S_harder_ASDP, groups)