verifiable-thinking-mcp 0.4.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/LICENSE +21 -0
- package/README.md +339 -0
- package/package.json +75 -0
- package/src/index.ts +38 -0
- package/src/lib/cache.ts +246 -0
- package/src/lib/compression.ts +804 -0
- package/src/lib/compute/cache.ts +86 -0
- package/src/lib/compute/classifier.ts +555 -0
- package/src/lib/compute/confidence.ts +79 -0
- package/src/lib/compute/context.ts +154 -0
- package/src/lib/compute/extract.ts +200 -0
- package/src/lib/compute/filter.ts +224 -0
- package/src/lib/compute/index.ts +171 -0
- package/src/lib/compute/math.ts +247 -0
- package/src/lib/compute/patterns.ts +564 -0
- package/src/lib/compute/registry.ts +145 -0
- package/src/lib/compute/solvers/arithmetic.ts +65 -0
- package/src/lib/compute/solvers/calculus.ts +249 -0
- package/src/lib/compute/solvers/derivation-core.ts +371 -0
- package/src/lib/compute/solvers/derivation-latex.ts +160 -0
- package/src/lib/compute/solvers/derivation-mistakes.ts +1046 -0
- package/src/lib/compute/solvers/derivation-simplify.ts +451 -0
- package/src/lib/compute/solvers/derivation-transform.ts +620 -0
- package/src/lib/compute/solvers/derivation.ts +67 -0
- package/src/lib/compute/solvers/facts.ts +120 -0
- package/src/lib/compute/solvers/formula.ts +728 -0
- package/src/lib/compute/solvers/index.ts +36 -0
- package/src/lib/compute/solvers/logic.ts +422 -0
- package/src/lib/compute/solvers/probability.ts +307 -0
- package/src/lib/compute/solvers/statistics.ts +262 -0
- package/src/lib/compute/solvers/word-problems.ts +408 -0
- package/src/lib/compute/types.ts +107 -0
- package/src/lib/concepts.ts +111 -0
- package/src/lib/domain.ts +731 -0
- package/src/lib/extraction.ts +912 -0
- package/src/lib/index.ts +122 -0
- package/src/lib/judge.ts +260 -0
- package/src/lib/math/ast.ts +842 -0
- package/src/lib/math/index.ts +8 -0
- package/src/lib/math/operators.ts +171 -0
- package/src/lib/math/tokenizer.ts +477 -0
- package/src/lib/patterns.ts +200 -0
- package/src/lib/session.ts +825 -0
- package/src/lib/think/challenge.ts +323 -0
- package/src/lib/think/complexity.ts +504 -0
- package/src/lib/think/confidence-drift.ts +507 -0
- package/src/lib/think/consistency.ts +347 -0
- package/src/lib/think/guidance.ts +188 -0
- package/src/lib/think/helpers.ts +568 -0
- package/src/lib/think/hypothesis.ts +216 -0
- package/src/lib/think/index.ts +127 -0
- package/src/lib/think/prompts.ts +262 -0
- package/src/lib/think/route.ts +358 -0
- package/src/lib/think/schema.ts +98 -0
- package/src/lib/think/scratchpad-schema.ts +662 -0
- package/src/lib/think/spot-check.ts +961 -0
- package/src/lib/think/types.ts +93 -0
- package/src/lib/think/verification.ts +260 -0
- package/src/lib/tokens.ts +177 -0
- package/src/lib/verification.ts +620 -0
- package/src/prompts/index.ts +10 -0
- package/src/prompts/templates.ts +336 -0
- package/src/resources/index.ts +8 -0
- package/src/resources/sessions.ts +196 -0
- package/src/tools/compress.ts +138 -0
- package/src/tools/index.ts +5 -0
- package/src/tools/scratchpad.ts +2659 -0
- package/src/tools/sessions.ts +144 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derivation Core - verifies multi-step algebraic derivations
|
|
3
|
+
*
|
|
4
|
+
* Uses compareExpressions to check that each step in a derivation is
|
|
5
|
+
* algebraically equivalent to the previous step. Catches "magic" steps
|
|
6
|
+
* in proofs where the transformation isn't valid.
|
|
7
|
+
*
|
|
8
|
+
* @module derivation-core
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { compareExpressions } from "../../verification.ts";
|
|
12
|
+
import type { ComputeResult } from "../types.ts";
|
|
13
|
+
|
|
14
|
+
/** Result of verifying a single derivation step */
|
|
15
|
+
export interface StepVerification {
|
|
16
|
+
step: number;
|
|
17
|
+
lhs: string;
|
|
18
|
+
rhs: string;
|
|
19
|
+
valid: boolean;
|
|
20
|
+
error?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Result of verifying a complete derivation */
|
|
24
|
+
export interface DerivationResult {
|
|
25
|
+
valid: boolean;
|
|
26
|
+
steps: StepVerification[];
|
|
27
|
+
invalidStep?: number;
|
|
28
|
+
error?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Extract derivation steps from text
|
|
33
|
+
* Looks for patterns like:
|
|
34
|
+
* expr1 = expr2
|
|
35
|
+
* expr3 = expr4
|
|
36
|
+
* ...
|
|
37
|
+
*
|
|
38
|
+
* Or chained: expr1 = expr2 = expr3 = ...
|
|
39
|
+
*
|
|
40
|
+
* Or sentence-separated: expr1 = expr2, then expr3 = expr4
|
|
41
|
+
*/
|
|
42
|
+
export function extractDerivationSteps(text: string): Array<{ lhs: string; rhs: string }> {
|
|
43
|
+
const steps: Array<{ lhs: string; rhs: string }> = [];
|
|
44
|
+
|
|
45
|
+
// Pre-process: split on sentence boundaries (comma, semicolon, "then", "so", etc.)
|
|
46
|
+
// to handle multi-step derivations in a single line
|
|
47
|
+
const segments = text
|
|
48
|
+
.split(/[,;]|\bthen\b|\bso\b|\btherefore\b|\bhence\b/i)
|
|
49
|
+
.map((s) => s.trim())
|
|
50
|
+
.filter(Boolean);
|
|
51
|
+
|
|
52
|
+
// Process each segment
|
|
53
|
+
for (const segment of segments) {
|
|
54
|
+
// Pattern 1: Chained equalities (a = b = c = d)
|
|
55
|
+
// Split on "=" and pair consecutive terms
|
|
56
|
+
const chainedMatch = segment.match(
|
|
57
|
+
/([^=\n]+(?:=[^=\n]+){2,})/g, // 3+ terms connected by =
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if (chainedMatch) {
|
|
61
|
+
for (const chain of chainedMatch) {
|
|
62
|
+
const parts = chain
|
|
63
|
+
.split("=")
|
|
64
|
+
.map((p) => cleanExpressionPart(p))
|
|
65
|
+
.filter(Boolean);
|
|
66
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
67
|
+
const lhs = parts[i];
|
|
68
|
+
const rhs = parts[i + 1];
|
|
69
|
+
if (lhs && rhs) {
|
|
70
|
+
steps.push({ lhs, rhs });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
continue; // Move to next segment
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Pattern 2: Simple equality (expr = expr)
|
|
78
|
+
const simpleMatch = segment.match(/^\s*([^=\n]+?)\s*=\s*([^=\n]+?)\s*$/);
|
|
79
|
+
if (simpleMatch) {
|
|
80
|
+
const lhs = cleanExpressionPart(simpleMatch[1] ?? "");
|
|
81
|
+
const rhs = cleanExpressionPart(simpleMatch[2] ?? "");
|
|
82
|
+
if (lhs && rhs) {
|
|
83
|
+
steps.push({ lhs, rhs });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// If no steps found via segment processing, try original line-by-line approach
|
|
89
|
+
if (steps.length === 0) {
|
|
90
|
+
// Pattern 3: Line-by-line equalities
|
|
91
|
+
const linePattern = /^\s*([^=\n]+?)\s*=\s*([^=\n]+?)\s*$/gm;
|
|
92
|
+
let match: RegExpExecArray | null;
|
|
93
|
+
while ((match = linePattern.exec(text)) !== null) {
|
|
94
|
+
const lhs = cleanExpressionPart(match[1] ?? "");
|
|
95
|
+
const rhs = cleanExpressionPart(match[2] ?? "");
|
|
96
|
+
if (lhs && rhs) {
|
|
97
|
+
steps.push({ lhs, rhs });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return steps;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Clean an expression part by removing non-math prefixes
|
|
107
|
+
* E.g., "prove: x + x" → "x + x"
|
|
108
|
+
*/
|
|
109
|
+
export function cleanExpressionPart(part: string): string {
|
|
110
|
+
let cleaned = part.trim();
|
|
111
|
+
|
|
112
|
+
// Remove common prefixes like "prove:", "show that", "verify:", etc.
|
|
113
|
+
cleaned = cleaned.replace(
|
|
114
|
+
/^(?:prove|show(?:\s+that)?|verify|therefore|thus|hence|so|then)[:.]?\s*/i,
|
|
115
|
+
"",
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// Remove leading non-math characters (but keep negative signs)
|
|
119
|
+
// Match from start: letters/punctuation that aren't part of a variable
|
|
120
|
+
cleaned = cleaned.replace(/^[^a-zA-Z0-9\-.(]+/, "");
|
|
121
|
+
|
|
122
|
+
return cleaned.trim();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Verify that each step in a derivation is algebraically valid
|
|
127
|
+
*
|
|
128
|
+
* For each step "A = B", we verify:
|
|
129
|
+
* 1. A and B are equivalent (within the same step)
|
|
130
|
+
*
|
|
131
|
+
* For consecutive steps, we verify:
|
|
132
|
+
* 2. The RHS of step N equals the LHS of step N+1 (continuity)
|
|
133
|
+
*
|
|
134
|
+
* @param steps Array of {lhs, rhs} pairs representing the derivation
|
|
135
|
+
* @returns DerivationResult with validity and error details
|
|
136
|
+
*/
|
|
137
|
+
export function verifyDerivationSteps(
|
|
138
|
+
steps: Array<{ lhs: string; rhs: string }>,
|
|
139
|
+
): DerivationResult {
|
|
140
|
+
if (steps.length === 0) {
|
|
141
|
+
return { valid: false, steps: [], error: "No derivation steps found" };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const verifiedSteps: StepVerification[] = [];
|
|
145
|
+
|
|
146
|
+
for (let i = 0; i < steps.length; i++) {
|
|
147
|
+
const step = steps[i];
|
|
148
|
+
if (!step) continue;
|
|
149
|
+
|
|
150
|
+
const { lhs, rhs } = step;
|
|
151
|
+
const stepNum = i + 1;
|
|
152
|
+
|
|
153
|
+
// Check 1: LHS and RHS of this step are equivalent
|
|
154
|
+
const equivalent = compareExpressions(lhs, rhs);
|
|
155
|
+
|
|
156
|
+
if (!equivalent) {
|
|
157
|
+
verifiedSteps.push({
|
|
158
|
+
step: stepNum,
|
|
159
|
+
lhs,
|
|
160
|
+
rhs,
|
|
161
|
+
valid: false,
|
|
162
|
+
error: `Step ${stepNum}: "${lhs}" is not equivalent to "${rhs}"`,
|
|
163
|
+
});
|
|
164
|
+
return {
|
|
165
|
+
valid: false,
|
|
166
|
+
steps: verifiedSteps,
|
|
167
|
+
invalidStep: stepNum,
|
|
168
|
+
error: `Invalid step ${stepNum}: ${lhs} ≠ ${rhs}`,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Check 2: Continuity with previous step
|
|
173
|
+
// The LHS of this step should equal the RHS of the previous step
|
|
174
|
+
if (i > 0) {
|
|
175
|
+
const prevStep = steps[i - 1];
|
|
176
|
+
if (prevStep) {
|
|
177
|
+
const continuous = compareExpressions(prevStep.rhs, lhs);
|
|
178
|
+
if (!continuous) {
|
|
179
|
+
verifiedSteps.push({
|
|
180
|
+
step: stepNum,
|
|
181
|
+
lhs,
|
|
182
|
+
rhs,
|
|
183
|
+
valid: false,
|
|
184
|
+
error: `Step ${stepNum} doesn't follow from step ${stepNum - 1}: "${prevStep.rhs}" → "${lhs}"`,
|
|
185
|
+
});
|
|
186
|
+
return {
|
|
187
|
+
valid: false,
|
|
188
|
+
steps: verifiedSteps,
|
|
189
|
+
invalidStep: stepNum,
|
|
190
|
+
error: `Discontinuity at step ${stepNum}: previous RHS "${prevStep.rhs}" ≠ current LHS "${lhs}"`,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
verifiedSteps.push({
|
|
197
|
+
step: stepNum,
|
|
198
|
+
lhs,
|
|
199
|
+
rhs,
|
|
200
|
+
valid: true,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
valid: true,
|
|
206
|
+
steps: verifiedSteps,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Try to verify a derivation in text
|
|
212
|
+
*
|
|
213
|
+
* Extracts derivation steps and verifies each one is algebraically valid.
|
|
214
|
+
* Useful for checking mathematical proofs and simplification chains.
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* // Valid derivation
|
|
218
|
+
* tryDerivation("x + x = 2x = 2*x")
|
|
219
|
+
* // { solved: true, result: "Valid derivation (2 steps)", ... }
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* // Invalid derivation
|
|
223
|
+
* tryDerivation("x + x = 2x = 3x")
|
|
224
|
+
* // { solved: true, result: "Invalid step 2: 2x ≠ 3x", ... }
|
|
225
|
+
*/
|
|
226
|
+
export function tryDerivation(text: string): ComputeResult {
|
|
227
|
+
const start = performance.now();
|
|
228
|
+
const lower = text.toLowerCase();
|
|
229
|
+
|
|
230
|
+
// Guard: Only try derivation if text looks like a proof/derivation
|
|
231
|
+
const hasDerivationKeywords =
|
|
232
|
+
lower.includes("show") ||
|
|
233
|
+
lower.includes("prove") ||
|
|
234
|
+
lower.includes("verify") ||
|
|
235
|
+
lower.includes("derivation") ||
|
|
236
|
+
lower.includes("simplif") ||
|
|
237
|
+
lower.includes("therefore") ||
|
|
238
|
+
lower.includes("thus") ||
|
|
239
|
+
lower.includes("hence") ||
|
|
240
|
+
text.includes("⟹") ||
|
|
241
|
+
text.includes("→") ||
|
|
242
|
+
text.includes("=>") ||
|
|
243
|
+
// Multiple equals signs suggest a derivation chain
|
|
244
|
+
(text.match(/=/g)?.length ?? 0) >= 2;
|
|
245
|
+
|
|
246
|
+
if (!hasDerivationKeywords) {
|
|
247
|
+
return { solved: false, confidence: 0 };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const steps = extractDerivationSteps(text);
|
|
251
|
+
|
|
252
|
+
if (steps.length < 1) {
|
|
253
|
+
return { solved: false, confidence: 0 };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const result = verifyDerivationSteps(steps);
|
|
257
|
+
const time_ms = performance.now() - start;
|
|
258
|
+
|
|
259
|
+
if (result.valid) {
|
|
260
|
+
return {
|
|
261
|
+
solved: true,
|
|
262
|
+
result: `Valid derivation (${result.steps.length} steps verified)`,
|
|
263
|
+
method: "derivation_verification",
|
|
264
|
+
confidence: 1.0,
|
|
265
|
+
time_ms,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
solved: true,
|
|
271
|
+
result: result.error ?? "Invalid derivation",
|
|
272
|
+
method: "derivation_verification",
|
|
273
|
+
confidence: 0.9, // Slightly lower confidence for invalid results
|
|
274
|
+
time_ms,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** Human-readable explanation of a derivation error */
|
|
279
|
+
export interface DerivationErrorExplanation {
|
|
280
|
+
/** Short summary of the error */
|
|
281
|
+
summary: string;
|
|
282
|
+
/** Detailed explanation suitable for display */
|
|
283
|
+
explanation: string;
|
|
284
|
+
/** The problematic step (1-indexed) */
|
|
285
|
+
stepNumber: number;
|
|
286
|
+
/** What was expected */
|
|
287
|
+
expected?: string;
|
|
288
|
+
/** What was found */
|
|
289
|
+
found?: string;
|
|
290
|
+
/** Specific suggestions to fix the error */
|
|
291
|
+
fixSuggestions: string[];
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Generate a human-readable explanation for a derivation verification error
|
|
296
|
+
*
|
|
297
|
+
* Analyzes the DerivationResult and produces clear, actionable feedback
|
|
298
|
+
* about what went wrong and how to fix it.
|
|
299
|
+
*
|
|
300
|
+
* @param result The DerivationResult from verifyDerivationSteps
|
|
301
|
+
* @returns DerivationErrorExplanation with detailed feedback, or null if valid
|
|
302
|
+
*/
|
|
303
|
+
export function explainDerivationError(
|
|
304
|
+
result: DerivationResult,
|
|
305
|
+
): DerivationErrorExplanation | null {
|
|
306
|
+
// No error to explain if derivation is valid
|
|
307
|
+
if (result.valid) {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const invalidStep = result.invalidStep ?? 1;
|
|
312
|
+
const errorStep = result.steps.find((s) => !s.valid);
|
|
313
|
+
|
|
314
|
+
if (!errorStep) {
|
|
315
|
+
// Generic error (no steps found, etc.)
|
|
316
|
+
return {
|
|
317
|
+
summary: result.error ?? "Derivation verification failed",
|
|
318
|
+
explanation:
|
|
319
|
+
result.error ??
|
|
320
|
+
"The derivation could not be verified. Ensure each step follows logically from the previous.",
|
|
321
|
+
stepNumber: 0,
|
|
322
|
+
fixSuggestions: [
|
|
323
|
+
"Check that the derivation contains valid mathematical expressions",
|
|
324
|
+
"Ensure each line follows the format 'expression = expression'",
|
|
325
|
+
"Verify that equals signs (=) are used correctly",
|
|
326
|
+
],
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const { lhs, rhs, error } = errorStep;
|
|
331
|
+
|
|
332
|
+
// Determine error type and generate appropriate explanation
|
|
333
|
+
const isDiscontinuity = error?.includes("doesn't follow") || error?.includes("Discontinuity");
|
|
334
|
+
|
|
335
|
+
if (isDiscontinuity) {
|
|
336
|
+
// Continuity error: previous RHS doesn't match current LHS
|
|
337
|
+
return {
|
|
338
|
+
summary: `Derivation breaks at step ${invalidStep}`,
|
|
339
|
+
explanation:
|
|
340
|
+
`Step ${invalidStep} doesn't follow from the previous step. ` +
|
|
341
|
+
`The left side of step ${invalidStep} ('${lhs}') should equal the right side of step ${invalidStep - 1}. ` +
|
|
342
|
+
`Each step must connect to the previous step to form a valid chain.`,
|
|
343
|
+
stepNumber: invalidStep,
|
|
344
|
+
expected: `Continue from previous result`,
|
|
345
|
+
found: lhs,
|
|
346
|
+
fixSuggestions: [
|
|
347
|
+
`Ensure step ${invalidStep} starts with the result from step ${invalidStep - 1}`,
|
|
348
|
+
`Check for typos or missing intermediate steps`,
|
|
349
|
+
`If changing variables, show the substitution explicitly`,
|
|
350
|
+
],
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Equivalence error: LHS and RHS of the same step are not equivalent
|
|
355
|
+
return {
|
|
356
|
+
summary: `Invalid algebraic transformation at step ${invalidStep}`,
|
|
357
|
+
explanation:
|
|
358
|
+
`The expression '${lhs}' is not algebraically equivalent to '${rhs}'. ` +
|
|
359
|
+
`This transformation cannot be justified by standard algebraic rules. ` +
|
|
360
|
+
`The two expressions evaluate to different values.`,
|
|
361
|
+
stepNumber: invalidStep,
|
|
362
|
+
expected: lhs,
|
|
363
|
+
found: rhs,
|
|
364
|
+
fixSuggestions: [
|
|
365
|
+
`Verify the algebraic manipulation from '${lhs}' to '${rhs}'`,
|
|
366
|
+
`Check for sign errors or incorrect coefficient handling`,
|
|
367
|
+
`Consider adding intermediate steps to make the transformation clearer`,
|
|
368
|
+
`If this is a substitution, ensure the substituted value is correct`,
|
|
369
|
+
],
|
|
370
|
+
};
|
|
371
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derivation LaTeX - converts derivations to LaTeX format
|
|
3
|
+
*
|
|
4
|
+
* Produces LaTeX code suitable for mathematical documents with proper
|
|
5
|
+
* alignment of equals signs and optional step numbering.
|
|
6
|
+
*
|
|
7
|
+
* @module derivation-latex
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { extractDerivationSteps } from "./derivation-core.ts";
|
|
11
|
+
|
|
12
|
+
/** Options for LaTeX derivation formatting */
|
|
13
|
+
export interface DerivationLatexOptions {
|
|
14
|
+
/** Use align environment for multi-step (default: true) */
|
|
15
|
+
useAlign?: boolean;
|
|
16
|
+
/** Add step numbers as comments (default: false) */
|
|
17
|
+
showStepNumbers?: boolean;
|
|
18
|
+
/** Include "therefore" symbol before final step (default: false) */
|
|
19
|
+
showTherefore?: boolean;
|
|
20
|
+
/** Custom label for the derivation (default: none) */
|
|
21
|
+
label?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Convert an expression to LaTeX notation
|
|
26
|
+
*/
|
|
27
|
+
function toLatex(expr: string): string {
|
|
28
|
+
let result = expr;
|
|
29
|
+
|
|
30
|
+
// Convert multiplication: * or · → \cdot
|
|
31
|
+
result = result.replace(/\s*[*·×]\s*/g, " \\cdot ");
|
|
32
|
+
|
|
33
|
+
// Convert division: ÷ → \div (or could use \frac)
|
|
34
|
+
result = result.replace(/\s*÷\s*/g, " \\div ");
|
|
35
|
+
|
|
36
|
+
// Convert powers: x^2 → x^{2}, x^10 → x^{10}
|
|
37
|
+
result = result.replace(/\^(\d+)/g, "^{$1}");
|
|
38
|
+
result = result.replace(/\^([a-zA-Z])/g, "^{$1}");
|
|
39
|
+
|
|
40
|
+
// Convert sqrt: sqrt(x) → \sqrt{x}
|
|
41
|
+
result = result.replace(/sqrt\(([^)]+)\)/gi, "\\sqrt{$1}");
|
|
42
|
+
|
|
43
|
+
// Convert common functions
|
|
44
|
+
result = result.replace(/\b(sin|cos|tan|log|ln|exp)\b/g, "\\$1");
|
|
45
|
+
|
|
46
|
+
// Convert pi → \pi
|
|
47
|
+
result = result.replace(/\bpi\b/gi, "\\pi");
|
|
48
|
+
|
|
49
|
+
// Convert fractions: a/b → \frac{a}{b} (simple cases only)
|
|
50
|
+
result = result.replace(/(\d+)\s*\/\s*(\d+)/g, "\\frac{$1}{$2}");
|
|
51
|
+
|
|
52
|
+
// Handle minus signs for better rendering
|
|
53
|
+
result = result.replace(/−/g, "-");
|
|
54
|
+
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Convert a derivation chain to LaTeX format with aligned equations
|
|
60
|
+
*
|
|
61
|
+
* Produces LaTeX code suitable for mathematical documents with proper
|
|
62
|
+
* alignment of equals signs and optional step numbering.
|
|
63
|
+
*
|
|
64
|
+
* @param steps Array of {lhs, rhs} pairs representing the derivation
|
|
65
|
+
* @param options Formatting options
|
|
66
|
+
* @returns LaTeX string
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* derivationToLatex([
|
|
70
|
+
* { lhs: "x + x", rhs: "2x" },
|
|
71
|
+
* { lhs: "2x", rhs: "2 * x" }
|
|
72
|
+
* ])
|
|
73
|
+
* // Returns:
|
|
74
|
+
* // \begin{align}
|
|
75
|
+
* // x + x &= 2x \\
|
|
76
|
+
* // &= 2 \cdot x
|
|
77
|
+
* // \end{align}
|
|
78
|
+
*/
|
|
79
|
+
export function derivationToLatex(
|
|
80
|
+
steps: Array<{ lhs: string; rhs: string }>,
|
|
81
|
+
options: DerivationLatexOptions = {},
|
|
82
|
+
): string {
|
|
83
|
+
const { useAlign = true, showStepNumbers = false, showTherefore = false, label } = options;
|
|
84
|
+
|
|
85
|
+
if (steps.length === 0) {
|
|
86
|
+
return "";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const lines: string[] = [];
|
|
90
|
+
|
|
91
|
+
if (useAlign) {
|
|
92
|
+
const envStart = label ? `\\begin{align}\\label{${label}}` : "\\begin{align}";
|
|
93
|
+
lines.push(envStart);
|
|
94
|
+
|
|
95
|
+
for (let i = 0; i < steps.length; i++) {
|
|
96
|
+
const step = steps[i];
|
|
97
|
+
if (!step) continue;
|
|
98
|
+
|
|
99
|
+
const isFirst = i === 0;
|
|
100
|
+
const isLast = i === steps.length - 1;
|
|
101
|
+
|
|
102
|
+
const lhsLatex = toLatex(step.lhs);
|
|
103
|
+
const rhsLatex = toLatex(step.rhs);
|
|
104
|
+
|
|
105
|
+
let line: string;
|
|
106
|
+
|
|
107
|
+
if (isFirst) {
|
|
108
|
+
// First line shows full equation
|
|
109
|
+
line = ` ${lhsLatex} &= ${rhsLatex}`;
|
|
110
|
+
} else {
|
|
111
|
+
// Subsequent lines only show RHS (aligned at =)
|
|
112
|
+
if (showTherefore && isLast) {
|
|
113
|
+
line = ` &\\therefore ${rhsLatex}`;
|
|
114
|
+
} else {
|
|
115
|
+
line = ` &= ${rhsLatex}`;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Add step number comment
|
|
120
|
+
if (showStepNumbers) {
|
|
121
|
+
line += ` && \\text{(${i + 1})}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Add line continuation (except last line)
|
|
125
|
+
if (!isLast) {
|
|
126
|
+
line += " \\\\";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
lines.push(line);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
lines.push("\\end{align}");
|
|
133
|
+
} else {
|
|
134
|
+
// Simple equation environment (no alignment)
|
|
135
|
+
const allExprs = steps.map((s) => `${toLatex(s.lhs)} = ${toLatex(s.rhs)}`);
|
|
136
|
+
lines.push("\\begin{equation}");
|
|
137
|
+
lines.push(` ${allExprs.join(" = ")}`);
|
|
138
|
+
lines.push("\\end{equation}");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return lines.join("\n");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Convert text containing a derivation to LaTeX
|
|
146
|
+
*
|
|
147
|
+
* @param text Text containing a derivation
|
|
148
|
+
* @param options Formatting options
|
|
149
|
+
* @returns LaTeX string or null if no derivation found
|
|
150
|
+
*/
|
|
151
|
+
export function derivationTextToLatex(
|
|
152
|
+
text: string,
|
|
153
|
+
options: DerivationLatexOptions = {},
|
|
154
|
+
): string | null {
|
|
155
|
+
const steps = extractDerivationSteps(text);
|
|
156
|
+
if (steps.length === 0) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
return derivationToLatex(steps, options);
|
|
160
|
+
}
|