fuzzy-dl-owl2 1.0.6__py3-none-any.whl → 1.0.8__py3-none-any.whl
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.
- fuzzy_dl_owl2/fuzzydl/__init__.py +2 -1
- fuzzy_dl_owl2/fuzzydl/assertion/assertion.py +1 -0
- fuzzy_dl_owl2/fuzzydl/assertion/atomic_assertion.py +2 -0
- fuzzy_dl_owl2/fuzzydl/classification_node.py +64 -0
- fuzzy_dl_owl2/fuzzydl/concept/__init__.py +2 -0
- fuzzy_dl_owl2/fuzzydl/concept/atomic_concept.py +1 -1
- fuzzy_dl_owl2/fuzzydl/concept/choquet_integral.py +6 -0
- fuzzy_dl_owl2/fuzzydl/concept/concept.py +8 -0
- fuzzy_dl_owl2/fuzzydl/concept/concrete/crisp_concrete_concept.py +1 -0
- fuzzy_dl_owl2/fuzzydl/concept/concrete/fuzzy_concrete_concept.py +1 -0
- fuzzy_dl_owl2/fuzzydl/concept/concrete/fuzzy_number/triangular_fuzzy_number.py +12 -0
- fuzzy_dl_owl2/fuzzydl/concept/concrete/left_concrete_concept.py +3 -0
- fuzzy_dl_owl2/fuzzydl/concept/concrete/linear_concrete_concept.py +3 -0
- fuzzy_dl_owl2/fuzzydl/concept/concrete/modified_concrete_concept.py +4 -0
- fuzzy_dl_owl2/fuzzydl/concept/concrete/right_concrete_concept.py +2 -0
- fuzzy_dl_owl2/fuzzydl/concept/concrete/trapezoidal_concrete_concept.py +1 -0
- fuzzy_dl_owl2/fuzzydl/concept/concrete/triangular_concrete_concept.py +2 -0
- fuzzy_dl_owl2/fuzzydl/concept/modified/linearly_modified_concept.py +3 -0
- fuzzy_dl_owl2/fuzzydl/concept/modified/modified_concept.py +3 -0
- fuzzy_dl_owl2/fuzzydl/concept/modified/triangularly_modified_concept.py +3 -0
- fuzzy_dl_owl2/fuzzydl/concept/negated_nominal.py +3 -0
- fuzzy_dl_owl2/fuzzydl/concept/operator_concept.py +8 -0
- fuzzy_dl_owl2/fuzzydl/concept/qowa_concept.py +4 -0
- fuzzy_dl_owl2/fuzzydl/concept/quasi_sugeno_integral.py +3 -0
- fuzzy_dl_owl2/fuzzydl/concept/sigma_concept.py +71 -0
- fuzzy_dl_owl2/fuzzydl/concept/sigma_count.py +56 -0
- fuzzy_dl_owl2/fuzzydl/concept/sugeno_integral.py +4 -0
- fuzzy_dl_owl2/fuzzydl/concept_equivalence.py +5 -0
- fuzzy_dl_owl2/fuzzydl/concrete_feature.py +6 -0
- fuzzy_dl_owl2/fuzzydl/domain_axiom.py +3 -0
- fuzzy_dl_owl2/fuzzydl/feature_function.py +12 -3
- fuzzy_dl_owl2/fuzzydl/fuzzydl_to_owl2.py +3 -1
- fuzzy_dl_owl2/fuzzydl/general_concept_inclusion.py +6 -0
- fuzzy_dl_owl2/fuzzydl/individual/created_individual.py +41 -2
- fuzzy_dl_owl2/fuzzydl/individual/individual.py +14 -0
- fuzzy_dl_owl2/fuzzydl/individual/representative_individual.py +9 -0
- fuzzy_dl_owl2/fuzzydl/knowledge_base.py +2046 -250
- fuzzy_dl_owl2/fuzzydl/label.py +18 -10
- fuzzy_dl_owl2/fuzzydl/milp/expression.py +45 -24
- fuzzy_dl_owl2/fuzzydl/milp/inequation.py +20 -0
- fuzzy_dl_owl2/fuzzydl/milp/milp_helper.py +1398 -60
- fuzzy_dl_owl2/fuzzydl/milp/show_variables_helper.py +82 -0
- fuzzy_dl_owl2/fuzzydl/milp/solution.py +23 -0
- fuzzy_dl_owl2/fuzzydl/milp/term.py +4 -1
- fuzzy_dl_owl2/fuzzydl/milp/variable.py +7 -0
- fuzzy_dl_owl2/fuzzydl/modifier/linear_modifier.py +3 -0
- fuzzy_dl_owl2/fuzzydl/modifier/modifier.py +21 -0
- fuzzy_dl_owl2/fuzzydl/parser/dl_parser.py +48 -7
- fuzzy_dl_owl2/fuzzydl/primitive_concept_definition.py +7 -0
- fuzzy_dl_owl2/fuzzydl/query/__init__.py +1 -0
- fuzzy_dl_owl2/fuzzydl/query/all_instances_query.py +80 -1
- fuzzy_dl_owl2/fuzzydl/query/bnp_query.py +2 -0
- fuzzy_dl_owl2/fuzzydl/query/classification_query.py +26 -0
- fuzzy_dl_owl2/fuzzydl/query/defuzzify/defuzzify_query.py +2 -1
- fuzzy_dl_owl2/fuzzydl/query/defuzzify/lom_defuzzify_query.py +4 -0
- fuzzy_dl_owl2/fuzzydl/query/defuzzify/mom_defuzzify_query.py +6 -2
- fuzzy_dl_owl2/fuzzydl/query/defuzzify/som_defuzzify_query.py +2 -0
- fuzzy_dl_owl2/fuzzydl/query/instance_query.py +5 -0
- fuzzy_dl_owl2/fuzzydl/query/kb_satisfiable_query.py +12 -2
- fuzzy_dl_owl2/fuzzydl/query/max/max_instance_query.py +6 -1
- fuzzy_dl_owl2/fuzzydl/query/max/max_query.py +7 -1
- fuzzy_dl_owl2/fuzzydl/query/max/max_related_query.py +6 -1
- fuzzy_dl_owl2/fuzzydl/query/max/max_satisfiable_query.py +15 -1
- fuzzy_dl_owl2/fuzzydl/query/max/max_subsumes_query.py +4 -1
- fuzzy_dl_owl2/fuzzydl/query/min/min_instance_query.py +6 -1
- fuzzy_dl_owl2/fuzzydl/query/min/min_query.py +7 -1
- fuzzy_dl_owl2/fuzzydl/query/min/min_related_query.py +5 -1
- fuzzy_dl_owl2/fuzzydl/query/min/min_satisfiable_query.py +17 -1
- fuzzy_dl_owl2/fuzzydl/query/min/min_subsumes_query.py +47 -7
- fuzzy_dl_owl2/fuzzydl/query/query.py +5 -2
- fuzzy_dl_owl2/fuzzydl/query/related_query.py +8 -1
- fuzzy_dl_owl2/fuzzydl/query/satisfiable_query.py +17 -0
- fuzzy_dl_owl2/fuzzydl/query/subsumption_query.py +5 -0
- fuzzy_dl_owl2/fuzzydl/range_axiom.py +4 -0
- fuzzy_dl_owl2/fuzzydl/relation.py +5 -0
- fuzzy_dl_owl2/fuzzydl/restriction/has_value_restriction.py +2 -0
- fuzzy_dl_owl2/fuzzydl/restriction/restriction.py +3 -0
- fuzzy_dl_owl2/fuzzydl/role_parent_with_degree.py +6 -1
- fuzzy_dl_owl2/fuzzydl/util/config_reader.py +34 -2
- fuzzy_dl_owl2/fuzzydl/util/constants.py +105 -6
- fuzzy_dl_owl2/fuzzyowl2/fuzzyowl2.py +3 -1
- fuzzy_dl_owl2-1.0.8.dist-info/METADATA +817 -0
- {fuzzy_dl_owl2-1.0.6.dist-info → fuzzy_dl_owl2-1.0.8.dist-info}/RECORD +85 -81
- fuzzy_dl_owl2-1.0.6.dist-info/METADATA +0 -340
- {fuzzy_dl_owl2-1.0.6.dist-info → fuzzy_dl_owl2-1.0.8.dist-info}/LICENSE +0 -0
- {fuzzy_dl_owl2-1.0.6.dist-info → fuzzy_dl_owl2-1.0.8.dist-info}/WHEEL +0 -0
|
@@ -2,16 +2,19 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import copy
|
|
4
4
|
import os
|
|
5
|
+
import re
|
|
6
|
+
import time
|
|
7
|
+
import traceback
|
|
5
8
|
import typing
|
|
6
9
|
|
|
7
|
-
import
|
|
8
|
-
from gurobipy import GRB
|
|
10
|
+
import networkx as nx
|
|
9
11
|
|
|
10
12
|
from fuzzy_dl_owl2.fuzzydl.assertion.assertion import Assertion
|
|
11
13
|
from fuzzy_dl_owl2.fuzzydl.concept.concept import Concept
|
|
12
14
|
from fuzzy_dl_owl2.fuzzydl.concept.interface.has_value_interface import (
|
|
13
15
|
HasValueInterface,
|
|
14
16
|
)
|
|
17
|
+
from fuzzy_dl_owl2.fuzzydl.concept.sigma_count import SigmaCount
|
|
15
18
|
from fuzzy_dl_owl2.fuzzydl.degree.degree import Degree
|
|
16
19
|
from fuzzy_dl_owl2.fuzzydl.degree.degree_numeric import DegreeNumeric
|
|
17
20
|
from fuzzy_dl_owl2.fuzzydl.degree.degree_variable import DegreeVariable
|
|
@@ -30,6 +33,7 @@ from fuzzy_dl_owl2.fuzzydl.util.config_reader import ConfigReader
|
|
|
30
33
|
from fuzzy_dl_owl2.fuzzydl.util.constants import (
|
|
31
34
|
ConceptType,
|
|
32
35
|
InequalityType,
|
|
36
|
+
MILPProvider,
|
|
33
37
|
VariableType,
|
|
34
38
|
)
|
|
35
39
|
from fuzzy_dl_owl2.fuzzydl.util.util import Util
|
|
@@ -37,10 +41,17 @@ from fuzzy_dl_owl2.fuzzydl.util.util import Util
|
|
|
37
41
|
|
|
38
42
|
# @utils.singleton
|
|
39
43
|
class MILPHelper:
|
|
44
|
+
"""MILP problem manager, storing the problem and calling an external solver."""
|
|
45
|
+
|
|
46
|
+
PARTITION: bool = False
|
|
47
|
+
# Indicates whether we want to show the membership degrees to linguistic labels or not.
|
|
40
48
|
PRINT_LABELS: bool = True
|
|
49
|
+
# Indicates whether we want to show the value of the variables or not.
|
|
41
50
|
PRINT_VARIABLES: bool = True
|
|
42
51
|
|
|
43
52
|
def __init__(self) -> None:
|
|
53
|
+
self.nominal_variables: bool = False
|
|
54
|
+
self.cardinalities: list[SigmaCount] = list()
|
|
44
55
|
self.constraints: list[Inequation] = list()
|
|
45
56
|
self.crisp_concepts: set[str] = set()
|
|
46
57
|
self.crisp_roles: set[str] = set()
|
|
@@ -52,6 +63,8 @@ class MILPHelper:
|
|
|
52
63
|
|
|
53
64
|
def clone(self) -> typing.Self:
|
|
54
65
|
milp: MILPHelper = MILPHelper()
|
|
66
|
+
milp.nominal_variables = self.nominal_variables
|
|
67
|
+
milp.cardinalities = [c.clone() for c in self.cardinalities]
|
|
55
68
|
milp.constraints = [c.clone() for c in self.constraints]
|
|
56
69
|
milp.crisp_concepts = copy.deepcopy(self.crisp_concepts)
|
|
57
70
|
milp.crisp_roles = copy.deepcopy(self.crisp_roles)
|
|
@@ -62,8 +75,37 @@ class MILPHelper:
|
|
|
62
75
|
milp.variables = [v.clone() for v in self.variables]
|
|
63
76
|
return milp
|
|
64
77
|
|
|
65
|
-
def optimize(self, objective: Expression) -> Solution:
|
|
66
|
-
|
|
78
|
+
def optimize(self, objective: Expression) -> typing.Optional[Solution]:
|
|
79
|
+
"""
|
|
80
|
+
It optimizes an expression using a solvers from MILPProvider.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
objective (Expression): Expression to be optimized.
|
|
84
|
+
|
|
85
|
+
Raises:
|
|
86
|
+
ValueError: If MILPProvider is not known.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
typing.Optional[Solution]: An optimal solution of the expression
|
|
90
|
+
"""
|
|
91
|
+
Util.debug(f"Running MILP solver: {ConfigReader.MILP_PROVIDER.name}")
|
|
92
|
+
if ConfigReader.MILP_PROVIDER == MILPProvider.GUROBI:
|
|
93
|
+
return self.solve_gurobi(objective)
|
|
94
|
+
elif ConfigReader.MILP_PROVIDER == MILPProvider.MIP:
|
|
95
|
+
return self.solve_mip(objective)
|
|
96
|
+
elif ConfigReader.MILP_PROVIDER in [
|
|
97
|
+
MILPProvider.PULP,
|
|
98
|
+
MILPProvider.PULP_GLPK,
|
|
99
|
+
MILPProvider.PULP_HIGHS,
|
|
100
|
+
MILPProvider.PULP_CPLEX,
|
|
101
|
+
]:
|
|
102
|
+
return self.solve_pulp(objective)
|
|
103
|
+
# elif ConfigReader.MILP_PROVIDER == MILPProvider.SCIPY:
|
|
104
|
+
# return self.solve_scipy(objective)
|
|
105
|
+
else:
|
|
106
|
+
raise ValueError(
|
|
107
|
+
f"Unsupported MILP provider: {ConfigReader.MILP_PROVIDER.name}"
|
|
108
|
+
)
|
|
67
109
|
|
|
68
110
|
@typing.overload
|
|
69
111
|
def print_instance_of_labels(
|
|
@@ -74,6 +116,7 @@ class MILPHelper:
|
|
|
74
116
|
def print_instance_of_labels(self, name: str, value: float) -> None: ...
|
|
75
117
|
|
|
76
118
|
def print_instance_of_labels(self, *args) -> None:
|
|
119
|
+
"""Shows the membership degrees to some linguistic labels."""
|
|
77
120
|
assert len(args) in [2, 3]
|
|
78
121
|
assert isinstance(args[0], str)
|
|
79
122
|
if len(args) == 2:
|
|
@@ -87,7 +130,15 @@ class MILPHelper:
|
|
|
87
130
|
def __print_instance_of_labels_1(
|
|
88
131
|
self, f_name: str, ind_name: str, value: float
|
|
89
132
|
) -> None:
|
|
90
|
-
|
|
133
|
+
"""
|
|
134
|
+
Shows the membership degrees to some linguistic labels.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
f_name (str): Name of the feature.
|
|
138
|
+
ind_name (str): Name of the individual.
|
|
139
|
+
value (float): Value of the feature for the given individual.
|
|
140
|
+
"""
|
|
141
|
+
name: str = f"{f_name}({ind_name})"
|
|
91
142
|
labels = self.show_vars.get_labels(name)
|
|
92
143
|
for f in labels:
|
|
93
144
|
Util.info(
|
|
@@ -95,6 +146,13 @@ class MILPHelper:
|
|
|
95
146
|
)
|
|
96
147
|
|
|
97
148
|
def __print_instance_of_labels_2(self, name: str, value: float) -> None:
|
|
149
|
+
"""
|
|
150
|
+
Shows the membership degrees to some linguistic labels.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
name (str): Name of the feature (individual).
|
|
154
|
+
value (float): Value of the feature for the given individual.
|
|
155
|
+
"""
|
|
98
156
|
labels = self.show_vars.get_labels(name)
|
|
99
157
|
for f in labels:
|
|
100
158
|
Util.info(
|
|
@@ -102,6 +160,7 @@ class MILPHelper:
|
|
|
102
160
|
)
|
|
103
161
|
|
|
104
162
|
def get_new_variable(self, v_type: VariableType) -> Variable:
|
|
163
|
+
"""Gets a new variable with the indicated type."""
|
|
105
164
|
while True:
|
|
106
165
|
new_var: Variable = Variable.get_new_variable(v_type)
|
|
107
166
|
var_name = str(new_var)
|
|
@@ -210,6 +269,9 @@ class MILPHelper:
|
|
|
210
269
|
raise ValueError
|
|
211
270
|
|
|
212
271
|
def __get_variable_1(self, var_name: str) -> Variable:
|
|
272
|
+
"""
|
|
273
|
+
Gets a variable with the given name, creating a new one of type SEMI_CONTINUOUS in [0, 1] if it does not exist.
|
|
274
|
+
"""
|
|
213
275
|
if var_name in self.number_of_variables:
|
|
214
276
|
for variable in self.variables:
|
|
215
277
|
if str(variable) == var_name:
|
|
@@ -220,26 +282,69 @@ class MILPHelper:
|
|
|
220
282
|
return var
|
|
221
283
|
|
|
222
284
|
def __get_variable_2(self, var_name: str, v_type: VariableType) -> Variable:
|
|
285
|
+
"""
|
|
286
|
+
Gets a variable with the indicated name and bound.
|
|
287
|
+
|
|
288
|
+
Only used by DatatypeReasoner.
|
|
289
|
+
"""
|
|
223
290
|
var: Variable = self.get_variable(var_name)
|
|
224
291
|
var.set_type(v_type)
|
|
225
292
|
return var
|
|
226
293
|
|
|
227
294
|
def __get_variable_3(self, ass: Assertion) -> Variable:
|
|
295
|
+
"""
|
|
296
|
+
Gets a variable taking the value of a concept assertion, creating a new one of type SEMI_CONTINUOUS in [0, 1] if it does not exist.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
ass (Assertion): A fuzzy concept assertion.
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
Variable: A variable taking the value of the assertion.
|
|
303
|
+
"""
|
|
228
304
|
return self.get_variable(ass.get_individual(), ass.get_concept())
|
|
229
305
|
|
|
230
306
|
def __get_variable_4(self, rel: Relation) -> Variable:
|
|
307
|
+
"""
|
|
308
|
+
Gets a variable taking the value of a role assertion, creating a new one of type SEMI_CONTINUOUS in [0, 1] if it does not exist.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
ass (Assertion): A fuzzy role assertion.
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
Variable: A variable taking the value of the assertion.
|
|
315
|
+
"""
|
|
231
316
|
a: Individual = rel.get_subject_individual()
|
|
232
317
|
b: Individual = rel.get_object_individual()
|
|
233
318
|
role: str = rel.get_role_name()
|
|
234
319
|
return self.get_variable(a, b, role)
|
|
235
320
|
|
|
236
321
|
def __get_variable_5(self, ind: Individual, restrict: Restriction) -> Variable:
|
|
322
|
+
"""
|
|
323
|
+
Gets a variable taking the value of a universal restriction, creating a new one of type SEMI_CONTINUOUS in [0, 1] if it does not exist.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
ind (Individual): Subject individual of the restrictions.
|
|
327
|
+
restrict (Restriction): A fuzzy role assertion.
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
Variable: A variable taking the value of the assertion.
|
|
331
|
+
"""
|
|
237
332
|
var: Variable = self.get_variable(f"{ind}:{restrict.get_name_without_degree()}")
|
|
238
333
|
if self.show_vars.show_individuals(str(ind)):
|
|
239
334
|
self.show_vars.add_variable(var, str(var))
|
|
240
335
|
return var
|
|
241
336
|
|
|
242
337
|
def __get_variable_6(self, ind: Individual, c: Concept) -> Variable:
|
|
338
|
+
"""
|
|
339
|
+
Gets a variable taking the value of a concept assertion, creating a new one of type SEMI_CONTINUOUS in [0, 1] if it does not exist.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
ind (Individual): An individual.
|
|
343
|
+
c (Concept): A fuzzy concept.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
Variable: A variable taking the value of the assertion.
|
|
347
|
+
"""
|
|
243
348
|
if c.type == ConceptType.HAS_VALUE:
|
|
244
349
|
assert isinstance(c, HasValueInterface)
|
|
245
350
|
|
|
@@ -249,6 +354,16 @@ class MILPHelper:
|
|
|
249
354
|
return self.get_variable(ind, str(c))
|
|
250
355
|
|
|
251
356
|
def __get_variable_7(self, ind: Individual, concept_name: str) -> Variable:
|
|
357
|
+
"""
|
|
358
|
+
Gets a variable taking the value of a concept assertion, creating a new one of type SEMI_CONTINUOUS in [0, 1] if it does not exist.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
ind (Individual): An individual.
|
|
362
|
+
concept_name (str): A fuzzy concept name.
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
Variable: A variable taking the value of the assertion.
|
|
366
|
+
"""
|
|
252
367
|
var: Variable = self.get_variable(f"{ind}:{concept_name}")
|
|
253
368
|
if concept_name in self.crisp_concepts:
|
|
254
369
|
var.set_binary_variable()
|
|
@@ -259,11 +374,34 @@ class MILPHelper:
|
|
|
259
374
|
return var
|
|
260
375
|
|
|
261
376
|
def __get_variable_8(self, a: Individual, b: Individual, role: str) -> Variable:
|
|
377
|
+
"""
|
|
378
|
+
Gets a variable taking the value of a role assertion, creating a new one of type SEMI_CONTINUOUS in [0, 1] if it does not exist.
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
a (Individual): Object individual.
|
|
382
|
+
b (Individual): Subject individual.
|
|
383
|
+
role (str): A role name.
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
Variable: A variable taking the value of the assertion.
|
|
387
|
+
"""
|
|
262
388
|
return self.get_variable(a, b, role, VariableType.SEMI_CONTINUOUS)
|
|
263
389
|
|
|
264
390
|
def __get_variable_9(
|
|
265
391
|
self, a: Individual, b: Individual, role: str, v_type: VariableType
|
|
266
392
|
) -> Variable:
|
|
393
|
+
"""
|
|
394
|
+
Gets a variable taking the value of a role assertion, creating a new one of type SEMI_CONTINUOUS in [0, 1] if it does not exist.
|
|
395
|
+
|
|
396
|
+
Args:
|
|
397
|
+
a (Individual): Object individual.
|
|
398
|
+
b (Individual): Subject individual.
|
|
399
|
+
role (str): A role name.
|
|
400
|
+
v_type (VariableType): Type of the variable.
|
|
401
|
+
|
|
402
|
+
Returns:
|
|
403
|
+
Variable: A variable taking the value of the assertion.
|
|
404
|
+
"""
|
|
267
405
|
return self.get_variable(str(a), str(b), role, v_type)
|
|
268
406
|
|
|
269
407
|
def __get_variable_10(
|
|
@@ -281,9 +419,28 @@ class MILPHelper:
|
|
|
281
419
|
return var
|
|
282
420
|
|
|
283
421
|
def __get_variable_11(self, ind: CreatedIndividual) -> Variable:
|
|
422
|
+
"""
|
|
423
|
+
Gets a variable taking the value of a concrete individual.
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
ind (CreatedIndividual): A concrete individual.
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
Variable: A variable taking the value of the assertion.
|
|
430
|
+
"""
|
|
284
431
|
return self.get_variable(ind, VariableType.CONTINUOUS)
|
|
285
432
|
|
|
286
433
|
def __get_variable_12(self, ind: CreatedIndividual, v_type: VariableType) -> None:
|
|
434
|
+
"""
|
|
435
|
+
Gets a variable taking the value of a concrete individual.
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
ind (CreatedIndividual): A concrete individual.
|
|
439
|
+
v_type (VariableType): Type of the variable.
|
|
440
|
+
|
|
441
|
+
Returns:
|
|
442
|
+
Variable: A variable taking the value of the assertion.
|
|
443
|
+
"""
|
|
287
444
|
if ind.get_parent() is None:
|
|
288
445
|
parent_name: str = "unknown_parent"
|
|
289
446
|
else:
|
|
@@ -304,6 +461,18 @@ class MILPHelper:
|
|
|
304
461
|
x_c.set_type(v_type)
|
|
305
462
|
return x_c
|
|
306
463
|
|
|
464
|
+
def exists_variable(self, a: Individual, b: Individual, role: str) -> bool:
|
|
465
|
+
"""
|
|
466
|
+
Checks if a variable taking the value of a role assertion exists.
|
|
467
|
+
|
|
468
|
+
Args:
|
|
469
|
+
a (Individual): Object individual.
|
|
470
|
+
b (Individual): Subject individual.
|
|
471
|
+
role (str): A role name.
|
|
472
|
+
"""
|
|
473
|
+
var_name: str = f"({a},{b}):{role}"
|
|
474
|
+
return var_name in self.number_of_variables
|
|
475
|
+
|
|
307
476
|
@typing.overload
|
|
308
477
|
def has_variable(self, name: str) -> bool: ...
|
|
309
478
|
|
|
@@ -320,9 +489,11 @@ class MILPHelper:
|
|
|
320
489
|
raise ValueError
|
|
321
490
|
|
|
322
491
|
def __has_variable_1(self, name: str) -> bool:
|
|
492
|
+
"""Cheks if there is a variable with the given name."""
|
|
323
493
|
return name in self.number_of_variables
|
|
324
494
|
|
|
325
495
|
def __has_variable_2(self, ass: Assertion) -> bool:
|
|
496
|
+
"""Cheks if there is a variable for a concept assertion."""
|
|
326
497
|
return self.has_variable(ass.get_name_without_degree())
|
|
327
498
|
|
|
328
499
|
@typing.overload
|
|
@@ -341,22 +512,69 @@ class MILPHelper:
|
|
|
341
512
|
return self.__get_nominal_variable_2(*args)
|
|
342
513
|
|
|
343
514
|
def __get_nominal_variable_1(self, i1: str) -> Variable:
|
|
515
|
+
"""
|
|
516
|
+
Gets a variable taking the value of an individual i1 belonging to the nominal concept {i1}.
|
|
517
|
+
|
|
518
|
+
Args:
|
|
519
|
+
i1 (str): An individual.
|
|
520
|
+
|
|
521
|
+
Returns:
|
|
522
|
+
Variable: A variable taking the value of the assertion i1:{i1}.
|
|
523
|
+
"""
|
|
344
524
|
return self.get_nominal_variable(i1, i1)
|
|
345
525
|
|
|
346
526
|
def __get_nominal_variable_2(self, i1: str, i2: str) -> Variable:
|
|
527
|
+
"""
|
|
528
|
+
Gets a variable taking the value of an individual i1 belonging to the nominal concept {i2}.
|
|
529
|
+
|
|
530
|
+
Args:
|
|
531
|
+
i1 (str): An individual that is subject of the assertion.
|
|
532
|
+
i2 (str): An individual representing the nominal concept.
|
|
533
|
+
|
|
534
|
+
Returns:
|
|
535
|
+
Variable: A variable taking the value of the assertion i1:{i2}.
|
|
536
|
+
"""
|
|
347
537
|
var_name = f"{i1}:{{ {i2} }}"
|
|
348
538
|
v: Variable = self.get_variable(var_name)
|
|
349
539
|
v.set_type(VariableType.BINARY)
|
|
350
540
|
return v
|
|
351
541
|
|
|
542
|
+
def is_nominal_variable(self, i: str) -> bool:
|
|
543
|
+
"""Checks if a variable 'i' is a nominal variable."""
|
|
544
|
+
# s: list[str] = i.split(":{")
|
|
545
|
+
# if len(s) != 2:
|
|
546
|
+
# return False
|
|
547
|
+
# return s[1] == f"{s[0]}" + "}"
|
|
548
|
+
pattern = re.compile(r"([^:]+):\{\1\}")
|
|
549
|
+
return len(pattern.findall(i)) > 0
|
|
550
|
+
|
|
551
|
+
def has_nominal_variable(self, terms: list[Term]) -> bool:
|
|
552
|
+
"""Checks if a collection of terms has a nominal variable."""
|
|
553
|
+
for term in terms:
|
|
554
|
+
if self.is_nominal_variable(str(term.get_var())):
|
|
555
|
+
return True
|
|
556
|
+
return False
|
|
557
|
+
|
|
352
558
|
def exists_nominal_variable(self, i: str) -> bool:
|
|
559
|
+
"""Checks if there exists a variable taking the value of an individual i belonging to the nominal concept {i}."""
|
|
353
560
|
var_name: str = f"{i}:{{ {i} }}"
|
|
354
561
|
return var_name in list(map(str, self.variables))
|
|
355
562
|
|
|
356
563
|
def get_negated_nominal_variable(self, i1: str, i2: str) -> Variable:
|
|
564
|
+
"""
|
|
565
|
+
Gets a variable taking the value of an individual i1 not belonging to the nominal concept {i2}.
|
|
566
|
+
|
|
567
|
+
Args:
|
|
568
|
+
i1 (str): An individual that is subject of the assertion.
|
|
569
|
+
i2 (str): An individual representing the nominal concept.
|
|
570
|
+
|
|
571
|
+
Returns:
|
|
572
|
+
Variable: A variable taking the value of the assertion i1: not {i2}.
|
|
573
|
+
"""
|
|
357
574
|
var_name: str = f"{i1}: not {{ {i2} }}"
|
|
358
575
|
flag: bool = var_name in list(map(str, self.variables))
|
|
359
576
|
v: Variable = self.get_variable(var_name)
|
|
577
|
+
# First time the variable is created, x_{a:{o} } = 1 - x_{a: not {o} }
|
|
360
578
|
if not flag:
|
|
361
579
|
v.set_type(VariableType.BINARY)
|
|
362
580
|
not_v: Variable = self.get_nominal_variable(i1, i2)
|
|
@@ -433,9 +651,23 @@ class MILPHelper:
|
|
|
433
651
|
def __add_new_constraint_1(
|
|
434
652
|
self, expr: Expression, constraint_type: InequalityType
|
|
435
653
|
) -> None:
|
|
654
|
+
"""
|
|
655
|
+
Adds a new inequality of the form: expr constraint_type 0.
|
|
656
|
+
|
|
657
|
+
Args:
|
|
658
|
+
expr (Expression): An expression in the left side of the inequality.
|
|
659
|
+
constraint_type (InequalityType): Type of the constraint (EQ, GR, LE).
|
|
660
|
+
"""
|
|
436
661
|
self.constraints.append(Inequation(expr, constraint_type))
|
|
437
662
|
|
|
438
663
|
def __add_new_constraint_2(self, x: Variable, n: float) -> None:
|
|
664
|
+
"""
|
|
665
|
+
Adds a new inequality of the form: x >= n.
|
|
666
|
+
|
|
667
|
+
Args:
|
|
668
|
+
x (Variable): A variable.
|
|
669
|
+
n (float): A real number.
|
|
670
|
+
"""
|
|
439
671
|
self.add_new_constraint(
|
|
440
672
|
Expression(Term(1.0, x)),
|
|
441
673
|
InequalityType.GREATER_THAN,
|
|
@@ -443,14 +675,34 @@ class MILPHelper:
|
|
|
443
675
|
)
|
|
444
676
|
|
|
445
677
|
def __add_new_constraint_3(self, ass: Assertion, n: float) -> None:
|
|
678
|
+
"""
|
|
679
|
+
Given a fuzzy assertion a:C >= L and a number n, adds an inequality of the form: xAss >= n.
|
|
680
|
+
|
|
681
|
+
Args:
|
|
682
|
+
ass (Assertion): A fuzzy assertion.
|
|
683
|
+
n (float): A real number.
|
|
684
|
+
"""
|
|
446
685
|
self.add_new_constraint(self.get_variable(ass), n)
|
|
447
686
|
|
|
448
687
|
def __add_new_constraint_4(self, x: Variable, d: Degree) -> None:
|
|
688
|
+
"""
|
|
689
|
+
Add an inequality of the form: x >= d.
|
|
690
|
+
|
|
691
|
+
Args:
|
|
692
|
+
x (Variable): A variable.
|
|
693
|
+
d (Degree): A degree.
|
|
694
|
+
"""
|
|
449
695
|
self.add_new_constraint(
|
|
450
696
|
Expression(Term(1.0, x)), InequalityType.GREATER_THAN, d
|
|
451
697
|
)
|
|
452
698
|
|
|
453
699
|
def __add_new_constraint_5(self, ass: Assertion) -> None:
|
|
700
|
+
"""
|
|
701
|
+
Adds a new inequality encoded in a fuzzy assertion.
|
|
702
|
+
|
|
703
|
+
Args:
|
|
704
|
+
ass (Assertion): A fuzzy assertion.
|
|
705
|
+
"""
|
|
454
706
|
x_ass: Variable = self.get_variable(ass)
|
|
455
707
|
ass_name: str = str(x_ass)
|
|
456
708
|
deg: Degree = ass.get_lower_limit()
|
|
@@ -463,6 +715,14 @@ class MILPHelper:
|
|
|
463
715
|
def __add_new_constraint_6(
|
|
464
716
|
self, expr: Expression, constraint_type: InequalityType, degree: Degree
|
|
465
717
|
) -> None:
|
|
718
|
+
"""
|
|
719
|
+
Adds a new inequality of the form: expr constraint_type degree.
|
|
720
|
+
|
|
721
|
+
Args:
|
|
722
|
+
expr (Expression): An expression in the left side of the inequality.
|
|
723
|
+
constraint_type (InequalityType): Type of the constraint (EQ, GR, LE).
|
|
724
|
+
degree (Degree): A degree in the right side of the inequality.
|
|
725
|
+
"""
|
|
466
726
|
self.constraints.append(
|
|
467
727
|
degree.create_inequality_with_degree_rhs(expr, constraint_type)
|
|
468
728
|
)
|
|
@@ -470,22 +730,50 @@ class MILPHelper:
|
|
|
470
730
|
def __add_new_constraint_7(
|
|
471
731
|
self, expr: Expression, constraint_type: InequalityType, n: float
|
|
472
732
|
) -> None:
|
|
733
|
+
"""
|
|
734
|
+
Adds a new inequality of the form: expr constraint_type n.
|
|
735
|
+
|
|
736
|
+
Args:
|
|
737
|
+
expr (Expression): An expression in the left side of the inequality.
|
|
738
|
+
constraint_type (InequalityType): Type of the constraint (EQ, GR, LE).
|
|
739
|
+
n (float): A real number expression in the right side of the inequality.
|
|
740
|
+
"""
|
|
473
741
|
self.add_new_constraint(expr, constraint_type, DegreeNumeric.get_degree(n))
|
|
474
742
|
|
|
475
743
|
def add_equality(self, var1: Variable, var2: Variable) -> None:
|
|
744
|
+
"""
|
|
745
|
+
Add an equality of the form: var1 = var2.
|
|
746
|
+
"""
|
|
476
747
|
self.add_new_constraint(
|
|
477
748
|
Expression(Term(1.0, var1), Term(-1.0, var2)), InequalityType.EQUAL
|
|
478
749
|
)
|
|
479
750
|
|
|
480
751
|
def add_string_feature(self, role: str) -> None:
|
|
752
|
+
"""Adds a string feature."""
|
|
481
753
|
self.string_features.add(role)
|
|
482
754
|
|
|
483
755
|
def add_string_value(self, value: str, int_value: int) -> None:
|
|
756
|
+
"""
|
|
757
|
+
Relates the value of a string feature with an integer value.
|
|
758
|
+
|
|
759
|
+
Args:
|
|
760
|
+
value (str): Value of a string feature.
|
|
761
|
+
int_value (int): Corresponding integer value.
|
|
762
|
+
"""
|
|
484
763
|
self.string_values[int_value] = value
|
|
485
764
|
|
|
486
765
|
def change_variable_names(
|
|
487
766
|
self, old_name: str, new_name: str, old_is_created_individual: bool
|
|
488
767
|
) -> None:
|
|
768
|
+
"""
|
|
769
|
+
Replaces the name of the variables including an individual name with the name of another individual name.
|
|
770
|
+
|
|
771
|
+
Args:
|
|
772
|
+
old_name (str): Old individual name.
|
|
773
|
+
new_name (str): New individual name.
|
|
774
|
+
old_is_created_individual (bool): Indicates whether the old individual is a created individual or not.
|
|
775
|
+
"""
|
|
776
|
+
|
|
489
777
|
old_values: list[str] = [f"{old_name},", f",{old_name}", f"{old_name}:"]
|
|
490
778
|
new_values: list[str] = [f"{new_name},", f",{new_name}", f"{new_name}:"]
|
|
491
779
|
to_process: list[Variable] = copy.deepcopy(self.variables)
|
|
@@ -500,6 +788,7 @@ class MILPHelper:
|
|
|
500
788
|
if old_is_created_individual:
|
|
501
789
|
self.add_equality(v1, v2)
|
|
502
790
|
else:
|
|
791
|
+
# a:{b} => x_{a:C}) \geq x_{b:C}
|
|
503
792
|
a_is_b: Variable = self.get_nominal_variable(new_name, old_name)
|
|
504
793
|
self.add_new_constraint(
|
|
505
794
|
Expression(
|
|
@@ -515,8 +804,10 @@ class MILPHelper:
|
|
|
515
804
|
begin1: int = name1.index(s1)
|
|
516
805
|
name2: str = str(v2)
|
|
517
806
|
begin2: int = name2.index(s2)
|
|
807
|
+
# They are not similar because the parts before s1 and s2 have different lengths.
|
|
518
808
|
if begin1 != begin2:
|
|
519
809
|
return False
|
|
810
|
+
# If the parts before and after s1/s2 coincide, they are similar.
|
|
520
811
|
return (
|
|
521
812
|
name1[:begin1] == name2[:begin2]
|
|
522
813
|
and name1[begin1 + len(s1) :] == name2[begin2 + len(s2) :]
|
|
@@ -557,33 +848,48 @@ class MILPHelper:
|
|
|
557
848
|
def __get_ordered_permutation_2(
|
|
558
849
|
self, x: list[Variable], z: list[list[Variable]]
|
|
559
850
|
) -> list[Variable]:
|
|
851
|
+
"""
|
|
852
|
+
Gets an ordered permutation of the variables.
|
|
853
|
+
|
|
854
|
+
Args:
|
|
855
|
+
x (list[Variable]): A vector of input variables.
|
|
856
|
+
z (list[list[Variable]]): A matrix of intermediate variables.
|
|
857
|
+
|
|
858
|
+
Returns:
|
|
859
|
+
list[Variable]: A permutation of the input variables such that y[0] >= y[1] >= ... >= y[n-1]
|
|
860
|
+
"""
|
|
560
861
|
n: int = len(x)
|
|
862
|
+
# New n [0,1] variables yi
|
|
561
863
|
y: list[Variable] = [
|
|
562
864
|
self.get_new_variable(VariableType.SEMI_CONTINUOUS) for _ in range(n)
|
|
563
865
|
]
|
|
866
|
+
# y1 >= y2 >= ... >= yn
|
|
564
867
|
for i in range(n - 1):
|
|
565
868
|
self.add_new_constraint(
|
|
566
869
|
Expression(Term(1.0, y[i]), Term(-1.0, y[i + 1])),
|
|
567
870
|
InequalityType.GREATER_THAN,
|
|
568
871
|
)
|
|
872
|
+
# for each i,j : yi - kz_{ij} <= xj
|
|
569
873
|
for i in range(n):
|
|
570
874
|
for j in range(n):
|
|
571
875
|
self.add_new_constraint(
|
|
572
876
|
Expression(Term(1.0, x[j]), Term(-1.0, y[i]), Term(1.0, z[i][j])),
|
|
573
877
|
InequalityType.GREATER_THAN,
|
|
574
878
|
)
|
|
879
|
+
# for each i,j : xj <= yi + kz_{ij}
|
|
575
880
|
for i in range(n):
|
|
576
881
|
for j in range(n):
|
|
577
882
|
self.add_new_constraint(
|
|
578
883
|
Expression(Term(1.0, x[j]), Term(-1.0, y[i]), Term(-1.0, z[i][j])),
|
|
579
884
|
InequalityType.LESS_THAN,
|
|
580
885
|
)
|
|
886
|
+
# for each i : \sum_{j} z_{ij} = n - 1
|
|
581
887
|
for i in range(n):
|
|
582
888
|
exp: Expression = Expression(1.0 - n)
|
|
583
889
|
for j in range(n):
|
|
584
890
|
exp.add_term(Term(1.0, z[i][j]))
|
|
585
891
|
self.add_new_constraint(exp, InequalityType.EQUAL)
|
|
586
|
-
|
|
892
|
+
# for each j : \sum_{i} z_{ij} = n - 1
|
|
587
893
|
for i in range(n):
|
|
588
894
|
exp: Expression = Expression(1.0 - n)
|
|
589
895
|
for j in range(n):
|
|
@@ -591,9 +897,379 @@ class MILPHelper:
|
|
|
591
897
|
self.add_new_constraint(exp, InequalityType.EQUAL)
|
|
592
898
|
return y
|
|
593
899
|
|
|
900
|
+
def __bfs(self, graph: nx.Graph, solution: dict[int, int]) -> int:
|
|
901
|
+
# Number of nodes
|
|
902
|
+
n: int = graph.number_of_nodes()
|
|
903
|
+
|
|
904
|
+
# Solution is a mapping: variable -> partition
|
|
905
|
+
# Initial partition value is 0
|
|
906
|
+
for i in range(n):
|
|
907
|
+
solution[i] = 0
|
|
908
|
+
|
|
909
|
+
# Number of partition
|
|
910
|
+
p: int = 1
|
|
911
|
+
|
|
912
|
+
# Iterate over not processed nodes
|
|
913
|
+
queue: list[int] = list()
|
|
914
|
+
for i in range(n - 1):
|
|
915
|
+
# Skip node if processed
|
|
916
|
+
if solution[i] != 0:
|
|
917
|
+
continue
|
|
918
|
+
queue = [i]
|
|
919
|
+
solution[i] = p
|
|
920
|
+
self.__compute_partition(queue, solution, p, graph)
|
|
921
|
+
|
|
922
|
+
# Next partition
|
|
923
|
+
p += 1
|
|
924
|
+
return p - 1
|
|
925
|
+
|
|
926
|
+
def __compute_partition(
|
|
927
|
+
self, queue: list[int], solution: dict[int, int], p: int, graph: nx.Graph
|
|
928
|
+
) -> None:
|
|
929
|
+
|
|
930
|
+
while len(queue) > 0:
|
|
931
|
+
current: int = queue.pop()
|
|
932
|
+
neighbors: list[int] = list(graph.neighbors(current))
|
|
933
|
+
if len(neighbors) == 0:
|
|
934
|
+
continue
|
|
935
|
+
for j in neighbors:
|
|
936
|
+
if solution[j] != 0:
|
|
937
|
+
continue
|
|
938
|
+
solution[j] = p
|
|
939
|
+
queue.append(j)
|
|
940
|
+
|
|
941
|
+
def set_nominal_variables(self, value: bool) -> None:
|
|
942
|
+
self.nominal_variables = value
|
|
943
|
+
|
|
944
|
+
def __remove_nominal_variables(self) -> None:
|
|
945
|
+
constraints_to_remove: list[int] = []
|
|
946
|
+
variable_to_remove: list[int] = []
|
|
947
|
+
for i, constraint in enumerate(self.constraints):
|
|
948
|
+
terms: list[Term] = constraint.get_terms()
|
|
949
|
+
if self.has_nominal_variable(terms):
|
|
950
|
+
constraints_to_remove.append(i)
|
|
951
|
+
for i, variable in enumerate(self.variables):
|
|
952
|
+
if self.is_nominal_variable(str(variable)):
|
|
953
|
+
variable_to_remove.append(i)
|
|
954
|
+
|
|
955
|
+
self.constraints = [
|
|
956
|
+
constraint
|
|
957
|
+
for i, constraint in enumerate(self.constraints)
|
|
958
|
+
if i not in constraints_to_remove
|
|
959
|
+
]
|
|
960
|
+
self.variables = [
|
|
961
|
+
variable
|
|
962
|
+
for i, variable in enumerate(self.variables)
|
|
963
|
+
if i not in variable_to_remove
|
|
964
|
+
]
|
|
965
|
+
|
|
966
|
+
def __get_graph(self) -> nx.Graph:
|
|
967
|
+
g: nx.Graph = nx.Graph()
|
|
968
|
+
|
|
969
|
+
# Create nodes
|
|
970
|
+
n: int = len(self.variables)
|
|
971
|
+
for i in range(n):
|
|
972
|
+
g.add_node(i)
|
|
973
|
+
|
|
974
|
+
# Create edges
|
|
975
|
+
edge: int = 0
|
|
976
|
+
for constraint in self.constraints:
|
|
977
|
+
terms: list[Term] = constraint.get_terms()
|
|
978
|
+
if len(terms) == 0:
|
|
979
|
+
continue
|
|
980
|
+
first_var: int = self.variables.index(terms[0].get_var())
|
|
981
|
+
for term in terms[1:]:
|
|
982
|
+
other_var: int = self.variables.index(term.get_var())
|
|
983
|
+
# Edges between first and other
|
|
984
|
+
edge += 1
|
|
985
|
+
g.add_edge(first_var, other_var, number=edge)
|
|
986
|
+
|
|
987
|
+
return g
|
|
988
|
+
|
|
989
|
+
def __common_partition_part(
|
|
990
|
+
self, objective: Expression
|
|
991
|
+
) -> tuple[list[Variable], dict[int, int], int, list[int], int, int]:
|
|
992
|
+
|
|
993
|
+
objectives: list[Variable] = list()
|
|
994
|
+
|
|
995
|
+
# Partition time
|
|
996
|
+
init_time: int = time.perf_counter_ns()
|
|
997
|
+
|
|
998
|
+
# Graph
|
|
999
|
+
solution: dict[int, int] = dict()
|
|
1000
|
+
num_partitions: int = self.__bfs(self.__get_graph(), solution)
|
|
1001
|
+
|
|
1002
|
+
# Mapping partition -> number of objective variables in partition
|
|
1003
|
+
num_variables_in_partition: list[int] = [0] * num_partitions
|
|
1004
|
+
|
|
1005
|
+
# Compute objective coefficients
|
|
1006
|
+
for term in objective.get_terms():
|
|
1007
|
+
v: Variable = term.get_var()
|
|
1008
|
+
objectives.append(v)
|
|
1009
|
+
index: int = self.variables.index(v)
|
|
1010
|
+
num_partition: int = solution.get(index) - 1
|
|
1011
|
+
num_variables_in_partition[num_partition] += 1
|
|
1012
|
+
|
|
1013
|
+
# Compute two or more partitions
|
|
1014
|
+
two_or_more: int = 0
|
|
1015
|
+
count: int = 0
|
|
1016
|
+
for i in range(num_partitions):
|
|
1017
|
+
if num_variables_in_partition[i] > 1:
|
|
1018
|
+
two_or_more += 1
|
|
1019
|
+
count += num_variables_in_partition[i]
|
|
1020
|
+
|
|
1021
|
+
end_time: int = time.perf_counter_ns()
|
|
1022
|
+
total_time: float = (end_time - init_time) * 1e-9
|
|
1023
|
+
Util.debug(f"Partition time: {total_time} s")
|
|
1024
|
+
return (
|
|
1025
|
+
objectives,
|
|
1026
|
+
solution,
|
|
1027
|
+
num_partitions,
|
|
1028
|
+
num_variables_in_partition,
|
|
1029
|
+
two_or_more,
|
|
1030
|
+
count,
|
|
1031
|
+
)
|
|
1032
|
+
|
|
1033
|
+
def __solve_gurobi_using_partitions(
|
|
1034
|
+
self, objective: Expression
|
|
1035
|
+
) -> typing.Optional[Solution]:
|
|
1036
|
+
import gurobipy as gp
|
|
1037
|
+
from gurobipy import GRB
|
|
1038
|
+
|
|
1039
|
+
(
|
|
1040
|
+
objectives,
|
|
1041
|
+
solution,
|
|
1042
|
+
num_partitions,
|
|
1043
|
+
num_variables_in_partition,
|
|
1044
|
+
two_or_more,
|
|
1045
|
+
count,
|
|
1046
|
+
) = self.__common_partition_part(objective)
|
|
1047
|
+
|
|
1048
|
+
if two_or_more == 0:
|
|
1049
|
+
MILPHelper.PARTITION = False
|
|
1050
|
+
return self.solve_gurobi(objective)
|
|
1051
|
+
|
|
1052
|
+
# Specific algorithm starts here
|
|
1053
|
+
try:
|
|
1054
|
+
Util.debug(
|
|
1055
|
+
f"There are {two_or_more} partitions with {count} dependent objective variables"
|
|
1056
|
+
)
|
|
1057
|
+
|
|
1058
|
+
# PROBLEMS with 1 or less
|
|
1059
|
+
env = gp.Env(empty=True)
|
|
1060
|
+
if not ConfigReader.DEBUG_PRINT:
|
|
1061
|
+
env.setParam("OutputFlag", 0)
|
|
1062
|
+
env.setParam("IntFeasTol", 1e-9)
|
|
1063
|
+
env.setParam("BarConvTol", 0)
|
|
1064
|
+
env.start()
|
|
1065
|
+
|
|
1066
|
+
model: gp.Model = gp.Model("partition-model-1-or-less", env=env)
|
|
1067
|
+
|
|
1068
|
+
# Create variables
|
|
1069
|
+
vars_gurobi: dict[str, gp.Var] = dict()
|
|
1070
|
+
|
|
1071
|
+
var_types: dict[VariableType, str] = {
|
|
1072
|
+
VariableType.BINARY: GRB.BINARY,
|
|
1073
|
+
VariableType.INTEGER: GRB.INTEGER,
|
|
1074
|
+
VariableType.CONTINUOUS: GRB.CONTINUOUS,
|
|
1075
|
+
VariableType.SEMI_CONTINUOUS: GRB.SEMICONT,
|
|
1076
|
+
}
|
|
1077
|
+
var_name_map: dict[str, str] = {
|
|
1078
|
+
str(v): f"x{i}" for i, v in enumerate(self.variables)
|
|
1079
|
+
}
|
|
1080
|
+
for i, curr_variable in enumerate(self.variables):
|
|
1081
|
+
num_partition: int = solution.get(i) - 1
|
|
1082
|
+
if num_variables_in_partition[num_partition] > 1:
|
|
1083
|
+
continue # Next variable
|
|
1084
|
+
v_type: VariableType = curr_variable.get_type()
|
|
1085
|
+
|
|
1086
|
+
Util.debug(
|
|
1087
|
+
(
|
|
1088
|
+
f"Variable -- "
|
|
1089
|
+
f"[{curr_variable.get_lower_bound()}, {curr_variable.get_upper_bound()}] - "
|
|
1090
|
+
f"Obj value = 0 - "
|
|
1091
|
+
f"Var type = {v_type.name} -- "
|
|
1092
|
+
f"Var = {curr_variable}"
|
|
1093
|
+
)
|
|
1094
|
+
)
|
|
1095
|
+
|
|
1096
|
+
vars_gurobi[var_name_map[str(curr_variable)]] = model.addVar(
|
|
1097
|
+
lb=curr_variable.get_lower_bound(),
|
|
1098
|
+
ub=curr_variable.get_upper_bound(),
|
|
1099
|
+
obj=0,
|
|
1100
|
+
vtype=var_types[v_type],
|
|
1101
|
+
name=var_name_map[str(curr_variable)],
|
|
1102
|
+
)
|
|
1103
|
+
|
|
1104
|
+
# Integrate new variables
|
|
1105
|
+
model.update()
|
|
1106
|
+
|
|
1107
|
+
constraint_name: str = "constraint"
|
|
1108
|
+
# Add constraints
|
|
1109
|
+
for i, constraint in enumerate(self.constraints):
|
|
1110
|
+
if constraint in self.constraints[:i]:
|
|
1111
|
+
continue
|
|
1112
|
+
if constraint.is_zero():
|
|
1113
|
+
continue
|
|
1114
|
+
|
|
1115
|
+
curr_name: str = f"{constraint_name}_{i + 1}"
|
|
1116
|
+
expr: gp.LinExpr = gp.LinExpr()
|
|
1117
|
+
for term in constraint.get_terms():
|
|
1118
|
+
index: int = self.variables.index(term.get_var())
|
|
1119
|
+
num_partition: int = solution.get(index) - 1
|
|
1120
|
+
if num_variables_in_partition[num_partition] > 1:
|
|
1121
|
+
break # Exit for term loop
|
|
1122
|
+
v: gp.Var = vars_gurobi[var_name_map[str(term.get_var())]]
|
|
1123
|
+
c: float = term.get_coeff()
|
|
1124
|
+
if c == 0:
|
|
1125
|
+
continue
|
|
1126
|
+
expr.add(v, c)
|
|
1127
|
+
|
|
1128
|
+
if expr.size() == 0:
|
|
1129
|
+
continue
|
|
1130
|
+
|
|
1131
|
+
if constraint.get_type() == InequalityType.EQUAL:
|
|
1132
|
+
gp_constraint: gp.Constr = expr == constraint.get_constant()
|
|
1133
|
+
elif constraint.get_type() == InequalityType.LESS_THAN:
|
|
1134
|
+
gp_constraint: gp.Constr = expr <= constraint.get_constant()
|
|
1135
|
+
elif constraint.get_type() == InequalityType.GREATER_THAN:
|
|
1136
|
+
gp_constraint: gp.Constr = expr >= constraint.get_constant()
|
|
1137
|
+
|
|
1138
|
+
model.addConstr(gp_constraint, curr_name)
|
|
1139
|
+
Util.debug(f"{curr_name}: {constraint}")
|
|
1140
|
+
|
|
1141
|
+
# Integrate new constraints
|
|
1142
|
+
model.update()
|
|
1143
|
+
|
|
1144
|
+
# Optimize model
|
|
1145
|
+
model.optimize()
|
|
1146
|
+
Util.debug(f"Model:")
|
|
1147
|
+
|
|
1148
|
+
# Return solution
|
|
1149
|
+
if model.Status == GRB.INFEASIBLE:
|
|
1150
|
+
return Solution(Solution.INCONSISTENT_KB)
|
|
1151
|
+
|
|
1152
|
+
# One for each partition with two or more variables, plus one for the rest (all partitions with 0 and 1)
|
|
1153
|
+
sol: Solution = Solution(1.0)
|
|
1154
|
+
|
|
1155
|
+
# PROBLEMS with 2 or more
|
|
1156
|
+
for obj_var in objectives:
|
|
1157
|
+
env = gp.Env(empty=True)
|
|
1158
|
+
if not ConfigReader.DEBUG_PRINT:
|
|
1159
|
+
env.setParam("OutputFlag", 0)
|
|
1160
|
+
env.setParam("IntFeasTol", 1e-9)
|
|
1161
|
+
env.setParam("BarConvTol", 0)
|
|
1162
|
+
env.start()
|
|
1163
|
+
|
|
1164
|
+
model: gp.Model = gp.Model("partition-model-2-or-more", env=env)
|
|
1165
|
+
|
|
1166
|
+
index: int = self.variables.index(obj_var)
|
|
1167
|
+
problem: int = solution.get(index) - 1
|
|
1168
|
+
|
|
1169
|
+
vars_gurobi: dict[str, gp.Var] = dict()
|
|
1170
|
+
|
|
1171
|
+
# Create variables
|
|
1172
|
+
for i, curr_variable in enumerate(self.variables):
|
|
1173
|
+
num_partition: int = solution.get(i) - 1
|
|
1174
|
+
if num_partition != problem:
|
|
1175
|
+
continue
|
|
1176
|
+
|
|
1177
|
+
v_type: VariableType = curr_variable.get_type()
|
|
1178
|
+
ov: float = 1.0 if i == self.variables.index(obj_var) else 0.0
|
|
1179
|
+
|
|
1180
|
+
Util.debug(
|
|
1181
|
+
(
|
|
1182
|
+
f"Variable -- "
|
|
1183
|
+
f"[{curr_variable.get_lower_bound()}, {curr_variable.get_upper_bound()}] - "
|
|
1184
|
+
f"Obj value = {ov} - "
|
|
1185
|
+
f"Var type = {v_type.name} -- "
|
|
1186
|
+
f"Var = {curr_variable}"
|
|
1187
|
+
)
|
|
1188
|
+
)
|
|
1189
|
+
|
|
1190
|
+
vars_gurobi[var_name_map[str(curr_variable)]] = model.addVar(
|
|
1191
|
+
lb=curr_variable.get_lower_bound(),
|
|
1192
|
+
ub=curr_variable.get_upper_bound(),
|
|
1193
|
+
obj=ov,
|
|
1194
|
+
vtype=var_types[v_type],
|
|
1195
|
+
name=var_name_map[str(curr_variable)],
|
|
1196
|
+
)
|
|
1197
|
+
|
|
1198
|
+
# Integrate new variables
|
|
1199
|
+
model.update()
|
|
1200
|
+
|
|
1201
|
+
constraint_name: str = "constraint"
|
|
1202
|
+
# Add constraints
|
|
1203
|
+
for i, constraint in enumerate(self.constraints):
|
|
1204
|
+
if constraint in self.constraints[:i]:
|
|
1205
|
+
continue
|
|
1206
|
+
if constraint.is_zero():
|
|
1207
|
+
continue
|
|
1208
|
+
|
|
1209
|
+
curr_name: str = f"{constraint_name}_{i + 1}"
|
|
1210
|
+
expr: gp.LinExpr = gp.LinExpr()
|
|
1211
|
+
for term in constraint.get_terms():
|
|
1212
|
+
index: int = self.variables.index(term.get_var())
|
|
1213
|
+
num_partition: int = solution.get(index) - 1
|
|
1214
|
+
if num_partition != problem:
|
|
1215
|
+
break # Exit for term loop
|
|
1216
|
+
v: gp.Var = vars_gurobi[var_name_map[str(term.get_var())]]
|
|
1217
|
+
c: float = term.get_coeff()
|
|
1218
|
+
if c == 0:
|
|
1219
|
+
continue
|
|
1220
|
+
expr.add(v, c)
|
|
1221
|
+
|
|
1222
|
+
if expr.size() == 0:
|
|
1223
|
+
continue
|
|
1224
|
+
|
|
1225
|
+
if constraint.get_type() == InequalityType.EQUAL:
|
|
1226
|
+
gp_constraint: gp.Constr = expr == constraint.get_constant()
|
|
1227
|
+
elif constraint.get_type() == InequalityType.LESS_THAN:
|
|
1228
|
+
gp_constraint: gp.Constr = expr <= constraint.get_constant()
|
|
1229
|
+
elif constraint.get_type() == InequalityType.GREATER_THAN:
|
|
1230
|
+
gp_constraint: gp.Constr = expr >= constraint.get_constant()
|
|
1231
|
+
|
|
1232
|
+
model.addConstr(gp_constraint, curr_name)
|
|
1233
|
+
Util.debug(f"{curr_name}: {constraint}")
|
|
1234
|
+
|
|
1235
|
+
# Integrate new constraints
|
|
1236
|
+
model.update()
|
|
1237
|
+
|
|
1238
|
+
# Optimize model
|
|
1239
|
+
model.optimize()
|
|
1240
|
+
|
|
1241
|
+
# Return solution
|
|
1242
|
+
if model.Status == GRB.INFEASIBLE:
|
|
1243
|
+
return Solution(Solution.INCONSISTENT_KB)
|
|
1244
|
+
else:
|
|
1245
|
+
result: float = Util.round(abs(model.ObjVal))
|
|
1246
|
+
sol = Solution(result)
|
|
1247
|
+
name: str = str(obj_var)
|
|
1248
|
+
sol.add_showed_variable(name, result)
|
|
1249
|
+
|
|
1250
|
+
model.printQuality()
|
|
1251
|
+
model.printStats()
|
|
1252
|
+
|
|
1253
|
+
return sol
|
|
1254
|
+
except gp.GurobiError as e:
|
|
1255
|
+
Util.error(f"Error code: {e.errno}. {e.message}")
|
|
1256
|
+
return None
|
|
1257
|
+
|
|
594
1258
|
def solve_gurobi(self, objective: Expression) -> typing.Optional[Solution]:
|
|
1259
|
+
"""
|
|
1260
|
+
Solves a MILP problem using Gurobi.
|
|
1261
|
+
"""
|
|
1262
|
+
|
|
1263
|
+
import gurobipy as gp
|
|
1264
|
+
from gurobipy import GRB
|
|
1265
|
+
|
|
1266
|
+
if not self.nominal_variables:
|
|
1267
|
+
self.__remove_nominal_variables()
|
|
1268
|
+
|
|
1269
|
+
if MILPHelper.PARTITION:
|
|
1270
|
+
return self.__solve_gurobi_using_partitions(objective)
|
|
1271
|
+
|
|
595
1272
|
try:
|
|
596
|
-
Util.debug("Running MILP solver: Gurobi")
|
|
597
1273
|
Util.debug(f"Objective function -> {objective}")
|
|
598
1274
|
|
|
599
1275
|
num_binary_vars: int = 0
|
|
@@ -605,6 +1281,7 @@ class MILPHelper:
|
|
|
605
1281
|
|
|
606
1282
|
if objective is not None:
|
|
607
1283
|
for term in objective.get_terms():
|
|
1284
|
+
# Compute objective coefficients
|
|
608
1285
|
index = self.variables.index(term.get_var())
|
|
609
1286
|
objective_value[index] += term.get_coeff()
|
|
610
1287
|
|
|
@@ -615,38 +1292,46 @@ class MILPHelper:
|
|
|
615
1292
|
env.setParam("BarConvTol", 0)
|
|
616
1293
|
env.start()
|
|
617
1294
|
|
|
618
|
-
model = gp.Model("model", env=env)
|
|
619
|
-
vars_gurobi:
|
|
1295
|
+
model: gp.Model = gp.Model("model", env=env)
|
|
1296
|
+
vars_gurobi: dict[str, gp.Var] = dict()
|
|
620
1297
|
show_variable: list[bool] = [False] * size
|
|
621
1298
|
|
|
622
1299
|
my_vars: list[Variable] = self.show_vars.get_variables()
|
|
623
1300
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
1301
|
+
var_types: dict[VariableType, str] = {
|
|
1302
|
+
VariableType.BINARY: GRB.BINARY,
|
|
1303
|
+
VariableType.INTEGER: GRB.INTEGER,
|
|
1304
|
+
VariableType.CONTINUOUS: GRB.CONTINUOUS,
|
|
1305
|
+
VariableType.SEMI_CONTINUOUS: GRB.SEMICONT,
|
|
1306
|
+
}
|
|
1307
|
+
var_name_map: dict[str, str] = {
|
|
1308
|
+
str(v): f"x{i}" for i, v in enumerate(self.variables)
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
# Create variables
|
|
1312
|
+
for i, curr_variable in enumerate(self.variables):
|
|
1313
|
+
v_type: VariableType = curr_variable.get_type()
|
|
627
1314
|
ov: float = objective_value[i]
|
|
628
1315
|
|
|
629
1316
|
Util.debug(
|
|
630
1317
|
(
|
|
631
1318
|
f"Variable -- "
|
|
632
|
-
f"[{
|
|
1319
|
+
f"[{curr_variable.get_lower_bound()}, {curr_variable.get_upper_bound()}] - "
|
|
633
1320
|
f"Obj value = {ov} - "
|
|
634
1321
|
f"Var type = {v_type.name} -- "
|
|
635
|
-
f"Var = {
|
|
1322
|
+
f"Var = {curr_variable}"
|
|
636
1323
|
)
|
|
637
1324
|
)
|
|
638
1325
|
|
|
639
|
-
vars_gurobi.
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
name=str(v),
|
|
646
|
-
)
|
|
1326
|
+
vars_gurobi[var_name_map[str(curr_variable)]] = model.addVar(
|
|
1327
|
+
lb=curr_variable.get_lower_bound(),
|
|
1328
|
+
ub=curr_variable.get_upper_bound(),
|
|
1329
|
+
obj=ov,
|
|
1330
|
+
vtype=var_types[v_type],
|
|
1331
|
+
name=var_name_map[str(curr_variable)],
|
|
647
1332
|
)
|
|
648
1333
|
|
|
649
|
-
if
|
|
1334
|
+
if curr_variable in my_vars:
|
|
650
1335
|
show_variable[i] = True
|
|
651
1336
|
|
|
652
1337
|
if v_type == VariableType.BINARY:
|
|
@@ -658,16 +1343,22 @@ class MILPHelper:
|
|
|
658
1343
|
elif v_type == VariableType.SEMI_CONTINUOUS:
|
|
659
1344
|
num_up_vars += 1
|
|
660
1345
|
|
|
1346
|
+
# Integrate new variables
|
|
661
1347
|
model.update()
|
|
662
1348
|
|
|
663
1349
|
Util.debug(f"# constraints -> {len(self.constraints)}")
|
|
664
1350
|
constraint_name: str = "constraint"
|
|
1351
|
+
# Add constraints
|
|
665
1352
|
for i, constraint in enumerate(self.constraints):
|
|
1353
|
+
if constraint in self.constraints[:i]:
|
|
1354
|
+
continue
|
|
1355
|
+
if constraint.is_zero():
|
|
1356
|
+
continue
|
|
1357
|
+
|
|
666
1358
|
curr_name: str = f"{constraint_name}_{i + 1}"
|
|
667
1359
|
expr: gp.LinExpr = gp.LinExpr()
|
|
668
1360
|
for term in constraint.get_terms():
|
|
669
|
-
|
|
670
|
-
v: gp.Var = vars_gurobi[index]
|
|
1361
|
+
v: gp.Var = vars_gurobi[var_name_map[str(term.get_var())]]
|
|
671
1362
|
c: float = term.get_coeff()
|
|
672
1363
|
if c == 0:
|
|
673
1364
|
continue
|
|
@@ -684,32 +1375,38 @@ class MILPHelper:
|
|
|
684
1375
|
gp_constraint: gp.Constr = expr >= constraint.get_constant()
|
|
685
1376
|
|
|
686
1377
|
model.addConstr(gp_constraint, curr_name)
|
|
687
|
-
Util.debug(f"{curr_name}: {
|
|
1378
|
+
Util.debug(f"{curr_name}: {constraint}")
|
|
688
1379
|
|
|
1380
|
+
# Integrate new constraints
|
|
689
1381
|
model.update()
|
|
1382
|
+
|
|
1383
|
+
# Optimize model
|
|
690
1384
|
model.optimize()
|
|
691
1385
|
|
|
692
|
-
model.write(os.path.join(constants.RESULTS_PATH, "
|
|
693
|
-
model.write(os.path.join(constants.RESULTS_PATH, "
|
|
1386
|
+
model.write(os.path.join(constants.RESULTS_PATH, "gurobi_model.lp"))
|
|
1387
|
+
model.write(os.path.join(constants.RESULTS_PATH, "gurobi_solution.json"))
|
|
694
1388
|
|
|
695
1389
|
Util.debug(f"Model:")
|
|
696
1390
|
sol: Solution = None
|
|
697
|
-
if model.Status == GRB.INFEASIBLE and ConfigReader.RELAX_MILP:
|
|
698
|
-
|
|
1391
|
+
# if model.Status == GRB.INFEASIBLE and ConfigReader.RELAX_MILP:
|
|
1392
|
+
# self.__gurobi_handle_model_infeasibility(model)
|
|
699
1393
|
|
|
1394
|
+
# Return solution
|
|
700
1395
|
if model.Status == GRB.INFEASIBLE:
|
|
701
|
-
sol = Solution(
|
|
1396
|
+
sol = Solution(Solution.INCONSISTENT_KB)
|
|
702
1397
|
else:
|
|
1398
|
+
result: float = Util.round(abs(model.ObjVal))
|
|
1399
|
+
sol = Solution(result)
|
|
703
1400
|
for i in range(size):
|
|
704
1401
|
if ConfigReader.DEBUG_PRINT or show_variable[i]:
|
|
705
|
-
name: str =
|
|
706
|
-
value: float = round(vars_gurobi[
|
|
707
|
-
if
|
|
708
|
-
|
|
1402
|
+
name: str = self.variables[i].name
|
|
1403
|
+
value: float = round(vars_gurobi[var_name_map[name]].X, 6)
|
|
1404
|
+
if show_variable[i]:
|
|
1405
|
+
sol.add_showed_variable(name, value)
|
|
1406
|
+
# if self.PRINT_VARIABLES:
|
|
1407
|
+
Util.debug(f"{name} = {value}")
|
|
709
1408
|
if self.PRINT_LABELS:
|
|
710
1409
|
self.print_instance_of_labels(name, value)
|
|
711
|
-
result: float = Util.round(abs(model.ObjVal))
|
|
712
|
-
sol = Solution(result)
|
|
713
1410
|
|
|
714
1411
|
model.printQuality()
|
|
715
1412
|
model.printStats()
|
|
@@ -718,54 +1415,678 @@ class MILPHelper:
|
|
|
718
1415
|
f"{constants.STAR_SEPARATOR}Statistics{constants.STAR_SEPARATOR}"
|
|
719
1416
|
)
|
|
720
1417
|
Util.debug("MILP problem:")
|
|
1418
|
+
# Show number of variables
|
|
721
1419
|
Util.debug(f"\t\tSemi continuous variables: {num_up_vars}")
|
|
722
1420
|
Util.debug(f"\t\tBinary variables: {num_binary_vars}")
|
|
723
1421
|
Util.debug(f"\t\tContinuous variables: {num_free_vars}")
|
|
724
1422
|
Util.debug(f"\t\tInteger variables: {num_integer_vars}")
|
|
725
1423
|
Util.debug(f"\t\tTotal variables: {len(self.variables)}")
|
|
1424
|
+
# Show number of constraints
|
|
726
1425
|
Util.debug(f"\t\tConstraints: {len(self.constraints)}")
|
|
727
1426
|
return sol
|
|
728
1427
|
except gp.GurobiError as e:
|
|
729
1428
|
Util.error(f"Error code: {e.errno}. {e.message}")
|
|
730
1429
|
return None
|
|
731
1430
|
|
|
732
|
-
def
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
1431
|
+
# def __gurobi_handle_model_infeasibility(self, model: typing.Any) -> None:
|
|
1432
|
+
# import gurobipy as gp
|
|
1433
|
+
|
|
1434
|
+
# model: gp.Model = typing.cast(gp.Model, model)
|
|
1435
|
+
# model.computeIIS()
|
|
1436
|
+
# # Print out the IIS constraints and variables
|
|
1437
|
+
# Util.debug("The following constraints and variables are in the IIS:")
|
|
1438
|
+
# Util.debug("Constraints:")
|
|
1439
|
+
# for c in model.getConstrs():
|
|
1440
|
+
# assert isinstance(c, gp.Constr)
|
|
1441
|
+
# if c.IISConstr:
|
|
1442
|
+
# Util.debug(f"\t\t{c.ConstrName}: {model.getRow(c)} {c.Sense} {c.RHS}")
|
|
1443
|
+
|
|
1444
|
+
# Util.debug("Variables:")
|
|
1445
|
+
# for v in model.getVars():
|
|
1446
|
+
# if v.IISLB:
|
|
1447
|
+
# Util.debug(f"\t\t{v.VarName} ≥ {v.LB}")
|
|
1448
|
+
# if v.IISUB:
|
|
1449
|
+
# Util.debug(f"\t\t{v.VarName} ≤ {v.UB}")
|
|
1450
|
+
|
|
1451
|
+
# Util.debug("Relaxing the variable bounds:")
|
|
1452
|
+
# # relaxing only variable bounds
|
|
1453
|
+
# model.feasRelaxS(0, False, True, False)
|
|
1454
|
+
# # for relaxing variable bounds and constraint bounds use
|
|
1455
|
+
# # model.feasRelaxS(0, False, True, True)
|
|
1456
|
+
# model.optimize()
|
|
1457
|
+
|
|
1458
|
+
def solve_mip(self, objective: Expression) -> typing.Optional[Solution]:
|
|
1459
|
+
import mip
|
|
1460
|
+
|
|
1461
|
+
try:
|
|
1462
|
+
Util.debug(f"Objective function -> {objective}")
|
|
1463
|
+
|
|
1464
|
+
num_binary_vars: int = 0
|
|
1465
|
+
num_free_vars: int = 0
|
|
1466
|
+
num_integer_vars: int = 0
|
|
1467
|
+
num_up_vars: int = 0
|
|
1468
|
+
size: int = len(self.variables)
|
|
1469
|
+
objective_value: list[float] = [0.0] * size
|
|
1470
|
+
|
|
1471
|
+
if objective is not None:
|
|
1472
|
+
for term in objective.get_terms():
|
|
1473
|
+
index = self.variables.index(term.get_var())
|
|
1474
|
+
objective_value[index] += term.get_coeff()
|
|
1475
|
+
|
|
1476
|
+
model: mip.Model = mip.Model(
|
|
1477
|
+
name="FuzzyDL", sense=mip.MINIMIZE, solver_name=mip.CBC
|
|
1478
|
+
)
|
|
1479
|
+
model.verbose = 0
|
|
1480
|
+
model.infeas_tol = 1e-9
|
|
1481
|
+
model.integer_tol = 1e-9
|
|
1482
|
+
model.max_mip_gap = ConfigReader.EPSILON
|
|
1483
|
+
model.emphasis = mip.SearchEmphasis.OPTIMALITY
|
|
1484
|
+
model.opt_tol = 0
|
|
1485
|
+
model.preprocess = 1
|
|
1486
|
+
|
|
1487
|
+
if ConfigReader.DEBUG_PRINT:
|
|
1488
|
+
model.verbose = 1
|
|
1489
|
+
|
|
1490
|
+
vars_mip: dict[str, mip.Var] = dict()
|
|
1491
|
+
show_variable: list[bool] = [False] * size
|
|
1492
|
+
|
|
1493
|
+
my_vars: list[Variable] = self.show_vars.get_variables()
|
|
1494
|
+
var_types: dict[VariableType, str] = {
|
|
1495
|
+
VariableType.BINARY: mip.BINARY,
|
|
1496
|
+
VariableType.INTEGER: mip.INTEGER,
|
|
1497
|
+
VariableType.CONTINUOUS: mip.CONTINUOUS,
|
|
1498
|
+
VariableType.SEMI_CONTINUOUS: mip.CONTINUOUS,
|
|
1499
|
+
}
|
|
1500
|
+
var_name_map: dict[str, str] = {
|
|
1501
|
+
str(v): f"x{i}" for i, v in enumerate(self.variables)
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
for i, curr_variable in enumerate(self.variables):
|
|
1505
|
+
v_type: VariableType = curr_variable.get_type()
|
|
1506
|
+
ov: float = objective_value[i]
|
|
1507
|
+
|
|
1508
|
+
Util.debug(
|
|
1509
|
+
(
|
|
1510
|
+
f"Variable -- "
|
|
1511
|
+
f"[{curr_variable.get_lower_bound()}, {curr_variable.get_upper_bound()}] - "
|
|
1512
|
+
f"Obj value = {ov} - "
|
|
1513
|
+
f"Var type = {v_type.name} -- "
|
|
1514
|
+
f"Var = {curr_variable}"
|
|
1515
|
+
)
|
|
1516
|
+
)
|
|
1517
|
+
|
|
1518
|
+
vars_mip[var_name_map[str(curr_variable)]] = model.add_var(
|
|
1519
|
+
name=var_name_map[str(curr_variable)],
|
|
1520
|
+
var_type=var_types[v_type],
|
|
1521
|
+
lb=curr_variable.get_lower_bound(),
|
|
1522
|
+
ub=curr_variable.get_upper_bound(),
|
|
1523
|
+
obj=ov,
|
|
1524
|
+
)
|
|
1525
|
+
|
|
1526
|
+
if curr_variable in my_vars:
|
|
1527
|
+
show_variable[i] = True
|
|
1528
|
+
|
|
1529
|
+
if v_type == VariableType.BINARY:
|
|
1530
|
+
num_binary_vars += 1
|
|
1531
|
+
elif v_type == VariableType.CONTINUOUS:
|
|
1532
|
+
num_free_vars += 1
|
|
1533
|
+
elif v_type == VariableType.INTEGER:
|
|
1534
|
+
num_integer_vars += 1
|
|
1535
|
+
elif v_type == VariableType.SEMI_CONTINUOUS:
|
|
1536
|
+
num_up_vars += 1
|
|
1537
|
+
|
|
1538
|
+
Util.debug(f"# constraints -> {len(self.constraints)}")
|
|
1539
|
+
constraint_name: str = "constraint"
|
|
1540
|
+
for i, constraint in enumerate(self.constraints):
|
|
1541
|
+
if constraint in self.constraints[:i]:
|
|
1542
|
+
continue
|
|
1543
|
+
if constraint.is_zero():
|
|
1544
|
+
continue
|
|
1545
|
+
curr_name: str = f"{constraint_name}_{i + 1}"
|
|
1546
|
+
expr: mip.LinExpr = mip.xsum(
|
|
1547
|
+
term.get_coeff() * vars_mip[var_name_map[str(term.get_var())]]
|
|
1548
|
+
for term in constraint.get_terms()
|
|
1549
|
+
)
|
|
1550
|
+
|
|
1551
|
+
if constraint.get_type() == InequalityType.EQUAL:
|
|
1552
|
+
gp_constraint: mip.Constr = expr == constraint.get_constant()
|
|
1553
|
+
elif constraint.get_type() == InequalityType.LESS_THAN:
|
|
1554
|
+
gp_constraint: mip.Constr = expr <= constraint.get_constant()
|
|
1555
|
+
elif constraint.get_type() == InequalityType.GREATER_THAN:
|
|
1556
|
+
gp_constraint: mip.Constr = expr >= constraint.get_constant()
|
|
1557
|
+
|
|
1558
|
+
model.add_constr(gp_constraint, curr_name)
|
|
1559
|
+
Util.debug(f"{curr_name}: {constraint}")
|
|
1560
|
+
|
|
1561
|
+
model.objective = mip.xsum(
|
|
1562
|
+
ov * vars_mip[var_name_map[str(self.variables[i])]]
|
|
1563
|
+
for i, ov in enumerate(objective_value)
|
|
1564
|
+
if ov != 0
|
|
1565
|
+
)
|
|
1566
|
+
|
|
1567
|
+
# model.optimize(relax=ConfigReader.RELAX_MILP)
|
|
1568
|
+
model.optimize()
|
|
1569
|
+
|
|
1570
|
+
model.write(os.path.join(constants.RESULTS_PATH, "mip_model.lp"))
|
|
1571
|
+
|
|
1572
|
+
Util.debug(f"Model:")
|
|
1573
|
+
sol: Solution = None
|
|
1574
|
+
if model.status == mip.OptimizationStatus.INFEASIBLE:
|
|
1575
|
+
sol = Solution(Solution.INCONSISTENT_KB)
|
|
1576
|
+
else:
|
|
1577
|
+
model.write(os.path.join(constants.RESULTS_PATH, "mip_solution.sol"))
|
|
1578
|
+
result: float = Util.round(abs(model.objective_value))
|
|
1579
|
+
sol = Solution(result)
|
|
1580
|
+
for i in range(size):
|
|
1581
|
+
if ConfigReader.DEBUG_PRINT or show_variable[i]:
|
|
1582
|
+
name: str = self.variables[i].name
|
|
1583
|
+
value: float = round(vars_mip[var_name_map[name]].x, 6)
|
|
1584
|
+
if show_variable[i]:
|
|
1585
|
+
sol.add_showed_variable(name, value)
|
|
1586
|
+
# if self.PRINT_VARIABLES:
|
|
1587
|
+
Util.debug(f"{name} = {value}")
|
|
1588
|
+
if self.PRINT_LABELS:
|
|
1589
|
+
self.print_instance_of_labels(name, value)
|
|
1590
|
+
|
|
1591
|
+
Util.debug(
|
|
1592
|
+
f"{constants.STAR_SEPARATOR}Statistics{constants.STAR_SEPARATOR}"
|
|
1593
|
+
)
|
|
1594
|
+
Util.debug("MILP problem:")
|
|
1595
|
+
Util.debug(f"\t\tSemi continuous variables: {num_up_vars}")
|
|
1596
|
+
Util.debug(f"\t\tBinary variables: {num_binary_vars}")
|
|
1597
|
+
Util.debug(f"\t\tContinuous variables: {num_free_vars}")
|
|
1598
|
+
Util.debug(f"\t\tInteger variables: {num_integer_vars}")
|
|
1599
|
+
Util.debug(f"\t\tTotal variables: {len(self.variables)}")
|
|
1600
|
+
Util.debug(f"\t\tConstraints: {len(self.constraints)}")
|
|
1601
|
+
return sol
|
|
1602
|
+
except Exception as e:
|
|
1603
|
+
Util.error(f"Error: {e} {traceback.format_exc()}")
|
|
1604
|
+
return None
|
|
1605
|
+
|
|
1606
|
+
def solve_pulp(self, objective: Expression) -> typing.Optional[Solution]:
|
|
1607
|
+
import pulp
|
|
1608
|
+
|
|
1609
|
+
try:
|
|
1610
|
+
Util.debug(f"Objective function -> {objective}")
|
|
1611
|
+
|
|
1612
|
+
num_binary_vars: int = 0
|
|
1613
|
+
num_free_vars: int = 0
|
|
1614
|
+
num_integer_vars: int = 0
|
|
1615
|
+
num_up_vars: int = 0
|
|
1616
|
+
size: int = len(self.variables)
|
|
1617
|
+
objective_value: list[float] = [0.0] * size
|
|
1618
|
+
show_variable: list[bool] = [False] * size
|
|
1619
|
+
my_vars: list[Variable] = self.show_vars.get_variables()
|
|
1620
|
+
|
|
1621
|
+
if objective is not None:
|
|
1622
|
+
for term in objective.get_terms():
|
|
1623
|
+
objective_value[
|
|
1624
|
+
self.variables.index(term.get_var())
|
|
1625
|
+
] += term.get_coeff()
|
|
1626
|
+
|
|
1627
|
+
model = pulp.LpProblem(
|
|
1628
|
+
f"FuzzyDL-{ConfigReader.MILP_PROVIDER.upper()}", pulp.LpMinimize
|
|
1629
|
+
)
|
|
1630
|
+
|
|
1631
|
+
var_types: dict[VariableType, str] = {
|
|
1632
|
+
VariableType.BINARY: pulp.LpBinary,
|
|
1633
|
+
VariableType.INTEGER: pulp.LpInteger,
|
|
1634
|
+
VariableType.CONTINUOUS: pulp.LpContinuous,
|
|
1635
|
+
VariableType.SEMI_CONTINUOUS: pulp.LpContinuous,
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
vars_pulp: dict[str, pulp.LpVariable] = dict()
|
|
1639
|
+
var_name_map: dict[str, str] = {
|
|
1640
|
+
str(v): f"x{i}" for i, v in enumerate(self.variables)
|
|
1641
|
+
}
|
|
1642
|
+
semicontinuous_var_counter: int = 1
|
|
1643
|
+
semicontinuous_var_name: str = "semic_z"
|
|
1644
|
+
for i, curr_variable in enumerate(self.variables):
|
|
1645
|
+
v_type: VariableType = curr_variable.get_type()
|
|
1646
|
+
Util.debug(
|
|
1647
|
+
(
|
|
1648
|
+
f"Variable -- "
|
|
1649
|
+
f"[{curr_variable.get_lower_bound()}, {curr_variable.get_upper_bound()}] - "
|
|
1650
|
+
f"Obj value = {objective_value[i]} - "
|
|
1651
|
+
f"Var type = {v_type.name} -- "
|
|
1652
|
+
f"Var = {curr_variable}"
|
|
1653
|
+
)
|
|
1654
|
+
)
|
|
1655
|
+
|
|
1656
|
+
vars_pulp[var_name_map[str(curr_variable)]] = pulp.LpVariable(
|
|
1657
|
+
name=var_name_map[str(curr_variable)],
|
|
1658
|
+
lowBound=(
|
|
1659
|
+
curr_variable.get_lower_bound()
|
|
1660
|
+
if curr_variable.get_lower_bound() != float("-inf")
|
|
1661
|
+
else None
|
|
1662
|
+
),
|
|
1663
|
+
upBound=(
|
|
1664
|
+
curr_variable.get_upper_bound()
|
|
1665
|
+
if curr_variable.get_upper_bound() != float("inf")
|
|
1666
|
+
else None
|
|
1667
|
+
),
|
|
1668
|
+
cat=var_types[v_type],
|
|
1669
|
+
)
|
|
1670
|
+
|
|
1671
|
+
if curr_variable in my_vars:
|
|
1672
|
+
show_variable[i] = True
|
|
1673
|
+
|
|
1674
|
+
if (
|
|
1675
|
+
v_type == VariableType.SEMI_CONTINUOUS
|
|
1676
|
+
and ConfigReader.MILP_PROVIDER
|
|
1677
|
+
in [
|
|
1678
|
+
MILPProvider.PULP_GLPK,
|
|
1679
|
+
MILPProvider.PULP_CPLEX,
|
|
1680
|
+
]
|
|
1681
|
+
):
|
|
1682
|
+
# Semi Continuous variables are not handled by GLPK and HiGHS
|
|
1683
|
+
# if x in [L, U] u {0} is semi continuous, then add the following constraints
|
|
1684
|
+
# L * y <= x <= U * y, where y in {0, 1} is a binary variable
|
|
1685
|
+
bin_var = pulp.LpVariable(
|
|
1686
|
+
name=f"{semicontinuous_var_name}{semicontinuous_var_counter}",
|
|
1687
|
+
cat=pulp.LpBinary,
|
|
1688
|
+
)
|
|
1689
|
+
constraint_1 = (
|
|
1690
|
+
vars_pulp[var_name_map[str(curr_variable)]]
|
|
1691
|
+
>= bin_var * curr_variable.get_lower_bound()
|
|
1692
|
+
)
|
|
1693
|
+
constraint_2 = (
|
|
1694
|
+
vars_pulp[var_name_map[str(curr_variable)]]
|
|
1695
|
+
<= bin_var * curr_variable.get_upper_bound()
|
|
1696
|
+
)
|
|
1697
|
+
if constraint_1 not in model.constraints.values():
|
|
1698
|
+
model.addConstraint(
|
|
1699
|
+
constraint_1, name=f"constraint_{bin_var.name}_1"
|
|
1700
|
+
)
|
|
1701
|
+
if constraint_2 not in model.constraints.values():
|
|
1702
|
+
model.addConstraint(
|
|
1703
|
+
constraint_2, name=f"constraint_{bin_var.name}_2"
|
|
1704
|
+
)
|
|
1705
|
+
semicontinuous_var_counter += 1
|
|
1706
|
+
Util.debug(
|
|
1707
|
+
(
|
|
1708
|
+
f"New Variable -- "
|
|
1709
|
+
f"[{bin_var.lowBound}, {bin_var.upBound}] - "
|
|
1710
|
+
f"Var type = {bin_var.cat} -- "
|
|
1711
|
+
f"Var = {bin_var.name}"
|
|
1712
|
+
)
|
|
1713
|
+
)
|
|
1714
|
+
Util.debug(f"New Constraint 1 -- {constraint_1}")
|
|
1715
|
+
Util.debug(f"New Constraint 2 -- {constraint_2}")
|
|
1716
|
+
|
|
1717
|
+
if v_type == VariableType.BINARY:
|
|
1718
|
+
num_binary_vars += 1
|
|
1719
|
+
elif v_type == VariableType.CONTINUOUS:
|
|
1720
|
+
num_free_vars += 1
|
|
1721
|
+
elif v_type == VariableType.INTEGER:
|
|
1722
|
+
num_integer_vars += 1
|
|
1723
|
+
elif v_type == VariableType.SEMI_CONTINUOUS:
|
|
1724
|
+
num_up_vars += 1
|
|
1725
|
+
|
|
1726
|
+
Util.debug(f"# constraints -> {len(self.constraints)}")
|
|
1727
|
+
constraint_name: str = "constraint"
|
|
1728
|
+
pulp_sense: dict[InequalityType, int] = {
|
|
1729
|
+
InequalityType.EQUAL: pulp.LpConstraintEQ,
|
|
1730
|
+
InequalityType.LESS_THAN: pulp.LpConstraintLE,
|
|
1731
|
+
InequalityType.GREATER_THAN: pulp.LpConstraintGE,
|
|
1732
|
+
}
|
|
1733
|
+
for i, constraint in enumerate(self.constraints):
|
|
1734
|
+
if constraint in self.constraints[:i]:
|
|
1735
|
+
continue
|
|
1736
|
+
# ignore zero constraints
|
|
1737
|
+
if constraint.is_zero():
|
|
1738
|
+
continue
|
|
1739
|
+
|
|
1740
|
+
curr_name: str = f"{constraint_name}_{i + 1}"
|
|
1741
|
+
pulp_expr: pulp.LpAffineExpression = pulp.lpSum(
|
|
1742
|
+
term.get_coeff() * vars_pulp[var_name_map[str(term.get_var())]]
|
|
1743
|
+
for term in constraint.get_terms()
|
|
1744
|
+
)
|
|
1745
|
+
pulp_constraint: pulp.LpConstraint = pulp.LpConstraint(
|
|
1746
|
+
e=pulp_expr,
|
|
1747
|
+
sense=pulp_sense[constraint.get_type()],
|
|
1748
|
+
rhs=constraint.get_constant(),
|
|
1749
|
+
)
|
|
1750
|
+
|
|
1751
|
+
# ignore zero constraints of type a * x - a * x
|
|
1752
|
+
if (
|
|
1753
|
+
len(pulp_constraint) == 1
|
|
1754
|
+
and list(pulp_constraint.values())[0] == 0
|
|
1755
|
+
and pulp_constraint.constant == 0
|
|
1756
|
+
):
|
|
1757
|
+
continue
|
|
1758
|
+
|
|
1759
|
+
model.addConstraint(pulp_constraint, name=curr_name)
|
|
1760
|
+
Util.debug(f"{curr_name}: {constraint}")
|
|
1761
|
+
|
|
1762
|
+
if ConfigReader.MILP_PROVIDER == MILPProvider.PULP:
|
|
1763
|
+
solver = pulp.PULP_CBC_CMD(
|
|
1764
|
+
mip=True,
|
|
1765
|
+
msg=ConfigReader.DEBUG_PRINT,
|
|
1766
|
+
gapRel=1e-9,
|
|
1767
|
+
presolve=True,
|
|
1768
|
+
keepFiles=False, # ConfigReader.DEBUG_PRINT,
|
|
1769
|
+
logPath=(
|
|
1770
|
+
os.path.join(".", "logs", f"pulp_{pulp.PULP_CBC_CMD.name}.log")
|
|
1771
|
+
if ConfigReader.DEBUG_PRINT
|
|
1772
|
+
else None
|
|
1773
|
+
),
|
|
1774
|
+
options=[
|
|
1775
|
+
"--primalTolerance", # feasibility tolerance
|
|
1776
|
+
"1e-9",
|
|
1777
|
+
"--integerTolerance", # integer feasibility tolerance
|
|
1778
|
+
"1e-9",
|
|
1779
|
+
"--ratioGap", # relative mip gap
|
|
1780
|
+
str(ConfigReader.EPSILON),
|
|
1781
|
+
"--allowableGap", # optimality gap tolerance
|
|
1782
|
+
"0",
|
|
1783
|
+
"--preprocess", # enable preprocessing
|
|
1784
|
+
"on",
|
|
1785
|
+
],
|
|
1786
|
+
)
|
|
1787
|
+
elif ConfigReader.MILP_PROVIDER == MILPProvider.PULP_GLPK:
|
|
1788
|
+
solver = pulp.GLPK_CMD(
|
|
1789
|
+
mip=True,
|
|
1790
|
+
msg=ConfigReader.DEBUG_PRINT,
|
|
1791
|
+
keepFiles=False, # ConfigReader.DEBUG_PRINT,
|
|
1792
|
+
options=[
|
|
1793
|
+
"--presol", # use presolver (default; assumes --scale and --adv)
|
|
1794
|
+
"--exact", # use simplex method based on exact arithmetic
|
|
1795
|
+
"--xcheck", # check final basis using exact arithmetic
|
|
1796
|
+
"--intopt", # enforce MIP (Mixed Integer Programming)
|
|
1797
|
+
"--mipgap",
|
|
1798
|
+
str(
|
|
1799
|
+
ConfigReader.EPSILON
|
|
1800
|
+
), # no relative gap between primal & best bound
|
|
1801
|
+
]
|
|
1802
|
+
+ (
|
|
1803
|
+
[
|
|
1804
|
+
"--log",
|
|
1805
|
+
os.path.join(".", "logs", f"pulp_{pulp.GLPK_CMD.name}.log"),
|
|
1806
|
+
]
|
|
1807
|
+
if ConfigReader.DEBUG_PRINT
|
|
1808
|
+
else []
|
|
1809
|
+
),
|
|
1810
|
+
)
|
|
1811
|
+
elif ConfigReader.MILP_PROVIDER == MILPProvider.PULP_HIGHS:
|
|
1812
|
+
solver = pulp.HiGHS(
|
|
1813
|
+
mip=True,
|
|
1814
|
+
msg=ConfigReader.DEBUG_PRINT,
|
|
1815
|
+
gapRel=1e-6,
|
|
1816
|
+
log_file=(
|
|
1817
|
+
os.path.join(".", "logs", f"pulp_{pulp.HiGHS.name}.log")
|
|
1818
|
+
if ConfigReader.DEBUG_PRINT
|
|
1819
|
+
else None
|
|
1820
|
+
),
|
|
1821
|
+
primal_feasibility_tolerance=1e-9,
|
|
1822
|
+
dual_feasibility_tolerance=1e-9,
|
|
1823
|
+
mip_feasibility_tolerance=1e-9,
|
|
1824
|
+
presolve="on",
|
|
1825
|
+
parallel="on",
|
|
1826
|
+
write_solution_to_file=True,
|
|
1827
|
+
write_solution_style=1,
|
|
1828
|
+
solution_file=os.path.join(
|
|
1829
|
+
constants.RESULTS_PATH, "highs_solution.sol"
|
|
1830
|
+
),
|
|
1831
|
+
write_model_file=os.path.join(
|
|
1832
|
+
constants.RESULTS_PATH, "highs_model.lp"
|
|
1833
|
+
),
|
|
1834
|
+
)
|
|
1835
|
+
elif ConfigReader.MILP_PROVIDER == MILPProvider.PULP_CPLEX:
|
|
1836
|
+
solver = pulp.CPLEX_CMD(
|
|
1837
|
+
# path="/Applications/CPLEX_Studio2211/cplex/bin/arm64_osx/cplex",
|
|
1838
|
+
mip=True,
|
|
1839
|
+
msg=ConfigReader.DEBUG_PRINT,
|
|
1840
|
+
gapRel=1e-9,
|
|
1841
|
+
keepFiles=False, # ConfigReader.DEBUG_PRINT,
|
|
1842
|
+
logPath=(
|
|
1843
|
+
os.path.join(".", "logs", f"pulp_{pulp.CPLEX_CMD.name}.log")
|
|
1844
|
+
if ConfigReader.DEBUG_PRINT
|
|
1845
|
+
else None
|
|
1846
|
+
),
|
|
1847
|
+
)
|
|
1848
|
+
|
|
1849
|
+
model.objective = pulp.lpSum(
|
|
1850
|
+
ov * vars_pulp[var_name_map[str(self.variables[i])]]
|
|
1851
|
+
for i, ov in enumerate(objective_value)
|
|
1852
|
+
if ov != 0
|
|
1853
|
+
)
|
|
1854
|
+
result = model.solve(solver=solver)
|
|
1855
|
+
if ConfigReader.MILP_PROVIDER == MILPProvider.PULP_CPLEX:
|
|
1856
|
+
for file in os.listdir("./"):
|
|
1857
|
+
if "clone" in file:
|
|
1858
|
+
os.remove(file)
|
|
1859
|
+
|
|
1860
|
+
Util.debug(f"Model:")
|
|
1861
|
+
sol: Solution = None
|
|
1862
|
+
if result != pulp.LpStatusOptimal:
|
|
1863
|
+
sol = Solution(Solution.INCONSISTENT_KB)
|
|
1864
|
+
else:
|
|
1865
|
+
result: float = Util.round(abs(model.objective.value()))
|
|
1866
|
+
sol = Solution(result)
|
|
1867
|
+
var_dict: dict[str, pulp.LpVariable] = model.variablesDict()
|
|
1868
|
+
for i in range(size):
|
|
1869
|
+
if ConfigReader.DEBUG_PRINT or show_variable[i]:
|
|
1870
|
+
name: str = self.variables[i].name
|
|
1871
|
+
value: float = (
|
|
1872
|
+
round(var_dict[var_name_map[name]].value(), 6)
|
|
1873
|
+
if var_name_map[name] in var_dict
|
|
1874
|
+
else 0.0
|
|
1875
|
+
)
|
|
1876
|
+
if show_variable[i]:
|
|
1877
|
+
sol.add_showed_variable(name, value)
|
|
1878
|
+
# if self.PRINT_VARIABLES:
|
|
1879
|
+
Util.debug(f"{name} = {value}")
|
|
1880
|
+
if self.PRINT_LABELS:
|
|
1881
|
+
self.print_instance_of_labels(name, value)
|
|
1882
|
+
|
|
1883
|
+
Util.debug(
|
|
1884
|
+
f"{constants.STAR_SEPARATOR}Statistics{constants.STAR_SEPARATOR}"
|
|
1885
|
+
)
|
|
1886
|
+
Util.debug("MILP problem:")
|
|
1887
|
+
Util.debug(f"\t\tSemi continuous variables: {num_up_vars}")
|
|
1888
|
+
Util.debug(f"\t\tBinary variables: {num_binary_vars}")
|
|
1889
|
+
Util.debug(f"\t\tContinuous variables: {num_free_vars}")
|
|
1890
|
+
Util.debug(f"\t\tInteger variables: {num_integer_vars}")
|
|
1891
|
+
Util.debug(f"\t\tTotal variables: {len(self.variables)}")
|
|
1892
|
+
Util.debug(f"\t\tConstraints: {len(self.constraints)}")
|
|
1893
|
+
return sol
|
|
1894
|
+
except Exception as e:
|
|
1895
|
+
Util.error(f"Error: {e} {traceback.format_exc()}")
|
|
1896
|
+
return None
|
|
1897
|
+
|
|
1898
|
+
# def solve_scipy(self, objective: Expression) -> typing.Optional[Solution]:
|
|
1899
|
+
# import numpy as np
|
|
1900
|
+
# from scipy.optimize import milp, OptimizeResult, LinearConstraint, Bounds, linprog, linprog_verbose_callback, show_options
|
|
1901
|
+
|
|
1902
|
+
# num_binary_vars: int = 0
|
|
1903
|
+
# num_free_vars: int = 0
|
|
1904
|
+
# num_integer_vars: int = 0
|
|
1905
|
+
# num_up_vars: int = 0
|
|
1906
|
+
# size: int = len(self.variables)
|
|
1907
|
+
# objective_value: list[float] = [0.0] * size
|
|
1908
|
+
# show_variable: list[bool] = [False] * size
|
|
1909
|
+
# my_vars: list[Variable] = self.show_vars.get_variables()
|
|
1910
|
+
|
|
1911
|
+
# if objective is not None:
|
|
1912
|
+
# for term in objective.get_terms():
|
|
1913
|
+
# index = self.variables.index(term.get_var())
|
|
1914
|
+
# objective_value[index] += term.get_coeff()
|
|
1915
|
+
|
|
1916
|
+
# var_types: dict[VariableType, str] = {
|
|
1917
|
+
# VariableType.BINARY: 1,
|
|
1918
|
+
# VariableType.CONTINUOUS: 0,
|
|
1919
|
+
# VariableType.INTEGER: 1,
|
|
1920
|
+
# VariableType.SEMI_CONTINUOUS: 2,
|
|
1921
|
+
# }
|
|
1922
|
+
|
|
1923
|
+
# for i, curr_variable in enumerate(self.variables):
|
|
1924
|
+
# v_type: VariableType = curr_variable.get_type()
|
|
1925
|
+
|
|
1926
|
+
# Util.debug(
|
|
1927
|
+
# (
|
|
1928
|
+
# f"Variable -- "
|
|
1929
|
+
# f"[{curr_variable.get_lower_bound()}, {curr_variable.get_upper_bound()}] - "
|
|
1930
|
+
# f"Obj value = {objective_value[i]} - "
|
|
1931
|
+
# f"Var type = {v_type.name} -- "
|
|
1932
|
+
# f"Var = {curr_variable}"
|
|
1933
|
+
# )
|
|
1934
|
+
# )
|
|
1935
|
+
|
|
1936
|
+
# if curr_variable in my_vars:
|
|
1937
|
+
# show_variable[i] = True
|
|
1938
|
+
|
|
1939
|
+
# if v_type == VariableType.BINARY:
|
|
1940
|
+
# num_binary_vars += 1
|
|
1941
|
+
# elif v_type == VariableType.CONTINUOUS:
|
|
1942
|
+
# num_free_vars += 1
|
|
1943
|
+
# elif v_type == VariableType.INTEGER:
|
|
1944
|
+
# num_integer_vars += 1
|
|
1945
|
+
# elif v_type == VariableType.SEMI_CONTINUOUS:
|
|
1946
|
+
# num_up_vars += 1
|
|
1947
|
+
|
|
1948
|
+
# Util.debug(f"# constraints -> {len(self.constraints)}")
|
|
1949
|
+
# constraint_name: str = "constraint"
|
|
1950
|
+
# matrix_A = np.zeros((len(self.constraints), len(self.variables)))
|
|
1951
|
+
# inequality_A = np.zeros((len(self.constraints), len(self.variables)))
|
|
1952
|
+
# equality_A = np.zeros((len(self.constraints), len(self.variables)))
|
|
1953
|
+
# lb = np.zeros(len(self.constraints))
|
|
1954
|
+
# ub = np.zeros(len(self.constraints))
|
|
1955
|
+
# in_ub = np.zeros(len(self.constraints))
|
|
1956
|
+
# eq_ub = np.zeros(len(self.constraints))
|
|
1957
|
+
# for i, constraint in enumerate(self.constraints):
|
|
1958
|
+
# curr_name: str = f"{constraint_name}_{i + 1}"
|
|
1959
|
+
# row = np.zeros(len(self.variables))
|
|
1960
|
+
# for term in constraint.get_terms():
|
|
1961
|
+
# row[self.variables.index(term.get_var())] = term.get_coeff()
|
|
1962
|
+
# if np.allclose(row, 0):
|
|
1963
|
+
# continue
|
|
1964
|
+
# Util.debug(f"{curr_name}: {constraint}")
|
|
1965
|
+
# matrix_A[i, :] = row
|
|
1966
|
+
# if constraint.type == InequalityType.EQUAL:
|
|
1967
|
+
# equality_A[i, :] = row
|
|
1968
|
+
# eq_ub[i] = constraint.get_constant()
|
|
1969
|
+
|
|
1970
|
+
# lb[i] = constraint.get_constant()
|
|
1971
|
+
# ub[i] = constraint.get_constant()
|
|
1972
|
+
# elif constraint.type == InequalityType.LESS_THAN:
|
|
1973
|
+
# inequality_A[i, :] = row
|
|
1974
|
+
# in_ub[i] = constraint.get_constant()
|
|
1975
|
+
|
|
1976
|
+
# lb[i] = -np.inf
|
|
1977
|
+
# ub[i] = constraint.get_constant()
|
|
1978
|
+
# elif constraint.type == InequalityType.GREATER_THAN:
|
|
1979
|
+
# inequality_A[i, :] = -row
|
|
1980
|
+
# in_ub[i] = -constraint.get_constant()
|
|
1981
|
+
|
|
1982
|
+
# lb[i] = constraint.get_constant()
|
|
1983
|
+
# ub[i] = np.inf
|
|
1984
|
+
|
|
1985
|
+
# indices = np.all(matrix_A == 0, axis=1)
|
|
1986
|
+
# matrix_A = np.delete(matrix_A, indices, axis=0)
|
|
1987
|
+
# lb = np.delete(lb, indices, axis=0)
|
|
1988
|
+
# ub = np.delete(ub, indices, axis=0)
|
|
1989
|
+
|
|
1990
|
+
# indices = np.all(inequality_A == 0, axis=1)
|
|
1991
|
+
# inequality_A = np.delete(inequality_A, indices, axis=0)
|
|
1992
|
+
# in_ub = np.delete(in_ub, indices, axis=0)
|
|
1993
|
+
|
|
1994
|
+
# indices = np.all(equality_A == 0, axis=1)
|
|
1995
|
+
# equality_A = np.delete(equality_A, indices, axis=0)
|
|
1996
|
+
# eq_ub = np.delete(eq_ub, indices, axis=0)
|
|
1997
|
+
|
|
1998
|
+
# bounds = Bounds(
|
|
1999
|
+
# [var.get_lower_bound() for var in self.variables],
|
|
2000
|
+
# [var.get_upper_bound() for var in self.variables],
|
|
2001
|
+
# keep_feasible=True,
|
|
2002
|
+
# )
|
|
2003
|
+
# integrality = np.array([var_types[var.get_type()] for var in self.variables])
|
|
2004
|
+
# constraint = LinearConstraint(
|
|
2005
|
+
# matrix_A, lb, ub, keep_feasible=True
|
|
2006
|
+
# )
|
|
2007
|
+
|
|
2008
|
+
# result: OptimizeResult = milp(
|
|
2009
|
+
# c=np.array(objective_value),
|
|
2010
|
+
# integrality=integrality,
|
|
2011
|
+
# constraints=constraint,
|
|
2012
|
+
# bounds=bounds,
|
|
2013
|
+
# options={
|
|
2014
|
+
# "disp": ConfigReader.DEBUG_PRINT,
|
|
2015
|
+
# "presolve": True,
|
|
2016
|
+
# "mip_rel_gap": 1e-6,
|
|
2017
|
+
# },
|
|
2018
|
+
# )
|
|
2019
|
+
|
|
2020
|
+
# result: OptimizeResult = linprog(
|
|
2021
|
+
# c=np.array(objective_value),
|
|
2022
|
+
# A_ub=inequality_A,
|
|
2023
|
+
# b_ub=in_ub,
|
|
2024
|
+
# A_eq=equality_A,
|
|
2025
|
+
# b_eq=eq_ub,
|
|
2026
|
+
# method="highs-ipm",
|
|
2027
|
+
# integrality=integrality,
|
|
2028
|
+
# bounds=[(var.get_lower_bound(), var.get_upper_bound()) for var in self.variables],
|
|
2029
|
+
# options={
|
|
2030
|
+
# "disp": ConfigReader.DEBUG_PRINT,
|
|
2031
|
+
# "presolve": False,
|
|
2032
|
+
# "mip_rel_gap": 1e-3,
|
|
2033
|
+
# "ipm_optimality_tolerance": 1e-5,
|
|
2034
|
+
# },
|
|
2035
|
+
# # callback=linprog_verbose_callback if ConfigReader.DEBUG_PRINT else None
|
|
2036
|
+
# )
|
|
2037
|
+
|
|
2038
|
+
# Util.debug(f"Model:\n{result}")
|
|
2039
|
+
|
|
2040
|
+
# sol: Solution = None
|
|
2041
|
+
# if not result.success:
|
|
2042
|
+
# sol = Solution(Solution.INCONSISTENT_KB)
|
|
2043
|
+
# else:
|
|
2044
|
+
# for i in range(size):
|
|
2045
|
+
# if ConfigReader.DEBUG_PRINT or show_variable[i]:
|
|
2046
|
+
# name: str = self.variables[i].name
|
|
2047
|
+
# value: float = (
|
|
2048
|
+
# round(result.x[i], 6)
|
|
2049
|
+
# )
|
|
2050
|
+
# if self.PRINT_VARIABLES:
|
|
2051
|
+
# Util.debug(f"{name} = {value}")
|
|
2052
|
+
# if self.PRINT_LABELS:
|
|
2053
|
+
# self.print_instance_of_labels(name, value)
|
|
2054
|
+
# result: float = Util.round(abs(result.fun))
|
|
2055
|
+
# sol = Solution(result)
|
|
2056
|
+
|
|
2057
|
+
# Util.debug(
|
|
2058
|
+
# f"{constants.STAR_SEPARATOR}Statistics{constants.STAR_SEPARATOR}"
|
|
2059
|
+
# )
|
|
2060
|
+
# Util.debug("MILP problem:")
|
|
2061
|
+
# Util.debug(f"\t\tSemi continuous variables: {num_up_vars}")
|
|
2062
|
+
# Util.debug(f"\t\tBinary variables: {num_binary_vars}")
|
|
2063
|
+
# Util.debug(f"\t\tContinuous variables: {num_free_vars}")
|
|
2064
|
+
# Util.debug(f"\t\tInteger variables: {num_integer_vars}")
|
|
2065
|
+
# Util.debug(f"\t\tTotal variables: {len(self.variables)}")
|
|
2066
|
+
# Util.debug(f"\t\tConstraints: {len(self.constraints)}")
|
|
2067
|
+
# return sol
|
|
755
2068
|
|
|
756
2069
|
def add_crisp_concept(self, concept_name: str) -> None:
|
|
2070
|
+
"""Defines a concept to be crisp."""
|
|
757
2071
|
self.crisp_concepts.add(concept_name)
|
|
758
2072
|
|
|
759
2073
|
def add_crisp_role(self, role_name: str) -> None:
|
|
2074
|
+
"""Defines a role to be crisp."""
|
|
760
2075
|
self.crisp_roles.add(role_name)
|
|
761
2076
|
|
|
762
2077
|
def is_crisp_concept(self, concept_name: str) -> bool:
|
|
2078
|
+
"""Checks if a concept is crisp or not."""
|
|
763
2079
|
return concept_name in self.crisp_concepts
|
|
764
2080
|
|
|
765
2081
|
def is_crisp_role(self, role_name: str) -> bool:
|
|
2082
|
+
"""Checks if a role is crisp or not."""
|
|
766
2083
|
return role_name in self.crisp_roles
|
|
767
2084
|
|
|
768
2085
|
def set_binary_variables(self) -> None:
|
|
2086
|
+
"""Transforms every [0,1]-variable into a {0,1} variable."""
|
|
2087
|
+
# set all variables binary, except
|
|
2088
|
+
# - those that hold the value of a datatype filler
|
|
2089
|
+
# - free variables in constraints
|
|
769
2090
|
for v in self.variables:
|
|
770
2091
|
if v.get_datatype_filler_type() or v.get_type() in (
|
|
771
2092
|
VariableType.CONTINUOUS,
|
|
@@ -775,14 +2096,31 @@ class MILPHelper:
|
|
|
775
2096
|
v.set_binary_variable()
|
|
776
2097
|
|
|
777
2098
|
def get_name_for_integer(self, i: int) -> typing.Optional[str]:
|
|
2099
|
+
"""Gets the name of the i-th variable."""
|
|
778
2100
|
for name, i2 in self.number_of_variables.items():
|
|
779
2101
|
if i == i2:
|
|
780
2102
|
return name
|
|
781
2103
|
return None
|
|
782
2104
|
|
|
783
2105
|
def get_number_for_assertion(self, ass: Assertion) -> int:
|
|
2106
|
+
"""Gets an integer codification of an assertion."""
|
|
784
2107
|
return self.number_of_variables.get(str(self.get_variable(ass)))
|
|
785
2108
|
|
|
786
2109
|
def add_contradiction(self) -> None:
|
|
2110
|
+
"""Add a contradiction to make the fuzzy KB unsatisfiable"""
|
|
787
2111
|
self.constraints.clear()
|
|
788
2112
|
self.add_new_constraint(Expression(1.0), InequalityType.EQUAL)
|
|
2113
|
+
|
|
2114
|
+
def add_cardinality_list(self, sc: SigmaCount) -> None:
|
|
2115
|
+
"""
|
|
2116
|
+
SigmaCount(r,C,O,d)^I(w) = d^I(xSigma)
|
|
2117
|
+
|
|
2118
|
+
Args:
|
|
2119
|
+
sc (SigmaCount):
|
|
2120
|
+
xSigma: Free variable taking the value \sigma_{i2 \in O} r(i1, i2) \otimes C(i2)
|
|
2121
|
+
i1: Name of an individual, subject of the relation.
|
|
2122
|
+
O: Set of individuals candidates to be the object of the relation.
|
|
2123
|
+
r: Role.
|
|
2124
|
+
C: Concept.
|
|
2125
|
+
"""
|
|
2126
|
+
self.cardinalities.append(sc)
|