Repository: benchopt/benchopt (python)

Issue:
BUG `benchopt run` crashes when the value returned by compute and used in StoppingCriterion is zero
If the value returned by compute is zero, it crashes.

![value_zero](https://github.com/benchopt/benchopt/assets/32032118/ec319d0b-bc5a-403c-97d4-15399ed2bd19)


Pull request description:
FIX zero objective
fixes #582 

Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 3cf46b0..1250bc3 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -135,7 +135,11 @@ jobs:
               git remote -v
               git fetch origin
               git reset --hard origin/main
-              git clean -xdf
+              mv .git ..
+              mv .gitignore ..
+              rm -rf *
+              mv ../.git .
+              mv ../.gitignore .
               echo "Deploying dev docs for ${CIRCLE_BRANCH}.";
               cp -a /tmp/build/html/* .;
               if [[ -z $(git diff) ]]; then
diff --git a/benchopt/stopping_criterion.py b/benchopt/stopping_criterion.py
index c321058..ff34a4c 100644
--- a/benchopt/stopping_criterion.py
+++ b/benchopt/stopping_criterion.py
@@ -212,7 +212,9 @@ class StoppingCriterion():
         n_eval = len(objective_list) - 1
         objective = objective_list[-1][self.key_to_monitor]
         delta_objective = self._prev_objective - objective
-        delta_objective /= abs(objective_list[0][self.key_to_monitor])
+        first_objective = objective_list[0][self.key_to_monitor]
+        if first_objective != 0:
+            delta_objective /= abs(first_objective)
         self._prev_objective = objective
 
         # default value for is_flat
@@ -430,7 +432,9 @@ class SufficientProgressCriterion(StoppingCriterion):
         # Compute the current objective and update best value
         objective = objective_list[-1][self.key_to_monitor]
         delta_objective = self._best_objective - objective
-        delta_objective /= abs(objective_list[0][self.key_to_monitor])
+        first_objective = objective_list[0][self.key_to_monitor]
+        if first_objective != 0:
+            delta_objective /= abs(first_objective)
         self._best_objective = min(
             objective, self._best_objective
         )


Test patch:
diff --git a/benchopt/tests/test_stopping_criterion.py b/benchopt/tests/test_stopping_criterion.py
index b29ec01..bcc952d 100644
--- a/benchopt/tests/test_stopping_criterion.py
+++ b/benchopt/tests/test_stopping_criterion.py
@@ -294,3 +294,40 @@ def test_dual_strategy(no_debug_test):
                 run([str(benchmark.benchmark_dir),
                     *('-s test-solver -d test-dataset --no-plot').split()],
                     standalone_mode=False)
+
+
+def test_objective_equals_zero(no_debug_test):
+
+    objective = """from benchopt import BaseObjective
+
+        class Objective(BaseObjective):
+            name = "test_obj"
+            min_benchopt_version = "0.0.0"
+
+            def set_data(self, X, y): pass
+            def get_one_result(self): pass
+            def evaluate_result(self, beta): return dict(value=0)
+            def get_objective(self): return dict(X=0, y=0)
+    """
+
+    solver = """from benchopt import BaseSolver
+    from benchopt.stopping_criterion import SufficientDescentCriterion
+
+    class Solver(BaseSolver):
+        name = "test-solver"
+        stopping_criterion = SufficientDescentCriterion()
+        def set_objective(self, X, y): pass
+        def run(self, n_iter): pass
+        def get_result(self): return dict(beta=1)
+    """
+
+    with temp_benchmark(
+            objective=objective,
+            solvers=[solver]
+    ) as benchmark:
+        with CaptureRunOutput() as out:
+            run([str(benchmark.benchmark_dir),
+                *('-s test-solver -d test-dataset --no-plot -n 0').split()],
+                standalone_mode=False)
+
+    out.check_output('done', 1)