desdeo 1.2__py3-none-any.whl → 2.1.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.
- desdeo/__init__.py +8 -8
- desdeo/adm/ADMAfsar.py +551 -0
- desdeo/adm/ADMChen.py +414 -0
- desdeo/adm/BaseADM.py +119 -0
- desdeo/adm/__init__.py +11 -0
- desdeo/api/README.md +73 -0
- desdeo/api/__init__.py +15 -0
- desdeo/api/app.py +50 -0
- desdeo/api/config.py +90 -0
- desdeo/api/config.toml +64 -0
- desdeo/api/db.py +27 -0
- desdeo/api/db_init.py +85 -0
- desdeo/api/db_models.py +164 -0
- desdeo/api/malaga_db_init.py +27 -0
- desdeo/api/models/__init__.py +266 -0
- desdeo/api/models/archive.py +23 -0
- desdeo/api/models/emo.py +128 -0
- desdeo/api/models/enautilus.py +69 -0
- desdeo/api/models/gdm/gdm_aggregate.py +139 -0
- desdeo/api/models/gdm/gdm_base.py +69 -0
- desdeo/api/models/gdm/gdm_score_bands.py +114 -0
- desdeo/api/models/gdm/gnimbus.py +138 -0
- desdeo/api/models/generic.py +104 -0
- desdeo/api/models/generic_states.py +401 -0
- desdeo/api/models/nimbus.py +158 -0
- desdeo/api/models/preference.py +128 -0
- desdeo/api/models/problem.py +717 -0
- desdeo/api/models/reference_point_method.py +18 -0
- desdeo/api/models/session.py +49 -0
- desdeo/api/models/state.py +463 -0
- desdeo/api/models/user.py +52 -0
- desdeo/api/models/utopia.py +25 -0
- desdeo/api/routers/_EMO.backup +309 -0
- desdeo/api/routers/_NAUTILUS.py +245 -0
- desdeo/api/routers/_NAUTILUS_navigator.py +233 -0
- desdeo/api/routers/_NIMBUS.py +765 -0
- desdeo/api/routers/__init__.py +5 -0
- desdeo/api/routers/emo.py +497 -0
- desdeo/api/routers/enautilus.py +237 -0
- desdeo/api/routers/gdm/gdm_aggregate.py +234 -0
- desdeo/api/routers/gdm/gdm_base.py +420 -0
- desdeo/api/routers/gdm/gdm_score_bands/gdm_score_bands_manager.py +398 -0
- desdeo/api/routers/gdm/gdm_score_bands/gdm_score_bands_routers.py +377 -0
- desdeo/api/routers/gdm/gnimbus/gnimbus_manager.py +698 -0
- desdeo/api/routers/gdm/gnimbus/gnimbus_routers.py +591 -0
- desdeo/api/routers/generic.py +233 -0
- desdeo/api/routers/nimbus.py +705 -0
- desdeo/api/routers/problem.py +307 -0
- desdeo/api/routers/reference_point_method.py +93 -0
- desdeo/api/routers/session.py +100 -0
- desdeo/api/routers/test.py +16 -0
- desdeo/api/routers/user_authentication.py +520 -0
- desdeo/api/routers/utils.py +187 -0
- desdeo/api/routers/utopia.py +230 -0
- desdeo/api/schema.py +100 -0
- desdeo/api/tests/__init__.py +0 -0
- desdeo/api/tests/conftest.py +151 -0
- desdeo/api/tests/test_enautilus.py +330 -0
- desdeo/api/tests/test_models.py +1179 -0
- desdeo/api/tests/test_routes.py +1075 -0
- desdeo/api/utils/_database.py +263 -0
- desdeo/api/utils/_logger.py +29 -0
- desdeo/api/utils/database.py +36 -0
- desdeo/api/utils/emo_database.py +40 -0
- desdeo/core.py +34 -0
- desdeo/emo/__init__.py +159 -0
- desdeo/emo/hooks/archivers.py +188 -0
- desdeo/emo/methods/EAs.py +541 -0
- desdeo/emo/methods/__init__.py +0 -0
- desdeo/emo/methods/bases.py +12 -0
- desdeo/emo/methods/templates.py +111 -0
- desdeo/emo/operators/__init__.py +1 -0
- desdeo/emo/operators/crossover.py +1282 -0
- desdeo/emo/operators/evaluator.py +114 -0
- desdeo/emo/operators/generator.py +459 -0
- desdeo/emo/operators/mutation.py +1224 -0
- desdeo/emo/operators/scalar_selection.py +202 -0
- desdeo/emo/operators/selection.py +1778 -0
- desdeo/emo/operators/termination.py +286 -0
- desdeo/emo/options/__init__.py +108 -0
- desdeo/emo/options/algorithms.py +435 -0
- desdeo/emo/options/crossover.py +164 -0
- desdeo/emo/options/generator.py +131 -0
- desdeo/emo/options/mutation.py +260 -0
- desdeo/emo/options/repair.py +61 -0
- desdeo/emo/options/scalar_selection.py +66 -0
- desdeo/emo/options/selection.py +127 -0
- desdeo/emo/options/templates.py +383 -0
- desdeo/emo/options/termination.py +143 -0
- desdeo/explanations/__init__.py +6 -0
- desdeo/explanations/explainer.py +100 -0
- desdeo/explanations/utils.py +90 -0
- desdeo/gdm/__init__.py +22 -0
- desdeo/gdm/gdmtools.py +45 -0
- desdeo/gdm/score_bands.py +114 -0
- desdeo/gdm/voting_rules.py +50 -0
- desdeo/mcdm/__init__.py +41 -0
- desdeo/mcdm/enautilus.py +338 -0
- desdeo/mcdm/gnimbus.py +484 -0
- desdeo/mcdm/nautili.py +345 -0
- desdeo/mcdm/nautilus.py +477 -0
- desdeo/mcdm/nautilus_navigator.py +656 -0
- desdeo/mcdm/nimbus.py +417 -0
- desdeo/mcdm/pareto_navigator.py +269 -0
- desdeo/mcdm/reference_point_method.py +186 -0
- desdeo/problem/__init__.py +83 -0
- desdeo/problem/evaluator.py +561 -0
- desdeo/problem/external/__init__.py +18 -0
- desdeo/problem/external/core.py +356 -0
- desdeo/problem/external/pymoo_provider.py +266 -0
- desdeo/problem/external/runtime.py +44 -0
- desdeo/problem/gurobipy_evaluator.py +562 -0
- desdeo/problem/infix_parser.py +341 -0
- desdeo/problem/json_parser.py +944 -0
- desdeo/problem/pyomo_evaluator.py +487 -0
- desdeo/problem/schema.py +1829 -0
- desdeo/problem/simulator_evaluator.py +348 -0
- desdeo/problem/sympy_evaluator.py +244 -0
- desdeo/problem/testproblems/__init__.py +88 -0
- desdeo/problem/testproblems/benchmarks_server.py +120 -0
- desdeo/problem/testproblems/binh_and_korn_problem.py +88 -0
- desdeo/problem/testproblems/cake_problem.py +185 -0
- desdeo/problem/testproblems/dmitry_forest_problem_discrete.py +71 -0
- desdeo/problem/testproblems/dtlz2_problem.py +102 -0
- desdeo/problem/testproblems/forest_problem.py +283 -0
- desdeo/problem/testproblems/knapsack_problem.py +163 -0
- desdeo/problem/testproblems/mcwb_problem.py +831 -0
- desdeo/problem/testproblems/mixed_variable_dimenrions_problem.py +83 -0
- desdeo/problem/testproblems/momip_problem.py +172 -0
- desdeo/problem/testproblems/multi_valued_constraints.py +119 -0
- desdeo/problem/testproblems/nimbus_problem.py +143 -0
- desdeo/problem/testproblems/pareto_navigator_problem.py +89 -0
- desdeo/problem/testproblems/re_problem.py +492 -0
- desdeo/problem/testproblems/river_pollution_problems.py +440 -0
- desdeo/problem/testproblems/rocket_injector_design_problem.py +140 -0
- desdeo/problem/testproblems/simple_problem.py +351 -0
- desdeo/problem/testproblems/simulator_problem.py +92 -0
- desdeo/problem/testproblems/single_objective.py +289 -0
- desdeo/problem/testproblems/spanish_sustainability_problem.py +945 -0
- desdeo/problem/testproblems/zdt_problem.py +274 -0
- desdeo/problem/utils.py +245 -0
- desdeo/tools/GenerateReferencePoints.py +181 -0
- desdeo/tools/__init__.py +120 -0
- desdeo/tools/desc_gen.py +22 -0
- desdeo/tools/generics.py +165 -0
- desdeo/tools/group_scalarization.py +3090 -0
- desdeo/tools/gurobipy_solver_interfaces.py +258 -0
- desdeo/tools/indicators_binary.py +117 -0
- desdeo/tools/indicators_unary.py +362 -0
- desdeo/tools/interaction_schema.py +38 -0
- desdeo/tools/intersection.py +54 -0
- desdeo/tools/iterative_pareto_representer.py +99 -0
- desdeo/tools/message.py +265 -0
- desdeo/tools/ng_solver_interfaces.py +199 -0
- desdeo/tools/non_dominated_sorting.py +134 -0
- desdeo/tools/patterns.py +283 -0
- desdeo/tools/proximal_solver.py +99 -0
- desdeo/tools/pyomo_solver_interfaces.py +477 -0
- desdeo/tools/reference_vectors.py +229 -0
- desdeo/tools/scalarization.py +2065 -0
- desdeo/tools/scipy_solver_interfaces.py +454 -0
- desdeo/tools/score_bands.py +627 -0
- desdeo/tools/utils.py +388 -0
- desdeo/tools/visualizations.py +67 -0
- desdeo/utopia_stuff/__init__.py +0 -0
- desdeo/utopia_stuff/data/1.json +15 -0
- desdeo/utopia_stuff/data/2.json +13 -0
- desdeo/utopia_stuff/data/3.json +15 -0
- desdeo/utopia_stuff/data/4.json +17 -0
- desdeo/utopia_stuff/data/5.json +15 -0
- desdeo/utopia_stuff/from_json.py +40 -0
- desdeo/utopia_stuff/reinit_user.py +38 -0
- desdeo/utopia_stuff/utopia_db_init.py +212 -0
- desdeo/utopia_stuff/utopia_problem.py +403 -0
- desdeo/utopia_stuff/utopia_problem_old.py +415 -0
- desdeo/utopia_stuff/utopia_reference_solutions.py +79 -0
- desdeo-2.1.0.dist-info/METADATA +186 -0
- desdeo-2.1.0.dist-info/RECORD +180 -0
- {desdeo-1.2.dist-info → desdeo-2.1.0.dist-info}/WHEEL +1 -1
- desdeo-2.1.0.dist-info/licenses/LICENSE +21 -0
- desdeo-1.2.dist-info/METADATA +0 -16
- 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.enable_packrat(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: # noqa: PLR0911, PLR0912
|
|
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}")
|