google / or-tools

Google's Operations Research tools:
https://developers.google.com/optimization/
Apache License 2.0
11.29k stars 2.13k forks source link

MathOpt: Python3.13 python_math_opt_model_storage_test failure #4420

Open Mizux opened 1 month ago

Mizux commented 1 month ago

What version of OR-Tools and what language are you using? Version: main OS: Linux CMake based build using python 3.13 linux runner

Trace:

...
136/447 Test #136: python_math_opt_model_storage_test .............................................***Failed    0.43 sec
============================= test session starts ==============================
platform linux -- Python 3.13.0, pytest-8.3.3, pluggy-1.5.0
rootdir: /home/runner/work/or-tools/or-tools
collected 54 items

../../../ortools/math_opt/python/model_storage_test.py ................. [ 31%]
.............F.......................                                    [100%]

=================================== FAILURES ===================================
___________________ ModelStorageTest.test_nans_pass_through0 ___________________

self = <model_storage_test.ModelStorageTest testMethod=test_nans_pass_through0>
storage_class = <class 'ortools.math_opt.python.hash_model_storage.HashModelStorage'>

    def test_nans_pass_through(self, storage_class: _StorageClass) -> None:
        storage = storage_class("nan_model")
        nan = math.nan
        x = storage.add_variable(nan, 2.5, True, "x")
        y = storage.add_variable(-1.0, nan, True, "y")
        c = storage.add_linear_constraint(nan, math.inf, "c")
        d = storage.add_linear_constraint(0.0, nan, "d")
        storage.set_objective_offset(nan)
        storage.set_linear_objective_coefficient(x, 1.0)
        storage.set_linear_objective_coefficient(y, nan)
        storage.set_quadratic_objective_coefficient(x, x, 3.0)
        storage.set_quadratic_objective_coefficient(x, y, nan)
        storage.set_linear_constraint_coefficient(c, x, nan)
        storage.set_linear_constraint_coefficient(c, y, 1.0)
        storage.set_linear_constraint_coefficient(d, y, nan)

        # Test the getters.
        self.assertEqual("nan_model", storage.name)
        self._assert_nan(storage.get_objective_offset())
        self._assert_nan(storage.get_variable_lb(x))
        self.assertEqual(2.5, storage.get_variable_ub(x))
        self.assertEqual(-1.0, storage.get_variable_lb(y))
        self._assert_nan(storage.get_variable_ub(y))
        self.assertEqual(1.0, storage.get_linear_objective_coefficient(x))
        self._assert_nan(storage.get_linear_objective_coefficient(y))
        self._assert_nan(storage.get_linear_constraint_lb(c))
        self.assertEqual(math.inf, storage.get_linear_constraint_ub(c))
        self.assertEqual(0.0, storage.get_linear_constraint_lb(d))
        self._assert_nan(storage.get_linear_constraint_ub(d))
        self._assert_nan(storage.get_linear_constraint_coefficient(c, x))
        self.assertEqual(1.0, storage.get_linear_constraint_coefficient(c, y))
        self.assertEqual(0.0, storage.get_linear_constraint_coefficient(d, x))
        self.assertEqual(3.0, storage.get_quadratic_objective_coefficient(x, x))
        self.assertEqual(0.0, storage.get_quadratic_objective_coefficient(y, y))
        self._assert_nan(storage.get_quadratic_objective_coefficient(x, y))
        self._assert_nan(storage.get_linear_constraint_coefficient(d, y))

        # Test the iterators that interact with the NaN values.
        self.assertCountEqual([x, y], storage.get_variables_for_linear_constraint(c))
        self.assertCountEqual([y], storage.get_variables_for_linear_constraint(d))

        self.assertCountEqual([c], storage.get_linear_constraints_with_variable(x))
        self.assertCountEqual([c, d], storage.get_linear_constraints_with_variable(y))

>       self.assertCountEqual(
            [
                _MatEntry(linear_constraint_id=c, variable_id=x, coefficient=nan),
                _MatEntry(linear_constraint_id=c, variable_id=y, coefficient=1.0),
                _MatEntry(linear_constraint_id=d, variable_id=y, coefficient=nan),
            ],
            storage.get_linear_constraint_matrix_entries(),
        )
E       AssertionError: Element counts were not equal:
E       First has 1, Second has 0:  LinearConstraintMatrixIdEntry(linear_constraint_id=0, variable_id=0, coefficient=nan)
E       First has 1, Second has 0:  LinearConstraintMatrixIdEntry(linear_constraint_id=1, variable_id=1, coefficient=nan)
E       First has 0, Second has 1:  LinearConstraintMatrixIdEntry(linear_constraint_id=0, variable_id=0, coefficient=nan)
E       First has 0, Second has 1:  LinearConstraintMatrixIdEntry(linear_constraint_id=1, variable_id=1, coefficient=nan)

../../../ortools/math_opt/python/model_storage_test.py:876: AssertionError
=========================== short test summary info ============================
FAILED ../../../ortools/math_opt/python/model_storage_test.py::ModelStorageTest::test_nans_pass_through0 - AssertionError: Element counts were not equal:
First has 1, Second has 0:  LinearConstraintMatrixIdEntry(linear_constraint_id=0, variable_id=0, coefficient=nan)
First has 1, Second has 0:  LinearConstraintMatrixIdEntry(linear_constraint_id=1, variable_id=1, coefficient=nan)
First has 0, Second has 1:  LinearConstraintMatrixIdEntry(linear_constraint_id=0, variable_id=0, coefficient=nan)
First has 0, Second has 1:  LinearConstraintMatrixIdEntry(linear_constraint_id=1, variable_id=1, coefficient=nan)
========================= 1 failed, 53 passed in 0.23s =========================

ref: https://github.com/google/or-tools/actions/runs/11457583833/job/31878157585

soppera commented 1 month ago

Indeed testing with Python 3.13:

Python 3.13.0 (main, Oct 22 2024, 14:53:46) [GCC 13.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import dataclasses
>>> import math
>>> @dataclasses.dataclass(frozen=True)
... class X:
...   x: float
...   y: float
...   
>>> X(math.nan,2) == X(math.nan,2)
False

With Python 3.12:

Python 3.12.6 (main, Sep 19 2024, 15:00:19) [GCC 13.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import dataclasses
>>> import math
>>> @dataclasses.dataclass(frozen=True)
... class X:
...   x: float
...   y: float
... 
>>> X(math.nan,2) == X(math.nan,2)
True

I.e. now math.nan is not seen equal to NaN in dataclasses. I guess this is the expected behavior and we should simply fix the test.

rma350 commented 1 month ago

The problem referenced should now be fixed internally (the code still needs to be exported). However, there is a proto assertion immedaiately after also containing NaNs, I do not know if this will also be a python 3.13 issue.