evolutia 0.1.1__py3-none-any.whl → 0.1.3__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.
- evolutia/__init__.py +9 -0
- evolutia/async_llm_providers.py +157 -0
- evolutia/cache/__init__.py +9 -0
- evolutia/cache/exercise_cache.py +226 -0
- evolutia/cache/llm_cache.py +487 -0
- evolutia/complexity_validator.py +33 -31
- evolutia/config_manager.py +53 -40
- evolutia/evolutia_engine.py +341 -66
- evolutia/exam_generator.py +44 -43
- evolutia/exceptions.py +38 -0
- evolutia/exercise_analyzer.py +42 -59
- evolutia/imports.py +175 -0
- evolutia/llm_providers.py +223 -61
- evolutia/material_extractor.py +166 -88
- evolutia/rag/rag_indexer.py +107 -90
- evolutia/rag/rag_retriever.py +130 -103
- evolutia/retry_utils.py +280 -0
- evolutia/utils/json_parser.py +29 -19
- evolutia/utils/markdown_parser.py +185 -159
- evolutia/utils/math_extractor.py +153 -144
- evolutia/validation/__init__.py +1 -0
- evolutia/validation/args_validator.py +253 -0
- evolutia/validation/config_validator.py +502 -0
- evolutia/variation_generator.py +82 -70
- evolutia-0.1.3.dist-info/METADATA +536 -0
- evolutia-0.1.3.dist-info/RECORD +37 -0
- {evolutia-0.1.1.dist-info → evolutia-0.1.3.dist-info}/WHEEL +1 -1
- evolutia_cli.py +22 -9
- evolutia-0.1.1.dist-info/METADATA +0 -221
- evolutia-0.1.1.dist-info/RECORD +0 -27
- {evolutia-0.1.1.dist-info → evolutia-0.1.3.dist-info}/entry_points.txt +0 -0
- {evolutia-0.1.1.dist-info → evolutia-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {evolutia-0.1.1.dist-info → evolutia-0.1.3.dist-info}/top_level.txt +0 -0
|
@@ -1,160 +1,186 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Utilidades para parsear archivos Markdown/MyST y extraer ejercicios y soluciones.
|
|
3
|
-
"""
|
|
4
|
-
import re
|
|
5
|
-
import yaml
|
|
6
|
-
|
|
7
|
-
from
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
def
|
|
147
|
-
"""
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
Args:
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
1
|
+
"""
|
|
2
|
+
Utilidades para parsear archivos Markdown/MyST y extraer ejercicios y soluciones.
|
|
3
|
+
"""
|
|
4
|
+
import re
|
|
5
|
+
import yaml
|
|
6
|
+
import logging
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Dict, List, Optional, Tuple, Union
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def extract_frontmatter(content: str) -> Tuple[Dict[str, str], str]:
|
|
14
|
+
"""
|
|
15
|
+
Extrae el frontmatter YAML del contenido Markdown.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
content: Contenido completo del archivo
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
Tupla (frontmatter_dict, contenido_sin_frontmatter)
|
|
22
|
+
"""
|
|
23
|
+
if not content:
|
|
24
|
+
return {}, ""
|
|
25
|
+
|
|
26
|
+
frontmatter_pattern = r'^---\s*\n(.*?)\n---\s*\n'
|
|
27
|
+
match = re.match(frontmatter_pattern, content, re.DOTALL)
|
|
28
|
+
|
|
29
|
+
if match:
|
|
30
|
+
frontmatter_str = match.group(1)
|
|
31
|
+
try:
|
|
32
|
+
frontmatter = yaml.safe_load(frontmatter_str) or {}
|
|
33
|
+
content_without_frontmatter = content[match.end():]
|
|
34
|
+
logger.debug(f"[MarkdownParser] Frontmatter extraído: {len(frontmatter)} campos")
|
|
35
|
+
return frontmatter, content_without_frontmatter
|
|
36
|
+
except yaml.YAMLError as e:
|
|
37
|
+
logger.warning(f"[MarkdownParser] Error parseando YAML frontmatter: {e}")
|
|
38
|
+
return {}, content
|
|
39
|
+
logger.debug("[MarkdownParser] No se encontró frontmatter")
|
|
40
|
+
return {}, content
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def extract_exercise_blocks(content: str) -> List[Dict[str, Union[str, None]]]:
|
|
44
|
+
"""
|
|
45
|
+
Extrae bloques de ejercicio del formato MyST.
|
|
46
|
+
|
|
47
|
+
Busca bloques del tipo:
|
|
48
|
+
```{exercise} N
|
|
49
|
+
:label: exN-XX
|
|
50
|
+
...
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
content: Contenido Markdown
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Lista de diccionarios con información de cada ejercicio
|
|
58
|
+
"""
|
|
59
|
+
exercises = []
|
|
60
|
+
|
|
61
|
+
if not content:
|
|
62
|
+
return exercises
|
|
63
|
+
|
|
64
|
+
# Patrón para bloques de ejercicio MyST
|
|
65
|
+
# Captura delimitador (grupo 1), label (grupo 2) y contenido (grupo 3)
|
|
66
|
+
# Usa backreference \1 para coincidir con la longitud exacta del delimitador de cierre
|
|
67
|
+
exercise_pattern = r'(`{3,4})\{exercise\}(?:\s+\d+)?\s*\n:label:\s+(\S+)\s*\n(.*?)(?=\1)'
|
|
68
|
+
|
|
69
|
+
matches = re.finditer(exercise_pattern, content, re.DOTALL)
|
|
70
|
+
|
|
71
|
+
for match in matches:
|
|
72
|
+
# group(1) es el delimitador
|
|
73
|
+
label = match.group(2)
|
|
74
|
+
exercise_content = match.group(3).strip()
|
|
75
|
+
|
|
76
|
+
# Buscar si hay un include dentro
|
|
77
|
+
include_match = re.search(r'```\{include\}\s+(.+?)\s*```', exercise_content, re.DOTALL)
|
|
78
|
+
if include_match:
|
|
79
|
+
include_path = include_match.group(1).strip()
|
|
80
|
+
exercises.append({
|
|
81
|
+
'label': label,
|
|
82
|
+
'content': exercise_content,
|
|
83
|
+
'include_path': include_path,
|
|
84
|
+
'type': 'include'
|
|
85
|
+
})
|
|
86
|
+
else:
|
|
87
|
+
exercises.append({
|
|
88
|
+
'label': label,
|
|
89
|
+
'content': exercise_content,
|
|
90
|
+
'include_path': None,
|
|
91
|
+
'type': 'inline'
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
logger.debug(f"[MarkdownParser] Extraídos {len(exercises)} bloques de ejercicio")
|
|
95
|
+
return exercises
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def extract_solution_blocks(content: str) -> List[Dict[str, Union[str, List[str]]]]:
|
|
99
|
+
"""
|
|
100
|
+
Extrae bloques de solución del formato MyST.
|
|
101
|
+
|
|
102
|
+
Busca bloques del tipo:
|
|
103
|
+
````{solution} exN-XX
|
|
104
|
+
:label: solution-exN-XX
|
|
105
|
+
...
|
|
106
|
+
````
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
content: Contenido Markdown
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Lista de diccionarios con información de cada solución
|
|
113
|
+
"""
|
|
114
|
+
solutions = []
|
|
115
|
+
|
|
116
|
+
if not content:
|
|
117
|
+
return solutions
|
|
118
|
+
|
|
119
|
+
# Patrón para bloques de solución MyST
|
|
120
|
+
# Captura delimitador (grupo 1), exercise_label (grupo 2), solution_label (grupo 3), contenido (grupo 4)
|
|
121
|
+
solution_pattern = r'(`{3,4})\{solution\}\s+(\S+)\s*\n:label:\s+(\S+)\s*\n(.*?)(?=\1)'
|
|
122
|
+
|
|
123
|
+
matches = re.finditer(solution_pattern, content, re.DOTALL)
|
|
124
|
+
|
|
125
|
+
for match in matches:
|
|
126
|
+
# group(1) es delimitador
|
|
127
|
+
exercise_label = match.group(2)
|
|
128
|
+
solution_label = match.group(3)
|
|
129
|
+
solution_content = match.group(4).strip()
|
|
130
|
+
|
|
131
|
+
# Buscar includes dentro de la solución
|
|
132
|
+
include_matches = re.finditer(r'```\{include\}\s+(.+?)\s*```', solution_content, re.DOTALL)
|
|
133
|
+
include_paths = [m.group(1).strip() for m in include_matches]
|
|
134
|
+
|
|
135
|
+
solutions.append({
|
|
136
|
+
'exercise_label': exercise_label,
|
|
137
|
+
'label': solution_label,
|
|
138
|
+
'content': solution_content,
|
|
139
|
+
'include_paths': include_paths
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
logger.debug(f"[MarkdownParser] Extraídos {len(solutions)} bloques de solución")
|
|
143
|
+
return solutions
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def read_markdown_file(file_path: Union[Path, str]) -> str:
|
|
147
|
+
"""
|
|
148
|
+
Lee un archivo Markdown y retorna su contenido.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
file_path: Ruta al archivo
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Contenido del archivo
|
|
155
|
+
|
|
156
|
+
Raises:
|
|
157
|
+
IOError: Si hay error leyendo el archivo
|
|
158
|
+
"""
|
|
159
|
+
file_path = Path(file_path)
|
|
160
|
+
try:
|
|
161
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
162
|
+
content = f.read()
|
|
163
|
+
logger.debug(f"[MarkdownParser] Archivo leído exitosamente: {file_path} (longitud={len(content)})")
|
|
164
|
+
return content
|
|
165
|
+
except Exception as e:
|
|
166
|
+
logger.error(f"[MarkdownParser] Error leyendo archivo {file_path}: {e}")
|
|
167
|
+
raise IOError(f"Error leyendo archivo {file_path}: {e}")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def resolve_include_path(include_path: str, base_dir: Union[Path, str]) -> Path:
|
|
171
|
+
"""
|
|
172
|
+
Resuelve una ruta de include relativa a un directorio base.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
include_path: Ruta relativa del include
|
|
176
|
+
base_dir: Directorio base
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Ruta absoluta resuelta
|
|
180
|
+
"""
|
|
181
|
+
# Limpiar la ruta (puede tener ./ o espacios)
|
|
182
|
+
clean_path = include_path.strip().lstrip('./')
|
|
183
|
+
resolved_path = (Path(base_dir) / clean_path).resolve()
|
|
184
|
+
logger.debug(f"[MarkdownParser] Ruta include resuelta: {include_path} -> {resolved_path}")
|
|
185
|
+
return resolved_path
|
|
160
186
|
|
evolutia/utils/math_extractor.py
CHANGED
|
@@ -1,144 +1,153 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Utilidades para extraer y analizar expresiones matemáticas de archivos Markdown.
|
|
3
|
-
"""
|
|
4
|
-
import re
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
"""
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
"""
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
total_complexity +=
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
total_complexity +=
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
1
|
+
"""
|
|
2
|
+
Utilidades para extraer y analizar expresiones matemáticas de archivos Markdown.
|
|
3
|
+
"""
|
|
4
|
+
import re
|
|
5
|
+
import logging
|
|
6
|
+
from typing import List, Dict, Set, Tuple
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
# Patrones comunes para variables
|
|
11
|
+
# Variables latinas: \vec{A}, A, \mathbf{B}, etc.
|
|
12
|
+
LATIN_PATTERN = re.compile(r'\\vec\{([A-Za-z])\}|\\mathbf\{([A-Za-z])\}|\\hat\{([A-Za-z])\}|([A-Za-z])(?![a-z])')
|
|
13
|
+
|
|
14
|
+
# Letras griegas: \alpha, \beta, \theta, etc.
|
|
15
|
+
GREEK_PATTERN = re.compile(r'\\(alpha|beta|gamma|delta|epsilon|theta|phi|rho|omega|sigma|lambda|mu|nu|pi|tau)')
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def extract_math_expressions(content: str) -> List[str]:
|
|
19
|
+
r"""
|
|
20
|
+
Extrae todas las expresiones matemáticas del contenido.
|
|
21
|
+
|
|
22
|
+
Busca expresiones en formato LaTeX:
|
|
23
|
+
- Inline: $...$ o \(...\)
|
|
24
|
+
- Display: $$...$$ o \[...\]
|
|
25
|
+
- Math blocks: :::{math} ... :::
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
content: Contenido Markdown
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Lista de expresiones matemáticas encontradas
|
|
32
|
+
"""
|
|
33
|
+
if not content:
|
|
34
|
+
return []
|
|
35
|
+
|
|
36
|
+
expressions = []
|
|
37
|
+
|
|
38
|
+
# 1. Bloques math de MyST: :::{math} ... :::
|
|
39
|
+
# Se procesan primero y se eliminan del contenido para evitar duplicados si contienen $ o $$
|
|
40
|
+
math_block_pattern = r':::\{math\}\s*(.*?)\s*:::'
|
|
41
|
+
for match in re.finditer(math_block_pattern, content, re.DOTALL):
|
|
42
|
+
expr = match.group(1).strip()
|
|
43
|
+
if expr:
|
|
44
|
+
expressions.append(expr)
|
|
45
|
+
content = re.sub(math_block_pattern, '', content, flags=re.DOTALL)
|
|
46
|
+
|
|
47
|
+
# 2. Expresiones display: $$...$$ o \[...\]
|
|
48
|
+
display_pattern = r'\$\$([^$]+)\$\$|\\\[([^\]]+)\\\]'
|
|
49
|
+
for match in re.finditer(display_pattern, content, re.DOTALL):
|
|
50
|
+
expr = match.group(1) or match.group(2)
|
|
51
|
+
if expr:
|
|
52
|
+
expressions.append(expr.strip())
|
|
53
|
+
content = re.sub(display_pattern, '', content, flags=re.DOTALL)
|
|
54
|
+
|
|
55
|
+
# 3. Expresiones inline: $...$ o \(...\)
|
|
56
|
+
inline_pattern = r'\$([^$]+)\$|\\\(([^\)]+)\\\)'
|
|
57
|
+
for match in re.finditer(inline_pattern, content):
|
|
58
|
+
expr = match.group(1) or match.group(2)
|
|
59
|
+
if expr:
|
|
60
|
+
expressions.append(expr.strip())
|
|
61
|
+
|
|
62
|
+
logger.debug(f"[MathExtractor] Extraídas {len(expressions)} expresiones matemáticas del contenido")
|
|
63
|
+
return expressions
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def extract_variables(math_expressions: List[str]) -> Set[str]:
|
|
67
|
+
"""
|
|
68
|
+
Extrae variables de expresiones matemáticas.
|
|
69
|
+
|
|
70
|
+
Identifica letras griegas, variables latinas, y símbolos comunes.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
math_expressions: Lista de expresiones matemáticas
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Conjunto de variables identificadas
|
|
77
|
+
"""
|
|
78
|
+
variables = set()
|
|
79
|
+
|
|
80
|
+
for expr in math_expressions:
|
|
81
|
+
# Buscar variables latinas
|
|
82
|
+
for match in LATIN_PATTERN.finditer(expr):
|
|
83
|
+
var = match.group(1) or match.group(2) or match.group(3) or match.group(4)
|
|
84
|
+
if var and var.isalpha():
|
|
85
|
+
variables.add(var)
|
|
86
|
+
|
|
87
|
+
# Buscar letras griegas
|
|
88
|
+
for match in GREEK_PATTERN.finditer(expr):
|
|
89
|
+
variables.add(match.group(1))
|
|
90
|
+
|
|
91
|
+
logger.debug(f"[MathExtractor] Extraídas {len(variables)} variables de {len(math_expressions)} expresiones")
|
|
92
|
+
return variables
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def count_math_operations(expression: str) -> Dict[str, int]:
|
|
96
|
+
"""
|
|
97
|
+
Cuenta operaciones matemáticas en una expresión.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
expression: Expresión matemática
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Diccionario con conteo de operaciones
|
|
104
|
+
"""
|
|
105
|
+
operations = {
|
|
106
|
+
'integrals': len(re.findall(r'\\int|\\oint', expression)),
|
|
107
|
+
'derivatives': len(re.findall(r'\\partial|\\nabla|\\frac\{d', expression)),
|
|
108
|
+
'sums': len(re.findall(r'\\sum|\\prod', expression)),
|
|
109
|
+
'vectors': len(re.findall(r'\\vec|\\mathbf', expression)),
|
|
110
|
+
'matrices': len(re.findall(r'\\begin\{matrix\}|\\begin\{pmatrix\}|\\begin\{bmatrix\}', expression)),
|
|
111
|
+
'functions': len(re.findall(r'\\sin|\\cos|\\tan|\\exp|\\log|\\ln', expression)),
|
|
112
|
+
}
|
|
113
|
+
return operations
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def estimate_complexity(expressions: List[str]) -> float:
|
|
117
|
+
"""
|
|
118
|
+
Estima la complejidad matemática de un conjunto de expresiones.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
expressions: Lista de expresiones matemáticas
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Puntuación de complejidad (mayor = más complejo)
|
|
125
|
+
"""
|
|
126
|
+
if not expressions:
|
|
127
|
+
return 0.0
|
|
128
|
+
|
|
129
|
+
total_complexity = 0.0
|
|
130
|
+
|
|
131
|
+
for expr in expressions:
|
|
132
|
+
# Longitud de la expresión
|
|
133
|
+
total_complexity += len(expr) * 0.01
|
|
134
|
+
|
|
135
|
+
# Operaciones complejas
|
|
136
|
+
ops = count_math_operations(expr)
|
|
137
|
+
total_complexity += ops['integrals'] * 2.0
|
|
138
|
+
total_complexity += ops['derivatives'] * 1.5
|
|
139
|
+
total_complexity += ops['sums'] * 1.5
|
|
140
|
+
total_complexity += ops['vectors'] * 1.0
|
|
141
|
+
total_complexity += ops['matrices'] * 2.5
|
|
142
|
+
total_complexity += ops['functions'] * 0.5
|
|
143
|
+
|
|
144
|
+
# Número de variables
|
|
145
|
+
vars_count = len(extract_variables([expr]))
|
|
146
|
+
total_complexity += vars_count * 0.3
|
|
147
|
+
|
|
148
|
+
# Bloques align (ecuaciones múltiples)
|
|
149
|
+
if '\\begin{align' in expr or '\\begin{aligned' in expr:
|
|
150
|
+
total_complexity += 2.0
|
|
151
|
+
|
|
152
|
+
logger.debug(f"[MathExtractor] Complejidad estimada: {total_complexity:.2f} (de {len(expressions)} expresiones)")
|
|
153
|
+
return total_complexity
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Tests package for EvolutIA validation
|