algomath-extract 1.0.0
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.
- package/README.md +260 -0
- package/bin/algo-extract.js +143 -0
- package/bin/algo-generate.js +102 -0
- package/bin/algo-help.js +136 -0
- package/bin/algo-list.js +56 -0
- package/bin/algo-run.js +141 -0
- package/bin/algo-status.js +88 -0
- package/bin/algo-verify.js +189 -0
- package/bin/install.js +349 -0
- package/package.json +57 -0
- package/requirements.txt +20 -0
- package/src/__pycache__/intent.cpython-313.pyc +0 -0
- package/src/cli/__pycache__/commands.cpython-313.pyc +0 -0
- package/src/cli/cli_entry.py +106 -0
- package/src/cli/commands.py +339 -0
- package/src/execution/__init__.py +74 -0
- package/src/execution/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/execution/__pycache__/display.cpython-313.pyc +0 -0
- package/src/execution/__pycache__/errors.cpython-313.pyc +0 -0
- package/src/execution/__pycache__/executor.cpython-313.pyc +0 -0
- package/src/execution/__pycache__/sandbox.cpython-313.pyc +0 -0
- package/src/execution/display.py +261 -0
- package/src/execution/errors.py +158 -0
- package/src/execution/executor.py +253 -0
- package/src/execution/sandbox.py +333 -0
- package/src/extraction/__init__.py +102 -0
- package/src/extraction/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/boundaries.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/errors.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/llm_extraction.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/notation.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/parser.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/pdf_processor.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/prompts.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/review.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/schema.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/validation.cpython-313.pyc +0 -0
- package/src/extraction/boundaries.py +281 -0
- package/src/extraction/errors.py +156 -0
- package/src/extraction/llm_extraction.py +225 -0
- package/src/extraction/notation.py +240 -0
- package/src/extraction/parser.py +402 -0
- package/src/extraction/pdf_processor.py +281 -0
- package/src/extraction/prompts.py +90 -0
- package/src/extraction/review.py +298 -0
- package/src/extraction/schema.py +173 -0
- package/src/extraction/validation.py +202 -0
- package/src/generation/__init__.py +79 -0
- package/src/generation/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/code_generator.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/errors.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/hybrid.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/llm_generator.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/persistence.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/prompts.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/review.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/templates.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/types.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/validation.cpython-313.pyc +0 -0
- package/src/generation/code_generator.py +375 -0
- package/src/generation/errors.py +84 -0
- package/src/generation/hybrid.py +210 -0
- package/src/generation/llm_generator.py +223 -0
- package/src/generation/persistence.py +221 -0
- package/src/generation/prompts.py +202 -0
- package/src/generation/review.py +254 -0
- package/src/generation/templates.py +208 -0
- package/src/generation/types.py +196 -0
- package/src/generation/validation.py +278 -0
- package/src/intent.py +323 -0
- package/src/verification/__init__.py +63 -0
- package/src/verification/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/verification/__pycache__/checker.cpython-313.pyc +0 -0
- package/src/verification/__pycache__/comparison.cpython-313.pyc +0 -0
- package/src/verification/__pycache__/explainer.cpython-313.pyc +0 -0
- package/src/verification/__pycache__/static_analysis.cpython-313.pyc +0 -0
- package/src/verification/checker.py +220 -0
- package/src/verification/comparison.py +492 -0
- package/src/verification/explainer.py +414 -0
- package/src/verification/static_analysis.py +540 -0
- package/src/workflows/__init__.py +21 -0
- package/src/workflows/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/workflows/__pycache__/extract.cpython-313.pyc +0 -0
- package/src/workflows/__pycache__/generate.cpython-313.pyc +0 -0
- package/src/workflows/__pycache__/run.cpython-313.pyc +0 -0
- package/src/workflows/__pycache__/verify.cpython-313.pyc +0 -0
- package/src/workflows/extract.py +181 -0
- package/src/workflows/generate.py +155 -0
- package/src/workflows/run.py +187 -0
- package/src/workflows/verify.py +334 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
"""Algorithm explainer module for AlgoMath.
|
|
2
|
+
|
|
3
|
+
Provides natural language explanation of algorithm behavior based on
|
|
4
|
+
extracted algorithm structures and execution traces.
|
|
5
|
+
|
|
6
|
+
Per D-05 through D-08: Brief summaries, detailed walkthroughs, execution values,
|
|
7
|
+
and complexity-based explanation styles.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Any, Dict, List, Optional
|
|
13
|
+
import json
|
|
14
|
+
|
|
15
|
+
from src.extraction.schema import Algorithm, Step, StepType
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ExplanationLevel(Enum):
|
|
19
|
+
"""Level of detail for algorithm explanations."""
|
|
20
|
+
BRIEF = "brief"
|
|
21
|
+
DETAILED = "detailed"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class StepExplanation:
|
|
26
|
+
"""Explanation of a single step with execution context.
|
|
27
|
+
|
|
28
|
+
Per D-07: Shows actual values from execution trace.
|
|
29
|
+
Per D-142: Preserves mathematical notation.
|
|
30
|
+
"""
|
|
31
|
+
step_id: int
|
|
32
|
+
description: str
|
|
33
|
+
inputs: Dict[str, Any] = field(default_factory=dict)
|
|
34
|
+
outputs: Dict[str, Any] = field(default_factory=dict)
|
|
35
|
+
mathematical_notation: Optional[str] = None
|
|
36
|
+
|
|
37
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
38
|
+
"""Convert to dictionary representation."""
|
|
39
|
+
return {
|
|
40
|
+
"step_id": self.step_id,
|
|
41
|
+
"description": self.description,
|
|
42
|
+
"inputs": self.inputs,
|
|
43
|
+
"outputs": self.outputs,
|
|
44
|
+
"mathematical_notation": self.mathematical_notation,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class ExplanationResult:
|
|
50
|
+
"""Result of algorithm explanation.
|
|
51
|
+
|
|
52
|
+
Per D-05: Brief summary (1-2 sentences).
|
|
53
|
+
Per D-06: Detailed step-by-step walkthrough.
|
|
54
|
+
Per D-07: Execution trace integration.
|
|
55
|
+
Per D-08: Complexity score for recommendation.
|
|
56
|
+
"""
|
|
57
|
+
summary: str
|
|
58
|
+
detailed_explanation: Optional[str] = None
|
|
59
|
+
step_explanations: List[StepExplanation] = field(default_factory=list)
|
|
60
|
+
complexity_score: float = 0.0
|
|
61
|
+
recommended_level: ExplanationLevel = ExplanationLevel.BRIEF
|
|
62
|
+
execution_trace: Optional[Dict[int, Any]] = None
|
|
63
|
+
|
|
64
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
65
|
+
"""Convert to dictionary representation."""
|
|
66
|
+
return {
|
|
67
|
+
"summary": self.summary,
|
|
68
|
+
"detailed_explanation": self.detailed_explanation,
|
|
69
|
+
"step_explanations": [se.to_dict() for se in self.step_explanations],
|
|
70
|
+
"complexity_score": self.complexity_score,
|
|
71
|
+
"recommended_level": self.recommended_level.value,
|
|
72
|
+
"execution_trace": self.execution_trace,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
def to_json(self) -> str:
|
|
76
|
+
"""Convert to JSON string."""
|
|
77
|
+
return json.dumps(self.to_dict(), indent=2)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class AlgorithmExplainer:
|
|
81
|
+
"""Explains algorithm behavior in natural language.
|
|
82
|
+
|
|
83
|
+
Per D-05: Provides brief summaries.
|
|
84
|
+
Per D-06: Provides detailed step-by-step walkthroughs.
|
|
85
|
+
Per D-07: Shows actual execution values.
|
|
86
|
+
Per D-08: Adapts explanation style to complexity.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
def __init__(self, algorithm: Algorithm):
|
|
90
|
+
"""Initialize explainer with algorithm.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
algorithm: Algorithm to explain
|
|
94
|
+
"""
|
|
95
|
+
self.algorithm = algorithm
|
|
96
|
+
|
|
97
|
+
def explain(
|
|
98
|
+
self,
|
|
99
|
+
level: ExplanationLevel = ExplanationLevel.BRIEF,
|
|
100
|
+
execution_trace: Optional[Dict[int, Any]] = None
|
|
101
|
+
) -> ExplanationResult:
|
|
102
|
+
"""Generate explanation of the algorithm.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
level: Level of detail (brief or detailed)
|
|
106
|
+
execution_trace: Optional execution values per step
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
ExplanationResult with summary and details
|
|
110
|
+
"""
|
|
111
|
+
complexity = self._calculate_complexity()
|
|
112
|
+
recommended = self._recommend_level(complexity)
|
|
113
|
+
|
|
114
|
+
# Generate summary (always included)
|
|
115
|
+
summary = self._generate_summary()
|
|
116
|
+
|
|
117
|
+
# Generate detailed explanation if requested or recommended
|
|
118
|
+
detailed = None
|
|
119
|
+
step_explanations = []
|
|
120
|
+
if level == ExplanationLevel.DETAILED or complexity > 0.5:
|
|
121
|
+
detailed = self._generate_detailed(execution_trace)
|
|
122
|
+
step_explanations = self._generate_step_explanations(execution_trace)
|
|
123
|
+
|
|
124
|
+
return ExplanationResult(
|
|
125
|
+
summary=summary,
|
|
126
|
+
detailed_explanation=detailed,
|
|
127
|
+
step_explanations=step_explanations,
|
|
128
|
+
complexity_score=complexity,
|
|
129
|
+
recommended_level=recommended,
|
|
130
|
+
execution_trace=execution_trace
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def explain_step(self, step_id: int) -> StepExplanation:
|
|
134
|
+
"""Explain a specific step.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
step_id: ID of the step to explain
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
StepExplanation with description and details
|
|
141
|
+
"""
|
|
142
|
+
step = self._get_step_by_id(step_id)
|
|
143
|
+
if not step:
|
|
144
|
+
return StepExplanation(
|
|
145
|
+
step_id=step_id,
|
|
146
|
+
description=f"Step {step_id} not found"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
description = self._explain_step_by_type(step)
|
|
150
|
+
notation = self._extract_mathematical_notation(step)
|
|
151
|
+
|
|
152
|
+
return StepExplanation(
|
|
153
|
+
step_id=step_id,
|
|
154
|
+
description=description,
|
|
155
|
+
inputs={name: None for name in step.inputs},
|
|
156
|
+
outputs={name: None for name in step.outputs},
|
|
157
|
+
mathematical_notation=notation
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
def _calculate_complexity(self) -> float:
|
|
161
|
+
"""Calculate algorithm complexity score (0.0 to 1.0).
|
|
162
|
+
|
|
163
|
+
Per D-08: Simple (<5 steps, no loops) → low score
|
|
164
|
+
Moderate (5-15 steps, simple loops) → medium
|
|
165
|
+
Complex (>15 steps, nested loops) → high
|
|
166
|
+
"""
|
|
167
|
+
score = 0.0
|
|
168
|
+
num_steps = len(self.algorithm.steps)
|
|
169
|
+
|
|
170
|
+
# Step count contribution
|
|
171
|
+
if num_steps < 5:
|
|
172
|
+
score += 0.1
|
|
173
|
+
elif num_steps < 15:
|
|
174
|
+
score += 0.3
|
|
175
|
+
else:
|
|
176
|
+
score += 0.5
|
|
177
|
+
|
|
178
|
+
# Check for loops and conditionals
|
|
179
|
+
has_loops = False
|
|
180
|
+
has_conditionals = False
|
|
181
|
+
has_nested = False
|
|
182
|
+
loop_depths = {}
|
|
183
|
+
|
|
184
|
+
for step in self.algorithm.steps:
|
|
185
|
+
if step.type in (StepType.LOOP_FOR, StepType.LOOP_WHILE):
|
|
186
|
+
has_loops = True
|
|
187
|
+
loop_depths[step.id] = self._get_loop_depth(step)
|
|
188
|
+
if loop_depths[step.id] > 1:
|
|
189
|
+
has_nested = True
|
|
190
|
+
elif step.type == StepType.CONDITIONAL:
|
|
191
|
+
has_conditionals = True
|
|
192
|
+
|
|
193
|
+
if has_loops:
|
|
194
|
+
score += 0.2
|
|
195
|
+
if has_conditionals:
|
|
196
|
+
score += 0.1
|
|
197
|
+
if has_nested:
|
|
198
|
+
score += 0.2
|
|
199
|
+
|
|
200
|
+
return min(score, 1.0)
|
|
201
|
+
|
|
202
|
+
def _get_loop_depth(self, step: Step, depth: int = 1) -> int:
|
|
203
|
+
"""Calculate nesting depth of a loop."""
|
|
204
|
+
max_depth = depth
|
|
205
|
+
for body_step_id in step.body:
|
|
206
|
+
body_step = self._get_step_by_id(body_step_id)
|
|
207
|
+
if body_step and body_step.type in (StepType.LOOP_FOR, StepType.LOOP_WHILE):
|
|
208
|
+
nested_depth = self._get_loop_depth(body_step, depth + 1)
|
|
209
|
+
max_depth = max(max_depth, nested_depth)
|
|
210
|
+
return max_depth
|
|
211
|
+
|
|
212
|
+
def _recommend_level(self, complexity: float) -> ExplanationLevel:
|
|
213
|
+
"""Recommend explanation level based on complexity."""
|
|
214
|
+
if complexity < 0.3:
|
|
215
|
+
return ExplanationLevel.BRIEF
|
|
216
|
+
return ExplanationLevel.DETAILED
|
|
217
|
+
|
|
218
|
+
def _generate_summary(self) -> str:
|
|
219
|
+
"""Generate brief summary (1-2 sentences) per D-05."""
|
|
220
|
+
name = self.algorithm.name
|
|
221
|
+
description = self.algorithm.description
|
|
222
|
+
num_steps = len(self.algorithm.steps)
|
|
223
|
+
num_inputs = len(self.algorithm.inputs)
|
|
224
|
+
num_outputs = len(self.algorithm.outputs)
|
|
225
|
+
|
|
226
|
+
# Build input description
|
|
227
|
+
input_desc = ""
|
|
228
|
+
if num_inputs == 1:
|
|
229
|
+
input_name = self.algorithm.inputs[0].get("name", "input")
|
|
230
|
+
input_desc = f"a single {self.algorithm.inputs[0].get('type', 'value')} input {input_name}"
|
|
231
|
+
elif num_inputs > 1:
|
|
232
|
+
input_desc = f"{num_inputs} inputs"
|
|
233
|
+
else:
|
|
234
|
+
input_desc = "no inputs"
|
|
235
|
+
|
|
236
|
+
# Build output description
|
|
237
|
+
output_desc = ""
|
|
238
|
+
if num_outputs == 1:
|
|
239
|
+
output_name = self.algorithm.outputs[0].get("name", "result")
|
|
240
|
+
output_desc = f"the {output_name}"
|
|
241
|
+
elif num_outputs > 1:
|
|
242
|
+
output_desc = f"{num_outputs} values"
|
|
243
|
+
else:
|
|
244
|
+
output_desc = "a result"
|
|
245
|
+
|
|
246
|
+
# Generate summary sentence
|
|
247
|
+
parts = []
|
|
248
|
+
if description:
|
|
249
|
+
parts.append(f"This {name} algorithm {description.lower()}.")
|
|
250
|
+
else:
|
|
251
|
+
parts.append(f"This is the {name} algorithm.")
|
|
252
|
+
|
|
253
|
+
# Add complexity/structure info
|
|
254
|
+
if num_steps < 5:
|
|
255
|
+
parts.append(f"It takes {input_desc} and returns {output_desc} in a straightforward {num_steps}-step process.")
|
|
256
|
+
elif num_steps < 15:
|
|
257
|
+
parts.append(f"It processes {input_desc} through {num_steps} steps to produce {output_desc}.")
|
|
258
|
+
else:
|
|
259
|
+
parts.append(f"It implements a complex process with {num_steps} steps, taking {input_desc} and returning {output_desc}.")
|
|
260
|
+
|
|
261
|
+
return " ".join(parts)
|
|
262
|
+
|
|
263
|
+
def _generate_detailed(self, execution_trace: Optional[Dict[int, Any]] = None) -> str:
|
|
264
|
+
"""Generate detailed step-by-step walkthrough per D-06, D-07."""
|
|
265
|
+
lines = []
|
|
266
|
+
lines.append(f"Detailed walkthrough of {self.algorithm.name}:")
|
|
267
|
+
lines.append("")
|
|
268
|
+
|
|
269
|
+
for step in self.algorithm.steps:
|
|
270
|
+
step_desc = self._explain_step_by_type(step)
|
|
271
|
+
lines.append(f"Step {step.id}: {step_desc}")
|
|
272
|
+
|
|
273
|
+
# Add execution values if available
|
|
274
|
+
if execution_trace and step.id in execution_trace:
|
|
275
|
+
values = execution_trace[step.id]
|
|
276
|
+
if values:
|
|
277
|
+
lines.append(f" At this point: {self._format_values(values)}")
|
|
278
|
+
|
|
279
|
+
lines.append("")
|
|
280
|
+
|
|
281
|
+
return "\n".join(lines)
|
|
282
|
+
|
|
283
|
+
def _generate_step_explanations(
|
|
284
|
+
self,
|
|
285
|
+
execution_trace: Optional[Dict[int, Any]] = None
|
|
286
|
+
) -> List[StepExplanation]:
|
|
287
|
+
"""Generate explanations for all steps."""
|
|
288
|
+
explanations = []
|
|
289
|
+
for step in self.algorithm.steps:
|
|
290
|
+
desc = self._explain_step_by_type(step)
|
|
291
|
+
notation = self._extract_mathematical_notation(step)
|
|
292
|
+
|
|
293
|
+
# Get execution values if available
|
|
294
|
+
inputs = {}
|
|
295
|
+
outputs = {}
|
|
296
|
+
if execution_trace and step.id in execution_trace:
|
|
297
|
+
trace_data = execution_trace[step.id]
|
|
298
|
+
if isinstance(trace_data, dict):
|
|
299
|
+
for var in step.inputs:
|
|
300
|
+
if var in trace_data:
|
|
301
|
+
inputs[var] = trace_data[var]
|
|
302
|
+
for var in step.outputs:
|
|
303
|
+
if var in trace_data:
|
|
304
|
+
outputs[var] = trace_data[var]
|
|
305
|
+
|
|
306
|
+
explanations.append(StepExplanation(
|
|
307
|
+
step_id=step.id,
|
|
308
|
+
description=desc,
|
|
309
|
+
inputs=inputs,
|
|
310
|
+
outputs=outputs,
|
|
311
|
+
mathematical_notation=notation
|
|
312
|
+
))
|
|
313
|
+
|
|
314
|
+
return explanations
|
|
315
|
+
|
|
316
|
+
def _explain_step_by_type(self, step: Step) -> str:
|
|
317
|
+
"""Generate explanation based on step type."""
|
|
318
|
+
if step.type == StepType.ASSIGNMENT:
|
|
319
|
+
return self._explain_assignment(step)
|
|
320
|
+
elif step.type in (StepType.LOOP_FOR, StepType.LOOP_WHILE):
|
|
321
|
+
return self._explain_loop(step)
|
|
322
|
+
elif step.type == StepType.CONDITIONAL:
|
|
323
|
+
return self._explain_conditional(step)
|
|
324
|
+
elif step.type == StepType.RETURN:
|
|
325
|
+
return self._explain_return(step)
|
|
326
|
+
elif step.type == StepType.CALL:
|
|
327
|
+
return self._explain_call(step)
|
|
328
|
+
else:
|
|
329
|
+
return step.description
|
|
330
|
+
|
|
331
|
+
def _explain_assignment(self, step: Step) -> str:
|
|
332
|
+
"""Explain an assignment step."""
|
|
333
|
+
if step.expression:
|
|
334
|
+
return f"Assign {step.expression}"
|
|
335
|
+
elif step.outputs:
|
|
336
|
+
return f"Set {', '.join(step.outputs)} to new values"
|
|
337
|
+
return step.description
|
|
338
|
+
|
|
339
|
+
def _explain_loop(self, step: Step) -> str:
|
|
340
|
+
"""Explain a loop step."""
|
|
341
|
+
if step.iter_var and step.iter_range:
|
|
342
|
+
return f"Loop with {step.iter_var} in {step.iter_range}, processing {len(step.body)} step(s)"
|
|
343
|
+
elif step.condition:
|
|
344
|
+
return f"While {step.condition}, repeat {len(step.body)} step(s)"
|
|
345
|
+
return f"Iterate through {len(step.body)} step(s)"
|
|
346
|
+
|
|
347
|
+
def _explain_conditional(self, step: Step) -> str:
|
|
348
|
+
"""Explain a conditional step."""
|
|
349
|
+
if step.condition:
|
|
350
|
+
has_else = len(step.else_body) > 0
|
|
351
|
+
else_part = " with alternative steps" if has_else else ""
|
|
352
|
+
return f"If {step.condition}, execute {len(step.body)} step(s){else_part}"
|
|
353
|
+
return f"Branch based on condition, executing {len(step.body)} step(s)"
|
|
354
|
+
|
|
355
|
+
def _explain_return(self, step: Step) -> str:
|
|
356
|
+
"""Explain a return step."""
|
|
357
|
+
if step.expression:
|
|
358
|
+
return f"Return {step.expression}"
|
|
359
|
+
elif step.inputs:
|
|
360
|
+
return f"Return {', '.join(step.inputs)}"
|
|
361
|
+
return "Return result"
|
|
362
|
+
|
|
363
|
+
def _explain_call(self, step: Step) -> str:
|
|
364
|
+
"""Explain a function call step."""
|
|
365
|
+
if step.call_target:
|
|
366
|
+
args = f" with arguments {step.arguments}" if step.arguments else ""
|
|
367
|
+
return f"Call {step.call_target}{args}"
|
|
368
|
+
return step.description
|
|
369
|
+
|
|
370
|
+
def _extract_mathematical_notation(self, step: Step) -> Optional[str]:
|
|
371
|
+
"""Extract mathematical notation from step per D-142."""
|
|
372
|
+
if step.expression:
|
|
373
|
+
# Check for subscript notation (x_i, etc.)
|
|
374
|
+
if "_" in step.expression or "^" in step.expression:
|
|
375
|
+
return step.expression
|
|
376
|
+
if step.description and ("_" in step.description or "^" in step.description):
|
|
377
|
+
return step.description
|
|
378
|
+
return None
|
|
379
|
+
|
|
380
|
+
def _format_values(self, values: Dict[str, Any]) -> str:
|
|
381
|
+
"""Format execution values for display."""
|
|
382
|
+
parts = []
|
|
383
|
+
for name, value in values.items():
|
|
384
|
+
if isinstance(value, (list, tuple)) and len(value) > 3:
|
|
385
|
+
parts.append(f"{name} = [{value[0]}, ..., {value[-1]}] (len={len(value)})")
|
|
386
|
+
else:
|
|
387
|
+
parts.append(f"{name} = {value}")
|
|
388
|
+
return ", ".join(parts)
|
|
389
|
+
|
|
390
|
+
def _get_step_by_id(self, step_id: int) -> Optional[Step]:
|
|
391
|
+
"""Get step by ID."""
|
|
392
|
+
for step in self.algorithm.steps:
|
|
393
|
+
if step.id == step_id:
|
|
394
|
+
return step
|
|
395
|
+
return None
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def explain_algorithm(
|
|
399
|
+
algorithm: Algorithm,
|
|
400
|
+
level: ExplanationLevel = ExplanationLevel.BRIEF,
|
|
401
|
+
execution_trace: Optional[Dict[int, Any]] = None
|
|
402
|
+
) -> ExplanationResult:
|
|
403
|
+
"""Convenience function to explain an algorithm.
|
|
404
|
+
|
|
405
|
+
Args:
|
|
406
|
+
algorithm: Algorithm to explain
|
|
407
|
+
level: Level of detail for explanation
|
|
408
|
+
execution_trace: Optional execution values per step
|
|
409
|
+
|
|
410
|
+
Returns:
|
|
411
|
+
ExplanationResult with natural language explanation
|
|
412
|
+
"""
|
|
413
|
+
explainer = AlgorithmExplainer(algorithm)
|
|
414
|
+
return explainer.explain(level=level, execution_trace=execution_trace)
|