Skip to content

Commit

Permalink
fix: avoid infinite loop by bounding stopping criterion
Browse files Browse the repository at this point in the history
Introduce an EPSILON constant at 1e-10 to the relative stopping criterion,
to avoid machine precision issues when the primal is very small. Also
add a new test to check sparse vs dense comparisons
  • Loading branch information
jolars committed Feb 6, 2024
1 parent cb6eb09 commit 209414c
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 5 deletions.
10 changes: 8 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,14 @@ if(BUILD_TESTING)
endif()

add_executable(
tests tests/gaussian.cpp tests/prox.cpp tests/qnorm.cpp
tests/lambda_sequence.cpp tests/path.cpp tests/standardize.cpp)
tests
tests/gaussian.cpp
tests/prox.cpp
tests/qnorm.cpp
tests/lambda_sequence.cpp
tests/path.cpp
tests/standardize.cpp
tests/sparse.cpp)
target_link_libraries(tests PRIVATE slope Catch2::Catch2WithMain
Eigen3::Eigen)
target_include_directories(tests PUBLIC tests/ "${PROJECT_BINARY_DIR}")
Expand Down
8 changes: 8 additions & 0 deletions src/slope/constants.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* @internal
* @file
* @brief Definitions of constants used in libslope
*/
#pragma once

const double EPSILON = 1e-10;
5 changes: 3 additions & 2 deletions src/slope/slope.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "cd.h"
#include "clusters.h"
#include "constants.h"
#include "helpers.h"
#include "math.h"
#include "objectives.h"
Expand Down Expand Up @@ -390,7 +391,7 @@ class Slope

dual_gaps.emplace_back(dual_gap);

double tol_scaled = std::abs(primal) * this->tol;
double tol_scaled = (std::abs(primal) + EPSILON) * this->tol;

if (this->print_level > 1) {
std::cout << indent(1) << "IRLS iteration: " << it_outer << std::endl
Expand Down Expand Up @@ -431,7 +432,7 @@ class Slope

double dual_gap_inner = primal_inner - dual_inner;

double tol_inner = std::abs(primal_inner) * this->tol;
double tol_inner = (std::abs(primal_inner) + EPSILON) * this->tol;

if (this->print_level > 2) {
std::cout << indent(2) << "iteration: " << it << std::endl
Expand Down
2 changes: 1 addition & 1 deletion tests/gaussian.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ TEST_CASE("Simple low-dimensional design", "[gaussian][basic]")
double gap = dual_gaps.front().back();
double primal = primals.front().back();

REQUIRE(gap <= primal * 1e-4);
REQUIRE(gap <= (primal + 1e-10) * 1e-4);
}

TEST_CASE("X is identity", "[gaussian][identity]")
Expand Down
49 changes: 49 additions & 0 deletions tests/sparse.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#include "test_helpers.hpp"
#include <Eigen/Core>
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers.hpp>
#include <slope/slope.h>

TEST_CASE("Sparse and dense methods agree", "[gaussian][sparse]")
{
using namespace Catch::Matchers;

Eigen::Vector<double, 3> beta;
Eigen::Vector<double, 10> y;
Eigen::Matrix<double, 10, 3> x_dense;

x_dense << 0.0, 0.13339576, 0.49361983, 0.17769259, 0.66565742, 0.36972579,
0.0, 0.0, 0.0, 0.0, 0.0, 0.94280368, 0.0, 0.0, 0.3499374, 0.0, 0.22377115,
0.0, 0.0, 0.96893287, 0.95858229, 0.70486475, 0.60885162, 0.0, 0.0,
0.92902639, 0.0, 0.4978676, 0.0, 0.50022619;
Eigen::SparseMatrix<double> x_sparse = x_dense.sparseView();

beta << 1, 2, -0.9;

y = x_dense * beta;

Eigen::ArrayXd alpha = Eigen::ArrayXd::Zero(1);
Eigen::Array<double, 3, 1> lambda;
lambda << 0.5, 0.5, 0.2;

slope::Slope model;

model.setIntercept(false);
model.setStandardize(false);
model.setPrintLevel(3);
model.setPgdFreq(1);

model.fit(x_sparse, y, alpha, lambda);
auto coefs_sparse = model.getCoefs();

model.fit(x_dense, y, alpha, lambda);
auto coefs_dense = model.getCoefs();

auto dual_gaps = model.getDualGaps();
auto primals = model.getPrimals();

Eigen::VectorXd coef_sparse = coefs_sparse.col(0);
Eigen::VectorXd coef_dense = coefs_dense.col(0);

REQUIRE_THAT(coef_sparse, VectorApproxEqual(coef_dense, 1e-4));
}

0 comments on commit 209414c

Please sign in to comment.