desdeo 1.2__py3-none-any.whl → 2.0.0__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 (122) hide show
  1. desdeo/__init__.py +8 -8
  2. desdeo/api/README.md +73 -0
  3. desdeo/api/__init__.py +15 -0
  4. desdeo/api/app.py +40 -0
  5. desdeo/api/config.py +69 -0
  6. desdeo/api/config.toml +53 -0
  7. desdeo/api/db.py +25 -0
  8. desdeo/api/db_init.py +79 -0
  9. desdeo/api/db_models.py +164 -0
  10. desdeo/api/malaga_db_init.py +27 -0
  11. desdeo/api/models/__init__.py +66 -0
  12. desdeo/api/models/archive.py +34 -0
  13. desdeo/api/models/preference.py +90 -0
  14. desdeo/api/models/problem.py +507 -0
  15. desdeo/api/models/reference_point_method.py +18 -0
  16. desdeo/api/models/session.py +46 -0
  17. desdeo/api/models/state.py +96 -0
  18. desdeo/api/models/user.py +51 -0
  19. desdeo/api/routers/_NAUTILUS.py +245 -0
  20. desdeo/api/routers/_NAUTILUS_navigator.py +233 -0
  21. desdeo/api/routers/_NIMBUS.py +762 -0
  22. desdeo/api/routers/__init__.py +5 -0
  23. desdeo/api/routers/problem.py +110 -0
  24. desdeo/api/routers/reference_point_method.py +117 -0
  25. desdeo/api/routers/session.py +76 -0
  26. desdeo/api/routers/test.py +16 -0
  27. desdeo/api/routers/user_authentication.py +366 -0
  28. desdeo/api/schema.py +94 -0
  29. desdeo/api/tests/__init__.py +0 -0
  30. desdeo/api/tests/conftest.py +59 -0
  31. desdeo/api/tests/test_models.py +701 -0
  32. desdeo/api/tests/test_routes.py +216 -0
  33. desdeo/api/utils/database.py +274 -0
  34. desdeo/api/utils/logger.py +29 -0
  35. desdeo/core.py +27 -0
  36. desdeo/emo/__init__.py +29 -0
  37. desdeo/emo/hooks/archivers.py +172 -0
  38. desdeo/emo/methods/EAs.py +418 -0
  39. desdeo/emo/methods/__init__.py +0 -0
  40. desdeo/emo/methods/bases.py +59 -0
  41. desdeo/emo/operators/__init__.py +1 -0
  42. desdeo/emo/operators/crossover.py +780 -0
  43. desdeo/emo/operators/evaluator.py +118 -0
  44. desdeo/emo/operators/generator.py +356 -0
  45. desdeo/emo/operators/mutation.py +1053 -0
  46. desdeo/emo/operators/selection.py +1036 -0
  47. desdeo/emo/operators/termination.py +178 -0
  48. desdeo/explanations/__init__.py +6 -0
  49. desdeo/explanations/explainer.py +100 -0
  50. desdeo/explanations/utils.py +90 -0
  51. desdeo/mcdm/__init__.py +19 -0
  52. desdeo/mcdm/nautili.py +345 -0
  53. desdeo/mcdm/nautilus.py +477 -0
  54. desdeo/mcdm/nautilus_navigator.py +655 -0
  55. desdeo/mcdm/nimbus.py +417 -0
  56. desdeo/mcdm/pareto_navigator.py +269 -0
  57. desdeo/mcdm/reference_point_method.py +116 -0
  58. desdeo/problem/__init__.py +79 -0
  59. desdeo/problem/evaluator.py +561 -0
  60. desdeo/problem/gurobipy_evaluator.py +562 -0
  61. desdeo/problem/infix_parser.py +341 -0
  62. desdeo/problem/json_parser.py +944 -0
  63. desdeo/problem/pyomo_evaluator.py +468 -0
  64. desdeo/problem/schema.py +1808 -0
  65. desdeo/problem/simulator_evaluator.py +298 -0
  66. desdeo/problem/sympy_evaluator.py +244 -0
  67. desdeo/problem/testproblems/__init__.py +73 -0
  68. desdeo/problem/testproblems/binh_and_korn_problem.py +88 -0
  69. desdeo/problem/testproblems/dtlz2_problem.py +102 -0
  70. desdeo/problem/testproblems/forest_problem.py +275 -0
  71. desdeo/problem/testproblems/knapsack_problem.py +163 -0
  72. desdeo/problem/testproblems/mcwb_problem.py +831 -0
  73. desdeo/problem/testproblems/mixed_variable_dimenrions_problem.py +83 -0
  74. desdeo/problem/testproblems/momip_problem.py +172 -0
  75. desdeo/problem/testproblems/nimbus_problem.py +143 -0
  76. desdeo/problem/testproblems/pareto_navigator_problem.py +89 -0
  77. desdeo/problem/testproblems/re_problem.py +492 -0
  78. desdeo/problem/testproblems/river_pollution_problem.py +434 -0
  79. desdeo/problem/testproblems/rocket_injector_design_problem.py +140 -0
  80. desdeo/problem/testproblems/simple_problem.py +351 -0
  81. desdeo/problem/testproblems/simulator_problem.py +92 -0
  82. desdeo/problem/testproblems/spanish_sustainability_problem.py +945 -0
  83. desdeo/problem/testproblems/zdt_problem.py +271 -0
  84. desdeo/problem/utils.py +245 -0
  85. desdeo/tools/GenerateReferencePoints.py +181 -0
  86. desdeo/tools/__init__.py +102 -0
  87. desdeo/tools/generics.py +145 -0
  88. desdeo/tools/gurobipy_solver_interfaces.py +258 -0
  89. desdeo/tools/indicators_binary.py +11 -0
  90. desdeo/tools/indicators_unary.py +375 -0
  91. desdeo/tools/interaction_schema.py +38 -0
  92. desdeo/tools/intersection.py +54 -0
  93. desdeo/tools/iterative_pareto_representer.py +99 -0
  94. desdeo/tools/message.py +234 -0
  95. desdeo/tools/ng_solver_interfaces.py +199 -0
  96. desdeo/tools/non_dominated_sorting.py +133 -0
  97. desdeo/tools/patterns.py +281 -0
  98. desdeo/tools/proximal_solver.py +99 -0
  99. desdeo/tools/pyomo_solver_interfaces.py +464 -0
  100. desdeo/tools/reference_vectors.py +462 -0
  101. desdeo/tools/scalarization.py +3138 -0
  102. desdeo/tools/scipy_solver_interfaces.py +454 -0
  103. desdeo/tools/score_bands.py +464 -0
  104. desdeo/tools/utils.py +320 -0
  105. desdeo/utopia_stuff/__init__.py +0 -0
  106. desdeo/utopia_stuff/data/1.json +15 -0
  107. desdeo/utopia_stuff/data/2.json +13 -0
  108. desdeo/utopia_stuff/data/3.json +15 -0
  109. desdeo/utopia_stuff/data/4.json +17 -0
  110. desdeo/utopia_stuff/data/5.json +15 -0
  111. desdeo/utopia_stuff/from_json.py +40 -0
  112. desdeo/utopia_stuff/reinit_user.py +38 -0
  113. desdeo/utopia_stuff/utopia_db_init.py +212 -0
  114. desdeo/utopia_stuff/utopia_problem.py +403 -0
  115. desdeo/utopia_stuff/utopia_problem_old.py +415 -0
  116. desdeo/utopia_stuff/utopia_reference_solutions.py +79 -0
  117. desdeo-2.0.0.dist-info/LICENSE +21 -0
  118. desdeo-2.0.0.dist-info/METADATA +168 -0
  119. desdeo-2.0.0.dist-info/RECORD +120 -0
  120. {desdeo-1.2.dist-info → desdeo-2.0.0.dist-info}/WHEEL +1 -1
  121. desdeo-1.2.dist-info/METADATA +0 -16
  122. desdeo-1.2.dist-info/RECORD +0 -4
