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.
Files changed (83) hide show
  1. fuzzy_dl_owl2/fuzzydl/__init__.py +2 -1
  2. fuzzy_dl_owl2/fuzzydl/assertion/assertion.py +1 -0
  3. fuzzy_dl_owl2/fuzzydl/assertion/atomic_assertion.py +2 -0
  4. fuzzy_dl_owl2/fuzzydl/classification_node.py +64 -0
  5. fuzzy_dl_owl2/fuzzydl/concept/__init__.py +2 -0
  6. fuzzy_dl_owl2/fuzzydl/concept/atomic_concept.py +1 -1
  7. fuzzy_dl_owl2/fuzzydl/concept/choquet_integral.py +6 -0
  8. fuzzy_dl_owl2/fuzzydl/concept/concept.py +8 -0
  9. fuzzy_dl_owl2/fuzzydl/concept/concrete/crisp_concrete_concept.py +1 -0
  10. fuzzy_dl_owl2/fuzzydl/concept/concrete/fuzzy_concrete_concept.py +1 -0
  11. fuzzy_dl_owl2/fuzzydl/concept/concrete/fuzzy_number/triangular_fuzzy_number.py +12 -0
  12. fuzzy_dl_owl2/fuzzydl/concept/concrete/left_concrete_concept.py +3 -0
  13. fuzzy_dl_owl2/fuzzydl/concept/concrete/linear_concrete_concept.py +3 -0
  14. fuzzy_dl_owl2/fuzzydl/concept/concrete/modified_concrete_concept.py +4 -0
  15. fuzzy_dl_owl2/fuzzydl/concept/concrete/right_concrete_concept.py +2 -0
  16. fuzzy_dl_owl2/fuzzydl/concept/concrete/trapezoidal_concrete_concept.py +1 -0
  17. fuzzy_dl_owl2/fuzzydl/concept/concrete/triangular_concrete_concept.py +2 -0
  18. fuzzy_dl_owl2/fuzzydl/concept/modified/linearly_modified_concept.py +3 -0
  19. fuzzy_dl_owl2/fuzzydl/concept/modified/modified_concept.py +3 -0
  20. fuzzy_dl_owl2/fuzzydl/concept/modified/triangularly_modified_concept.py +3 -0
  21. fuzzy_dl_owl2/fuzzydl/concept/negated_nominal.py +3 -0
  22. fuzzy_dl_owl2/fuzzydl/concept/operator_concept.py +8 -0
  23. fuzzy_dl_owl2/fuzzydl/concept/qowa_concept.py +4 -0
  24. fuzzy_dl_owl2/fuzzydl/concept/quasi_sugeno_integral.py +3 -0
  25. fuzzy_dl_owl2/fuzzydl/concept/sigma_concept.py +71 -0
  26. fuzzy_dl_owl2/fuzzydl/concept/sigma_count.py +56 -0
  27. fuzzy_dl_owl2/fuzzydl/concept/sugeno_integral.py +4 -0
  28. fuzzy_dl_owl2/fuzzydl/concept_equivalence.py +5 -0
  29. fuzzy_dl_owl2/fuzzydl/concrete_feature.py +6 -0
  30. fuzzy_dl_owl2/fuzzydl/domain_axiom.py +3 -0
  31. fuzzy_dl_owl2/fuzzydl/feature_function.py +3 -0
  32. fuzzy_dl_owl2/fuzzydl/general_concept_inclusion.py +6 -0
  33. fuzzy_dl_owl2/fuzzydl/individual/created_individual.py +41 -2
  34. fuzzy_dl_owl2/fuzzydl/individual/individual.py +14 -0
  35. fuzzy_dl_owl2/fuzzydl/individual/representative_individual.py +9 -0
  36. fuzzy_dl_owl2/fuzzydl/knowledge_base.py +2033 -245
  37. fuzzy_dl_owl2/fuzzydl/label.py +18 -10
  38. fuzzy_dl_owl2/fuzzydl/milp/expression.py +33 -23
  39. fuzzy_dl_owl2/fuzzydl/milp/inequation.py +8 -0
  40. fuzzy_dl_owl2/fuzzydl/milp/milp_helper.py +720 -22
  41. fuzzy_dl_owl2/fuzzydl/milp/show_variables_helper.py +82 -0
  42. fuzzy_dl_owl2/fuzzydl/milp/solution.py +23 -0
  43. fuzzy_dl_owl2/fuzzydl/milp/variable.py +7 -0
  44. fuzzy_dl_owl2/fuzzydl/modifier/linear_modifier.py +3 -0
  45. fuzzy_dl_owl2/fuzzydl/modifier/modifier.py +21 -0
  46. fuzzy_dl_owl2/fuzzydl/parser/dl_parser.py +48 -7
  47. fuzzy_dl_owl2/fuzzydl/primitive_concept_definition.py +7 -0
  48. fuzzy_dl_owl2/fuzzydl/query/__init__.py +1 -0
  49. fuzzy_dl_owl2/fuzzydl/query/all_instances_query.py +80 -1
  50. fuzzy_dl_owl2/fuzzydl/query/bnp_query.py +2 -0
  51. fuzzy_dl_owl2/fuzzydl/query/classification_query.py +26 -0
  52. fuzzy_dl_owl2/fuzzydl/query/defuzzify/defuzzify_query.py +2 -1
  53. fuzzy_dl_owl2/fuzzydl/query/defuzzify/lom_defuzzify_query.py +4 -0
  54. fuzzy_dl_owl2/fuzzydl/query/defuzzify/mom_defuzzify_query.py +6 -2
  55. fuzzy_dl_owl2/fuzzydl/query/defuzzify/som_defuzzify_query.py +2 -0
  56. fuzzy_dl_owl2/fuzzydl/query/instance_query.py +5 -0
  57. fuzzy_dl_owl2/fuzzydl/query/kb_satisfiable_query.py +12 -2
  58. fuzzy_dl_owl2/fuzzydl/query/max/max_instance_query.py +6 -1
  59. fuzzy_dl_owl2/fuzzydl/query/max/max_query.py +7 -1
  60. fuzzy_dl_owl2/fuzzydl/query/max/max_related_query.py +6 -1
  61. fuzzy_dl_owl2/fuzzydl/query/max/max_satisfiable_query.py +15 -1
  62. fuzzy_dl_owl2/fuzzydl/query/max/max_subsumes_query.py +4 -1
  63. fuzzy_dl_owl2/fuzzydl/query/min/min_instance_query.py +6 -1
  64. fuzzy_dl_owl2/fuzzydl/query/min/min_query.py +7 -1
  65. fuzzy_dl_owl2/fuzzydl/query/min/min_related_query.py +5 -1
  66. fuzzy_dl_owl2/fuzzydl/query/min/min_satisfiable_query.py +17 -1
  67. fuzzy_dl_owl2/fuzzydl/query/min/min_subsumes_query.py +47 -7
  68. fuzzy_dl_owl2/fuzzydl/query/query.py +5 -2
  69. fuzzy_dl_owl2/fuzzydl/query/related_query.py +8 -1
  70. fuzzy_dl_owl2/fuzzydl/query/satisfiable_query.py +17 -0
  71. fuzzy_dl_owl2/fuzzydl/query/subsumption_query.py +5 -0
  72. fuzzy_dl_owl2/fuzzydl/range_axiom.py +4 -0
  73. fuzzy_dl_owl2/fuzzydl/relation.py +5 -0
  74. fuzzy_dl_owl2/fuzzydl/restriction/has_value_restriction.py +2 -0
  75. fuzzy_dl_owl2/fuzzydl/restriction/restriction.py +3 -0
  76. fuzzy_dl_owl2/fuzzydl/role_parent_with_degree.py +6 -1
  77. fuzzy_dl_owl2/fuzzydl/util/config_reader.py +17 -27
  78. fuzzy_dl_owl2/fuzzydl/util/constants.py +100 -0
  79. fuzzy_dl_owl2-1.0.8.dist-info/METADATA +817 -0
  80. {fuzzy_dl_owl2-1.0.7.dist-info → fuzzy_dl_owl2-1.0.8.dist-info}/RECORD +82 -78
  81. fuzzy_dl_owl2-1.0.7.dist-info/METADATA +0 -408
  82. {fuzzy_dl_owl2-1.0.7.dist-info → fuzzy_dl_owl2-1.0.8.dist-info}/LICENSE +0 -0
  83. {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, MILPProvider
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
- name = f"{f_name}({ind_name})"
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(False)
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 self.PRINT_VARIABLES:
737
- Util.debug(f"{name} = {value}")
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(False)
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 self.PRINT_VARIABLES:
914
- Util.debug(f"{name} = {value}")
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(False)
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 self.PRINT_VARIABLES:
1204
- Util.debug(f"{name} = {value}")
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(False)
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)