ommx-python-mip-adapter 1.9.4__tar.gz → 2.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (19) hide show
  1. {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/PKG-INFO +2 -2
  2. {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/ommx_python_mip_adapter/adapter.py +33 -37
  3. {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/ommx_python_mip_adapter/python_mip_to_ommx.py +24 -21
  4. {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/ommx_python_mip_adapter.egg-info/PKG-INFO +2 -2
  5. ommx_python_mip_adapter-2.0.0/ommx_python_mip_adapter.egg-info/requires.txt +2 -0
  6. {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/pyproject.toml +2 -2
  7. ommx_python_mip_adapter-2.0.0/tests/test_adapter.py +41 -0
  8. {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/tests/test_constant_constraint.py +1 -1
  9. {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/tests/test_integration.py +2 -3
  10. {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/tests/test_model_to_instance.py +56 -77
  11. ommx_python_mip_adapter-1.9.4/ommx_python_mip_adapter.egg-info/requires.txt +0 -2
  12. ommx_python_mip_adapter-1.9.4/tests/test_adapter.py +0 -90
  13. {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/README.md +0 -0
  14. {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/ommx_python_mip_adapter/__init__.py +0 -0
  15. {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/ommx_python_mip_adapter/exception.py +0 -0
  16. {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/ommx_python_mip_adapter.egg-info/SOURCES.txt +0 -0
  17. {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/ommx_python_mip_adapter.egg-info/dependency_links.txt +0 -0
  18. {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/ommx_python_mip_adapter.egg-info/top_level.txt +0 -0
  19. {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ommx_python_mip_adapter
3
- Version: 1.9.4
3
+ Version: 2.0.0
4
4
  Summary: An adapter for the Python-MIP from/to OMMX.
5
5
  Author-email: "Jij Inc." <info@j-ij.com>
6
6
  Project-URL: Repository, https://github.com/Jij-Inc/ommx
@@ -15,7 +15,7 @@ Classifier: License :: OSI Approved :: Apache Software License
15
15
  Classifier: License :: OSI Approved :: MIT License
16
16
  Requires-Python: >=3.9
17
17
  Description-Content-Type: text/markdown
18
- Requires-Dist: ommx<2.0.0,>=1.9.0rc1
18
+ Requires-Dist: ommx<3.0.0,>=2.0.0b3
19
19
  Requires-Dist: mip<2.0.0,>=1.15.0
20
20
 
21
21
  OMMX adapter for Python-MIP
@@ -5,9 +5,7 @@ from typing import Optional
5
5
  import mip
6
6
 
7
7
  from ommx.adapter import SolverAdapter, InfeasibleDetected, UnboundedDetected
8
- from ommx.v1 import Instance, Constraint, DecisionVariable, Solution, State, Optimality
9
- from ommx.v1.function_pb2 import Function
10
- from ommx.v1.solution_pb2 import Relaxation
8
+ from ommx.v1 import Instance, Constraint, DecisionVariable, Solution, State, Function
11
9
 
12
10
  from .exception import OMMXPythonMIPAdapterError
13
11
 
@@ -29,14 +27,12 @@ class OMMXPythonMIPAdapter(SolverAdapter):
29
27
  :param solver: Passes a specific solver to the Python-MIP model.
30
28
  :param verbose: If True, enable Python-MIP's verbose mode
31
29
  """
32
- if ommx_instance.raw.sense == Instance.MAXIMIZE:
30
+ if ommx_instance.sense == Instance.MAXIMIZE:
33
31
  sense = mip.MAXIMIZE
34
- elif ommx_instance.raw.sense == Instance.MINIMIZE:
32
+ elif ommx_instance.sense == Instance.MINIMIZE:
35
33
  sense = mip.MINIMIZE
36
34
  else:
37
- raise OMMXPythonMIPAdapterError(
38
- f"Unsupported sense: {ommx_instance.raw.sense}"
39
- )
35
+ raise OMMXPythonMIPAdapterError(f"Unsupported sense: {ommx_instance.sense}")
40
36
  self.instance = ommx_instance
41
37
  self.model = mip.Model(
42
38
  sense=sense,
@@ -80,7 +76,6 @@ class OMMXPythonMIPAdapter(SolverAdapter):
80
76
  .. doctest::
81
77
 
82
78
  >>> from ommx.v1 import Instance, DecisionVariable
83
- >>> from ommx.v1.solution_pb2 import Optimality
84
79
  >>> from ommx_python_mip_adapter import OMMXPythonMIPAdapter
85
80
 
86
81
  >>> p = [10, 13, 18, 32, 7, 15]
@@ -89,7 +84,7 @@ class OMMXPythonMIPAdapter(SolverAdapter):
89
84
  >>> instance = Instance.from_components(
90
85
  ... decision_variables=x,
91
86
  ... objective=sum(p[i] * x[i] for i in range(6)),
92
- ... constraints=[sum(w[i] * x[i] for i in range(6)) <= 47],
87
+ ... constraints=[(sum(w[i] * x[i] for i in range(6)) <= 47).set_id(0)],
93
88
  ... sense=Instance.MAXIMIZE,
94
89
  ... )
95
90
 
@@ -103,14 +98,14 @@ class OMMXPythonMIPAdapter(SolverAdapter):
103
98
  [(0, 1.0), (1, 0.0), (2, 0.0), (3, 1.0), (4, 0.0), (5, 0.0)]
104
99
  >>> solution.feasible
105
100
  True
106
- >>> assert solution.optimality == Optimality.OPTIMALITY_OPTIMAL
101
+ >>> assert solution.optimality == Solution.OPTIMAL
107
102
 
108
103
  p[0] + p[3] = 42
109
104
  w[0] + w[3] = 46 <= 47
110
105
 
111
106
  >>> solution.objective
112
107
  42.0
113
- >>> solution.raw.evaluated_constraints[0].evaluated_value
108
+ >>> solution.get_constraint_value(0)
114
109
  -1.0
115
110
 
116
111
  Infeasible Problem
@@ -165,12 +160,12 @@ class OMMXPythonMIPAdapter(SolverAdapter):
165
160
  >>> instance = Instance.from_components(
166
161
  ... decision_variables=[x, y],
167
162
  ... objective=x + y,
168
- ... constraints=[x + y <= 1],
163
+ ... constraints=[(x + y <= 1).set_id(0)],
169
164
  ... sense=Instance.MAXIMIZE,
170
165
  ... )
171
166
 
172
167
  >>> solution = OMMXPythonMIPAdapter.solve(instance)
173
- >>> solution.raw.evaluated_constraints[0].dual_variable
168
+ >>> solution.get_dual_variable(0)
174
169
  1.0
175
170
 
176
171
  """
@@ -250,16 +245,14 @@ class OMMXPythonMIPAdapter(SolverAdapter):
250
245
  if pi is not None:
251
246
  id = int(constraint.name)
252
247
  dual_variables[id] = pi
253
- for constraint in solution.raw.evaluated_constraints:
254
- id = constraint.id
255
- if id in dual_variables:
256
- constraint.dual_variable = dual_variables[id]
248
+ for constraint_id, dual_value in dual_variables.items():
249
+ solution.set_dual_variable(constraint_id, dual_value)
257
250
 
258
251
  if data.status == mip.OptimizationStatus.OPTIMAL:
259
- solution.raw.optimality = Optimality.OPTIMALITY_OPTIMAL
252
+ solution.optimality = Solution.OPTIMAL
260
253
 
261
254
  if self._relax:
262
- solution.raw.relaxation = Relaxation.RELAXATION_LP_RELAXED
255
+ solution.relaxation = Solution.LP_RELAXED
263
256
  return solution
264
257
 
265
258
  def decode_to_state(self, data: mip.Model) -> State:
@@ -303,12 +296,12 @@ class OMMXPythonMIPAdapter(SolverAdapter):
303
296
  return State(
304
297
  entries={
305
298
  var.id: data.var_by_name(str(var.id)).x # type: ignore
306
- for var in self.instance.raw.decision_variables
299
+ for var in self.instance.decision_variables
307
300
  }
308
301
  )
309
302
 
310
303
  def _set_decision_variables(self):
311
- for var in self.instance.raw.decision_variables:
304
+ for var in self.instance.decision_variables:
312
305
  if var.kind == DecisionVariable.BINARY:
313
306
  self.model.add_var(
314
307
  name=str(var.id),
@@ -341,26 +334,29 @@ class OMMXPythonMIPAdapter(SolverAdapter):
341
334
  """
342
335
  Translate ommx.v1.Function to `mip.LinExpr` or `float`.
343
336
  """
344
- if f.HasField("constant"):
345
- return mip.LinExpr(const=f.constant) # type: ignore
346
- elif f.HasField("linear"):
347
- ommx_linear = f.linear
348
- return (
349
- mip.xsum(
350
- term.coefficient * self.model.vars[str(term.id)] # type: ignore
351
- for term in ommx_linear.terms
352
- )
353
- + ommx_linear.constant
354
- ) # type: ignore
355
- raise OMMXPythonMIPAdapterError(
356
- "The function must be either `constant` or `linear`."
337
+ degree = f.degree()
338
+ constant = f.constant_term
339
+ if degree > 1:
340
+ raise OMMXPythonMIPAdapterError(
341
+ f"Function with degree {degree} is not supported. "
342
+ "Only linear (degree 1) and constant (degree 0) functions are supported."
343
+ )
344
+ if degree == 0:
345
+ return mip.LinExpr(const=constant) # type: ignore
346
+ assert degree == 1
347
+ return (
348
+ mip.xsum(
349
+ coeff * self.model.vars[str(var_id)] # type: ignore
350
+ for var_id, coeff in f.linear_terms.items()
351
+ )
352
+ + constant
357
353
  )
358
354
 
359
355
  def _set_objective(self):
360
- self.model.objective = self._as_lin_expr(self.instance.raw.objective) # type: ignore
356
+ self.model.objective = self._as_lin_expr(self.instance.objective)
361
357
 
362
358
  def _set_constraints(self):
363
- for constraint in self.instance.raw.constraints:
359
+ for constraint in self.instance.constraints:
364
360
  lin_expr = self._as_lin_expr(constraint.function)
365
361
  if constraint.equality == Constraint.EQUAL_TO_ZERO:
366
362
  constr_expr = lin_expr == 0
@@ -4,10 +4,7 @@ from typing import final
4
4
  import mip
5
5
 
6
6
  from mip.exceptions import ParameterNotAvailable
7
- from ommx.v1.constraint_pb2 import Constraint, Equality
8
- from ommx.v1.function_pb2 import Function
9
- from ommx.v1.linear_pb2 import Linear
10
- from ommx.v1 import Instance, DecisionVariable
7
+ from ommx.v1 import Instance, DecisionVariable, Constraint, Function, Linear
11
8
 
12
9
  from .exception import OMMXPythonMIPAdapterError
13
10
 
@@ -27,35 +24,41 @@ class OMMXInstanceBuilder:
27
24
  decision_variables = []
28
25
  for var in self.model.vars:
29
26
  if var.var_type == mip.BINARY:
30
- kind = DecisionVariable.BINARY
27
+ decision_variables.append(
28
+ DecisionVariable.binary(var.idx, name=var.name)
29
+ )
31
30
  elif var.var_type == mip.INTEGER:
32
- kind = DecisionVariable.INTEGER
31
+ decision_variables.append(
32
+ DecisionVariable.integer(
33
+ var.idx, lower=var.lb, upper=var.ub, name=var.name
34
+ )
35
+ )
33
36
  elif var.var_type == mip.CONTINUOUS:
34
- kind = DecisionVariable.CONTINUOUS
37
+ decision_variables.append(
38
+ DecisionVariable.continuous(
39
+ var.idx, lower=var.lb, upper=var.ub, name=var.name
40
+ )
41
+ )
35
42
  else:
36
43
  raise OMMXPythonMIPAdapterError(
37
44
  f"Not supported variable type. "
38
45
  f"idx: {var.idx} name: {var.name}, type: {var.var_type}"
39
46
  )
40
- decision_variables.append(
41
- DecisionVariable.of_type(
42
- kind, var.idx, lower=var.lb, upper=var.ub, name=var.name
43
- )
44
- )
45
47
  return decision_variables
46
48
 
47
49
  def as_ommx_function(self, lin_expr: mip.LinExpr) -> Function:
48
- terms = [
49
- Linear.Term(id=var.idx, coefficient=coefficient) # type: ignore
50
+ terms = {
51
+ var.idx: coefficient # type: ignore
50
52
  for var, coefficient in lin_expr.expr.items()
51
- ]
53
+ }
52
54
  constant: float = lin_expr.const # type: ignore
53
55
 
54
56
  # If the terms are empty, the function is a constant.
55
57
  if len(terms) == 0:
56
- return Function(constant=constant)
58
+ return Function(constant)
57
59
  else:
58
- return Function(linear=Linear(terms=terms, constant=constant))
60
+ linear = Linear(terms=terms, constant=constant) # type: ignore
61
+ return Function(linear)
59
62
 
60
63
  def objective(self) -> Function:
61
64
  # In Python-MIP, it is allowed not to set the objective function.
@@ -65,7 +68,7 @@ class OMMXInstanceBuilder:
65
68
  try:
66
69
  objective = self.model.objective
67
70
  except ParameterNotAvailable:
68
- return Function(constant=0)
71
+ return Function(0.0)
69
72
 
70
73
  return self.as_ommx_function(objective)
71
74
 
@@ -80,15 +83,15 @@ class OMMXInstanceBuilder:
80
83
  if lin_expr.sense == "=":
81
84
  constraint = Constraint(
82
85
  id=id,
83
- equality=Equality.EQUALITY_EQUAL_TO_ZERO,
84
86
  function=self.as_ommx_function(lin_expr),
87
+ equality=Constraint.EQUAL_TO_ZERO,
85
88
  name=name,
86
89
  )
87
90
  elif lin_expr.sense == "<":
88
91
  constraint = Constraint(
89
92
  id=id,
90
- equality=Equality.EQUALITY_LESS_THAN_OR_EQUAL_TO_ZERO,
91
93
  function=self.as_ommx_function(lin_expr),
94
+ equality=Constraint.LESS_THAN_OR_EQUAL_TO_ZERO,
92
95
  name=name,
93
96
  )
94
97
  elif lin_expr.sense == ">":
@@ -96,8 +99,8 @@ class OMMXInstanceBuilder:
96
99
  # So multiply the linear expression by -1.
97
100
  constraint = Constraint(
98
101
  id=id,
99
- equality=Equality.EQUALITY_LESS_THAN_OR_EQUAL_TO_ZERO,
100
102
  function=self.as_ommx_function(-lin_expr),
103
+ equality=Constraint.LESS_THAN_OR_EQUAL_TO_ZERO,
101
104
  name=name,
102
105
  )
103
106
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ommx_python_mip_adapter
3
- Version: 1.9.4
3
+ Version: 2.0.0
4
4
  Summary: An adapter for the Python-MIP from/to OMMX.
5
5
  Author-email: "Jij Inc." <info@j-ij.com>
6
6
  Project-URL: Repository, https://github.com/Jij-Inc/ommx
@@ -15,7 +15,7 @@ Classifier: License :: OSI Approved :: Apache Software License
15
15
  Classifier: License :: OSI Approved :: MIT License
16
16
  Requires-Python: >=3.9
17
17
  Description-Content-Type: text/markdown
18
- Requires-Dist: ommx<2.0.0,>=1.9.0rc1
18
+ Requires-Dist: ommx<3.0.0,>=2.0.0b3
19
19
  Requires-Dist: mip<2.0.0,>=1.15.0
20
20
 
21
21
  OMMX adapter for Python-MIP
@@ -0,0 +1,2 @@
1
+ ommx<3.0.0,>=2.0.0b3
2
+ mip<2.0.0,>=1.15.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ommx_python_mip_adapter"
7
- version = "1.9.4"
7
+ version = "2.0.0"
8
8
 
9
9
  description = "An adapter for the Python-MIP from/to OMMX."
10
10
  authors = [{ name = "Jij Inc.", email = "info@j-ij.com" }]
@@ -27,7 +27,7 @@ classifiers = [
27
27
  # To support Python 3.12 and Apple silicon systems, this requires latest version of Python-MIP (will be 1.16.0?),
28
28
  # which does not release yet. https://github.com/coin-or/python-mip/issues/384
29
29
  # This project uses dev-dependency to use the latest version of Python-MIP not to include pre-release version in the release.
30
- dependencies = ["ommx >= 1.9.0rc1, < 2.0.0","mip >= 1.15.0, < 2.0.0"]
30
+ dependencies = ["ommx >= 2.0.0b3, < 3.0.0", "mip >= 1.15.0, < 2.0.0"]
31
31
 
32
32
  [project.urls]
33
33
  Repository = "https://github.com/Jij-Inc/ommx"
@@ -0,0 +1,41 @@
1
+ import pytest
2
+ from ommx.v1 import Instance, DecisionVariable
3
+ from ommx_python_mip_adapter import OMMXPythonMIPAdapter
4
+ from ommx_python_mip_adapter.exception import OMMXPythonMIPAdapterError
5
+
6
+
7
+ def test_error_nonlinear_objective():
8
+ # Objective function: 2.3 * x * x (variable ID should match)
9
+ x = DecisionVariable.continuous(0)
10
+ ommx_instance = Instance.from_components(
11
+ decision_variables=[x],
12
+ objective=2.3 * x * x,
13
+ constraints=[],
14
+ sense=Instance.MINIMIZE,
15
+ )
16
+
17
+ with pytest.raises(OMMXPythonMIPAdapterError) as e:
18
+ OMMXPythonMIPAdapter(ommx_instance)
19
+ assert (
20
+ "Function with degree 2 is not supported. Only linear (degree 1) and constant (degree 0) functions are supported."
21
+ in str(e.value)
22
+ )
23
+
24
+
25
+ def test_error_nonlinear_constraint():
26
+ # Objective function: 0
27
+ # Constraint: 2.3 * x * x = 0
28
+ x = DecisionVariable.continuous(1)
29
+ ommx_instance = Instance.from_components(
30
+ decision_variables=[x],
31
+ objective=0.0,
32
+ constraints=[(2.3 * x * x == 0).set_id(0)],
33
+ sense=Instance.MINIMIZE,
34
+ )
35
+
36
+ with pytest.raises(OMMXPythonMIPAdapterError) as e:
37
+ OMMXPythonMIPAdapter(ommx_instance)
38
+ assert (
39
+ "Function with degree 2 is not supported. Only linear (degree 1) and constant (degree 0) functions are supported."
40
+ in str(e.value)
41
+ )
@@ -24,7 +24,7 @@ def test_constant_constraint_feasible():
24
24
  assert solution.state.entries == {0: 1.0}
25
25
  assert solution.objective == 1.0
26
26
 
27
- assert len(solution.raw.evaluated_constraints) == 2
27
+ assert len(solution.constraints) == 2
28
28
 
29
29
 
30
30
  @pytest.mark.skip(
@@ -1,7 +1,6 @@
1
1
  import pytest
2
2
 
3
- from ommx.v1 import Instance, DecisionVariable
4
- from ommx.v1.solution_pb2 import Optimality
3
+ from ommx.v1 import Instance, DecisionVariable, Solution
5
4
  from ommx.testing import SingleFeasibleLPGenerator, DataType
6
5
 
7
6
  from ommx_python_mip_adapter import OMMXPythonMIPAdapter
@@ -74,4 +73,4 @@ def test_solution_optimality():
74
73
  )
75
74
 
76
75
  solution = OMMXPythonMIPAdapter.solve(ommx_instance)
77
- assert solution.optimality == Optimality.OPTIMALITY_OPTIMAL
76
+ assert solution.optimality == Solution.OPTIMAL
@@ -1,6 +1,6 @@
1
1
  import mip
2
2
 
3
- from ommx.v1 import Instance, DecisionVariable, Constraint
3
+ from ommx.v1 import DecisionVariable, Instance, Constraint
4
4
 
5
5
  from ommx_python_mip_adapter import model_to_instance
6
6
 
@@ -43,25 +43,25 @@ def test_milp():
43
43
  model.add_constr(-7 * x1 + 8 * x3 - 9 <= 0) # type: ignore
44
44
  model.add_constr(10 * x2 - 11 * x3 + 12 >= 0) # type: ignore
45
45
 
46
- ommx_instance = model_to_instance(model).raw
46
+ ommx_instance = model_to_instance(model)
47
47
 
48
48
  assert ommx_instance.sense == Instance.MINIMIZE
49
49
 
50
50
  # Check the decision variables
51
51
  assert len(ommx_instance.decision_variables) == 3
52
- decision_variables_x1 = ommx_instance.decision_variables[0]
52
+ decision_variables_x1 = ommx_instance.get_decision_variable_by_id(0)
53
53
  assert decision_variables_x1.id == 0
54
54
  assert decision_variables_x1.kind == DecisionVariable.CONTINUOUS
55
55
  assert decision_variables_x1.bound.lower == CONTINUOUS_LOWER_BOUND
56
56
  assert decision_variables_x1.bound.upper == CONTINUOUS_UPPER_BOUND
57
57
  assert decision_variables_x1.name == "1"
58
- decision_variables_x2 = ommx_instance.decision_variables[1]
58
+ decision_variables_x2 = ommx_instance.get_decision_variable_by_id(1)
59
59
  assert decision_variables_x2.id == 1
60
60
  assert decision_variables_x2.kind == DecisionVariable.INTEGER
61
61
  assert decision_variables_x2.bound.lower == INTEGER_LOWER_BOUND
62
62
  assert decision_variables_x2.bound.upper == INTEGER_UPPER_BOUND
63
63
  assert decision_variables_x2.name == "2"
64
- decision_variables_x3 = ommx_instance.decision_variables[2]
64
+ decision_variables_x3 = ommx_instance.get_decision_variable_by_id(2)
65
65
  assert decision_variables_x3.id == 2
66
66
  assert decision_variables_x3.kind == DecisionVariable.BINARY
67
67
  assert decision_variables_x3.bound.lower == 0
@@ -69,58 +69,43 @@ def test_milp():
69
69
  assert decision_variables_x3.name == "3"
70
70
 
71
71
  # Check the objective function
72
- assert ommx_instance.objective.HasField("linear")
73
- objective = ommx_instance.objective.linear
74
- assert objective.constant == 0
75
- assert len(objective.terms) == 3
76
- objective_term_x1 = objective.terms[0]
77
- assert objective_term_x1.id == 0
78
- assert objective_term_x1.coefficient == -1
79
- objective_term_x2 = objective.terms[1]
80
- assert objective_term_x2.id == 1
81
- assert objective_term_x2.coefficient == -2
82
- objective_term_x3 = objective.terms[2]
83
- assert objective_term_x3.id == 2
84
- assert objective_term_x3.coefficient == -3
72
+ assert ommx_instance.objective.degree() == 1
73
+ assert ommx_instance.objective.constant_term == 0
74
+ linear_terms = ommx_instance.objective.linear_terms
75
+ assert len(linear_terms) == 3
76
+ assert linear_terms[0] == -1
77
+ assert linear_terms[1] == -2
78
+ assert linear_terms[2] == -3
85
79
 
86
80
  # Check the constraints
87
81
  assert len(ommx_instance.constraints) == 3
88
82
 
89
- constraint1 = ommx_instance.constraints[0]
83
+ constraint1 = ommx_instance.get_constraint_by_id(0)
90
84
  assert constraint1.equality == Constraint.EQUAL_TO_ZERO
91
- assert constraint1.function.HasField("linear")
92
- assert constraint1.function.linear.constant == 6
93
- assert len(constraint1.function.linear.terms) == 2
94
- constraint1_term_x1 = constraint1.function.linear.terms[0]
95
- assert constraint1_term_x1.id == 0
96
- assert constraint1_term_x1.coefficient == 4
97
- constraint1_term_x2 = constraint1.function.linear.terms[1]
98
- assert constraint1_term_x2.id == 1
99
- assert constraint1_term_x2.coefficient == -5
100
-
101
- constraint2 = ommx_instance.constraints[1]
85
+ assert constraint1.function.degree() == 1
86
+ assert constraint1.function.constant_term == 6
87
+ constraint1_terms = constraint1.function.linear_terms
88
+ assert len(constraint1_terms) == 2
89
+ assert constraint1_terms[0] == 4
90
+ assert constraint1_terms[1] == -5
91
+
92
+ constraint2 = ommx_instance.get_constraint_by_id(1)
102
93
  assert constraint2.equality == Constraint.LESS_THAN_OR_EQUAL_TO_ZERO
103
- assert constraint2.function.HasField("linear")
104
- assert constraint2.function.linear.constant == -9
105
- assert len(constraint2.function.linear.terms) == 2
106
- constraint2_term_x1 = constraint2.function.linear.terms[0]
107
- assert constraint2_term_x1.id == 0
108
- assert constraint2_term_x1.coefficient == -7
109
- constraint2_term_x3 = constraint2.function.linear.terms[1]
110
- assert constraint2_term_x3.id == 2
111
- assert constraint2_term_x3.coefficient == 8
112
-
113
- constraint3 = ommx_instance.constraints[2]
94
+ assert constraint2.function.degree() == 1
95
+ assert constraint2.function.constant_term == -9
96
+ constraint2_terms = constraint2.function.linear_terms
97
+ assert len(constraint2_terms) == 2
98
+ assert constraint2_terms[0] == -7
99
+ assert constraint2_terms[2] == 8
100
+
101
+ constraint3 = ommx_instance.get_constraint_by_id(2)
114
102
  assert constraint3.equality == Constraint.LESS_THAN_OR_EQUAL_TO_ZERO
115
- assert constraint3.function.HasField("linear")
116
- assert constraint3.function.linear.constant == -12
117
- assert len(constraint3.function.linear.terms) == 2
118
- constraint3_term_x2 = constraint3.function.linear.terms[0]
119
- assert constraint3_term_x2.id == 1
120
- assert constraint3_term_x2.coefficient == -10
121
- constraint3_term_x3 = constraint3.function.linear.terms[1]
122
- assert constraint3_term_x3.id == 2
123
- assert constraint3_term_x3.coefficient == 11
103
+ assert constraint3.function.degree() == 1
104
+ assert constraint3.function.constant_term == -12
105
+ constraint3_terms = constraint3.function.linear_terms
106
+ assert len(constraint3_terms) == 2
107
+ assert constraint3_terms[1] == -10
108
+ assert constraint3_terms[2] == 11
124
109
 
125
110
 
126
111
  def test_no_objective_model():
@@ -151,52 +136,46 @@ def test_no_objective_model():
151
136
  model.add_constr(1 * x1 + 2 * x2 - 5 == 0) # type: ignore
152
137
  model.add_constr(4 * x1 + 3 * x2 - 10 == 0) # type: ignore
153
138
 
154
- ommx_instance = model_to_instance(model).raw
139
+ ommx_instance = model_to_instance(model)
155
140
 
156
141
  assert ommx_instance.sense == Instance.MAXIMIZE
157
142
 
158
143
  # Check the decision variables
159
144
  assert len(ommx_instance.decision_variables) == 2
160
- decision_variables_x1 = ommx_instance.decision_variables[0]
145
+ decision_variables_x1 = ommx_instance.get_decision_variable_by_id(0)
161
146
  assert decision_variables_x1.id == 0
162
147
  assert decision_variables_x1.kind == DecisionVariable.CONTINUOUS
163
148
  assert decision_variables_x1.bound.lower == LOWER_BOUND
164
149
  assert decision_variables_x1.bound.upper == UPPER_BOUND
165
150
  assert decision_variables_x1.name == "1"
166
- decision_variables_x2 = ommx_instance.decision_variables[1]
151
+ decision_variables_x2 = ommx_instance.get_decision_variable_by_id(1)
167
152
  assert decision_variables_x2.id == 1
168
153
  assert decision_variables_x2.kind == DecisionVariable.CONTINUOUS
169
154
  assert decision_variables_x2.bound.lower == LOWER_BOUND
170
155
  assert decision_variables_x2.bound.upper == UPPER_BOUND
171
156
  assert decision_variables_x2.name == "2"
172
157
 
173
- # check the objective function
174
- assert ommx_instance.objective.HasField("constant")
175
- assert ommx_instance.objective.constant == 0
158
+ # check the objective function - should be a zero constant
159
+ assert ommx_instance.objective.degree() == 0
160
+ assert ommx_instance.objective.num_terms() == 0 # Zero function has 0 terms
176
161
 
177
162
  # Check the constraints
178
163
  assert len(ommx_instance.constraints) == 2
179
164
 
180
- constraint1 = ommx_instance.constraints[0]
165
+ constraint1 = ommx_instance.get_constraint_by_id(0)
181
166
  assert constraint1.equality == Constraint.EQUAL_TO_ZERO
182
- assert constraint1.function.HasField("linear")
183
- assert constraint1.function.linear.constant == -5
184
- assert len(constraint1.function.linear.terms) == 2
185
- constraint1_term_x1 = constraint1.function.linear.terms[0]
186
- assert constraint1_term_x1.id == 0
187
- assert constraint1_term_x1.coefficient == 1
188
- constraint1_term_x2 = constraint1.function.linear.terms[1]
189
- assert constraint1_term_x2.id == 1
190
- assert constraint1_term_x2.coefficient == 2
191
-
192
- constraint2 = ommx_instance.constraints[1]
167
+ assert constraint1.function.degree() == 1
168
+ assert constraint1.function.constant_term == -5
169
+ constraint1_terms = constraint1.function.linear_terms
170
+ assert len(constraint1_terms) == 2
171
+ assert constraint1_terms[0] == 1
172
+ assert constraint1_terms[1] == 2
173
+
174
+ constraint2 = ommx_instance.get_constraint_by_id(1)
193
175
  assert constraint2.equality == Constraint.EQUAL_TO_ZERO
194
- assert constraint2.function.HasField("linear")
195
- assert constraint2.function.linear.constant == -10
196
- assert len(constraint2.function.linear.terms) == 2
197
- constraint2_term_x1 = constraint2.function.linear.terms[0]
198
- assert constraint2_term_x1.id == 0
199
- assert constraint2_term_x1.coefficient == 4
200
- constraint2_term_x3 = constraint2.function.linear.terms[1]
201
- assert constraint2_term_x3.id == 1
202
- assert constraint2_term_x3.coefficient == 3
176
+ assert constraint2.function.degree() == 1
177
+ assert constraint2.function.constant_term == -10
178
+ constraint2_terms = constraint2.function.linear_terms
179
+ assert len(constraint2_terms) == 2
180
+ assert constraint2_terms[0] == 4
181
+ assert constraint2_terms[1] == 3
@@ -1,2 +0,0 @@
1
- ommx<2.0.0,>=1.9.0rc1
2
- mip<2.0.0,>=1.15.0
@@ -1,90 +0,0 @@
1
- import pytest
2
-
3
- from ommx.v1.constraint_pb2 import Constraint, Equality
4
- from ommx.v1.decision_variables_pb2 import DecisionVariable as _DecisionVariable
5
- from ommx.v1.function_pb2 import Function
6
- from ommx.v1.linear_pb2 import Linear
7
- from ommx.v1.quadratic_pb2 import Quadratic
8
- from ommx.v1 import Instance, DecisionVariable
9
-
10
- from ommx_python_mip_adapter import OMMXPythonMIPAdapter
11
-
12
- from ommx_python_mip_adapter.exception import OMMXPythonMIPAdapterError
13
-
14
-
15
- def test_error_not_suppoerted_decision_variable():
16
- ommx_instance = Instance.from_components(
17
- decision_variables=[
18
- DecisionVariable(
19
- _DecisionVariable(id=1, kind=_DecisionVariable.KIND_UNSPECIFIED)
20
- )
21
- ],
22
- objective=Function(constant=0),
23
- constraints=[],
24
- sense=Instance.MINIMIZE,
25
- )
26
- with pytest.raises(OMMXPythonMIPAdapterError) as e:
27
- OMMXPythonMIPAdapter(ommx_instance)
28
- assert "Not supported decision variable" in str(e.value)
29
-
30
-
31
- def test_error_nonlinear_objective():
32
- # Objective function: 2.3 * x * x
33
- ommx_instance = Instance.from_components(
34
- decision_variables=[DecisionVariable.continuous(0)],
35
- objective=Function(
36
- quadratic=Quadratic(rows=[1], columns=[1], values=[2.3]),
37
- ),
38
- constraints=[],
39
- sense=Instance.MINIMIZE,
40
- )
41
-
42
- with pytest.raises(OMMXPythonMIPAdapterError) as e:
43
- OMMXPythonMIPAdapter(ommx_instance)
44
- assert "The function must be either `constant` or `linear`." in str(e.value)
45
-
46
-
47
- def test_error_nonlinear_constraint():
48
- # Objective function: 0
49
- # Constraint: 2.3 * x * x = 0
50
- ommx_instance = Instance.from_components(
51
- decision_variables=[DecisionVariable.continuous(1)],
52
- objective=Function(
53
- constant=0,
54
- ),
55
- constraints=[
56
- Constraint(
57
- function=Function(
58
- quadratic=Quadratic(rows=[1], columns=[1], values=[2.3]),
59
- ),
60
- equality=Equality.EQUALITY_EQUAL_TO_ZERO,
61
- ),
62
- ],
63
- sense=Instance.MINIMIZE,
64
- )
65
-
66
- with pytest.raises(OMMXPythonMIPAdapterError) as e:
67
- OMMXPythonMIPAdapter(ommx_instance)
68
- assert "The function must be either `constant` or `linear`." in str(e.value)
69
-
70
-
71
- def test_error_not_supported_constraint_equality():
72
- # Objective function: 0
73
- # Constraint: 2x ?? 0 (equality: unspecified)
74
- ommx_instance = Instance.from_components(
75
- decision_variables=[DecisionVariable.continuous(1)],
76
- objective=Function(constant=0),
77
- constraints=[
78
- Constraint(
79
- function=Function(
80
- linear=Linear(terms=[Linear.Term(id=1, coefficient=2)])
81
- ),
82
- equality=Equality.EQUALITY_UNSPECIFIED,
83
- ),
84
- ],
85
- sense=Instance.MINIMIZE,
86
- )
87
-
88
- with pytest.raises(OMMXPythonMIPAdapterError) as e:
89
- OMMXPythonMIPAdapter(ommx_instance)
90
- assert "Not supported constraint equality" in str(e.value)