fuzzy-dl-owl2 1.0.6__py3-none-any.whl → 1.0.7__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.
@@ -122,26 +122,32 @@ class FeatureFunction:
122
122
  self, a: Individual, milp: MILPHelper
123
123
  ) -> typing.Optional[Expression]:
124
124
  if self.type == FeatureFunctionType.ATOMIC:
125
+ # Get the filler "b" for feature(a)
125
126
  rel_set: list[Relation] = a.role_relations.get(self.feature)
127
+ assert len(rel_set) > 0
126
128
  b: CreatedIndividual = typing.cast(
127
129
  CreatedIndividual, rel_set[0].get_object_individual()
128
130
  )
131
+ # Get the variable xB
129
132
  x_b: Variable = milp.get_variable(b)
130
133
  return Expression(Term(1.0, x_b))
131
134
  elif self.type == FeatureFunctionType.NUMBER:
132
135
  return Expression(self.n)
133
136
  elif self.type == FeatureFunctionType.PRODUCT:
137
+ assert len(self.f) == 1
134
138
  ex: Expression = self.f[0].to_expression(a, milp)
135
- return Expression.multiply_constant(ex, self.n)
139
+ return ex * self.n
136
140
  elif self.type == FeatureFunctionType.SUBTRACTION:
141
+ assert len(self.f) == 2
137
142
  ex1: Expression = self.f[0].to_expression(a, milp)
138
143
  ex2: Expression = self.f[1].to_expression(a, milp)
139
- return Expression.subtract_expressions(ex1, ex2)
144
+ return ex1 - ex2
140
145
  elif self.type == FeatureFunctionType.SUM:
146
+ assert len(self.f) >= 1
141
147
  ex1: Expression = self.f[0].to_expression(a, milp)
142
148
  for i in range(1, len(self.f)):
143
149
  ex2: Expression = self.f[i].to_expression(a, milp)
144
- ex1: Expression = Expression.add_expressions(ex1, ex2)
150
+ ex1 = ex1 + ex2
145
151
  return ex1
146
152
  return None
147
153
 
@@ -129,6 +129,8 @@ from pyowl2.individual.named_individual import OWLNamedIndividual
129
129
  from pyowl2.literal.literal import OWLLiteral
130
130
  from pyowl2.ontology import OWLOntology
131
131
 
132
+ from fuzzy_dl_owl2.fuzzydl.util.config_reader import ConfigReader
133
+
132
134
 
133
135
  # @utils.timer_decorator
134
136
  class FuzzydlToOwl2:
@@ -149,7 +151,7 @@ class FuzzydlToOwl2:
149
151
  self.ontology_iri, OWL1_annotations=True
150
152
  )
151
153
  self.fuzzyLabel: OWLAnnotationProperty = OWLAnnotationProperty(
152
- IRI(self.ontology_iri.namespace, "fuzzyLabel")
154
+ IRI(self.ontology_iri.namespace, ConfigReader.OWL_ANNOTATION_LABEL)
153
155
  )
154
156
 
155
157
  self.ontology.add_axiom(OWLDeclaration(self.fuzzyLabel))
@@ -6435,6 +6435,7 @@ class DatatypeReasoner:
6435
6435
  k: list[float],
6436
6436
  type: InequalityType,
6437
6437
  ) -> None:
6438
+ # Gets fillers bi from every feature fi
6438
6439
  array: set[str] = fun.get_features()
6439
6440
  new_variable: bool = False
6440
6441
  for feature in array:
@@ -6454,21 +6455,28 @@ class DatatypeReasoner:
6454
6455
  x_fi: Variable = kb.milp.get_variable(
6455
6456
  ind, bi, feature, VariableType.BINARY
6456
6457
  )
6458
+ # (a,bi):Fi >= x_{(a,bi):Fi}
6457
6459
  IndividualHandler.add_relation(
6458
6460
  ind, feature, bi, DegreeVariable.get_degree(x_fi), kb
6459
6461
  )
