fuzzy-dl-owl2 1.0.7__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 +3 -0
- 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 +2033 -245
- fuzzy_dl_owl2/fuzzydl/label.py +18 -10
- fuzzy_dl_owl2/fuzzydl/milp/expression.py +33 -23
- fuzzy_dl_owl2/fuzzydl/milp/inequation.py +8 -0
- fuzzy_dl_owl2/fuzzydl/milp/milp_helper.py +720 -22
- 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/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 +17 -27
- fuzzy_dl_owl2/fuzzydl/util/constants.py +100 -0
- fuzzy_dl_owl2-1.0.8.dist-info/METADATA +817 -0
- {fuzzy_dl_owl2-1.0.7.dist-info → fuzzy_dl_owl2-1.0.8.dist-info}/RECORD +82 -78
- fuzzy_dl_owl2-1.0.7.dist-info/METADATA +0 -408
- {fuzzy_dl_owl2-1.0.7.dist-info → fuzzy_dl_owl2-1.0.8.dist-info}/LICENSE +0 -0
- {fuzzy_dl_owl2-1.0.7.dist-info → fuzzy_dl_owl2-1.0.8.dist-info}/WHEEL +0 -0
|
@@ -2,14 +2,19 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import copy
|
|
4
4
|
import os
|
|
5
|
+
import re
|
|
6
|
+
import time
|
|
5
7
|
import traceback
|
|
6
8
|
import typing
|
|
7
9
|
|
|
10
|
+
import networkx as nx
|
|
11
|
+
|
|
8
12
|
from fuzzy_dl_owl2.fuzzydl.assertion.assertion import Assertion
|
|
9
13
|
from fuzzy_dl_owl2.fuzzydl.concept.concept import Concept
|
|
10
14
|
from fuzzy_dl_owl2.fuzzydl.concept.interface.has_value_interface import (
|
|
11
15
|
HasValueInterface,
|
|
12
16
|
)
|
|
17
|
+
from fuzzy_dl_owl2.fuzzydl.concept.sigma_count import SigmaCount
|
|
13
18
|
from fuzzy_dl_owl2.fuzzydl.degree.degree import Degree
|
|
14
19
|
from fuzzy_dl_owl2.fuzzydl.degree.degree_numeric import DegreeNumeric
|
|
15
20
|
from fuzzy_dl_owl2.fuzzydl.degree.degree_variable import DegreeVariable
|
|
@@ -24,10 +29,11 @@ from fuzzy_dl_owl2.fuzzydl.milp.variable import Variable
|
|
|
24
29
|
from fuzzy_dl_owl2.fuzzydl.relation import Relation
|
|
25
30
|
from fuzzy_dl_owl2.fuzzydl.restriction.restriction import Restriction
|
|
26
31
|
from fuzzy_dl_owl2.fuzzydl.util import constants
|
|
27
|
-
from fuzzy_dl_owl2.fuzzydl.util.config_reader import ConfigReader
|
|
32
|
+
from fuzzy_dl_owl2.fuzzydl.util.config_reader import ConfigReader
|
|
28
33
|
from fuzzy_dl_owl2.fuzzydl.util.constants import (
|
|
29
34
|
ConceptType,
|
|
30
35
|
InequalityType,
|
|
36
|
+
MILPProvider,
|
|
31
37
|
VariableType,
|
|
32
38
|
)
|
|
33
39
|
from fuzzy_dl_owl2.fuzzydl.util.util import Util
|
|
@@ -35,10 +41,17 @@ from fuzzy_dl_owl2.fuzzydl.util.util import Util
|
|
|
35
41
|
|
|
36
42
|
# @utils.singleton
|
|
37
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.
|
|
38
48
|
PRINT_LABELS: bool = True
|
|
49
|
+
# Indicates whether we want to show the value of the variables or not.
|
|
39
50
|
PRINT_VARIABLES: bool = True
|
|
40
51
|
|
|
41
52
|
def __init__(self) -> None:
|
|
53
|
+
self.nominal_variables: bool = False
|
|
54
|
+
self.cardinalities: list[SigmaCount] = list()
|
|
42
55
|
self.constraints: list[Inequation] = list()
|
|
43
56
|
self.crisp_concepts: set[str] = set()
|
|
44
57
|
self.crisp_roles: set[str] = set()
|
|
@@ -50,6 +63,8 @@ class MILPHelper:
|
|
|
50
63
|
|
|
51
64
|
def clone(self) -> typing.Self:
|
|
52
65
|
milp: MILPHelper = MILPHelper()
|
|
66
|
+
milp.nominal_variables = self.nominal_variables
|
|
67
|
+
milp.cardinalities = [c.clone() for c in self.cardinalities]
|
|
53
68
|
milp.constraints = [c.clone() for c in self.constraints]
|
|
54
69
|
milp.crisp_concepts = copy.deepcopy(self.crisp_concepts)
|
|
55
70
|
milp.crisp_roles = copy.deepcopy(self.crisp_roles)
|
|
@@ -61,6 +76,18 @@ class MILPHelper:
|
|
|
61
76
|
return milp
|
|
62
77
|
|
|
63
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
|
+
"""
|
|
64
91
|
Util.debug(f"Running MILP solver: {ConfigReader.MILP_PROVIDER.name}")
|
|
65
92
|
if ConfigReader.MILP_PROVIDER == MILPProvider.GUROBI:
|
|
66
93
|
return self.solve_gurobi(objective)
|
|
@@ -89,6 +116,7 @@ class MILPHelper:
|
|
|
89
116
|
def print_instance_of_labels(self, name: str, value: float) -> None: ...
|
|
90
117
|
|
|
91
118
|
def print_instance_of_labels(self, *args) -> None:
|
|
119
|
+
"""Shows the membership degrees to some linguistic labels."""
|
|
92
120
|
assert len(args) in [2, 3]
|
|
93
121
|
assert isinstance(args[0], str)
|
|
94
122
|
if len(args) == 2:
|
|
@@ -102,7 +130,15 @@ class MILPHelper:
|
|
|
102
130
|
def __print_instance_of_labels_1(
|
|
103
131
|
self, f_name: str, ind_name: str, value: float
|
|
104
132
|
) -> None:
|
|
105
|
-
|
|
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})"
|
|
106
142
|
labels = self.show_vars.get_labels(name)
|
|
107
143
|
for f in labels:
|
|
108
144
|
Util.info(
|
|
@@ -110,6 +146,13 @@ class MILPHelper:
|
|
|
110
146
|
)
|
|
111
147
|
|
|
112
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
|
+
"""
|
|
113
156
|
labels = self.show_vars.get_labels(name)
|
|
114
157
|
for f in labels:
|
|
115
158
|
Util.info(
|
|
@@ -117,6 +160,7 @@ class MILPHelper:
|
|
|
117
160
|
)
|
|
118
161
|
|
|
119
162
|
def get_new_variable(self, v_type: VariableType) -> Variable:
|
|
163
|
+
"""Gets a new variable with the indicated type."""
|
|
120
164
|
while True:
|
|
121
165
|
new_var: Variable = Variable.get_new_variable(v_type)
|
|
122
166
|
var_name = str(new_var)
|
|
@@ -225,6 +269,9 @@ class MILPHelper:
|
|
|
225
269
|
raise ValueError
|
|
226
270
|
|
|
227
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
|
+
"""
|
|
228
275
|
if var_name in self.number_of_variables:
|
|
229
276
|
for variable in self.variables:
|
|
230
277
|
if str(variable) == var_name:
|
|
@@ -235,26 +282,69 @@ class MILPHelper:
|
|
|
235
282
|
return var
|
|
236
283
|
|
|
237
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
|
+
"""
|
|
238
290
|
var: Variable = self.get_variable(var_name)
|
|
239
291
|
var.set_type(v_type)
|
|
240
292
|
return var
|
|
241
293
|
|
|
242
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
|
+
"""
|
|
243
304
|
return self.get_variable(ass.get_individual(), ass.get_concept())
|
|
244
305
|
|
|
245
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
|
+
"""
|
|
246
316
|
a: Individual = rel.get_subject_individual()
|
|
247
317
|
b: Individual = rel.get_object_individual()
|
|
248
318
|
role: str = rel.get_role_name()
|
|
249
319
|
return self.get_variable(a, b, role)
|
|
250
320
|
|
|
251
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
|
+
"""
|
|
252
332
|
var: Variable = self.get_variable(f"{ind}:{restrict.get_name_without_degree()}")
|
|
253
333
|
if self.show_vars.show_individuals(str(ind)):
|
|
254
334
|
self.show_vars.add_variable(var, str(var))
|
|
255
335
|
return var
|
|
256
336
|
|
|
257
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
|
+
"""
|
|
258
348
|
if c.type == ConceptType.HAS_VALUE:
|
|
259
349
|
assert isinstance(c, HasValueInterface)
|
|
260
350
|
|
|
@@ -264,6 +354,16 @@ class MILPHelper:
|
|
|
264
354
|
return self.get_variable(ind, str(c))
|
|
265
355
|
|
|
266
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
|
+
"""
|
|
267
367
|
var: Variable = self.get_variable(f"{ind}:{concept_name}")
|
|
268
368
|
if concept_name in self.crisp_concepts:
|
|
269
369
|
var.set_binary_variable()
|
|
@@ -274,11 +374,34 @@ class MILPHelper:
|
|
|
274
374
|
return var
|
|
275
375
|
|
|
276
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
|
+
"""
|
|
277
388
|
return self.get_variable(a, b, role, VariableType.SEMI_CONTINUOUS)
|
|
278
389
|
|
|
279
390
|
def __get_variable_9(
|
|
280
391
|
self, a: Individual, b: Individual, role: str, v_type: VariableType
|
|
281
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
|
+
"""
|
|
282
405
|
return self.get_variable(str(a), str(b), role, v_type)
|
|
283
406
|
|
|
284
407
|
def __get_variable_10(
|
|
@@ -296,9 +419,28 @@ class MILPHelper:
|
|
|
296
419
|
return var
|
|
297
420
|
|
|
298
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
|
+
"""
|
|
299
431
|
return self.get_variable(ind, VariableType.CONTINUOUS)
|
|
300
432
|
|
|
301
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
|
+
"""
|
|
302
444
|
if ind.get_parent() is None:
|
|
303
445
|
parent_name: str = "unknown_parent"
|
|
304
446
|
else:
|
|
@@ -319,6 +461,18 @@ class MILPHelper:
|
|
|
319
461
|
x_c.set_type(v_type)
|
|
320
462
|
return x_c
|
|
321
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
|
+
|
|
322
476
|
@typing.overload
|
|
323
477
|
def has_variable(self, name: str) -> bool: ...
|
|
324
478
|
|
|
@@ -335,9 +489,11 @@ class MILPHelper:
|
|
|
335
489
|
raise ValueError
|
|
336
490
|
|
|
337
491
|
def __has_variable_1(self, name: str) -> bool:
|
|
492
|
+
"""Cheks if there is a variable with the given name."""
|
|
338
493
|
return name in self.number_of_variables
|
|
339
494
|
|
|
340
495
|
def __has_variable_2(self, ass: Assertion) -> bool:
|
|
496
|
+
"""Cheks if there is a variable for a concept assertion."""
|
|
341
497
|
return self.has_variable(ass.get_name_without_degree())
|
|
342
498
|
|
|
343
499
|
@typing.overload
|
|
@@ -356,22 +512,69 @@ class MILPHelper:
|
|
|
356
512
|
return self.__get_nominal_variable_2(*args)
|
|
357
513
|
|
|
358
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
|
+
"""
|
|
359
524
|
return self.get_nominal_variable(i1, i1)
|
|
360
525
|
|
|
361
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
|
+
"""
|
|
362
537
|
var_name = f"{i1}:{{ {i2} }}"
|
|
363
538
|
v: Variable = self.get_variable(var_name)
|
|
364
539
|
v.set_type(VariableType.BINARY)
|
|
365
540
|
return v
|
|
366
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
|
+
|
|
367
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}."""
|
|
368
560
|
var_name: str = f"{i}:{{ {i} }}"
|
|
369
561
|
return var_name in list(map(str, self.variables))
|
|
370
562
|
|
|
371
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
|
+
"""
|
|
372
574
|
var_name: str = f"{i1}: not {{ {i2} }}"
|
|
373
575
|
flag: bool = var_name in list(map(str, self.variables))
|
|
374
576
|
v: Variable = self.get_variable(var_name)
|
|
577
|
+
# First time the variable is created, x_{a:{o} } = 1 - x_{a: not {o} }
|
|
375
578
|
if not flag:
|
|
376
579
|
v.set_type(VariableType.BINARY)
|
|
377
580
|
not_v: Variable = self.get_nominal_variable(i1, i2)
|
|
@@ -448,9 +651,23 @@ class MILPHelper:
|
|
|
448
651
|
def __add_new_constraint_1(
|
|
449
652
|
self, expr: Expression, constraint_type: InequalityType
|
|
450
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
|
+
"""
|
|
451
661
|
self.constraints.append(Inequation(expr, constraint_type))
|
|
452
662
|
|
|
453
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
|
+
"""
|
|
454
671
|
self.add_new_constraint(
|
|
455
672
|
Expression(Term(1.0, x)),
|
|
456
673
|
InequalityType.GREATER_THAN,
|
|
@@ -458,14 +675,34 @@ class MILPHelper:
|
|
|
458
675
|
)
|
|
459
676
|
|
|
460
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
|
+
"""
|
|
461
685
|
self.add_new_constraint(self.get_variable(ass), n)
|
|
462
686
|
|
|
463
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
|
+
"""
|
|
464
695
|
self.add_new_constraint(
|
|
465
696
|
Expression(Term(1.0, x)), InequalityType.GREATER_THAN, d
|
|
466
697
|
)
|
|
467
698
|
|
|
468
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
|
+
"""
|
|
469
706
|
x_ass: Variable = self.get_variable(ass)
|
|
470
707
|
ass_name: str = str(x_ass)
|
|
471
708
|
deg: Degree = ass.get_lower_limit()
|
|
@@ -478,6 +715,14 @@ class MILPHelper:
|
|
|
478
715
|
def __add_new_constraint_6(
|
|
479
716
|
self, expr: Expression, constraint_type: InequalityType, degree: Degree
|
|
480
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
|
+
"""
|
|
481
726
|
self.constraints.append(
|
|
482
727
|
degree.create_inequality_with_degree_rhs(expr, constraint_type)
|
|
483
728
|
)
|
|
@@ -485,22 +730,50 @@ class MILPHelper:
|
|
|
485
730
|
def __add_new_constraint_7(
|
|
486
731
|
self, expr: Expression, constraint_type: InequalityType, n: float
|
|
487
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
|
+
"""
|
|
488
741
|
self.add_new_constraint(expr, constraint_type, DegreeNumeric.get_degree(n))
|
|
489
742
|
|
|
490
743
|
def add_equality(self, var1: Variable, var2: Variable) -> None:
|
|
744
|
+
"""
|
|
745
|
+
Add an equality of the form: var1 = var2.
|
|
746
|
+
"""
|
|
491
747
|
self.add_new_constraint(
|
|
492
748
|
Expression(Term(1.0, var1), Term(-1.0, var2)), InequalityType.EQUAL
|
|
493
749
|
)
|
|
494
750
|
|
|
495
751
|
def add_string_feature(self, role: str) -> None:
|
|
752
|
+
"""Adds a string feature."""
|
|
496
753
|
self.string_features.add(role)
|
|
497
754
|
|
|
498
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
|
+
"""
|
|
499
763
|
self.string_values[int_value] = value
|
|
500
764
|
|
|
501
765
|
def change_variable_names(
|
|
502
766
|
self, old_name: str, new_name: str, old_is_created_individual: bool
|
|
503
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
|
+
|
|
504
777
|
old_values: list[str] = [f"{old_name},", f",{old_name}", f"{old_name}:"]
|
|
505
778
|
new_values: list[str] = [f"{new_name},", f",{new_name}", f"{new_name}:"]
|
|
506
779
|
to_process: list[Variable] = copy.deepcopy(self.variables)
|
|
@@ -515,6 +788,7 @@ class MILPHelper:
|
|
|
515
788
|
if old_is_created_individual:
|
|
516
789
|
self.add_equality(v1, v2)
|
|
517
790
|
else:
|
|
791
|
+
# a:{b} => x_{a:C}) \geq x_{b:C}
|
|
518
792
|
a_is_b: Variable = self.get_nominal_variable(new_name, old_name)
|
|
519
793
|
self.add_new_constraint(
|
|
520
794
|
Expression(
|
|
@@ -530,8 +804,10 @@ class MILPHelper:
|
|
|
530
804
|
begin1: int = name1.index(s1)
|
|
531
805
|
name2: str = str(v2)
|
|
532
806
|
begin2: int = name2.index(s2)
|
|
807
|
+
# They are not similar because the parts before s1 and s2 have different lengths.
|
|
533
808
|
if begin1 != begin2:
|
|
534
809
|
return False
|
|
810
|
+
# If the parts before and after s1/s2 coincide, they are similar.
|
|
535
811
|
return (
|
|
536
812
|
name1[:begin1] == name2[:begin2]
|
|
537
813
|
and name1[begin1 + len(s1) :] == name2[begin2 + len(s2) :]
|
|
@@ -572,33 +848,48 @@ class MILPHelper:
|
|
|
572
848
|
def __get_ordered_permutation_2(
|
|
573
849
|
self, x: list[Variable], z: list[list[Variable]]
|
|
574
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
|
+
"""
|
|
575
861
|
n: int = len(x)
|
|
862
|
+
# New n [0,1] variables yi
|
|
576
863
|
y: list[Variable] = [
|
|
577
864
|
self.get_new_variable(VariableType.SEMI_CONTINUOUS) for _ in range(n)
|
|
578
865
|
]
|
|
866
|
+
# y1 >= y2 >= ... >= yn
|
|
579
867
|
for i in range(n - 1):
|
|
580
868
|
self.add_new_constraint(
|
|
581
869
|
Expression(Term(1.0, y[i]), Term(-1.0, y[i + 1])),
|
|
582
870
|
InequalityType.GREATER_THAN,
|
|
583
871
|
)
|
|
872
|
+
# for each i,j : yi - kz_{ij} <= xj
|
|
584
873
|
for i in range(n):
|
|
585
874
|
for j in range(n):
|
|
586
875
|
self.add_new_constraint(
|
|
587
876
|
Expression(Term(1.0, x[j]), Term(-1.0, y[i]), Term(1.0, z[i][j])),
|
|
588
877
|
InequalityType.GREATER_THAN,
|
|
589
878
|
)
|
|
879
|
+
# for each i,j : xj <= yi + kz_{ij}
|
|
590
880
|
for i in range(n):
|
|
591
881
|
for j in range(n):
|
|
592
882
|
self.add_new_constraint(
|
|
593
883
|
Expression(Term(1.0, x[j]), Term(-1.0, y[i]), Term(-1.0, z[i][j])),
|
|
594
884
|
InequalityType.LESS_THAN,
|
|
595
885
|
)
|
|
886
|
+
# for each i : \sum_{j} z_{ij} = n - 1
|
|
596
887
|
for i in range(n):
|
|
597
888
|
exp: Expression = Expression(1.0 - n)
|
|
598
889
|
for j in range(n):
|
|
599
890
|
exp.add_term(Term(1.0, z[i][j]))
|
|
600
891
|
self.add_new_constraint(exp, InequalityType.EQUAL)
|
|
601
|
-
|
|
892
|
+
# for each j : \sum_{i} z_{ij} = n - 1
|
|
602
893
|
for i in range(n):
|
|
603
894
|
exp: Expression = Expression(1.0 - n)
|
|
604
895
|
for j in range(n):
|
|
@@ -606,10 +897,378 @@ class MILPHelper:
|
|
|
606
897
|
self.add_new_constraint(exp, InequalityType.EQUAL)
|
|
607
898
|
return y
|
|
608
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
|
+
|
|
609
1258
|
def solve_gurobi(self, objective: Expression) -> typing.Optional[Solution]:
|
|
1259
|
+
"""
|
|
1260
|
+
Solves a MILP problem using Gurobi.
|
|
1261
|
+
"""
|
|
1262
|
+
|
|
610
1263
|
import gurobipy as gp
|
|
611
1264
|
from gurobipy import GRB
|
|
612
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
|
+
|
|
613
1272
|
try:
|
|
614
1273
|
Util.debug(f"Objective function -> {objective}")
|
|
615
1274
|
|
|
@@ -622,6 +1281,7 @@ class MILPHelper:
|
|
|
622
1281
|
|
|
623
1282
|
if objective is not None:
|
|
624
1283
|
for term in objective.get_terms():
|
|
1284
|
+
# Compute objective coefficients
|
|
625
1285
|
index = self.variables.index(term.get_var())
|
|
626
1286
|
objective_value[index] += term.get_coeff()
|
|
627
1287
|
|
|
@@ -632,7 +1292,7 @@ class MILPHelper:
|
|
|
632
1292
|
env.setParam("BarConvTol", 0)
|
|
633
1293
|
env.start()
|
|
634
1294
|
|
|
635
|
-
model = gp.Model("model", env=env)
|
|
1295
|
+
model: gp.Model = gp.Model("model", env=env)
|
|
636
1296
|
vars_gurobi: dict[str, gp.Var] = dict()
|
|
637
1297
|
show_variable: list[bool] = [False] * size
|
|
638
1298
|
|
|
@@ -647,8 +1307,8 @@ class MILPHelper:
|
|
|
647
1307
|
var_name_map: dict[str, str] = {
|
|
648
1308
|
str(v): f"x{i}" for i, v in enumerate(self.variables)
|
|
649
1309
|
}
|
|
650
|
-
inv_var_name_map: dict[str, str] = {v: k for k, v in var_name_map.items()}
|
|
651
1310
|
|
|
1311
|
+
# Create variables
|
|
652
1312
|
for i, curr_variable in enumerate(self.variables):
|
|
653
1313
|
v_type: VariableType = curr_variable.get_type()
|
|
654
1314
|
ov: float = objective_value[i]
|
|
@@ -683,10 +1343,12 @@ class MILPHelper:
|
|
|
683
1343
|
elif v_type == VariableType.SEMI_CONTINUOUS:
|
|
684
1344
|
num_up_vars += 1
|
|
685
1345
|
|
|
1346
|
+
# Integrate new variables
|
|
686
1347
|
model.update()
|
|
687
1348
|
|
|
688
1349
|
Util.debug(f"# constraints -> {len(self.constraints)}")
|
|
689
1350
|
constraint_name: str = "constraint"
|
|
1351
|
+
# Add constraints
|
|
690
1352
|
for i, constraint in enumerate(self.constraints):
|
|
691
1353
|
if constraint in self.constraints[:i]:
|
|
692
1354
|
continue
|
|
@@ -715,7 +1377,10 @@ class MILPHelper:
|
|
|
715
1377
|
model.addConstr(gp_constraint, curr_name)
|
|
716
1378
|
Util.debug(f"{curr_name}: {constraint}")
|
|
717
1379
|
|
|
1380
|
+
# Integrate new constraints
|
|
718
1381
|
model.update()
|
|
1382
|
+
|
|
1383
|
+
# Optimize model
|
|
719
1384
|
model.optimize()
|
|
720
1385
|
|
|
721
1386
|
model.write(os.path.join(constants.RESULTS_PATH, "gurobi_model.lp"))
|
|
@@ -726,19 +1391,22 @@ class MILPHelper:
|
|
|
726
1391
|
# if model.Status == GRB.INFEASIBLE and ConfigReader.RELAX_MILP:
|
|
727
1392
|
# self.__gurobi_handle_model_infeasibility(model)
|
|
728
1393
|
|
|
1394
|
+
# Return solution
|
|
729
1395
|
if model.Status == GRB.INFEASIBLE:
|
|
730
|
-
sol = Solution(
|
|
1396
|
+
sol = Solution(Solution.INCONSISTENT_KB)
|
|
731
1397
|
else:
|
|
1398
|
+
result: float = Util.round(abs(model.ObjVal))
|
|
1399
|
+
sol = Solution(result)
|
|
732
1400
|
for i in range(size):
|
|
733
1401
|
if ConfigReader.DEBUG_PRINT or show_variable[i]:
|
|
734
1402
|
name: str = self.variables[i].name
|
|
735
1403
|
value: float = round(vars_gurobi[var_name_map[name]].X, 6)
|
|
736
|
-
if
|
|
737
|
-
|
|
1404
|
+
if show_variable[i]:
|
|
1405
|
+
sol.add_showed_variable(name, value)
|
|
1406
|
+
# if self.PRINT_VARIABLES:
|
|
1407
|
+
Util.debug(f"{name} = {value}")
|
|
738
1408
|
if self.PRINT_LABELS:
|
|
739
1409
|
self.print_instance_of_labels(name, value)
|
|
740
|
-
result: float = Util.round(abs(model.ObjVal))
|
|
741
|
-
sol = Solution(result)
|
|
742
1410
|
|
|
743
1411
|
model.printQuality()
|
|
744
1412
|
model.printStats()
|
|
@@ -747,11 +1415,13 @@ class MILPHelper:
|
|
|
747
1415
|
f"{constants.STAR_SEPARATOR}Statistics{constants.STAR_SEPARATOR}"
|
|
748
1416
|
)
|
|
749
1417
|
Util.debug("MILP problem:")
|
|
1418
|
+
# Show number of variables
|
|
750
1419
|
Util.debug(f"\t\tSemi continuous variables: {num_up_vars}")
|
|
751
1420
|
Util.debug(f"\t\tBinary variables: {num_binary_vars}")
|
|
752
1421
|
Util.debug(f"\t\tContinuous variables: {num_free_vars}")
|
|
753
1422
|
Util.debug(f"\t\tInteger variables: {num_integer_vars}")
|
|
754
1423
|
Util.debug(f"\t\tTotal variables: {len(self.variables)}")
|
|
1424
|
+
# Show number of constraints
|
|
755
1425
|
Util.debug(f"\t\tConstraints: {len(self.constraints)}")
|
|
756
1426
|
return sol
|
|
757
1427
|
except gp.GurobiError as e:
|
|
@@ -830,7 +1500,6 @@ class MILPHelper:
|
|
|
830
1500
|
var_name_map: dict[str, str] = {
|
|
831
1501
|
str(v): f"x{i}" for i, v in enumerate(self.variables)
|
|
832
1502
|
}
|
|
833
|
-
inv_var_name_map: dict[str, str] = {v: k for k, v in var_name_map.items()}
|
|
834
1503
|
|
|
835
1504
|
for i, curr_variable in enumerate(self.variables):
|
|
836
1505
|
v_type: VariableType = curr_variable.get_type()
|
|
@@ -903,19 +1572,21 @@ class MILPHelper:
|
|
|
903
1572
|
Util.debug(f"Model:")
|
|
904
1573
|
sol: Solution = None
|
|
905
1574
|
if model.status == mip.OptimizationStatus.INFEASIBLE:
|
|
906
|
-
sol = Solution(
|
|
1575
|
+
sol = Solution(Solution.INCONSISTENT_KB)
|
|
907
1576
|
else:
|
|
908
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)
|
|
909
1580
|
for i in range(size):
|
|
910
1581
|
if ConfigReader.DEBUG_PRINT or show_variable[i]:
|
|
911
1582
|
name: str = self.variables[i].name
|
|
912
1583
|
value: float = round(vars_mip[var_name_map[name]].x, 6)
|
|
913
|
-
if
|
|
914
|
-
|
|
1584
|
+
if show_variable[i]:
|
|
1585
|
+
sol.add_showed_variable(name, value)
|
|
1586
|
+
# if self.PRINT_VARIABLES:
|
|
1587
|
+
Util.debug(f"{name} = {value}")
|
|
915
1588
|
if self.PRINT_LABELS:
|
|
916
1589
|
self.print_instance_of_labels(name, value)
|
|
917
|
-
result: float = Util.round(abs(model.objective_value))
|
|
918
|
-
sol = Solution(result)
|
|
919
1590
|
|
|
920
1591
|
Util.debug(
|
|
921
1592
|
f"{constants.STAR_SEPARATOR}Statistics{constants.STAR_SEPARATOR}"
|
|
@@ -1189,8 +1860,10 @@ class MILPHelper:
|
|
|
1189
1860
|
Util.debug(f"Model:")
|
|
1190
1861
|
sol: Solution = None
|
|
1191
1862
|
if result != pulp.LpStatusOptimal:
|
|
1192
|
-
sol = Solution(
|
|
1863
|
+
sol = Solution(Solution.INCONSISTENT_KB)
|
|
1193
1864
|
else:
|
|
1865
|
+
result: float = Util.round(abs(model.objective.value()))
|
|
1866
|
+
sol = Solution(result)
|
|
1194
1867
|
var_dict: dict[str, pulp.LpVariable] = model.variablesDict()
|
|
1195
1868
|
for i in range(size):
|
|
1196
1869
|
if ConfigReader.DEBUG_PRINT or show_variable[i]:
|
|
@@ -1200,12 +1873,12 @@ class MILPHelper:
|
|
|
1200
1873
|
if var_name_map[name] in var_dict
|
|
1201
1874
|
else 0.0
|
|
1202
1875
|
)
|
|
1203
|
-
if
|
|
1204
|
-
|
|
1876
|
+
if show_variable[i]:
|
|
1877
|
+
sol.add_showed_variable(name, value)
|
|
1878
|
+
# if self.PRINT_VARIABLES:
|
|
1879
|
+
Util.debug(f"{name} = {value}")
|
|
1205
1880
|
if self.PRINT_LABELS:
|
|
1206
1881
|
self.print_instance_of_labels(name, value)
|
|
1207
|
-
result: float = Util.round(abs(model.objective.value()))
|
|
1208
|
-
sol = Solution(result)
|
|
1209
1882
|
|
|
1210
1883
|
Util.debug(
|
|
1211
1884
|
f"{constants.STAR_SEPARATOR}Statistics{constants.STAR_SEPARATOR}"
|
|
@@ -1366,7 +2039,7 @@ class MILPHelper:
|
|
|
1366
2039
|
|
|
1367
2040
|
# sol: Solution = None
|
|
1368
2041
|
# if not result.success:
|
|
1369
|
-
# sol = Solution(
|
|
2042
|
+
# sol = Solution(Solution.INCONSISTENT_KB)
|
|
1370
2043
|
# else:
|
|
1371
2044
|
# for i in range(size):
|
|
1372
2045
|
# if ConfigReader.DEBUG_PRINT or show_variable[i]:
|
|
@@ -1394,18 +2067,26 @@ class MILPHelper:
|
|
|
1394
2067
|
# return sol
|
|
1395
2068
|
|
|
1396
2069
|
def add_crisp_concept(self, concept_name: str) -> None:
|
|
2070
|
+
"""Defines a concept to be crisp."""
|
|
1397
2071
|
self.crisp_concepts.add(concept_name)
|
|
1398
2072
|
|
|
1399
2073
|
def add_crisp_role(self, role_name: str) -> None:
|
|
2074
|
+
"""Defines a role to be crisp."""
|
|
1400
2075
|
self.crisp_roles.add(role_name)
|
|
1401
2076
|
|
|
1402
2077
|
def is_crisp_concept(self, concept_name: str) -> bool:
|
|
2078
|
+
"""Checks if a concept is crisp or not."""
|
|
1403
2079
|
return concept_name in self.crisp_concepts
|
|
1404
2080
|
|
|
1405
2081
|
def is_crisp_role(self, role_name: str) -> bool:
|
|
2082
|
+
"""Checks if a role is crisp or not."""
|
|
1406
2083
|
return role_name in self.crisp_roles
|
|
1407
2084
|
|
|
1408
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
|
|
1409
2090
|
for v in self.variables:
|
|
1410
2091
|
if v.get_datatype_filler_type() or v.get_type() in (
|
|
1411
2092
|
VariableType.CONTINUOUS,
|
|
@@ -1415,14 +2096,31 @@ class MILPHelper:
|
|
|
1415
2096
|
v.set_binary_variable()
|
|
1416
2097
|
|
|
1417
2098
|
def get_name_for_integer(self, i: int) -> typing.Optional[str]:
|
|
2099
|
+
"""Gets the name of the i-th variable."""
|
|
1418
2100
|
for name, i2 in self.number_of_variables.items():
|
|
1419
2101
|
if i == i2:
|
|
1420
2102
|
return name
|
|
1421
2103
|
return None
|
|
1422
2104
|
|
|
1423
2105
|
def get_number_for_assertion(self, ass: Assertion) -> int:
|
|
2106
|
+
"""Gets an integer codification of an assertion."""
|
|
1424
2107
|
return self.number_of_variables.get(str(self.get_variable(ass)))
|
|
1425
2108
|
|
|
1426
2109
|
def add_contradiction(self) -> None:
|
|
2110
|
+
"""Add a contradiction to make the fuzzy KB unsatisfiable"""
|
|
1427
2111
|
self.constraints.clear()
|
|
1428
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)
|