335 lines
12 KiB
Python
335 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
# Copyright 2010-2025 Google LLC
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""Tests that the primary and auxiliary objectives are correct for model.py."""
|
|
|
|
from typing import Dict, Tuple
|
|
|
|
from absl.testing import absltest
|
|
from ortools.math_opt import model_pb2
|
|
from ortools.math_opt import model_update_pb2
|
|
from ortools.math_opt import sparse_containers_pb2
|
|
from ortools.math_opt.python import model
|
|
from ortools.math_opt.python import objectives
|
|
from ortools.math_opt.python import variables
|
|
from ortools.math_opt.python.testing import compare_proto
|
|
|
|
|
|
def _lin_terms(obj: objectives.Objective) -> Dict[variables.Variable, float]:
|
|
return {term.variable: term.coefficient for term in obj.linear_terms()}
|
|
|
|
|
|
def _quad_terms(
|
|
obj: objectives.Objective,
|
|
) -> Dict[Tuple[variables.Variable, variables.Variable], float]:
|
|
return {
|
|
(term.key.first_var, term.key.second_var): term.coefficient
|
|
for term in obj.quadratic_terms()
|
|
}
|
|
|
|
|
|
class ModelSetObjectiveTest(absltest.TestCase):
|
|
|
|
def test_maximize(self) -> None:
|
|
mod = model.Model()
|
|
x = mod.add_variable()
|
|
y = mod.add_variable()
|
|
mod.objective.set_linear_coefficient(x, 10.0)
|
|
mod.objective.set_linear_coefficient(y, 11.0)
|
|
|
|
mod.maximize(3 * x * x + 2 * x + 1)
|
|
|
|
self.assertTrue(mod.objective.is_maximize)
|
|
self.assertEqual(mod.objective.offset, 1.0)
|
|
self.assertDictEqual(_lin_terms(mod.objective), {x: 2.0})
|
|
self.assertDictEqual(_quad_terms(mod.objective), {(x, x): 3.0})
|
|
|
|
def test_maximize_linear_obj(self) -> None:
|
|
mod = model.Model()
|
|
x = mod.add_variable()
|
|
y = mod.add_variable()
|
|
mod.objective.set_linear_coefficient(x, 10.0)
|
|
mod.objective.set_linear_coefficient(y, 11.0)
|
|
|
|
mod.maximize_linear_objective(2 * x + 1)
|
|
|
|
self.assertTrue(mod.objective.is_maximize)
|
|
self.assertEqual(mod.objective.offset, 1.0)
|
|
self.assertDictEqual(_lin_terms(mod.objective), {x: 2.0})
|
|
self.assertEmpty(_quad_terms(mod.objective))
|
|
|
|
def test_maximize_linear_obj_type_error_quadratic(self) -> None:
|
|
mod = model.Model()
|
|
x = mod.add_variable()
|
|
|
|
with self.assertRaisesRegex(TypeError, "Quadratic"):
|
|
mod.maximize_linear_objective(x * x)
|
|
|
|
def test_maximize_quadratic_objective(self) -> None:
|
|
mod = model.Model()
|
|
x = mod.add_variable()
|
|
y = mod.add_variable()
|
|
mod.objective.set_linear_coefficient(x, 10.0)
|
|
mod.objective.set_linear_coefficient(y, 11.0)
|
|
|
|
mod.maximize_quadratic_objective(3 * x * x + 2 * x + 1)
|
|
|
|
self.assertTrue(mod.objective.is_maximize)
|
|
self.assertEqual(mod.objective.offset, 1.0)
|
|
self.assertDictEqual(_lin_terms(mod.objective), {x: 2.0})
|
|
self.assertDictEqual(_quad_terms(mod.objective), {(x, x): 3.0})
|
|
|
|
def test_minimize(self) -> None:
|
|
mod = model.Model()
|
|
x = mod.add_variable()
|
|
y = mod.add_variable()
|
|
mod.objective.set_linear_coefficient(x, 10.0)
|
|
mod.objective.set_linear_coefficient(y, 11.0)
|
|
mod.objective.is_maximize = True
|
|
|
|
mod.minimize(3 * x * x + 2 * x + 1)
|
|
|
|
self.assertFalse(mod.objective.is_maximize)
|
|
self.assertEqual(mod.objective.offset, 1.0)
|
|
self.assertDictEqual(_lin_terms(mod.objective), {x: 2.0})
|
|
self.assertDictEqual(_quad_terms(mod.objective), {(x, x): 3.0})
|
|
|
|
def test_minimize_linear_obj(self) -> None:
|
|
mod = model.Model()
|
|
x = mod.add_variable()
|
|
y = mod.add_variable()
|
|
mod.objective.set_linear_coefficient(x, 10.0)
|
|
mod.objective.set_linear_coefficient(y, 11.0)
|
|
mod.objective.is_maximize = True
|
|
|
|
mod.minimize_linear_objective(2 * x + 1)
|
|
|
|
self.assertFalse(mod.objective.is_maximize)
|
|
self.assertEqual(mod.objective.offset, 1.0)
|
|
self.assertDictEqual(_lin_terms(mod.objective), {x: 2.0})
|
|
self.assertEmpty(_quad_terms(mod.objective))
|
|
|
|
def test_minimize_linear_obj_type_error_quadratic(self) -> None:
|
|
mod = model.Model()
|
|
x = mod.add_variable()
|
|
|
|
with self.assertRaisesRegex(TypeError, "Quadratic"):
|
|
mod.minimize_linear_objective(x * x)
|
|
|
|
def test_minimize_quadratic_objective(self) -> None:
|
|
mod = model.Model()
|
|
x = mod.add_variable()
|
|
y = mod.add_variable()
|
|
mod.objective.set_linear_coefficient(x, 10.0)
|
|
mod.objective.set_linear_coefficient(y, 11.0)
|
|
mod.objective.is_maximize = True
|
|
|
|
mod.minimize_quadratic_objective(3 * x * x + 2 * x + 1)
|
|
|
|
self.assertFalse(mod.objective.is_maximize)
|
|
self.assertEqual(mod.objective.offset, 1.0)
|
|
self.assertDictEqual(_lin_terms(mod.objective), {x: 2.0})
|
|
self.assertDictEqual(_quad_terms(mod.objective), {(x, x): 3.0})
|
|
|
|
def test_set_objective(self) -> None:
|
|
mod = model.Model()
|
|
x = mod.add_variable()
|
|
y = mod.add_variable()
|
|
mod.objective.set_linear_coefficient(x, 10.0)
|
|
mod.objective.set_linear_coefficient(y, 11.0)
|
|
|
|
mod.set_objective(3 * x * x + 2 * x + 1, is_maximize=True)
|
|
|
|
self.assertTrue(mod.objective.is_maximize)
|
|
self.assertEqual(mod.objective.offset, 1.0)
|
|
self.assertDictEqual(_lin_terms(mod.objective), {x: 2.0})
|
|
self.assertDictEqual(_quad_terms(mod.objective), {(x, x): 3.0})
|
|
|
|
def test_set_objective_linear_obj(self) -> None:
|
|
mod = model.Model()
|
|
x = mod.add_variable()
|
|
y = mod.add_variable()
|
|
mod.objective.set_linear_coefficient(x, 10.0)
|
|
mod.objective.set_linear_coefficient(y, 11.0)
|
|
mod.objective.is_maximize = True
|
|
|
|
mod.set_linear_objective(2 * x + 1, is_maximize=False)
|
|
|
|
self.assertFalse(mod.objective.is_maximize)
|
|
self.assertEqual(mod.objective.offset, 1.0)
|
|
self.assertDictEqual(_lin_terms(mod.objective), {x: 2.0})
|
|
self.assertEmpty(_quad_terms(mod.objective))
|
|
|
|
def test_set_objective_linear_obj_type_error_quadratic(self) -> None:
|
|
mod = model.Model()
|
|
x = mod.add_variable()
|
|
|
|
with self.assertRaisesRegex(TypeError, "Quadratic"):
|
|
mod.set_linear_objective(x * x, is_maximize=True)
|
|
|
|
def test_set_objective_quadratic_objective(self) -> None:
|
|
mod = model.Model()
|
|
x = mod.add_variable()
|
|
y = mod.add_variable()
|
|
mod.objective.set_linear_coefficient(x, 10.0)
|
|
mod.objective.set_linear_coefficient(y, 11.0)
|
|
|
|
mod.set_quadratic_objective(3 * x * x + 2 * x + 1, is_maximize=True)
|
|
|
|
self.assertTrue(mod.objective.is_maximize)
|
|
self.assertEqual(mod.objective.offset, 1.0)
|
|
self.assertDictEqual(_lin_terms(mod.objective), {x: 2.0})
|
|
self.assertDictEqual(_quad_terms(mod.objective), {(x, x): 3.0})
|
|
|
|
|
|
class ModelAuxObjTest(absltest.TestCase):
|
|
|
|
def test_add_aux_obj_with_expr(self) -> None:
|
|
mod = model.Model()
|
|
x = mod.add_variable()
|
|
aux = mod.add_auxiliary_objective(priority=10, expr=3.0 * x + 4.0, name="aux")
|
|
|
|
self.assertEqual(aux.offset, 4.0)
|
|
self.assertDictEqual(_lin_terms(aux), {x: 3.0})
|
|
self.assertFalse(aux.is_maximize)
|
|
self.assertEqual(aux.priority, 10)
|
|
self.assertEqual(aux.name, "aux")
|
|
|
|
def test_add_aux_obj_with_maximize(self) -> None:
|
|
mod = model.Model()
|
|
x = mod.add_variable()
|
|
aux = mod.add_maximization_objective(3.0 * x + 4.0, priority=10, name="aux")
|
|
|
|
self.assertEqual(aux.offset, 4.0)
|
|
self.assertDictEqual(_lin_terms(aux), {x: 3.0})
|
|
self.assertTrue(aux.is_maximize)
|
|
self.assertEqual(aux.priority, 10)
|
|
self.assertEqual(aux.name, "aux")
|
|
|
|
def test_add_aux_obj_with_minimize(self) -> None:
|
|
mod = model.Model()
|
|
x = mod.add_variable()
|
|
aux = mod.add_minimization_objective(3.0 * x + 4.0, priority=10, name="aux")
|
|
|
|
self.assertEqual(aux.offset, 4.0)
|
|
self.assertDictEqual(_lin_terms(aux), {x: 3.0})
|
|
self.assertFalse(aux.is_maximize)
|
|
self.assertEqual(aux.priority, 10)
|
|
self.assertEqual(aux.name, "aux")
|
|
|
|
|
|
_PROTO_VARS = model_pb2.VariablesProto(
|
|
ids=[0],
|
|
lower_bounds=[-2.0],
|
|
upper_bounds=[2.0],
|
|
integers=[False],
|
|
names=[""],
|
|
)
|
|
|
|
|
|
class ModelObjectiveExportProtoIntegrationTest(
|
|
compare_proto.MathOptProtoAssertions, absltest.TestCase
|
|
):
|
|
"""Test Model.export_model() and UpdateTracker.export_update() for objectives.
|
|
|
|
These tests are not comprehensive, the proto generation code is completely
|
|
tested in the tests for Elemental. We just want to make sure everything is
|
|
connected.
|
|
"""
|
|
|
|
def test_export_model_with_objective(self) -> None:
|
|
mod = model.Model(primary_objective_name="obj-A")
|
|
x = mod.add_variable(lb=-2.0, ub=2.0)
|
|
mod.objective.priority = 3
|
|
mod.maximize(3 * x * x + 4 * x + 5)
|
|
proto_obj = model_pb2.ObjectiveProto(
|
|
maximize=True,
|
|
name="obj-A",
|
|
priority=3,
|
|
offset=5.0,
|
|
linear_coefficients=sparse_containers_pb2.SparseDoubleVectorProto(
|
|
ids=[0], values=[4.0]
|
|
),
|
|
quadratic_coefficients=sparse_containers_pb2.SparseDoubleMatrixProto(
|
|
row_ids=[0], column_ids=[0], coefficients=[3.0]
|
|
),
|
|
)
|
|
expected = model_pb2.ModelProto(variables=_PROTO_VARS, objective=proto_obj)
|
|
|
|
self.assert_protos_equal(mod.export_model(), expected)
|
|
|
|
def test_export_model_update_with_objective(self) -> None:
|
|
mod = model.Model(primary_objective_name="obj-A")
|
|
x = mod.add_variable(lb=-2.0, ub=2.0)
|
|
tracker = mod.add_update_tracker()
|
|
mod.objective.set_linear_coefficient(x, 2.5)
|
|
|
|
expected = model_update_pb2.ModelUpdateProto(
|
|
objective_updates=model_update_pb2.ObjectiveUpdatesProto(
|
|
linear_coefficients=sparse_containers_pb2.SparseDoubleVectorProto(
|
|
ids=[0], values=[2.5]
|
|
)
|
|
)
|
|
)
|
|
|
|
self.assert_protos_equal(tracker.export_update(), expected)
|
|
|
|
def test_export_model_with_auxiliary_objective(self) -> None:
|
|
mod = model.Model()
|
|
x = mod.add_variable(lb=-2.0, ub=2.0)
|
|
aux = mod.add_auxiliary_objective(priority=3, name="obj-A")
|
|
aux.set_to_expression(4 * x + 5)
|
|
aux.is_maximize = True
|
|
|
|
proto_obj = model_pb2.ObjectiveProto(
|
|
maximize=True,
|
|
name="obj-A",
|
|
priority=3,
|
|
offset=5.0,
|
|
linear_coefficients=sparse_containers_pb2.SparseDoubleVectorProto(
|
|
ids=[0], values=[4.0]
|
|
),
|
|
)
|
|
|
|
expected = model_pb2.ModelProto(
|
|
variables=_PROTO_VARS, auxiliary_objectives={0: proto_obj}
|
|
)
|
|
|
|
self.assert_protos_equal(mod.export_model(), expected)
|
|
|
|
def test_export_model_update_with_aux_obj_update(self) -> None:
|
|
mod = model.Model(primary_objective_name="obj-A")
|
|
x = mod.add_variable(lb=-2.0, ub=2.0)
|
|
aux = mod.add_auxiliary_objective(priority=20)
|
|
tracker = mod.add_update_tracker()
|
|
aux.set_linear_coefficient(x, 2.5)
|
|
|
|
expected = model_update_pb2.ModelUpdateProto(
|
|
auxiliary_objectives_updates=model_update_pb2.AuxiliaryObjectivesUpdatesProto(
|
|
objective_updates={
|
|
0: model_update_pb2.ObjectiveUpdatesProto(
|
|
linear_coefficients=sparse_containers_pb2.SparseDoubleVectorProto(
|
|
ids=[0], values=[2.5]
|
|
)
|
|
)
|
|
}
|
|
)
|
|
)
|
|
|
|
self.assert_protos_equal(tracker.export_update(), expected)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
absltest.main()
|