@@ -0,0 +1,341 @@
1
+ """Defines parsers for parsing mathematical expression in an infix format and expressed as string.
2
+
3
+ Currently, mostly parses to MathJSON, e.g., "n / (1 + n)" -> ['Divide', 'n', ['Add', 1, 'n']].
4
+ """
5
+
6
+ from typing import ClassVar
7
+
8
+ from pyparsing import (
9
+ DelimitedList,
10
+ Forward,
11
+ Group,
12
+ Literal,
13
+ OpAssoc,
14
+ ParserElement,
15
+ Regex,
16
+ Suppress,
17
+ infix_notation,
18
+ one_of,
19
+ pyparsing_common,
20
+ )
21
+
22
+ # Enable Packrat for better performance in recursive parsing
23
+ ParserElement.enablePackrat(None)
24
+
25
+
26
+ class InfixExpressionParser:
27
+ """A class for defining infix notation parsers."""
28
+
29
+ SUPPORTED_TARGETS: ClassVar[list] = ["MathJSON"]
30
+
31
+ # Supported infix binary operators, i.e., '1+1'. The key is the notation of the operator in infix format,
32
+ # and the value the notation in parsed format.
33
+ BINARY_OPERATORS: ClassVar[dict] = {
34
+ "+": "Add",
35
+ "-": "Subtract",
36
+ "*": "Multiply",
37
+ "/": "Divide",
38
+ "**": "Power",
39
+ "@": "MatMul",
40
+ }
41
+
42
+ # Supported infix unary operators, i.e., 'Cos(90)'. The key is the notation of the operator in infix format,
43
+ # and the value the notation in parsed format.
44
+ UNARY_OPERATORS: ClassVar[dict] = {
45
+ "Cos": "Cos",
46
+ "Sin": "Sin",
47
+ "Tan": "Tan",
48
+ "Exp": "Exp",
49
+ "Ln": "Ln",
50
+ "Lb": "Lb",
51
+ "Lg": "Lg",
52
+ "LogOnePlus": "LogOnePlus",
53
+ "Sqrt": "Sqrt",
54
+ "Square": "Square",
55
+ "Abs": "Abs",
56
+ "Ceil": "Ceil",
57
+ "Floor": "Floor",
58
+ "Arccos": "Arccos",
59
+ "Arccosh": "Arccosh",
60
+ "Arcsin": "Arcsin",
61
+ "Arcsinh": "Arcsinh",
62
+ "Arctan": "Arctan",
63
+ "Arctanh": "Arctanh",
64
+ "Cosh": "Cosh",
65
+ "Sinh": "Sinh",
66
+ "Tanh": "Tanh",
67
+ "Rational": "Rational",
68
+ "-": "Negate",
69
+ "Sum": "Sum",
70
+ }
71
+
72
+ # Supported infix variadic operators (operators that take one or more comma separated arguments),
73
+ # i.e., 'Max(1,2, Cos(3)). The key is the notation of the operator in infix format,
74
+ # and the value the notation in parsed format.
75
+ VARIADIC_OPERATORS: ClassVar[dict] = {"Max": "Max", "Min": "Min"}
76
+
77
+ def __init__(self, target="MathJSON"):
78
+ """A parser for infix notation, e.g., the huma readable way of notating mathematical expressions.
79
+
80
+ The parser can parse infix notation stored in a string to different formats. For instance,
81
+ "Cos(2 + f_1) - 7.2 + Max(f_2, -f_3)" is first parsed to the list:
82
+ ['Cos', [[2, '+', 'f_1']]], '-', 7.2, '+', ['Max', ['f_2', ['-', 'f_3']]. Then, if parsed to
83
+ the MathJSON format, it will be parsed to the list:
84
+ [
85
+ [["Subtract", ["Cos", ["Add", 2, "f_1"]], ["Add", 7.2, ["Max", ["f_2", ["Negate", "f_3"]]]]]]
86
+ ].
87
+
88
+
89
+ Args:
90
+ target (str, optional): The target format to parse an infix expression to.
91
+ Currently only "MathJSON" is supported. Defaults to "MathJSON".
92
+ """
93
+ if target not in InfixExpressionParser.SUPPORTED_TARGETS:
94
+ msg = f"The target '{target} is not supported. Should be one of {InfixExpressionParser.SUPPORTED_TARGETS}"
95
+ raise ValueError(msg)
96
+ self.target = target
97
+
98
+ # Scope limiters
99
+ lparen = Suppress("(")
100
+ rparen = Suppress(")")
101
+ lbracket = Suppress("[")
102
+ rbracket = Suppress("]")
103
+
104
+ # Define keywords (Note that binary operators must be defined manually)
105
+ symbols_variadic = set(InfixExpressionParser.VARIADIC_OPERATORS)
106
+ symbols_unary = set(InfixExpressionParser.UNARY_OPERATORS)
107
+
108
+ # Define binary operation symbols (this is the manual part)
109
+ # If new binary operators are to be added, they must be defined here.
110
+ signop = one_of("+ -")
111
+ multop = one_of("* / . @")
112
+ plusop = one_of("+ -")
113
+ expop = Literal("**")
114
+
115
+ # Dynamically create Keyword objects for variadric functions
116
+ variadic_pattern = r"\b(" + f"{'|'.join([*symbols_variadic])}" + r")\b"
117
+ variadic_func_names = Regex(variadic_pattern)
118
+
119
+ # Dynamically create Keyword objects for unary functions
120
+ # unary_func_names = reduce(or_, (Keyword(k) for k in symbols_unary))
121
+ unary_pattern = r"\b(" + f"{'|'.join([*symbols_unary])}" + r")\b"
122
+ unary_func_names = Regex(unary_pattern)
123
+
124
+ # Define operands
125
+ integer = pyparsing_common.integer
126
+
127
+ # Scientific notation
128
+ scientific = pyparsing_common.sci_real
129
+
130
+ # Complete regex pattern with exclusions and identifier pattern
131
+ exclude = f"{'|'.join([*symbols_variadic, *symbols_unary])}"
132
+ pattern = r"(?!\b(" + exclude + r")\b)(\b[a-zA-Z_][a-zA-Z0-9_]*\b)"
133
+ variable = Regex(pattern)("variable")
134
+
135
+ # Forward declarations of variadic, unary, and bracket function calls
136
+ variadic_call = Forward()
137
+ unary_call = Forward()
138
+ bracket_access = Forward()
139
+
140
+ operands = variable | scientific | integer
141
+
142
+ # Define bracket access. Brackets following a variable may contain only integer values.
143
+ index_list = Group(DelimitedList(integer))("bracket_indices")
144
+ bracket_access <<= Group(variable + lbracket + index_list + rbracket)
145
+
146
+ # The parsed expressions are assumed to follow a standard infix syntax. The operands
147
+ # of the infix syntax can be either the literal 'operands' defined above (these are singletons),
148
+ # or either a variadic function call or a unary function call. These latter two will be
149
+ # defined to be recursive.
150
+ #
151
+ # Note that the order of the operators in the second argument (the list) of infixNotation matters!
152
+ # The operation with the highest precedence is listed first.
153
+ infix_expn = infix_notation(
154
+ bracket_access | operands | variadic_call | unary_call,
155
+ [
156
+ (expop, 2, OpAssoc.LEFT),
157
+ (signop, 1, OpAssoc.RIGHT),
158
+ (multop, 2, OpAssoc.LEFT),
159
+ (plusop, 2, OpAssoc.LEFT),
160
+ ],
161
+ )("binary_operator")
162
+
163
+ # These are recursive definitions of the forward declarations of the two type of function calls.
164
+ # In essence, the recursion continues until a singleton operand is encountered.
165
+ variadic_call <<= Group(variadic_func_names + lparen + Group(DelimitedList(infix_expn)) + rparen)(
166
+ "variadic_call"
167
+ )
168
+ unary_call <<= Group(unary_func_names + lparen + Group(infix_expn) + rparen)("unary_call")
169
+
170
+ self.expn = infix_expn
171
+
172
+ # The infix operations do not need to be in this list because they are handled by infixNotation() above.
173
+ # If new binary operations are to be added, they must be updated in the infixNotation() call (the list).
174
+ self.reserved_symbols: set[str] = symbols_unary | symbols_variadic
175
+
176
+ # It is assumed that the dicts in the three class variables have unique keys.
177
+ self.operator_mapping = {
178
+ **InfixExpressionParser.BINARY_OPERATORS,
179
+ **InfixExpressionParser.UNARY_OPERATORS,
180
+ **InfixExpressionParser.VARIADIC_OPERATORS,
181
+ }
182
+
183
+ if self.target == "MathJSON":
184
+ self.pre_parse = self._pre_parse
185
+ self.parse_to_target = self._to_math_json
186
+ else:
187
+ self.pre_parse = None
188
+ self.parse_to_target = None
189
+
190
+ def _pre_parse(self, str_expr: str):
191
+ return self.expn.parse_string(str_expr, parse_all=True)
192
+
193
+ def _is_number_or_variable(self, c):
194
+ return isinstance(c, int | float) or (isinstance(c, str) and c not in self.reserved_symbols)
195
+
196
+ def _to_math_json(self, parsed: list | str) -> list:
197
+ """Converts a list of expressions into a MathJSON compliant format.
198
+
199
+ The conversion happens recursively. Each list of recursed until a terminal character is reached.
200
+ Terminal characters are integers (int), floating point numbers (float), or non-keyword strings (str).
201
+ Keyword strings are reserved for operations, such as 'Cos' or 'Max'.
202
+
203
+ Args:
204
+ parsed (list): A list possibly containing other lists. Represents a mathematical expression.
205
+
206
+ Returns:
207
+ list: A list representing a mathematical expression in a MathJSON compliant format.
208
+ """
209
+ # Directly return the input if it is an integer or a float
210
+ if self._is_number_or_variable(parsed):
211
+ return parsed
212
+
213
+ # Handle bracket access
214
+ # Assume that anything following variable in a bracket list is only integer.
215
+ if "bracket_indices" in parsed:
216
+ variable = parsed["variable"]
217
+ indices = parsed["bracket_indices"]
218
+ return ["At", variable, *indices]
219
+
220
+ # Flatten binary operations like 1 + 2 + 3 into ["Add", 1, 2, 3]
221
+ # Last check is to make sure that in cases like ["Max", ["x", "y", ...]] the 'y' is not confused to
222
+ # be an operator.
223
+ num_binary_elements = 3
224
+ if (
225
+ len(parsed) >= num_binary_elements
226
+ and isinstance(parsed[1], str)
227
+ and parsed[1] in InfixExpressionParser.BINARY_OPERATORS
228
+ ):
229
+ # Initialize the list to collect operands for the current operation
230
+ operands = []
231
+
232
+ # Check if the first operation is subtraction and handle it specially
233
+ if parsed[1] == "-":
234
+ current_operator = "Add"
235
+ # Negate the operand immediately following the subtraction operator
236
+ operands.append(self._to_math_json(parsed[0])) # Add the first operand
237
+ operands.append(["Negate", self._to_math_json(parsed[2])]) # Negate the second operand
238
+ start_index = 3 # Start processing the rest of the expression from the next element
239
+ else:
240
+ current_operator = self.operator_mapping[parsed[1]]
241
+ operands.append(self._to_math_json(parsed[0])) # Add the first operand as is
242
+ start_index = 1 # Start processing the rest of the expression from the second element
243
+
244
+ i = start_index
245
+
246
+ while i < len(parsed) - 1:
247
+ op = parsed[i]
248
+
249
+ if isinstance(parsed[i], str) and i + 2 < len(parsed) and parsed[i] == parsed[i + 2]:
250
+ next_operand = self._to_math_json(parsed[i + 1]) # Next operand
251
+
252
+ if op == "-": # If subtraction, negate and add
253
+ operands.append(["Negate", next_operand])
254
+ else:
255
+ operands.append(next_operand)
256
+ i += 2
257
+ else:
258
+ # Handle last expression, negate if needed.
259
+ if op == "-":
260
+ return [
261
+ [
262
+ current_operator,
263
+ *operands,
264
+ ["Negate", self._to_math_json(parsed[i + 1])],
265
+ *(self._to_math_json(parsed[(i + 1) + 2 :]) if parsed[(i + 1) + 2 :] else []),
266
+ ]
267
+ ]
268
+
269
+ return [[current_operator, *operands, *self._to_math_json(parsed[i + 1 :])]]
270
+
271
+ return [[current_operator, *operands]]
272
+
273
+ # Handle unary operations and functions
274
+ if isinstance(parsed[0], str) and parsed[0] in self.reserved_symbols:
275
+ if parsed[0] in self.operator_mapping:
276
+ operator = self.operator_mapping.get(parsed[0], parsed[0])
277
+ operands = [self._to_math_json(p) for p in parsed[1:]]
278
+
279
+ while isinstance(operands, list) and len(operands) == 1 and isinstance(operands[0], list):
280
+ operands = operands[0]
281
+
282
+ return [operator, [*operands]]
283
+
284
+ operand = self._to_math_json(parsed[1])
285
+
286
+ return [parsed[0], operand]
287
+
288
+ # For lists and nested expressions
289
+ return [self._to_math_json(part) for part in parsed]
290
+
291
+ def _remove_extra_brackets(self, lst: list) -> list:
292
+ """Removes recursively extra brackets from a nested list that may have been left when parsing an expression.
293
+
294
+ Args:
295
+ lst (list): A (nested) list that needs extra bracket removal.
296
+
297
+ Returns:
298
+ list: A list with extra brackets removed.
299
+ """
300
+ # Base case: if it's not a list, just return the item itself
301
+ if not isinstance(lst, list):
302
+ return lst
303
+
304
+ # If the list has only one element and that element is a list, unpack it
305
+ if len(lst) == 1 and (isinstance(lst[0], list) or self._is_number_or_variable(lst[0])):
306
+ return self._remove_extra_brackets(lst[0])
307
+
308
+ # Otherwise, process each element of the list
309
+ return [self._remove_extra_brackets(item) for item in lst]
310
+
311
+ def parse(self, str_expr: str) -> list:
312
+ """The method to call when parsing an infix expression in represented by a string.
313
+
314
+ Args:
315
+ str_expr (str): A string expression to be parsed.
316
+
317
+ Returns:
318
+ list: A list representing the parsed expression.
319
+ """
320
+ expr = self._remove_extra_brackets(self.parse_to_target(self.pre_parse(str_expr)))
321
+
322
+ # if the expression is just a string after removing extra brackets,
323
+ # wrap it in a list to keep the return type consistent.
324
+ # simple expressions, like 'x_1', are parsed into just a string after removing any extra
325
+ # brackets, so we add them back there in case it is needed
326
+ return expr if isinstance(expr, list) else [expr]
327
+
328
+
329
+ if __name__ == "__main__":
330
+ # Parse and convert
331
+ test = "(x_1 - c_2) ** 2 + x_2 ** 2 - 25"
332
+
333
+ ohh_no = "['Add', ['Negate', ['Square', ['Subtract', 'x_1', 8]]], ['Negate', ['Square', ['Add', 'x_2', 3]]], 7.7]"
334
+
335
+ to_json_parser = InfixExpressionParser(target="MathJSON")
336
+
337
+ parsed_expression = to_json_parser.parse(test)
338
+
339
+ print(f"Expresion:\n{test}")
340
+ print(f"Parsed:\n{to_json_parser._pre_parse(test)}")
341
+ print(f"MathJSON:\n{parsed_expression}")