fuzzy-dl-owl2 1.0.5__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.
@@ -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)