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,620 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derivation Transform - AST utilities and transformation patterns
|
|
3
|
+
*
|
|
4
|
+
* Contains helper functions for AST manipulation and a registry of
|
|
5
|
+
* algebraic transformation patterns for simplification.
|
|
6
|
+
*
|
|
7
|
+
* @module derivation-transform
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ASTNode, BinaryNode } from "../../math/ast.ts";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Normalize operator symbols to canonical form
|
|
14
|
+
*/
|
|
15
|
+
export function normalizeOperator(op: string): string {
|
|
16
|
+
if (op === "−") return "-";
|
|
17
|
+
if (op === "×" || op === "·") return "*";
|
|
18
|
+
if (op === "÷") return "/";
|
|
19
|
+
return op;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check if an AST node contains a specific pattern
|
|
24
|
+
*/
|
|
25
|
+
export function containsPattern(node: ASTNode, predicate: (n: ASTNode) => boolean): boolean {
|
|
26
|
+
if (predicate(node)) return true;
|
|
27
|
+
switch (node.type) {
|
|
28
|
+
case "number":
|
|
29
|
+
case "variable":
|
|
30
|
+
return false;
|
|
31
|
+
case "unary":
|
|
32
|
+
return containsPattern(node.operand, predicate);
|
|
33
|
+
case "binary":
|
|
34
|
+
return containsPattern(node.left, predicate) || containsPattern(node.right, predicate);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check if node is a binary operation with given operator
|
|
40
|
+
*/
|
|
41
|
+
export function isBinaryOp(node: ASTNode, op: string): node is BinaryNode {
|
|
42
|
+
if (node.type !== "binary") return false;
|
|
43
|
+
const normalized =
|
|
44
|
+
node.operator === "−"
|
|
45
|
+
? "-"
|
|
46
|
+
: node.operator === "×" || node.operator === "·"
|
|
47
|
+
? "*"
|
|
48
|
+
: node.operator === "÷"
|
|
49
|
+
? "/"
|
|
50
|
+
: node.operator;
|
|
51
|
+
return normalized === op;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Check if two AST nodes are structurally equal
|
|
56
|
+
*/
|
|
57
|
+
export function nodesEqual(a: ASTNode, b: ASTNode): boolean {
|
|
58
|
+
if (a.type !== b.type) return false;
|
|
59
|
+
switch (a.type) {
|
|
60
|
+
case "number":
|
|
61
|
+
return a.value === (b as typeof a).value;
|
|
62
|
+
case "variable":
|
|
63
|
+
return a.name === (b as typeof a).name;
|
|
64
|
+
case "unary":
|
|
65
|
+
return (
|
|
66
|
+
a.operator === (b as typeof a).operator && nodesEqual(a.operand, (b as typeof a).operand)
|
|
67
|
+
);
|
|
68
|
+
case "binary":
|
|
69
|
+
return (
|
|
70
|
+
a.operator === (b as typeof a).operator &&
|
|
71
|
+
nodesEqual(a.left, (b as typeof a).left) &&
|
|
72
|
+
nodesEqual(a.right, (b as typeof a).right)
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Greatest common divisor (for fraction simplification check)
|
|
79
|
+
*/
|
|
80
|
+
export function gcd(a: number, b: number): number {
|
|
81
|
+
a = Math.abs(Math.floor(a));
|
|
82
|
+
b = Math.abs(Math.floor(b));
|
|
83
|
+
while (b !== 0) {
|
|
84
|
+
const t = b;
|
|
85
|
+
b = a % b;
|
|
86
|
+
a = t;
|
|
87
|
+
}
|
|
88
|
+
return a;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Pattern for transformation suggestions */
|
|
92
|
+
export interface TransformPattern {
|
|
93
|
+
name: string;
|
|
94
|
+
description: string;
|
|
95
|
+
priority: number;
|
|
96
|
+
applies: (ast: ASTNode) => boolean;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Transformation patterns in priority order */
|
|
100
|
+
export const TRANSFORM_PATTERNS: TransformPattern[] = [
|
|
101
|
+
// Constant folding (highest priority - immediate simplification)
|
|
102
|
+
{
|
|
103
|
+
name: "constant_fold",
|
|
104
|
+
description: "Evaluate numeric operations",
|
|
105
|
+
priority: 100,
|
|
106
|
+
applies: (ast) =>
|
|
107
|
+
containsPattern(ast, (n) => {
|
|
108
|
+
if (n.type !== "binary" || n.left.type !== "number" || n.right.type !== "number") {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
// Exclude 0^0 (indeterminate)
|
|
112
|
+
if (normalizeOperator(n.operator) === "^" && n.left.value === 0 && n.right.value === 0) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
return true;
|
|
116
|
+
}),
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
// Identity elimination
|
|
120
|
+
{
|
|
121
|
+
name: "add_zero",
|
|
122
|
+
description: "Remove addition of zero (x + 0 = x)",
|
|
123
|
+
priority: 90,
|
|
124
|
+
applies: (ast) =>
|
|
125
|
+
containsPattern(ast, (n) => {
|
|
126
|
+
if (!isBinaryOp(n, "+")) return false;
|
|
127
|
+
const bn = n as BinaryNode;
|
|
128
|
+
return (
|
|
129
|
+
(bn.left.type === "number" && bn.left.value === 0) ||
|
|
130
|
+
(bn.right.type === "number" && bn.right.value === 0)
|
|
131
|
+
);
|
|
132
|
+
}),
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: "multiply_one",
|
|
136
|
+
description: "Remove multiplication by one (x * 1 = x)",
|
|
137
|
+
priority: 90,
|
|
138
|
+
applies: (ast) =>
|
|
139
|
+
containsPattern(ast, (n) => {
|
|
140
|
+
if (!isBinaryOp(n, "*")) return false;
|
|
141
|
+
const bn = n as BinaryNode;
|
|
142
|
+
return (
|
|
143
|
+
(bn.left.type === "number" && bn.left.value === 1) ||
|
|
144
|
+
(bn.right.type === "number" && bn.right.value === 1)
|
|
145
|
+
);
|
|
146
|
+
}),
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: "multiply_zero",
|
|
150
|
+
description: "Simplify multiplication by zero (x * 0 = 0)",
|
|
151
|
+
priority: 90,
|
|
152
|
+
applies: (ast) =>
|
|
153
|
+
containsPattern(ast, (n) => {
|
|
154
|
+
if (!isBinaryOp(n, "*")) return false;
|
|
155
|
+
const bn = n as BinaryNode;
|
|
156
|
+
return (
|
|
157
|
+
(bn.left.type === "number" && bn.left.value === 0) ||
|
|
158
|
+
(bn.right.type === "number" && bn.right.value === 0)
|
|
159
|
+
);
|
|
160
|
+
}),
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: "power_one",
|
|
164
|
+
description: "Remove exponent of one (x^1 = x)",
|
|
165
|
+
priority: 90,
|
|
166
|
+
applies: (ast) =>
|
|
167
|
+
containsPattern(ast, (n) => {
|
|
168
|
+
if (!isBinaryOp(n, "^")) return false;
|
|
169
|
+
const bn = n as BinaryNode;
|
|
170
|
+
return bn.right.type === "number" && bn.right.value === 1;
|
|
171
|
+
}),
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: "power_zero",
|
|
175
|
+
description: "Simplify exponent of zero (x^0 = 1, except 0^0)",
|
|
176
|
+
priority: 90,
|
|
177
|
+
applies: (ast) =>
|
|
178
|
+
containsPattern(ast, (n) => {
|
|
179
|
+
if (!isBinaryOp(n, "^")) return false;
|
|
180
|
+
const bn = n as BinaryNode;
|
|
181
|
+
// x^0 where x is not 0
|
|
182
|
+
if (bn.right.type === "number" && bn.right.value === 0) {
|
|
183
|
+
// Exclude 0^0 (indeterminate)
|
|
184
|
+
if (bn.left.type === "number" && bn.left.value === 0) return false;
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
return false;
|
|
188
|
+
}),
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: "indeterminate_zero_power_zero",
|
|
192
|
+
description: "Warning: 0^0 is indeterminate",
|
|
193
|
+
priority: 95, // Higher priority to catch before other transformations
|
|
194
|
+
applies: (ast) =>
|
|
195
|
+
containsPattern(ast, (n) => {
|
|
196
|
+
if (!isBinaryOp(n, "^")) return false;
|
|
197
|
+
const bn = n as BinaryNode;
|
|
198
|
+
return (
|
|
199
|
+
bn.left.type === "number" &&
|
|
200
|
+
bn.left.value === 0 &&
|
|
201
|
+
bn.right.type === "number" &&
|
|
202
|
+
bn.right.value === 0
|
|
203
|
+
);
|
|
204
|
+
}),
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: "base_one",
|
|
208
|
+
description: "Simplify base of one (1^x = 1, (1^a)^b = 1)",
|
|
209
|
+
priority: 90,
|
|
210
|
+
applies: (ast) =>
|
|
211
|
+
containsPattern(ast, (n) => {
|
|
212
|
+
if (!isBinaryOp(n, "^")) return false;
|
|
213
|
+
const bn = n as BinaryNode;
|
|
214
|
+
// Direct: 1^x
|
|
215
|
+
if (bn.left.type === "number" && bn.left.value === 1) return true;
|
|
216
|
+
// Nested: (1^a)^b where inner base is 1
|
|
217
|
+
if (
|
|
218
|
+
bn.left.type === "binary" &&
|
|
219
|
+
normalizeOperator(bn.left.operator) === "^" &&
|
|
220
|
+
bn.left.left.type === "number" &&
|
|
221
|
+
bn.left.left.value === 1
|
|
222
|
+
) {
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
return false;
|
|
226
|
+
}),
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
// Self-cancellation
|
|
230
|
+
{
|
|
231
|
+
name: "subtract_self",
|
|
232
|
+
description: "Simplify self-subtraction (x - x = 0)",
|
|
233
|
+
priority: 85,
|
|
234
|
+
applies: (ast) =>
|
|
235
|
+
containsPattern(ast, (n) => {
|
|
236
|
+
if (!isBinaryOp(n, "-")) return false;
|
|
237
|
+
const bn = n as BinaryNode;
|
|
238
|
+
return nodesEqual(bn.left, bn.right);
|
|
239
|
+
}),
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
name: "divide_self",
|
|
243
|
+
description: "Simplify self-division (x / x = 1)",
|
|
244
|
+
priority: 85,
|
|
245
|
+
applies: (ast) =>
|
|
246
|
+
containsPattern(ast, (n) => {
|
|
247
|
+
if (!isBinaryOp(n, "/")) return false;
|
|
248
|
+
const bn = n as BinaryNode;
|
|
249
|
+
return nodesEqual(bn.left, bn.right);
|
|
250
|
+
}),
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
// Combine like terms
|
|
254
|
+
{
|
|
255
|
+
name: "combine_like_terms",
|
|
256
|
+
description: "Combine like terms (x + x = 2x, ax + bx = (a+b)x)",
|
|
257
|
+
priority: 70,
|
|
258
|
+
applies: (ast) =>
|
|
259
|
+
containsPattern(ast, (n) => {
|
|
260
|
+
if (!isBinaryOp(n, "+")) return false;
|
|
261
|
+
const bn = n as BinaryNode;
|
|
262
|
+
// x + x pattern
|
|
263
|
+
if (nodesEqual(bn.left, bn.right)) return true;
|
|
264
|
+
// ax + bx pattern (coefficient * same base)
|
|
265
|
+
if (
|
|
266
|
+
bn.left.type === "binary" &&
|
|
267
|
+
bn.right.type === "binary" &&
|
|
268
|
+
isBinaryOp(bn.left, "*") &&
|
|
269
|
+
isBinaryOp(bn.right, "*")
|
|
270
|
+
) {
|
|
271
|
+
const leftBin = bn.left as BinaryNode;
|
|
272
|
+
const rightBin = bn.right as BinaryNode;
|
|
273
|
+
return nodesEqual(leftBin.right, rightBin.right);
|
|
274
|
+
}
|
|
275
|
+
return false;
|
|
276
|
+
}),
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
// Distributive law expansion
|
|
280
|
+
{
|
|
281
|
+
name: "distribute",
|
|
282
|
+
description: "Apply distributive law (a(b + c) = ab + ac)",
|
|
283
|
+
priority: 60,
|
|
284
|
+
applies: (ast) =>
|
|
285
|
+
containsPattern(ast, (n) => {
|
|
286
|
+
if (!isBinaryOp(n, "*")) return false;
|
|
287
|
+
const bn = n as BinaryNode;
|
|
288
|
+
return (
|
|
289
|
+
isBinaryOp(bn.left, "+") ||
|
|
290
|
+
isBinaryOp(bn.left, "-") ||
|
|
291
|
+
isBinaryOp(bn.right, "+") ||
|
|
292
|
+
isBinaryOp(bn.right, "-")
|
|
293
|
+
);
|
|
294
|
+
}),
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
// Factor common terms
|
|
298
|
+
{
|
|
299
|
+
name: "factor_common",
|
|
300
|
+
description: "Factor out common terms (ab + ac = a(b + c))",
|
|
301
|
+
priority: 55,
|
|
302
|
+
applies: (ast) =>
|
|
303
|
+
containsPattern(ast, (n) => {
|
|
304
|
+
if (!isBinaryOp(n, "+") && !isBinaryOp(n, "-")) return false;
|
|
305
|
+
const bn = n as BinaryNode;
|
|
306
|
+
// Check if both sides share a common factor
|
|
307
|
+
// Simple check: both are multiplications with a shared operand
|
|
308
|
+
if (bn.left.type === "binary" && bn.right.type === "binary") {
|
|
309
|
+
if (isBinaryOp(bn.left, "*") && isBinaryOp(bn.right, "*")) {
|
|
310
|
+
const leftBin = bn.left as BinaryNode;
|
|
311
|
+
const rightBin = bn.right as BinaryNode;
|
|
312
|
+
return (
|
|
313
|
+
nodesEqual(leftBin.left, rightBin.left) ||
|
|
314
|
+
nodesEqual(leftBin.left, rightBin.right) ||
|
|
315
|
+
nodesEqual(leftBin.right, rightBin.left) ||
|
|
316
|
+
nodesEqual(leftBin.right, rightBin.right)
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return false;
|
|
321
|
+
}),
|
|
322
|
+
},
|
|
323
|
+
|
|
324
|
+
// Double negation
|
|
325
|
+
{
|
|
326
|
+
name: "double_negation",
|
|
327
|
+
description: "Remove double negation (--x = x)",
|
|
328
|
+
priority: 80,
|
|
329
|
+
applies: (ast) =>
|
|
330
|
+
containsPattern(
|
|
331
|
+
ast,
|
|
332
|
+
(n) =>
|
|
333
|
+
n.type === "unary" &&
|
|
334
|
+
(n.operator === "-" || n.operator === "−") &&
|
|
335
|
+
n.operand.type === "unary" &&
|
|
336
|
+
(n.operand.operator === "-" || n.operand.operator === "−"),
|
|
337
|
+
),
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
// Fraction simplification
|
|
341
|
+
{
|
|
342
|
+
name: "simplify_fraction",
|
|
343
|
+
description: "Simplify fraction (reduce common factors)",
|
|
344
|
+
priority: 50,
|
|
345
|
+
applies: (ast) =>
|
|
346
|
+
containsPattern(ast, (n) => {
|
|
347
|
+
if (!isBinaryOp(n, "/")) return false;
|
|
348
|
+
const bn = n as BinaryNode;
|
|
349
|
+
return (
|
|
350
|
+
bn.left.type === "number" &&
|
|
351
|
+
bn.right.type === "number" &&
|
|
352
|
+
bn.right.value !== 0 &&
|
|
353
|
+
gcd(Math.abs(bn.left.value), Math.abs(bn.right.value)) > 1
|
|
354
|
+
);
|
|
355
|
+
}),
|
|
356
|
+
},
|
|
357
|
+
|
|
358
|
+
// Power rules
|
|
359
|
+
{
|
|
360
|
+
name: "power_of_power",
|
|
361
|
+
description: "Simplify power of power ((x^a)^b = x^(a*b))",
|
|
362
|
+
priority: 45,
|
|
363
|
+
applies: (ast) =>
|
|
364
|
+
containsPattern(ast, (n) => {
|
|
365
|
+
if (!isBinaryOp(n, "^")) return false;
|
|
366
|
+
const bn = n as BinaryNode;
|
|
367
|
+
return isBinaryOp(bn.left, "^");
|
|
368
|
+
}),
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
name: "multiply_powers",
|
|
372
|
+
description: "Combine powers with same base (x^a * x^b = x^(a+b))",
|
|
373
|
+
priority: 45,
|
|
374
|
+
applies: (ast) =>
|
|
375
|
+
containsPattern(ast, (n) => {
|
|
376
|
+
if (!isBinaryOp(n, "*")) return false;
|
|
377
|
+
const bn = n as BinaryNode;
|
|
378
|
+
const leftIsPower = isBinaryOp(bn.left, "^");
|
|
379
|
+
const rightIsPower = isBinaryOp(bn.right, "^");
|
|
380
|
+
if (leftIsPower && rightIsPower) {
|
|
381
|
+
const leftBin = bn.left as BinaryNode;
|
|
382
|
+
const rightBin = bn.right as BinaryNode;
|
|
383
|
+
return nodesEqual(leftBin.left, rightBin.left);
|
|
384
|
+
}
|
|
385
|
+
// x * x^a or x^a * x
|
|
386
|
+
if (leftIsPower) {
|
|
387
|
+
const leftBin = bn.left as BinaryNode;
|
|
388
|
+
return nodesEqual(leftBin.left, bn.right);
|
|
389
|
+
}
|
|
390
|
+
if (rightIsPower) {
|
|
391
|
+
const rightBin = bn.right as BinaryNode;
|
|
392
|
+
return nodesEqual(rightBin.left, bn.left);
|
|
393
|
+
}
|
|
394
|
+
return false;
|
|
395
|
+
}),
|
|
396
|
+
},
|
|
397
|
+
];
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Apply a single transformation to an AST and return the result
|
|
401
|
+
*/
|
|
402
|
+
export function applyTransformation(
|
|
403
|
+
ast: ASTNode,
|
|
404
|
+
transformName: string,
|
|
405
|
+
): { transformed: ASTNode; applied: boolean } {
|
|
406
|
+
// Deep clone the AST to avoid mutation
|
|
407
|
+
const clone = JSON.parse(JSON.stringify(ast)) as ASTNode;
|
|
408
|
+
|
|
409
|
+
let applied = false;
|
|
410
|
+
|
|
411
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: AST transformation requires exhaustive pattern matching across all operators
|
|
412
|
+
const transform = (node: ASTNode): ASTNode => {
|
|
413
|
+
if (applied) return node; // Only apply once per call
|
|
414
|
+
|
|
415
|
+
switch (node.type) {
|
|
416
|
+
case "number":
|
|
417
|
+
case "variable":
|
|
418
|
+
return node;
|
|
419
|
+
|
|
420
|
+
case "unary": {
|
|
421
|
+
// Handle double negation
|
|
422
|
+
if (
|
|
423
|
+
transformName === "double_negation" &&
|
|
424
|
+
node.operator === "-" &&
|
|
425
|
+
node.operand.type === "unary" &&
|
|
426
|
+
node.operand.operator === "-"
|
|
427
|
+
) {
|
|
428
|
+
applied = true;
|
|
429
|
+
return node.operand.operand;
|
|
430
|
+
}
|
|
431
|
+
return { ...node, operand: transform(node.operand) };
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
case "binary": {
|
|
435
|
+
const op = normalizeOperator(node.operator);
|
|
436
|
+
|
|
437
|
+
// Constant folding
|
|
438
|
+
if (
|
|
439
|
+
transformName === "constant_fold" &&
|
|
440
|
+
node.left.type === "number" &&
|
|
441
|
+
node.right.type === "number"
|
|
442
|
+
) {
|
|
443
|
+
const l = node.left.value;
|
|
444
|
+
const r = node.right.value;
|
|
445
|
+
let result: number | null = null;
|
|
446
|
+
|
|
447
|
+
switch (op) {
|
|
448
|
+
case "+":
|
|
449
|
+
result = l + r;
|
|
450
|
+
break;
|
|
451
|
+
case "-":
|
|
452
|
+
result = l - r;
|
|
453
|
+
break;
|
|
454
|
+
case "*":
|
|
455
|
+
result = l * r;
|
|
456
|
+
break;
|
|
457
|
+
case "/":
|
|
458
|
+
if (r !== 0) result = l / r;
|
|
459
|
+
break;
|
|
460
|
+
case "^":
|
|
461
|
+
// Skip 0^0 - it's indeterminate
|
|
462
|
+
if (l === 0 && r === 0) {
|
|
463
|
+
result = null;
|
|
464
|
+
} else {
|
|
465
|
+
result = l ** r;
|
|
466
|
+
}
|
|
467
|
+
break;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (result !== null && Number.isFinite(result)) {
|
|
471
|
+
applied = true;
|
|
472
|
+
return { type: "number", value: result };
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Add zero: x + 0 = x or 0 + x = x
|
|
477
|
+
if (transformName === "add_zero" && op === "+") {
|
|
478
|
+
if (node.right.type === "number" && node.right.value === 0) {
|
|
479
|
+
applied = true;
|
|
480
|
+
return node.left;
|
|
481
|
+
}
|
|
482
|
+
if (node.left.type === "number" && node.left.value === 0) {
|
|
483
|
+
applied = true;
|
|
484
|
+
return node.right;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Multiply one: x * 1 = x or 1 * x = x
|
|
489
|
+
if (transformName === "multiply_one" && op === "*") {
|
|
490
|
+
if (node.right.type === "number" && node.right.value === 1) {
|
|
491
|
+
applied = true;
|
|
492
|
+
return node.left;
|
|
493
|
+
}
|
|
494
|
+
if (node.left.type === "number" && node.left.value === 1) {
|
|
495
|
+
applied = true;
|
|
496
|
+
return node.right;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Multiply zero: x * 0 = 0 or 0 * x = 0
|
|
501
|
+
if (transformName === "multiply_zero" && op === "*") {
|
|
502
|
+
if (
|
|
503
|
+
(node.right.type === "number" && node.right.value === 0) ||
|
|
504
|
+
(node.left.type === "number" && node.left.value === 0)
|
|
505
|
+
) {
|
|
506
|
+
applied = true;
|
|
507
|
+
return { type: "number", value: 0 };
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Power one: x^1 = x
|
|
512
|
+
if (transformName === "power_one" && op === "^") {
|
|
513
|
+
if (node.right.type === "number" && node.right.value === 1) {
|
|
514
|
+
applied = true;
|
|
515
|
+
return node.left;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Power zero: x^0 = 1 (except 0^0 which is indeterminate)
|
|
520
|
+
if (transformName === "power_zero" && op === "^") {
|
|
521
|
+
if (node.right.type === "number" && node.right.value === 0) {
|
|
522
|
+
// Skip 0^0 - it's indeterminate
|
|
523
|
+
if (node.left.type === "number" && node.left.value === 0) {
|
|
524
|
+
return { ...node, left: transform(node.left), right: transform(node.right) };
|
|
525
|
+
}
|
|
526
|
+
applied = true;
|
|
527
|
+
return { type: "number", value: 1 };
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Indeterminate form: 0^0 - cannot simplify
|
|
532
|
+
// Returns applied=false so suggestSimplificationPath knows to stop
|
|
533
|
+
if (transformName === "indeterminate_zero_power_zero" && op === "^") {
|
|
534
|
+
if (
|
|
535
|
+
node.left.type === "number" &&
|
|
536
|
+
node.left.value === 0 &&
|
|
537
|
+
node.right.type === "number" &&
|
|
538
|
+
node.right.value === 0
|
|
539
|
+
) {
|
|
540
|
+
// Don't set applied=true - this is a terminal state, not a transformation
|
|
541
|
+
return node;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Base one: 1^x = 1, also handles nested (1^a)^b = 1
|
|
546
|
+
if (transformName === "base_one" && op === "^") {
|
|
547
|
+
// Direct case: 1^x = 1
|
|
548
|
+
if (node.left.type === "number" && node.left.value === 1) {
|
|
549
|
+
applied = true;
|
|
550
|
+
return { type: "number", value: 1 };
|
|
551
|
+
}
|
|
552
|
+
// Nested case: (1^a)^b = 1 (base is a power with base 1)
|
|
553
|
+
if (
|
|
554
|
+
node.left.type === "binary" &&
|
|
555
|
+
normalizeOperator(node.left.operator) === "^" &&
|
|
556
|
+
node.left.left.type === "number" &&
|
|
557
|
+
node.left.left.value === 1
|
|
558
|
+
) {
|
|
559
|
+
applied = true;
|
|
560
|
+
return { type: "number", value: 1 };
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Subtract self: x - x = 0
|
|
565
|
+
if (transformName === "subtract_self" && op === "-") {
|
|
566
|
+
if (nodesEqual(node.left, node.right)) {
|
|
567
|
+
applied = true;
|
|
568
|
+
return { type: "number", value: 0 };
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Divide self: x / x = 1
|
|
573
|
+
if (transformName === "divide_self" && op === "/") {
|
|
574
|
+
if (nodesEqual(node.left, node.right)) {
|
|
575
|
+
applied = true;
|
|
576
|
+
return { type: "number", value: 1 };
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Combine like terms: x + x = 2x
|
|
581
|
+
if (transformName === "combine_like_terms" && op === "+") {
|
|
582
|
+
if (nodesEqual(node.left, node.right)) {
|
|
583
|
+
applied = true;
|
|
584
|
+
return {
|
|
585
|
+
type: "binary",
|
|
586
|
+
operator: "*",
|
|
587
|
+
left: { type: "number", value: 2 },
|
|
588
|
+
right: node.left,
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Simplify fraction: 4/2 = 2
|
|
594
|
+
if (
|
|
595
|
+
transformName === "simplify_fraction" &&
|
|
596
|
+
op === "/" &&
|
|
597
|
+
node.left.type === "number" &&
|
|
598
|
+
node.right.type === "number"
|
|
599
|
+
) {
|
|
600
|
+
const num = node.left.value;
|
|
601
|
+
const den = node.right.value;
|
|
602
|
+
if (den !== 0 && num % den === 0) {
|
|
603
|
+
applied = true;
|
|
604
|
+
return { type: "number", value: num / den };
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Recurse into children
|
|
609
|
+
return {
|
|
610
|
+
...node,
|
|
611
|
+
left: transform(node.left),
|
|
612
|
+
right: transform(node.right),
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
const result = transform(clone);
|
|
619
|
+
return { transformed: result, applied };
|
|
620
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derivation solver - 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
|
+
* This is a barrel file re-exporting from focused modules:
|
|
9
|
+
* - derivation-core.ts: Core verification logic
|
|
10
|
+
* - derivation-transform.ts: Algebraic transformation patterns
|
|
11
|
+
* - derivation-simplify.ts: Simplification and next-step suggestions
|
|
12
|
+
* - derivation-mistakes.ts: Common mistake detection
|
|
13
|
+
* - derivation-latex.ts: LaTeX conversion
|
|
14
|
+
*
|
|
15
|
+
* @module derivation
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// Core verification
|
|
19
|
+
export {
|
|
20
|
+
cleanExpressionPart,
|
|
21
|
+
type DerivationErrorExplanation,
|
|
22
|
+
type DerivationResult,
|
|
23
|
+
explainDerivationError,
|
|
24
|
+
extractDerivationSteps,
|
|
25
|
+
type StepVerification,
|
|
26
|
+
tryDerivation,
|
|
27
|
+
verifyDerivationSteps,
|
|
28
|
+
} from "./derivation-core.ts";
|
|
29
|
+
// LaTeX conversion
|
|
30
|
+
export {
|
|
31
|
+
type DerivationLatexOptions,
|
|
32
|
+
derivationTextToLatex,
|
|
33
|
+
derivationToLatex,
|
|
34
|
+
} from "./derivation-latex.ts";
|
|
35
|
+
// Mistake detection
|
|
36
|
+
export {
|
|
37
|
+
type DetectedMistake,
|
|
38
|
+
detectCommonMistakes,
|
|
39
|
+
detectCommonMistakesFromText,
|
|
40
|
+
type MistakeDetectionResult,
|
|
41
|
+
type MistakeType,
|
|
42
|
+
} from "./derivation-mistakes.ts";
|
|
43
|
+
// Simplification
|
|
44
|
+
export {
|
|
45
|
+
type NextStepSuggestion,
|
|
46
|
+
parseToAST,
|
|
47
|
+
type SimplificationPath,
|
|
48
|
+
type SimplificationStep,
|
|
49
|
+
type SimplifiedStep,
|
|
50
|
+
type SimplifyDerivationResult,
|
|
51
|
+
simplifyDerivation,
|
|
52
|
+
simplifyDerivationText,
|
|
53
|
+
suggestNextStep,
|
|
54
|
+
suggestNextStepFromText,
|
|
55
|
+
suggestSimplificationPath,
|
|
56
|
+
} from "./derivation-simplify.ts";
|
|
57
|
+
// Transform utilities and patterns
|
|
58
|
+
export {
|
|
59
|
+
applyTransformation,
|
|
60
|
+
containsPattern,
|
|
61
|
+
gcd,
|
|
62
|
+
isBinaryOp,
|
|
63
|
+
nodesEqual,
|
|
64
|
+
normalizeOperator,
|
|
65
|
+
TRANSFORM_PATTERNS,
|
|
66
|
+
type TransformPattern,
|
|
67
|
+
} from "./derivation-transform.ts";
|