6460
- x_bi: Variable = (
6461
- kb.milp.get_variable(bi, VariableType.INTEGER)
6462
- if t.get_type() == ConcreteFeatureType.INTEGER
6463
- else kb.milp.get_variable(bi, VariableType.CONTINUOUS)
6462
+ x_bi: Variable = kb.milp.get_variable(
6463
+ bi,
6464
+ (
6465
+ VariableType.INTEGER
6466
+ if t.get_type() == ConcreteFeatureType.INTEGER
6467
+ else VariableType.CONTINUOUS
6468
+ ),
6464
6469
  )
6465
- if new_variable is not None and ki is not None:
6470
+ if new_variable and ki is not None:
6466
6471
  kb.restrict_range(x_bi, x_fi, ki[0], ki[1])
6472
+ # xIsC <= xFi
6467
6473
  kb.milp.add_new_constraint(
6468
6474
  Expression(Term(1.0, x_is_c), Term(-1.0, x_fi)),
6469
6475
  InequalityType.LESS_THAN,
6470
6476
  )
6477
+ # xF \in {0,1}
6471
6478
  x_fi.set_binary_variable()
6479
+ # xB is a datatype filler
6472
6480
  x_bi.set_datatype_filler_variable()
6473
6481
  DatatypeReasoner.write_feature_equation(ind, fun, x_b, x_is_c, x_f, k, type, kb)
6474
6482
 
@@ -98,7 +98,7 @@ class Expression:
98
98
 
99
99
  def add_term(self, term: Term) -> None:
100
100
  for idx, t in enumerate(self.terms):
101
- if t == term:
101
+ if t.get_var() == term.get_var():
102
102
  self.terms[idx] = t + term
103
103
  return
104
104
  self.terms.append(term)
@@ -169,6 +169,17 @@ class Expression:
169
169
  def __truediv__(self, scalar: typing.Union[int, float]) -> typing.Self:
170
170
  return self * (1 / scalar)
171
171
 
172
+ def __hash__(self) -> int:
173
+ return hash(str(self))
174
+
175
+ def __eq__(self, value: typing.Self) -> bool:
176
+ if not isinstance(value, Expression):
177
+ return False
178
+ return len(self.terms) == len(value.terms) and all(term in value.terms for term in self.terms)
179
+
180
+ def __ne__(self, value: typing.Self) -> bool:
181
+ return not (self == value)
182
+
172
183
  def __repr__(self) -> str:
173
184
  return str(self)
174
185
 
@@ -184,5 +195,5 @@ class Expression:
184
195
  elif n == -1.0:
185
196
  parts.append(f"- {term.get_var()}")
186
197
  else:
187
- parts.append(f"{'+ ' if n >= 0 else '- '}{n} {term.get_var()}")
198
+ parts.append(f"{'+ ' if n >= 0 else '- '}{abs(n)} {term.get_var()}")
188
199
  return " ".join(parts)
@@ -43,6 +43,18 @@ class Inequation:
43
43
  assert self.type == InequalityType.GREATER_THAN
44
44
  return ">="
45
45
 
46
+ def is_zero(self) -> bool:
47
+ return all(term.get_coeff() == 0 for term in self.expr.get_terms()) and self.expr.get_constant() == 0
48
+
49
+ def __hash__(self) -> int:
50
+ return hash(str(self))
51
+
52
+ def __eq__(self, value: typing.Self) -> bool:
53
+ return self.expr == value.expr and self.type == value.type
54
+
55
+ def __ne__(self, value: typing.Self) -> bool:
56
+ return not (self == value)
57
+
46
58
  def __repr__(self) -> str:
47
59
  return str(self)
48
60
 
@@ -2,11 +2,9 @@ from __future__ import annotations
2
2
 
3
3
  import copy
4
4
  import os
5
+ import traceback
5
6
  import typing
6
7
 
7
- import gurobipy as gp
8
- from gurobipy import GRB
9
-
10
8
  from fuzzy_dl_owl2.fuzzydl.assertion.assertion import Assertion
11
9
  from fuzzy_dl_owl2.fuzzydl.concept.concept import Concept
12
10
  from fuzzy_dl_owl2.fuzzydl.concept.interface.has_value_interface import (
@@ -26,7 +24,7 @@ from fuzzy_dl_owl2.fuzzydl.milp.variable import Variable
26
24
  from fuzzy_dl_owl2.fuzzydl.relation import Relation
27
25
  from fuzzy_dl_owl2.fuzzydl.restriction.restriction import Restriction
28
26
  from fuzzy_dl_owl2.fuzzydl.util import constants
29
- from fuzzy_dl_owl2.fuzzydl.util.config_reader import ConfigReader
27
+ from fuzzy_dl_owl2.fuzzydl.util.config_reader import ConfigReader, MILPProvider
30
28
  from fuzzy_dl_owl2.fuzzydl.util.constants import (
31
29
  ConceptType,
32
30
  InequalityType,
@@ -62,8 +60,25 @@ class MILPHelper:
62
60
  milp.variables = [v.clone() for v in self.variables]
63
61
  return milp
64
62
 
65
- def optimize(self, objective: Expression) -> Solution:
66
- return self.solve_gurobi(objective)
63
+ def optimize(self, objective: Expression) -> typing.Optional[Solution]:
64
+ Util.debug(f"Running MILP solver: {ConfigReader.MILP_PROVIDER.name}")
65
+ if ConfigReader.MILP_PROVIDER == MILPProvider.GUROBI:
66
+ return self.solve_gurobi(objective)
67
+ elif ConfigReader.MILP_PROVIDER == MILPProvider.MIP:
68
+ return self.solve_mip(objective)
69
+ elif ConfigReader.MILP_PROVIDER in [
70
+ MILPProvider.PULP,
71
+ MILPProvider.PULP_GLPK,
72
+ MILPProvider.PULP_HIGHS,
73
+ MILPProvider.PULP_CPLEX,
74
+ ]:
75
+ return self.solve_pulp(objective)
76
+ # elif ConfigReader.MILP_PROVIDER == MILPProvider.SCIPY:
77
+ # return self.solve_scipy(objective)
78
+ else:
79
+ raise ValueError(
80
+ f"Unsupported MILP provider: {ConfigReader.MILP_PROVIDER.name}"
81
+ )
67
82
 
68
83
  @typing.overload
69
84
  def print_instance_of_labels(
@@ -592,8 +607,10 @@ class MILPHelper:
592
607
  return y
593
608
 
594
609
  def solve_gurobi(self, objective: Expression) -> typing.Optional[Solution]:
610
+ import gurobipy as gp
611
+ from gurobipy import GRB
612
+
595
613
  try:
596
- Util.debug("Running MILP solver: Gurobi")
597
614
  Util.debug(f"Objective function -> {objective}")
598
615
 
599
616
  num_binary_vars: int = 0
@@ -616,37 +633,45 @@ class MILPHelper:
616
633
  env.start()
617
634
 
618
635
  model = gp.Model("model", env=env)
619
- vars_gurobi: list[gp.Var] = []
636
+ vars_gurobi: dict[str, gp.Var] = dict()
620
637
  show_variable: list[bool] = [False] * size
621
638
 
622
639
  my_vars: list[Variable] = self.show_vars.get_variables()
623
640
 
624
- for i in range(size):
625
- v: Variable = self.variables[i]
626
- v_type: VariableType = v.get_type()
641
+ var_types: dict[VariableType, str] = {
642
+ VariableType.BINARY: GRB.BINARY,
643
+ VariableType.INTEGER: GRB.INTEGER,
644
+ VariableType.CONTINUOUS: GRB.CONTINUOUS,
645
+ VariableType.SEMI_CONTINUOUS: GRB.SEMICONT,
646
+ }
647
+ var_name_map: dict[str, str] = {
648
+ str(v): f"x{i}" for i, v in enumerate(self.variables)
649
+ }
650
+ inv_var_name_map: dict[str, str] = {v: k for k, v in var_name_map.items()}
651
+
652
+ for i, curr_variable in enumerate(self.variables):
653
+ v_type: VariableType = curr_variable.get_type()
627
654
  ov: float = objective_value[i]
628
655
 
629
656
  Util.debug(
630
657
  (
631
658
  f"Variable -- "
632
- f"[{v.get_lower_bound()}, {v.get_upper_bound()}] - "
659
+ f"[{curr_variable.get_lower_bound()}, {curr_variable.get_upper_bound()}] - "
633
660
  f"Obj value = {ov} - "
634
661
  f"Var type = {v_type.name} -- "
635
- f"Var = {v}"
662
+ f"Var = {curr_variable}"
636
663
  )
637
664
  )
638
665
 
639
- vars_gurobi.append(
640
- model.addVar(
641
- lb=v.get_lower_bound(),
642
- ub=v.get_upper_bound(),
643
- obj=ov,
644
- vtype=v_type.name,
645
- name=str(v),
646
- )
666
+ vars_gurobi[var_name_map[str(curr_variable)]] = model.addVar(
667
+ lb=curr_variable.get_lower_bound(),
668
+ ub=curr_variable.get_upper_bound(),
669
+ obj=ov,
670
+ vtype=var_types[v_type],
671
+ name=var_name_map[str(curr_variable)],
647
672
  )
648
673
 
649
- if v in my_vars:
674
+ if curr_variable in my_vars:
650
675
  show_variable[i] = True
651
676
 
652
677
  if v_type == VariableType.BINARY:
@@ -663,11 +688,15 @@ class MILPHelper:
663
688
  Util.debug(f"# constraints -> {len(self.constraints)}")
664
689
  constraint_name: str = "constraint"
665
690
  for i, constraint in enumerate(self.constraints):
691
+ if constraint in self.constraints[:i]:
692
+ continue
693
+ if constraint.is_zero():
694
+ continue
695
+
666
696
  curr_name: str = f"{constraint_name}_{i + 1}"
667
697
  expr: gp.LinExpr = gp.LinExpr()
668
698
  for term in constraint.get_terms():
669
- index: int = self.variables.index(term.get_var())
670
- v: gp.Var = vars_gurobi[index]
699
+ v: gp.Var = vars_gurobi[var_name_map[str(term.get_var())]]
671
700
  c: float = term.get_coeff()
672
701
  if c == 0:
673
702
  continue
@@ -684,26 +713,26 @@ class MILPHelper:
684
713
  gp_constraint: gp.Constr = expr >= constraint.get_constant()
685
714
 
686
715
  model.addConstr(gp_constraint, curr_name)
687
- Util.debug(f"{curr_name}: {gp_constraint}")
716
+ Util.debug(f"{curr_name}: {constraint}")
688
717
 
689
718
  model.update()
690
719
  model.optimize()
691
720
 
692
- model.write(os.path.join(constants.RESULTS_PATH, "model.lp"))
693
- model.write(os.path.join(constants.RESULTS_PATH, "solution.json"))
721
+ model.write(os.path.join(constants.RESULTS_PATH, "gurobi_model.lp"))
722
+ model.write(os.path.join(constants.RESULTS_PATH, "gurobi_solution.json"))
694
723
 
695
724
  Util.debug(f"Model:")
696
725
  sol: Solution = None
697
- if model.Status == GRB.INFEASIBLE and ConfigReader.RELAX_MILP:
698
- self.__handle_model_infeasibility(model)
726
+ # if model.Status == GRB.INFEASIBLE and ConfigReader.RELAX_MILP:
727
+ # self.__gurobi_handle_model_infeasibility(model)
699
728
 
700
729
  if model.Status == GRB.INFEASIBLE:
701
730
  sol = Solution(False)
702
731
  else:
703
732
  for i in range(size):
704
733
  if ConfigReader.DEBUG_PRINT or show_variable[i]:
705
- name: str = vars_gurobi[i].VarName
706
- value: float = round(vars_gurobi[i].X, 6)
734
+ name: str = self.variables[i].name
735
+ value: float = round(vars_gurobi[var_name_map[name]].X, 6)
707
736
  if self.PRINT_VARIABLES:
708
737
  Util.debug(f"{name} = {value}")
709
738
  if self.PRINT_LABELS:
@@ -729,29 +758,640 @@ class MILPHelper:
729
758
  Util.error(f"Error code: {e.errno}. {e.message}")
730
759
  return None
731
760
 
732
- def __handle_model_infeasibility(self, model: gp.Model) -> None:
733
- model.computeIIS()
734
- # Print out the IIS constraints and variables
735
- Util.debug("The following constraints and variables are in the IIS:")
736
- Util.debug("Constraints:")
737
- for c in model.getConstrs():
738
- assert isinstance(c, gp.Constr)
739
- if c.IISConstr:
740
- Util.debug(f"\t\t{c.ConstrName}: {model.getRow(c)} {c.Sense} {c.RHS}")
741
-
742
- Util.debug("Variables:")
743
- for v in model.getVars():
744
- if v.IISLB:
745
- Util.debug(f"\t\t{v.VarName} ≥ {v.LB}")
746
- if v.IISUB:
747
- Util.debug(f"\t\t{v.VarName} ≤ {v.UB}")
748
-
749
- Util.debug("Relaxing the variable bounds:")
750
- # relaxing only variable bounds
751
- model.feasRelaxS(0, False, True, False)
752
- # for relaxing variable bounds and constraint bounds use
753
- # model.feasRelaxS(0, False, True, True)
754
- model.optimize()
761
+ # def __gurobi_handle_model_infeasibility(self, model: typing.Any) -> None:
762
+ # import gurobipy as gp
763
+
764
+ # model: gp.Model = typing.cast(gp.Model, model)
765
+ # model.computeIIS()
766
+ # # Print out the IIS constraints and variables
767
+ # Util.debug("The following constraints and variables are in the IIS:")
768
+ # Util.debug("Constraints:")
769
+ # for c in model.getConstrs():
770
+ # assert isinstance(c, gp.Constr)
771
+ # if c.IISConstr:
772
+ # Util.debug(f"\t\t{c.ConstrName}: {model.getRow(c)} {c.Sense} {c.RHS}")
773
+
774
+ # Util.debug("Variables:")
775
+ # for v in model.getVars():
776
+ # if v.IISLB:
777
+ # Util.debug(f"\t\t{v.VarName} ≥ {v.LB}")
778
+ # if v.IISUB:
779
+ # Util.debug(f"\t\t{v.VarName} {v.UB}")
780
+
781
+ # Util.debug("Relaxing the variable bounds:")
782
+ # # relaxing only variable bounds
783
+ # model.feasRelaxS(0, False, True, False)
784
+ # # for relaxing variable bounds and constraint bounds use
785
+ # # model.feasRelaxS(0, False, True, True)
786
+ # model.optimize()
787
+
788
+ def solve_mip(self, objective: Expression) -> typing.Optional[Solution]:
789
+ import mip
790
+
791
+ try:
792
+ Util.debug(f"Objective function -> {objective}")
793
+
794
+ num_binary_vars: int = 0
795
+ num_free_vars: int = 0
796
+ num_integer_vars: int = 0
797
+ num_up_vars: int = 0
798
+ size: int = len(self.variables)
799
+ objective_value: list[float] = [0.0] * size
800
+
801
+ if objective is not None:
802
+ for term in objective.get_terms():
803
+ index = self.variables.index(term.get_var())
804
+ objective_value[index] += term.get_coeff()
805
+
806
+ model: mip.Model = mip.Model(
807
+ name="FuzzyDL", sense=mip.MINIMIZE, solver_name=mip.CBC
808
+ )
809
+ model.verbose = 0
810
+ model.infeas_tol = 1e-9
811
+ model.integer_tol = 1e-9
812
+ model.max_mip_gap = ConfigReader.EPSILON
813
+ model.emphasis = mip.SearchEmphasis.OPTIMALITY
814
+ model.opt_tol = 0
815
+ model.preprocess = 1
816
+
817
+ if ConfigReader.DEBUG_PRINT:
818
+ model.verbose = 1
819
+
820
+ vars_mip: dict[str, mip.Var] = dict()
821
+ show_variable: list[bool] = [False] * size
822
+
823
+ my_vars: list[Variable] = self.show_vars.get_variables()
824
+ var_types: dict[VariableType, str] = {
825
+ VariableType.BINARY: mip.BINARY,
826
+ VariableType.INTEGER: mip.INTEGER,
827
+ VariableType.CONTINUOUS: mip.CONTINUOUS,
828
+ VariableType.SEMI_CONTINUOUS: mip.CONTINUOUS,
829
+ }
830
+ var_name_map: dict[str, str] = {
831
+ str(v): f"x{i}" for i, v in enumerate(self.variables)
832
+ }
833
+ inv_var_name_map: dict[str, str] = {v: k for k, v in var_name_map.items()}
834
+
835
+ for i, curr_variable in enumerate(self.variables):
836
+ v_type: VariableType = curr_variable.get_type()
837
+ ov: float = objective_value[i]
838
+
839
+ Util.debug(
840
+ (
841
+ f"Variable -- "
842
+ f"[{curr_variable.get_lower_bound()}, {curr_variable.get_upper_bound()}] - "
843
+ f"Obj value = {ov} - "
844
+ f"Var type = {v_type.name} -- "
845
+ f"Var = {curr_variable}"
846
+ )
847
+ )
848
+
849
+ vars_mip[var_name_map[str(curr_variable)]] = model.add_var(
850
+ name=var_name_map[str(curr_variable)],
851
+ var_type=var_types[v_type],
852
+ lb=curr_variable.get_lower_bound(),
853
+ ub=curr_variable.get_upper_bound(),
854
+ obj=ov,
855
+ )
856
+
857
+ if curr_variable in my_vars:
858
+ show_variable[i] = True
859
+
860
+ if v_type == VariableType.BINARY:
861
+ num_binary_vars += 1
862
+ elif v_type == VariableType.CONTINUOUS:
863
+ num_free_vars += 1
864
+ elif v_type == VariableType.INTEGER:
865
+ num_integer_vars += 1
866
+ elif v_type == VariableType.SEMI_CONTINUOUS:
867
+ num_up_vars += 1
868
+
869
+ Util.debug(f"# constraints -> {len(self.constraints)}")
870
+ constraint_name: str = "constraint"
871
+ for i, constraint in enumerate(self.constraints):
872
+ if constraint in self.constraints[:i]:
873
+ continue
874
+ if constraint.is_zero():
875
+ continue
876
+ curr_name: str = f"{constraint_name}_{i + 1}"
877
+ expr: mip.LinExpr = mip.xsum(
878
+ term.get_coeff() * vars_mip[var_name_map[str(term.get_var())]]
879
+ for term in constraint.get_terms()
880
+ )
881
+
882
+ if constraint.get_type() == InequalityType.EQUAL:
883
+ gp_constraint: mip.Constr = expr == constraint.get_constant()
884
+ elif constraint.get_type() == InequalityType.LESS_THAN:
885
+ gp_constraint: mip.Constr = expr <= constraint.get_constant()
886
+ elif constraint.get_type() == InequalityType.GREATER_THAN:
887
+ gp_constraint: mip.Constr = expr >= constraint.get_constant()
888
+
889
+ model.add_constr(gp_constraint, curr_name)
890
+ Util.debug(f"{curr_name}: {constraint}")
891
+
892
+ model.objective = mip.xsum(
893
+ ov * vars_mip[var_name_map[str(self.variables[i])]]
894
+ for i, ov in enumerate(objective_value)
895
+ if ov != 0
896
+ )
897
+
898
+ # model.optimize(relax=ConfigReader.RELAX_MILP)
899
+ model.optimize()
900
+
901
+ model.write(os.path.join(constants.RESULTS_PATH, "mip_model.lp"))
902
+
903
+ Util.debug(f"Model:")
904
+ sol: Solution = None
905
+ if model.status == mip.OptimizationStatus.INFEASIBLE:
906
+ sol = Solution(False)
907
+ else:
908
+ model.write(os.path.join(constants.RESULTS_PATH, "mip_solution.sol"))
909
+ for i in range(size):
910
+ if ConfigReader.DEBUG_PRINT or show_variable[i]:
911
+ name: str = self.variables[i].name
912
+ value: float = round(vars_mip[var_name_map[name]].x, 6)
913
+ if self.PRINT_VARIABLES:
914
+ Util.debug(f"{name} = {value}")
915
+ if self.PRINT_LABELS:
916
+ self.print_instance_of_labels(name, value)
917
+ result: float = Util.round(abs(model.objective_value))
918
+ sol = Solution(result)
919
+
920
+ Util.debug(
921
+ f"{constants.STAR_SEPARATOR}Statistics{constants.STAR_SEPARATOR}"
922
+ )
923
+ Util.debug("MILP problem:")
924
+ Util.debug(f"\t\tSemi continuous variables: {num_up_vars}")
925
+ Util.debug(f"\t\tBinary variables: {num_binary_vars}")
926
+ Util.debug(f"\t\tContinuous variables: {num_free_vars}")
927
+ Util.debug(f"\t\tInteger variables: {num_integer_vars}")
928
+ Util.debug(f"\t\tTotal variables: {len(self.variables)}")
929
+ Util.debug(f"\t\tConstraints: {len(self.constraints)}")
930
+ return sol
931
+ except Exception as e:
932
+ Util.error(f"Error: {e} {traceback.format_exc()}")
933
+ return None
934
+
935
+ def solve_pulp(self, objective: Expression) -> typing.Optional[Solution]:
936
+ import pulp
937
+
938
+ try:
939
+ Util.debug(f"Objective function -> {objective}")
940
+
941
+ num_binary_vars: int = 0
942
+ num_free_vars: int = 0
943
+ num_integer_vars: int = 0
944
+ num_up_vars: int = 0
945
+ size: int = len(self.variables)
946
+ objective_value: list[float] = [0.0] * size
947
+ show_variable: list[bool] = [False] * size
948
+ my_vars: list[Variable] = self.show_vars.get_variables()
949
+
950
+ if objective is not None:
951
+ for term in objective.get_terms():
952
+ objective_value[
953
+ self.variables.index(term.get_var())
954
+ ] += term.get_coeff()
955
+
956
+ model = pulp.LpProblem(
957
+ f"FuzzyDL-{ConfigReader.MILP_PROVIDER.upper()}", pulp.LpMinimize
958
+ )
959
+
960
+ var_types: dict[VariableType, str] = {
961
+ VariableType.BINARY: pulp.LpBinary,
962
+ VariableType.INTEGER: pulp.LpInteger,
963
+ VariableType.CONTINUOUS: pulp.LpContinuous,
964
+ VariableType.SEMI_CONTINUOUS: pulp.LpContinuous,
965
+ }
966
+
967
+ vars_pulp: dict[str, pulp.LpVariable] = dict()
968
+ var_name_map: dict[str, str] = {
969
+ str(v): f"x{i}" for i, v in enumerate(self.variables)
970
+ }
971
+ semicontinuous_var_counter: int = 1
972
+ semicontinuous_var_name: str = "semic_z"
973
+ for i, curr_variable in enumerate(self.variables):
974
+ v_type: VariableType = curr_variable.get_type()
975
+ Util.debug(
976
+ (
977
+ f"Variable -- "
978
+ f"[{curr_variable.get_lower_bound()}, {curr_variable.get_upper_bound()}] - "
979
+ f"Obj value = {objective_value[i]} - "
980
+ f"Var type = {v_type.name} -- "
981
+ f"Var = {curr_variable}"
982
+ )
983
+ )
984
+
985
+ vars_pulp[var_name_map[str(curr_variable)]] = pulp.LpVariable(
986
+ name=var_name_map[str(curr_variable)],
987
+ lowBound=(
988
+ curr_variable.get_lower_bound()
989
+ if curr_variable.get_lower_bound() != float("-inf")
990
+ else None
991
+ ),
992
+ upBound=(
993
+ curr_variable.get_upper_bound()
994
+ if curr_variable.get_upper_bound() != float("inf")
995
+ else None
996
+ ),
997
+ cat=var_types[v_type],
998
+ )
999
+
1000
+ if curr_variable in my_vars:
1001
+ show_variable[i] = True
1002
+
1003
+ if (
1004
+ v_type == VariableType.SEMI_CONTINUOUS
1005
+ and ConfigReader.MILP_PROVIDER
1006
+ in [
1007
+ MILPProvider.PULP_GLPK,
1008
+ MILPProvider.PULP_CPLEX,
1009
+ ]
1010
+ ):
1011
+ # Semi Continuous variables are not handled by GLPK and HiGHS
1012
+ # if x in [L, U] u {0} is semi continuous, then add the following constraints
1013
+ # L * y <= x <= U * y, where y in {0, 1} is a binary variable
1014
+ bin_var = pulp.LpVariable(
1015
+ name=f"{semicontinuous_var_name}{semicontinuous_var_counter}",
1016
+ cat=pulp.LpBinary,
1017
+ )
1018
+ constraint_1 = (
1019
+ vars_pulp[var_name_map[str(curr_variable)]]
1020
+ >= bin_var * curr_variable.get_lower_bound()
1021
+ )
1022
+ constraint_2 = (
1023
+ vars_pulp[var_name_map[str(curr_variable)]]
1024
+ <= bin_var * curr_variable.get_upper_bound()
1025
+ )
1026
+ if constraint_1 not in model.constraints.values():
1027
+ model.addConstraint(
1028
+ constraint_1, name=f"constraint_{bin_var.name}_1"
1029
+ )
1030
+ if constraint_2 not in model.constraints.values():
1031
+ model.addConstraint(
1032
+ constraint_2, name=f"constraint_{bin_var.name}_2"
1033
+ )
1034
+ semicontinuous_var_counter += 1
1035
+ Util.debug(
1036
+ (
1037
+ f"New Variable -- "
1038
+ f"[{bin_var.lowBound}, {bin_var.upBound}] - "
1039
+ f"Var type = {bin_var.cat} -- "
1040
+ f"Var = {bin_var.name}"
1041
+ )
1042
+ )
1043
+ Util.debug(f"New Constraint 1 -- {constraint_1}")
1044
+ Util.debug(f"New Constraint 2 -- {constraint_2}")
1045
+
1046
+ if v_type == VariableType.BINARY:
1047
+ num_binary_vars += 1
1048
+ elif v_type == VariableType.CONTINUOUS:
1049
+ num_free_vars += 1
1050
+ elif v_type == VariableType.INTEGER:
1051
+ num_integer_vars += 1
1052
+ elif v_type == VariableType.SEMI_CONTINUOUS:
1053
+ num_up_vars += 1
1054
+
1055
+ Util.debug(f"# constraints -> {len(self.constraints)}")
1056
+ constraint_name: str = "constraint"
1057
+ pulp_sense: dict[InequalityType, int] = {
1058
+ InequalityType.EQUAL: pulp.LpConstraintEQ,
1059
+ InequalityType.LESS_THAN: pulp.LpConstraintLE,
1060
+ InequalityType.GREATER_THAN: pulp.LpConstraintGE,
1061
+ }
1062
+ for i, constraint in enumerate(self.constraints):
1063
+ if constraint in self.constraints[:i]:
1064
+ continue
1065
+ # ignore zero constraints
1066
+ if constraint.is_zero():
1067
+ continue
1068
+
1069
+ curr_name: str = f"{constraint_name}_{i + 1}"
1070
+ pulp_expr: pulp.LpAffineExpression = pulp.lpSum(
1071
+ term.get_coeff() * vars_pulp[var_name_map[str(term.get_var())]]
1072
+ for term in constraint.get_terms()
1073
+ )
1074
+ pulp_constraint: pulp.LpConstraint = pulp.LpConstraint(
1075
+ e=pulp_expr,
1076
+ sense=pulp_sense[constraint.get_type()],
1077
+ rhs=constraint.get_constant(),
1078
+ )
1079
+
1080
+ # ignore zero constraints of type a * x - a * x
1081
+ if (
1082
+ len(pulp_constraint) == 1
1083
+ and list(pulp_constraint.values())[0] == 0
1084
+ and pulp_constraint.constant == 0
1085
+ ):
1086
+ continue
1087
+
1088
+ model.addConstraint(pulp_constraint, name=curr_name)
1089
+ Util.debug(f"{curr_name}: {constraint}")
1090
+
1091
+ if ConfigReader.MILP_PROVIDER == MILPProvider.PULP:
1092
+ solver = pulp.PULP_CBC_CMD(
1093
+ mip=True,
1094
+ msg=ConfigReader.DEBUG_PRINT,
1095
+ gapRel=1e-9,
1096
+ presolve=True,
1097
+ keepFiles=False, # ConfigReader.DEBUG_PRINT,
1098
+ logPath=(
1099
+ os.path.join(".", "logs", f"pulp_{pulp.PULP_CBC_CMD.name}.log")
1100
+ if ConfigReader.DEBUG_PRINT
1101
+ else None
1102
+ ),
1103
+ options=[
1104
+ "--primalTolerance", # feasibility tolerance
1105
+ "1e-9",
1106
+ "--integerTolerance", # integer feasibility tolerance
1107
+ "1e-9",
1108
+ "--ratioGap", # relative mip gap
1109
+ str(ConfigReader.EPSILON),
1110
+ "--allowableGap", # optimality gap tolerance
1111
+ "0",
1112
+ "--preprocess", # enable preprocessing
1113
+ "on",
1114
+ ],
1115
+ )
1116
+ elif ConfigReader.MILP_PROVIDER == MILPProvider.PULP_GLPK:
1117
+ solver = pulp.GLPK_CMD(
1118
+ mip=True,
1119
+ msg=ConfigReader.DEBUG_PRINT,
1120
+ keepFiles=False, # ConfigReader.DEBUG_PRINT,
1121
+ options=[
1122
+ "--presol", # use presolver (default; assumes --scale and --adv)
1123
+ "--exact", # use simplex method based on exact arithmetic
1124
+ "--xcheck", # check final basis using exact arithmetic
1125
+ "--intopt", # enforce MIP (Mixed Integer Programming)
1126
+ "--mipgap",
1127
+ str(
1128
+ ConfigReader.EPSILON
1129
+ ), # no relative gap between primal & best bound
1130
+ ]
1131
+ + (
1132
+ [
1133
+ "--log",
1134
+ os.path.join(".", "logs", f"pulp_{pulp.GLPK_CMD.name}.log"),
1135
+ ]
1136
+ if ConfigReader.DEBUG_PRINT
1137
+ else []
1138
+ ),
1139
+ )
1140
+ elif ConfigReader.MILP_PROVIDER == MILPProvider.PULP_HIGHS:
1141
+ solver = pulp.HiGHS(
1142
+ mip=True,
1143
+ msg=ConfigReader.DEBUG_PRINT,
1144
+ gapRel=1e-6,
1145
+ log_file=(
1146
+ os.path.join(".", "logs", f"pulp_{pulp.HiGHS.name}.log")
1147
+ if ConfigReader.DEBUG_PRINT
1148
+ else None
1149
+ ),
1150
+ primal_feasibility_tolerance=1e-9,
1151
+ dual_feasibility_tolerance=1e-9,
1152
+ mip_feasibility_tolerance=1e-9,
1153
+ presolve="on",
1154
+ parallel="on",
1155
+ write_solution_to_file=True,
1156
+ write_solution_style=1,
1157
+ solution_file=os.path.join(
1158
+ constants.RESULTS_PATH, "highs_solution.sol"
1159
+ ),
1160
+ write_model_file=os.path.join(
1161
+ constants.RESULTS_PATH, "highs_model.lp"
1162
+ ),
1163
+ )
1164
+ elif ConfigReader.MILP_PROVIDER == MILPProvider.PULP_CPLEX:
1165
+ solver = pulp.CPLEX_CMD(
1166
+ # path="/Applications/CPLEX_Studio2211/cplex/bin/arm64_osx/cplex",
1167
+ mip=True,
1168
+ msg=ConfigReader.DEBUG_PRINT,
1169
+ gapRel=1e-9,
1170
+ keepFiles=False, # ConfigReader.DEBUG_PRINT,
1171
+ logPath=(
1172
+ os.path.join(".", "logs", f"pulp_{pulp.CPLEX_CMD.name}.log")
1173
+ if ConfigReader.DEBUG_PRINT
1174
+ else None
1175
+ ),
1176
+ )
1177
+
1178
+ model.objective = pulp.lpSum(
1179
+ ov * vars_pulp[var_name_map[str(self.variables[i])]]
1180
+ for i, ov in enumerate(objective_value)
1181
+ if ov != 0
1182
+ )
1183
+ result = model.solve(solver=solver)
1184
+ if ConfigReader.MILP_PROVIDER == MILPProvider.PULP_CPLEX:
1185
+ for file in os.listdir("./"):
1186
+ if "clone" in file:
1187
+ os.remove(file)
1188
+
1189
+ Util.debug(f"Model:")
1190
+ sol: Solution = None
1191
+ if result != pulp.LpStatusOptimal:
1192
+ sol = Solution(False)
1193
+ else:
1194
+ var_dict: dict[str, pulp.LpVariable] = model.variablesDict()
1195
+ for i in range(size):
1196
+ if ConfigReader.DEBUG_PRINT or show_variable[i]:
1197
+ name: str = self.variables[i].name
1198
+ value: float = (
1199
+ round(var_dict[var_name_map[name]].value(), 6)
1200
+ if var_name_map[name] in var_dict
1201
+ else 0.0
1202
+ )
1203
+ if self.PRINT_VARIABLES:
1204
+ Util.debug(f"{name} = {value}")
1205
+ if self.PRINT_LABELS:
1206
+ self.print_instance_of_labels(name, value)
1207
+ result: float = Util.round(abs(model.objective.value()))
1208
+ sol = Solution(result)
1209
+
1210
+ Util.debug(
1211
+ f"{constants.STAR_SEPARATOR}Statistics{constants.STAR_SEPARATOR}"
1212
+ )
1213
+ Util.debug("MILP problem:")
1214
+ Util.debug(f"\t\tSemi continuous variables: {num_up_vars}")
1215
+ Util.debug(f"\t\tBinary variables: {num_binary_vars}")
1216
+ Util.debug(f"\t\tContinuous variables: {num_free_vars}")
1217
+ Util.debug(f"\t\tInteger variables: {num_integer_vars}")
1218
+ Util.debug(f"\t\tTotal variables: {len(self.variables)}")
1219
+ Util.debug(f"\t\tConstraints: {len(self.constraints)}")
1220
+ return sol
1221
+ except Exception as e:
1222
+ Util.error(f"Error: {e} {traceback.format_exc()}")
1223
+ return None
1224
+
1225
+ # def solve_scipy(self, objective: Expression) -> typing.Optional[Solution]:
1226
+ # import numpy as np
1227
+ # from scipy.optimize import milp, OptimizeResult, LinearConstraint, Bounds, linprog, linprog_verbose_callback, show_options
1228
+
1229
+ # num_binary_vars: int = 0
1230
+ # num_free_vars: int = 0
1231
+ # num_integer_vars: int = 0
1232
+ # num_up_vars: int = 0
1233
+ # size: int = len(self.variables)
1234
+ # objective_value: list[float] = [0.0] * size
1235
+ # show_variable: list[bool] = [False] * size
1236
+ # my_vars: list[Variable] = self.show_vars.get_variables()
1237
+
1238
+ # if objective is not None:
1239
+ # for term in objective.get_terms():
1240
+ # index = self.variables.index(term.get_var())
1241
+ # objective_value[index] += term.get_coeff()
1242
+
1243
+ # var_types: dict[VariableType, str] = {
1244
+ # VariableType.BINARY: 1,
1245
+ # VariableType.CONTINUOUS: 0,
1246
+ # VariableType.INTEGER: 1,
1247
+ # VariableType.SEMI_CONTINUOUS: 2,
1248
+ # }
1249
+
1250
+ # for i, curr_variable in enumerate(self.variables):
1251
+ # v_type: VariableType = curr_variable.get_type()
1252
+
1253
+ # Util.debug(
1254
+ # (
1255
+ # f"Variable -- "
1256
+ # f"[{curr_variable.get_lower_bound()}, {curr_variable.get_upper_bound()}] - "
1257
+ # f"Obj value = {objective_value[i]} - "
1258
+ # f"Var type = {v_type.name} -- "
1259
+ # f"Var = {curr_variable}"
1260
+ # )
1261
+ # )
1262
+
1263
+ # if curr_variable in my_vars:
1264
+ # show_variable[i] = True
1265
+
1266
+ # if v_type == VariableType.BINARY:
1267
+ # num_binary_vars += 1
1268
+ # elif v_type == VariableType.CONTINUOUS:
1269
+ # num_free_vars += 1
1270
+ # elif v_type == VariableType.INTEGER:
1271
+ # num_integer_vars += 1
1272
+ # elif v_type == VariableType.SEMI_CONTINUOUS:
1273
+ # num_up_vars += 1
1274
+
1275
+ # Util.debug(f"# constraints -> {len(self.constraints)}")
1276
+ # constraint_name: str = "constraint"
1277
+ # matrix_A = np.zeros((len(self.constraints), len(self.variables)))
1278
+ # inequality_A = np.zeros((len(self.constraints), len(self.variables)))
1279
+ # equality_A = np.zeros((len(self.constraints), len(self.variables)))
1280
+ # lb = np.zeros(len(self.constraints))
1281
+ # ub = np.zeros(len(self.constraints))
1282
+ # in_ub = np.zeros(len(self.constraints))
1283
+ # eq_ub = np.zeros(len(self.constraints))
1284
+ # for i, constraint in enumerate(self.constraints):
1285
+ # curr_name: str = f"{constraint_name}_{i + 1}"
1286
+ # row = np.zeros(len(self.variables))
1287
+ # for term in constraint.get_terms():
1288
+ # row[self.variables.index(term.get_var())] = term.get_coeff()
1289
+ # if np.allclose(row, 0):
1290
+ # continue
1291
+ # Util.debug(f"{curr_name}: {constraint}")
1292
+ # matrix_A[i, :] = row
1293
+ # if constraint.type == InequalityType.EQUAL:
1294
+ # equality_A[i, :] = row
1295
+ # eq_ub[i] = constraint.get_constant()
1296
+
1297
+ # lb[i] = constraint.get_constant()
1298
+ # ub[i] = constraint.get_constant()
1299
+ # elif constraint.type == InequalityType.LESS_THAN:
1300
+ # inequality_A[i, :] = row
1301
+ # in_ub[i] = constraint.get_constant()
1302
+
1303
+ # lb[i] = -np.inf
1304
+ # ub[i] = constraint.get_constant()
1305
+ # elif constraint.type == InequalityType.GREATER_THAN:
1306
+ # inequality_A[i, :] = -row
1307
+ # in_ub[i] = -constraint.get_constant()
1308
+
1309
+ # lb[i] = constraint.get_constant()
1310
+ # ub[i] = np.inf
1311
+
1312
+ # indices = np.all(matrix_A == 0, axis=1)
1313
+ # matrix_A = np.delete(matrix_A, indices, axis=0)
1314
+ # lb = np.delete(lb, indices, axis=0)
1315
+ # ub = np.delete(ub, indices, axis=0)
1316
+
1317
+ # indices = np.all(inequality_A == 0, axis=1)
1318
+ # inequality_A = np.delete(inequality_A, indices, axis=0)
1319
+ # in_ub = np.delete(in_ub, indices, axis=0)
1320
+
1321
+ # indices = np.all(equality_A == 0, axis=1)
1322
+ # equality_A = np.delete(equality_A, indices, axis=0)
1323
+ # eq_ub = np.delete(eq_ub, indices, axis=0)
1324
+
1325
+ # bounds = Bounds(
1326
+ # [var.get_lower_bound() for var in self.variables],
1327
+ # [var.get_upper_bound() for var in self.variables],
1328
+ # keep_feasible=True,
1329
+ # )
1330
+ # integrality = np.array([var_types[var.get_type()] for var in self.variables])
1331
+ # constraint = LinearConstraint(
1332
+ # matrix_A, lb, ub, keep_feasible=True
1333
+ # )
1334
+
1335
+ # result: OptimizeResult = milp(
1336
+ # c=np.array(objective_value),
1337
+ # integrality=integrality,
1338
+ # constraints=constraint,
1339
+ # bounds=bounds,
1340
+ # options={
1341
+ # "disp": ConfigReader.DEBUG_PRINT,
1342
+ # "presolve": True,
1343
+ # "mip_rel_gap": 1e-6,
1344
+ # },
1345
+ # )
1346
+
1347
+ # result: OptimizeResult = linprog(
1348
+ # c=np.array(objective_value),
1349
+ # A_ub=inequality_A,
1350
+ # b_ub=in_ub,
1351
+ # A_eq=equality_A,
1352
+ # b_eq=eq_ub,
1353
+ # method="highs-ipm",
1354
+ # integrality=integrality,
1355
+ # bounds=[(var.get_lower_bound(), var.get_upper_bound()) for var in self.variables],
1356
+ # options={
1357
+ # "disp": ConfigReader.DEBUG_PRINT,
1358
+ # "presolve": False,
1359
+ # "mip_rel_gap": 1e-3,
1360
+ # "ipm_optimality_tolerance": 1e-5,
1361
+ # },
1362
+ # # callback=linprog_verbose_callback if ConfigReader.DEBUG_PRINT else None
1363
+ # )
1364
+
1365
+ # Util.debug(f"Model:\n{result}")
1366
+
1367
+ # sol: Solution = None
1368
+ # if not result.success:
1369
+ # sol = Solution(False)
1370
+ # else:
1371
+ # for i in range(size):
1372
+ # if ConfigReader.DEBUG_PRINT or show_variable[i]:
1373
+ # name: str = self.variables[i].name
1374
+ # value: float = (
1375
+ # round(result.x[i], 6)
1376
+ # )
1377
+ # if self.PRINT_VARIABLES:
1378
+ # Util.debug(f"{name} = {value}")
1379
+ # if self.PRINT_LABELS:
1380
+ # self.print_instance_of_labels(name, value)
1381
+ # result: float = Util.round(abs(result.fun))
1382
+ # sol = Solution(result)
1383
+
1384
+ # Util.debug(
1385
+ # f"{constants.STAR_SEPARATOR}Statistics{constants.STAR_SEPARATOR}"
1386
+ # )
1387
+ # Util.debug("MILP problem:")
1388
+ # Util.debug(f"\t\tSemi continuous variables: {num_up_vars}")
1389
+ # Util.debug(f"\t\tBinary variables: {num_binary_vars}")
1390
+ # Util.debug(f"\t\tContinuous variables: {num_free_vars}")
1391
+ # Util.debug(f"\t\tInteger variables: {num_integer_vars}")
1392
+ # Util.debug(f"\t\tTotal variables: {len(self.variables)}")
1393
+ # Util.debug(f"\t\tConstraints: {len(self.constraints)}")
1394
+ # return sol
755
1395
 
756
1396
  def add_crisp_concept(self, concept_name: str) -> None:
757
1397
  self.crisp_concepts.add(concept_name)
@@ -64,11 +64,14 @@ class Term:
64
64
  def __eq__(self, term: typing.Self) -> bool:
65
65
  if not isinstance(term, Term):
66
66
  return False
67
- return self.var == term.var
67
+ return self.var == term.var and self.coeff == term.coeff
68
68
 
69
69
  def __ne__(self, term: typing.Self) -> bool:
70
70
  return not (self == term)
71
71
 
72
+ def __hash__(self) -> int:
73
+ return hash(str(self))
74
+
72
75
  def __repr__(self) -> str:
73
76
  return str(self)
74
77
 
@@ -1,19 +1,42 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import configparser
4
+ import enum
4
5
  import math
5
6
 
7
+ from fuzzy_dl_owl2.fuzzydl.util import constants
8
+
9
+
10
+ class MILPProvider(enum.StrEnum):
11
+ GUROBI = enum.auto()
12
+ MIP = enum.auto()
13
+ # SCIPY = enum.auto()
14
+ PULP = enum.auto()
15
+ PULP_GLPK = enum.auto()
16
+ PULP_HIGHS = enum.auto()
17
+ PULP_CPLEX = enum.auto()
18
+
19
+ @staticmethod
20
+ def from_str(value: str) -> MILPProvider:
21
+ try:
22
+ return MILPProvider(value.lower())
23
+ except ValueError:
24
+ raise ValueError(
25
+ f"Invalid MILP provider: {value}. Valid options are: {list(MILPProvider)}"
26
+ )
27
+
6
28
 
7
29
  class ConfigReader:
8
30
  ANYWHERE_DOUBLE_BLOCKING: bool = True
9
31
  ANYWHERE_SIMPLE_BLOCKING: bool = True
10
- RELAX_MILP: bool = False
11
32
  DEBUG_PRINT: bool = True
12
33
  EPSILON: float = 0.001
13
34
  MAX_INDIVIDUALS: int = -1
14
35
  NUMBER_DIGITS: int = 2
15
36
  OPTIMIZATIONS: int = 1
16
37
  RULE_ACYCLIC_TBOXES: bool = True
38
+ OWL_ANNOTATION_LABEL: str = "fuzzyLabel"
39
+ MILP_PROVIDER: MILPProvider = MILPProvider.GUROBI
17
40
 
18
41
  @staticmethod
19
42
  def load_parameters(config_file: str, args: list[str]) -> None:
@@ -33,13 +56,32 @@ class ConfigReader:
33
56
  # "author": False,
34
57
  # }
35
58
 
36
- ConfigReader.RELAX_MILP = config.getboolean("DEFAULT", "relaxMilp")
37
59
  ConfigReader.DEBUG_PRINT = config.getboolean("DEFAULT", "debugPrint")
38
60
  ConfigReader.EPSILON = config.getfloat("DEFAULT", "epsilon")
39
61
  ConfigReader.MAX_INDIVIDUALS = config.getint("DEFAULT", "maxIndividuals")
62
+ ConfigReader.OWL_ANNOTATION_LABEL = config.get(
63
+ "DEFAULT", "owlAnnotationLabel"
64
+ )
65
+ ConfigReader.MILP_PROVIDER = MILPProvider(
66
+ config.get("DEFAULT", "milpProvider").lower()
67
+ )
40
68
  ConfigReader.NUMBER_DIGITS = int(
41
69
  round(abs(math.log10(ConfigReader.EPSILON) - 1.0))
42
70
  )
71
+ if ConfigReader.MILP_PROVIDER in [
72
+ MILPProvider.MIP,
73
+ MILPProvider.PULP,
74
+ ]:
75
+ constants.MAXVAL = (1 << 31) - 1
76
+ constants.MAXVAL2 = constants.MAXVAL * 2
77
+ elif ConfigReader.MILP_PROVIDER in [
78
+ MILPProvider.PULP_GLPK,
79
+ MILPProvider.PULP_HIGHS,
80
+ MILPProvider.PULP_CPLEX,
81
+ # MILPProvider.SCIPY,
82
+ ]:
83
+ constants.MAXVAL = (1 << 28) - 1
84
+ constants.MAXVAL2 = constants.MAXVAL * 2
43
85
 
44
86
  if ConfigReader.DEBUG_PRINT:
45
87
  print(f"Debugging mode = {ConfigReader.DEBUG_PRINT}")
@@ -4,7 +4,6 @@ import re
4
4
  import typing
5
5
 
6
6
  import pyparsing as pp
7
- from gurobipy import GRB
8
7
 
9
8
  SEPARATOR: str = "-" * 25
10
9
  STAR_SEPARATOR: str = "*" * 25
@@ -239,10 +238,10 @@ class InequalityType(enum.StrEnum):
239
238
 
240
239
 
241
240
  class VariableType(enum.StrEnum):
242
- BINARY = GRB.BINARY
243
- CONTINUOUS = GRB.CONTINUOUS
244
- INTEGER = GRB.INTEGER
245
- SEMI_CONTINUOUS = GRB.SEMICONT
241
+ BINARY = enum.auto()
242
+ CONTINUOUS = enum.auto()
243
+ INTEGER = enum.auto()
244
+ SEMI_CONTINUOUS = enum.auto()
246
245
 
247
246
  def __repr__(self) -> str:
248
247
  return self.name
@@ -421,5 +420,5 @@ class FuzzyLogic(enum.StrEnum):
421
420
 
422
421
 
423
422
  KNOWLEDGE_BASE_SEMANTICS: FuzzyLogic = FuzzyLogic.CLASSICAL
424
- MAXVAL: float = 2.147483647e12
423
+ MAXVAL: float = ((1 << 31) - 1) * 1000 # 2.147483647e12
425
424
  MAXVAL2: float = MAXVAL * 2
@@ -146,6 +146,8 @@ from pyowl2.individual.anonymous_individual import OWLAnonymousIndividual
146
146
  from pyowl2.literal.literal import OWLLiteral
147
147
  from pyowl2.ontology import OWLOntology
148
148
 
149
+ from fuzzy_dl_owl2.fuzzydl.util.config_reader import ConfigReader
150
+
149
151
 
150
152
  class FuzzyOwl2(object):
151
153
  POS_INFINITY: float = 10000.0
@@ -174,7 +176,7 @@ class FuzzyOwl2(object):
174
176
  self.ontology_iri, self.ontology_path, OWL1_annotations=True
175
177
  )
176
178
  self.fuzzy_label: OWLAnnotationProperty = OWLAnnotationProperty(
177
- IRI(self.ontology_iri.namespace, "fuzzyLabel")
179
+ IRI(self.ontology_iri.namespace, ConfigReader.OWL_ANNOTATION_LABEL)
178
180
  )
179
181
  self.ontologies.add(self.ontology)
180
182
  # self.ontologies.update(self.manager.getImportsClosure(self.ontology))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: fuzzy-dl-owl2
3
- Version: 1.0.6
3
+ Version: 1.0.7
4
4
  Summary: A python porting of the Fuzzy Description Language (see https://www.umbertostraccia.it/cs/software/fuzzyDL/fuzzyDL.html) and the Fuzzy OWL 2 framework (see https://www.umbertostraccia.it/cs/software/FuzzyOWL/index.html).
5
5
  License: CC-BY-SA-4.0
6
6
  Author: Giuseppe Filippone
@@ -11,12 +11,12 @@ Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
13
  Classifier: Programming Language :: Python :: 3.13
14
- Requires-Dist: gurobipy (>=12.0.0,<13.0.0)
15
14
  Requires-Dist: networkx (>=3.3,<4.0)
16
15
  Requires-Dist: owlready2 (>=0.47,<0.48)
17
16
  Requires-Dist: pyowl2 (>=1.0.2,<2.0.0)
18
17
  Requires-Dist: pyparsing (>=3.2.3,<4.0.0)
19
18
  Requires-Dist: rdflib (>=7.1.4,<8.0.0)
19
+ Requires-Dist: setuptools (>=80.8.0,<81.0.0)
20
20
  Requires-Dist: sortedcontainers (>=2.4.0,<3.0.0)
21
21
  Requires-Dist: trycast (>=1.2.0,<2.0.0)
22
22
  Project-URL: Repository, https://github.com/giuseppefilippone/fuzzy_dl_owl2
@@ -78,17 +78,85 @@ The file `CONFIG.ini` is structured as follows:
78
78
  ```text
79
79
  [DEFAULT]
80
80
  debugPrint = False
81
- relaxMilp = False
82
81
  epsilon = 0.001
83
82
  maxIndividuals = -1
83
+ owlAnnotationLabel = fuzzyLabel
84
+ milpProvider = mip
84
85
  ```
85
86
 
86
87
  | Configuration Variable | Description |
87
88
  |----------------------|-----------------------------------|
88
89
  | debugPrint | Enable/disable debugging |
89
- | relaxMilp | Enable/disable MILP constraint relaxation. Important: The solution may be wrong by enabling this flag |
90
90
  | epsilon | Define the precision of the solution. For instance, epsilon = 0.001 means that the solution will be calculated with an accuracy to the third decimal place |
91
91
  | maxIndividuals | Define the maximal number of individuals to handle. The value -1 indicate that there is no maximum |
92
+ | owlAnnotationLabel | Define the Annotation label used to build the Fuzzy OWL 2 RDF/XML ontology |
93
+ | milpProvider | Define the MILP provider used by the reasoner. The supported providers are listed below. |
94
+
95
+ Supported MILP Providers:
96
+ | Provider | milpProvider |
97
+ |--------------|----------------------|
98
+ | Gurobi | gurobi |
99
+ | CPLEX | pulp_cplex |
100
+ | CBC | pulp |
101
+ | GLPK | pulp_glpk |
102
+ | HiGHS | pulp_highs |
103
+ | MIP | mip |
104
+
105
+
106
+
107
+ # MILP Provider Usage and Configuration
108
+
109
+ ## GUROBI
110
+
111
+ - Install [gurobipy](https://pypi.org/project/gurobipy/):
112
+ ```python
113
+ pip install gurobipy==12.0.0
114
+ ```
115
+ - Download the GUROBI license from their [website](https://www.gurobi.com/solutions/licensing/).
116
+ - Add Gurobi to the PATH
117
+
118
+ ## MIP
119
+
120
+ - Install python [MIP](https://www.python-mip.com/):
121
+ ```python
122
+ pip install mip==1.16rc0
123
+ ```
124
+
125
+ ## GLPK
126
+
127
+ - Install [GLPK](https://www.gnu.org/software/glpk/) v5.0 and [GMP](https://gmplib.org/) v6.3.0
128
+ - Install python [pulp](https://github.com/coin-or/PuLP?tab=readme-ov-file):
129
+ ```python
130
+ pip install pulp==3.2.1
131
+ ```
132
+ - Add GLPK to the PATH
133
+
134
+ ## CBC
135
+
136
+ - Install [CBC](https://github.com/coin-or/Cbc)
137
+ - Install python [pulp](https://github.com/coin-or/PuLP?tab=readme-ov-file):
138
+ ```python
139
+ pip install pulp==3.2.1
140
+ ```
141
+ - Add CBC to the PATH
142
+
143
+ ## CPLEX
144
+
145
+ - Install [CPLEX](https://www.ibm.com/it-it/products/ilog-cplex-optimization-studio) v22.11
146
+ - Install python [pulp](https://github.com/coin-or/PuLP?tab=readme-ov-file):
147
+ ```python
148
+ pip install pulp==3.2.1
149
+ ```
150
+ - Add CPLEX to the PATH
151
+
152
+ ## HiGHS
153
+
154
+ - Install [HiGHS](https://ergo-code.github.io/HiGHS/dev/interfaces/cpp/) v1.10.0
155
+ - Install python [pulp](https://github.com/coin-or/PuLP?tab=readme-ov-file):
156
+ ```python
157
+ pip install pulp==3.2.1
158
+ ```
159
+ - Add HiGHS to the PATH
92
160
 
93
161
 
94
162
 
@@ -61,22 +61,22 @@ fuzzy_dl_owl2/fuzzydl/domain_axiom.py,sha256=qPjeqmA7zQt6VT-bLtT82F74fiORPegQytd
61
61
  fuzzy_dl_owl2/fuzzydl/exception/__init__.py,sha256=IoYY5IQoU4UYyoB-dFOVLZyDvJN20kgZVBqWkF6J30w,135
62
62
  fuzzy_dl_owl2/fuzzydl/exception/fuzzy_ontology_exception.py,sha256=eH1ybBCx1QoZaf8PLCq1rC_3tiBRA-gKCwECTcUUuro,122
63
63
  fuzzy_dl_owl2/fuzzydl/exception/inconsistent_ontology_exception.py,sha256=ez0RQN4KGlNRcfB7IXfPz3bM86CFLl6zo-RRTBRpa_o,129
64
- fuzzy_dl_owl2/fuzzydl/feature_function.py,sha256=KSQvCSYfJK5N6FFfDlyS1Y7UPHSYZNXzdYYZMWzXF8U,6399
65
- fuzzy_dl_owl2/fuzzydl/fuzzydl_to_owl2.py,sha256=aqFuf7xH7_5fJL57RlLhN2tzpQEX8i6LzlTmKRAkZak,53023
64
+ fuzzy_dl_owl2/fuzzydl/feature_function.py,sha256=Ipfd_UuKWKqCp8jdyV3Hxia0l3yj8jjNu-kOGQ7qf3Y,6525
65
+ fuzzy_dl_owl2/fuzzydl/fuzzydl_to_owl2.py,sha256=VjmHtokMAE0jC9jI8WjGeiCTQ-A8YmmUH14FEVTyrDU,53111
66
66
  fuzzy_dl_owl2/fuzzydl/general_concept_inclusion.py,sha256=Noom35WAeJosPrliGd9_gvEtcK4BthazsSuZH-cxfyU,2380
67
67
  fuzzy_dl_owl2/fuzzydl/individual/__init__.py,sha256=zBCa24kE2nv08VgtphjLshpbGNEARUJgCdtyv9r6JGc,110
68
68
  fuzzy_dl_owl2/fuzzydl/individual/created_individual.py,sha256=HOo_UXjPPPtm7B0a7k90kE1veuR9gOCK5hp2f_aaJnY,7653
69
69
  fuzzy_dl_owl2/fuzzydl/individual/individual.py,sha256=FgtOmJwkPUKIOr-o8a1NqHepL-sN5fGe314e895DHYg,3846
70
70
  fuzzy_dl_owl2/fuzzydl/individual/representative_individual.py,sha256=C5ZG43Xy0Iz71S5IYPzaBGeT-fWIqQGpg85DPgU1NjM,1070
71
- fuzzy_dl_owl2/fuzzydl/knowledge_base.py,sha256=MmzpyLpiAkxZBbRJP2IywuS0A87lmv26lYv9RyTxN74,385654
71
+ fuzzy_dl_owl2/fuzzydl/knowledge_base.py,sha256=ve0NYDz1b2Up-ZoTzJO-JltyupMFhyy9meIzMvFIt-I,385863
72
72
  fuzzy_dl_owl2/fuzzydl/label.py,sha256=KD39rTIlg0XhDJSVqNC6t0Pu60MVfO8xNGSjrcJFtD0,1023
73
73
  fuzzy_dl_owl2/fuzzydl/milp/__init__.py,sha256=g2oFT2Ge8W5Li2kP2CJjpjJ1a0PRI2vDoDdzYDsEtDY,246
74
- fuzzy_dl_owl2/fuzzydl/milp/expression.py,sha256=j_77ddxfktzjRCRmjvew6zr5Rj8pmMfL2N0PvdpGfL4,6197
75
- fuzzy_dl_owl2/fuzzydl/milp/inequation.py,sha256=dQhRXj7xQm6021bUV58WPNnxH7EqsonPrWOCoCBqR5A,1631
76
- fuzzy_dl_owl2/fuzzydl/milp/milp_helper.py,sha256=Dfeh7eGknCez6Ku5m7wdxsdqJkSq4vtgfH93xth7ILM,30388
74
+ fuzzy_dl_owl2/fuzzydl/milp/expression.py,sha256=HTCSk_EGTO7ATIQuV65mKa06eY433PVs_8jyw4kbELg,6596
75
+ fuzzy_dl_owl2/fuzzydl/milp/inequation.py,sha256=4agNmrrT8K1uEnm553nD8OPwVY4l4rF3Dxw-_PBj5lw,2040
76
+ fuzzy_dl_owl2/fuzzydl/milp/milp_helper.py,sha256=aghOzlFqkZUK53lIPXIeDbwgt2E6cCy805bN35DGq0E,58324
77
77
  fuzzy_dl_owl2/fuzzydl/milp/show_variables_helper.py,sha256=7y-lfhoERoE8qNSMr0cq_-Ezo46BGsNszp44YSVWA2A,5754
78
78
  fuzzy_dl_owl2/fuzzydl/milp/solution.py,sha256=suoE1F2XORwzP4pNm-6Ivf6eK5Z98KFxFUdarh1CjQk,1210
79
- fuzzy_dl_owl2/fuzzydl/milp/term.py,sha256=IDnmmpXIgewY9NkPwM1YFzkcp21umo4ZoPBzAhGdn8Q,2202
79
+ fuzzy_dl_owl2/fuzzydl/milp/term.py,sha256=JIRO3rrMz1e6I3g0tElFbwXyOxtiy8L7Wh6EisQfSuM,2294
80
80
  fuzzy_dl_owl2/fuzzydl/milp/variable.py,sha256=_qqLZpOkXCaeyguQQWLIQvTPzyPaMNSpPIAL4T0A9C8,2725
81
81
  fuzzy_dl_owl2/fuzzydl/modifier/__init__.py,sha256=mgJml-9PVs566EYpwBxfmmiw8oPXiSgJRUyIX9hiSNA,126
82
82
  fuzzy_dl_owl2/fuzzydl/modifier/linear_modifier.py,sha256=Rn6q4ytIdTiCI-TRZIK5qjWSA4K3q78fYSrYxlQklVc,2022
@@ -118,12 +118,12 @@ fuzzy_dl_owl2/fuzzydl/restriction/has_value_restriction.py,sha256=DL-50TlD289OXV
118
118
  fuzzy_dl_owl2/fuzzydl/restriction/restriction.py,sha256=SyeFvaJeKcTe750eAbLoAVYkWTK_UjdqWr2Lw8OF1S0,948
119
119
  fuzzy_dl_owl2/fuzzydl/role_parent_with_degree.py,sha256=1J5dtlVbiascxHBwAJ7uc97IHnJke9RWqxq5rgrrkoA,282
120
120
  fuzzy_dl_owl2/fuzzydl/util/__init__.py,sha256=3GufSMUuWGISGukXM5T5ZGA0BC1WX-5eLFSuttVYueU,109
121
- fuzzy_dl_owl2/fuzzydl/util/config_reader.py,sha256=8kyrfK-wQaiGrKfrLsiVr77jg1NRaviGniWOtQwrZRU,1797
122
- fuzzy_dl_owl2/fuzzydl/util/constants.py,sha256=_BM9maPkVPzz-GjWjVHjPEtNOA72c5R4qjP9w-2bAdQ,12817
121
+ fuzzy_dl_owl2/fuzzydl/util/config_reader.py,sha256=dh2Xa1sYVlKavbkKShHkxzA0ieLoQOV1JmzkOvIIVMI,3174
122
+ fuzzy_dl_owl2/fuzzydl/util/constants.py,sha256=tZKlMOZlZD9iKgaLv8NIId5WK04JuFg6FuXyA7zEe8E,12815
123
123
  fuzzy_dl_owl2/fuzzydl/util/util.py,sha256=5COC79TAJz8fNrRzXLbNpAT9rLd_0KrRI1OU-hob3oU,1903
124
124
  fuzzy_dl_owl2/fuzzydl/util/utils.py,sha256=TPVLEL9NJXgReuxEIOTEOVYqojSymg_-kyrLETXnYdo,2344
125
125
  fuzzy_dl_owl2/fuzzyowl2/__init__.py,sha256=C44P0-Sn35FQdVMqYLzeDWa5qPmokbcs8GPiD_zQaSg,153
126
- fuzzy_dl_owl2/fuzzyowl2/fuzzyowl2.py,sha256=5nMIJfNxes5OXyGJxFkdG5VIueOUaK-5TNN4KMLeESk,71603
126
+ fuzzy_dl_owl2/fuzzyowl2/fuzzyowl2.py,sha256=TYnSLXK-oR7jsOEFR46WryA0gKVBLbBI0_3Cd-dHUuU,71691
127
127
  fuzzy_dl_owl2/fuzzyowl2/fuzzyowl2_to_fuzzydl.py,sha256=egspxTzY4kNvhsQLP0AAaqJgFUqyO6VJuXLLOej06PA,36994
128
128
  fuzzy_dl_owl2/fuzzyowl2/owl_types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
129
129
  fuzzy_dl_owl2/fuzzyowl2/owl_types/choquet_concept.py,sha256=ymnyNfvkvWA3y2UMXyPviD0U98vJ6jrX6zddj09SUoI,671
@@ -158,7 +158,7 @@ fuzzy_dl_owl2/fuzzyowl2/parser/owl2_xml_parser.py,sha256=qbx6SsrYUdDOn1-qClq-0EC
158
158
  fuzzy_dl_owl2/fuzzyowl2/util/__init__.py,sha256=4qG4CwkdYoT0vh3uX9N8DlmXh4QfKimVUqR4h2DR8Gg,50
159
159
  fuzzy_dl_owl2/fuzzyowl2/util/constants.py,sha256=Oev5Q-H4mhmdvV3nrp0fZKZx8jpo9beXjJwm0dfDUSc,6970
160
160
  fuzzy_dl_owl2/fuzzyowl2/util/fuzzy_xml.py,sha256=iYAodtBkZYt3cOvMBA0VEMOU87ljb-06_aUfH14gvwE,3416
161
- fuzzy_dl_owl2-1.0.6.dist-info/LICENSE,sha256=er4Z7Ju3OzYUG5mbhh0krYVegIuv4PgehMzihVb2wpc,20131
162
- fuzzy_dl_owl2-1.0.6.dist-info/METADATA,sha256=BjS_oE4GrApDHsEvGzus0rGZ6yXtLmf_U3ZmBa9VVe8,13057
163
- fuzzy_dl_owl2-1.0.6.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
164
- fuzzy_dl_owl2-1.0.6.dist-info/RECORD,,
161
+ fuzzy_dl_owl2-1.0.7.dist-info/LICENSE,sha256=er4Z7Ju3OzYUG5mbhh0krYVegIuv4PgehMzihVb2wpc,20131
162
+ fuzzy_dl_owl2-1.0.7.dist-info/METADATA,sha256=NM2tQrq1p7MNs5PbRMBl4ECC1qYG1EC-eqmWjTGHyNk,14681
163
+ fuzzy_dl_owl2-1.0.7.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
164
+ fuzzy_dl_owl2-1.0.7.dist-info/RECORD,,