gradient-script 0.1.0 → 0.3.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.
Files changed (51) hide show
  1. package/README.md +52 -9
  2. package/dist/cli.js +134 -19
  3. package/dist/dsl/AST.d.ts +8 -0
  4. package/dist/dsl/CodeGen.d.ts +8 -3
  5. package/dist/dsl/CodeGen.js +583 -132
  6. package/dist/dsl/Errors.d.ts +6 -1
  7. package/dist/dsl/Errors.js +70 -1
  8. package/dist/dsl/Expander.js +5 -2
  9. package/dist/dsl/ExpressionUtils.d.ts +14 -0
  10. package/dist/dsl/ExpressionUtils.js +56 -0
  11. package/dist/dsl/GradientChecker.d.ts +21 -0
  12. package/dist/dsl/GradientChecker.js +109 -23
  13. package/dist/dsl/Guards.d.ts +3 -1
  14. package/dist/dsl/Guards.js +86 -43
  15. package/dist/dsl/Inliner.d.ts +5 -0
  16. package/dist/dsl/Inliner.js +11 -2
  17. package/dist/dsl/Lexer.js +3 -1
  18. package/dist/dsl/Parser.js +11 -5
  19. package/dist/dsl/Simplify.d.ts +7 -0
  20. package/dist/dsl/Simplify.js +183 -0
  21. package/dist/dsl/egraph/Convert.d.ts +23 -0
  22. package/dist/dsl/egraph/Convert.js +84 -0
  23. package/dist/dsl/egraph/EGraph.d.ts +93 -0
  24. package/dist/dsl/egraph/EGraph.js +292 -0
  25. package/dist/dsl/egraph/ENode.d.ts +63 -0
  26. package/dist/dsl/egraph/ENode.js +94 -0
  27. package/dist/dsl/egraph/Extractor.d.ts +49 -0
  28. package/dist/dsl/egraph/Extractor.js +1068 -0
  29. package/dist/dsl/egraph/Optimizer.d.ts +50 -0
  30. package/dist/dsl/egraph/Optimizer.js +88 -0
  31. package/dist/dsl/egraph/Pattern.d.ts +80 -0
  32. package/dist/dsl/egraph/Pattern.js +325 -0
  33. package/dist/dsl/egraph/Rewriter.d.ts +44 -0
  34. package/dist/dsl/egraph/Rewriter.js +131 -0
  35. package/dist/dsl/egraph/Rules.d.ts +44 -0
  36. package/dist/dsl/egraph/Rules.js +187 -0
  37. package/dist/dsl/egraph/index.d.ts +15 -0
  38. package/dist/dsl/egraph/index.js +21 -0
  39. package/package.json +1 -1
  40. package/dist/dsl/CSE.d.ts +0 -21
  41. package/dist/dsl/CSE.js +0 -194
  42. package/dist/symbolic/AST.d.ts +0 -113
  43. package/dist/symbolic/AST.js +0 -128
  44. package/dist/symbolic/CodeGen.d.ts +0 -35
  45. package/dist/symbolic/CodeGen.js +0 -280
  46. package/dist/symbolic/Parser.d.ts +0 -64
  47. package/dist/symbolic/Parser.js +0 -329
  48. package/dist/symbolic/Simplify.d.ts +0 -10
  49. package/dist/symbolic/Simplify.js +0 -244
  50. package/dist/symbolic/SymbolicDiff.d.ts +0 -35
  51. package/dist/symbolic/SymbolicDiff.js +0 -339
package/README.md CHANGED
@@ -1,16 +1,26 @@
1
1
  # GradientScript
2
2
 
3
+ [![npm version](https://badge.fury.io/js/gradient-script.svg)](https://www.npmjs.com/package/gradient-script)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![GitHub release](https://img.shields.io/github/v/release/mfagerlund/gradient-script)](https://github.com/mfagerlund/gradient-script/releases)
6
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D14.0.0-brightgreen)](https://nodejs.org/)
7
+
8
+ > **For LLMs:** This README is available in raw format at:
9
+ > `https://raw.githubusercontent.com/mfagerlund/gradient-script/main/README.md`
10
+
3
11
  **Symbolic automatic differentiation for structured types**
4
12
 
5
13
  GradientScript is a source-to-source compiler that automatically generates gradient functions from your mathematical code. Unlike numerical AD frameworks (JAX, PyTorch), it produces clean, human-readable gradient formulas you can inspect, optimize, and integrate directly into your codebase.
6
14
 
15
+ It's perfect for LLM usage where the LLM can verify existing gradients or construct gradients you require with less risks of making errors.
16
+
7
17
  ## Why GradientScript?
8
18
 
9
19
  - **From real code to gradients**: Write natural math code, get symbolic derivatives
10
20
  - **Verified correctness**: Every gradient automatically checked against numerical differentiation
11
21
  - **Structured types**: Work with vectors `{x, y}` and custom structures, not just scalars
12
22
  - **Zero runtime overhead**: No tape, no graph - just pure gradient functions
13
- - **Multiple output languages**: TypeScript, JavaScript, or Python
23
+ - **Multiple output languages**: TypeScript, JavaScript, Python, or C#
14
24
  - **Readable output**: Human-reviewable formulas with automatic optimization
15
25
 
16
26
  ## Installation
@@ -32,7 +42,7 @@ function distance(u: Vec2, v: Vec2): number {
32
42
  }
33
43
  ```
34
44
 
35
- Convert it to GradientScript by marking what you need gradients for:
45
+ Convert it to GradientScript (realistically, let your LLM convert it giving it a reference here - and/or free usage of the CLI) by marking what you need gradients for:
36
46
 
37
47
  ```typescript
38
48
  // distance.gs
@@ -230,13 +240,18 @@ function angle_between_grad(u, v) {
230
240
  gradient-script <file.gs> [options]
231
241
 
232
242
  Options:
233
- --format <format> typescript (default), javascript, python
234
- --no-simplify Disable gradient simplification
235
- --no-cse Disable common subexpression elimination
236
- --no-comments Omit comments in generated code
237
- --help, -h Show help message
243
+ --format <format> typescript (default), javascript, python, csharp
244
+ --no-simplify Disable gradient simplification
245
+ --no-cse Disable common subexpression elimination
246
+ --no-comments Omit comments in generated code
247
+ --guards Emit runtime guards for potential singularities
248
+ --epsilon <value> Epsilon value for guards (default: 1e-10)
249
+ --csharp-float-type <type> C# float precision: float (default) or double
250
+ --help, -h Show help message
238
251
  ```
239
252
 
253
+ GradientScript automatically generates gradient functions for all functions in your `.gs` file.
254
+
240
255
  **Examples:**
241
256
  ```bash
242
257
  # Generate TypeScript (default)
@@ -247,6 +262,12 @@ gradient-script spring.gs --format python
247
262
 
248
263
  # Generate JavaScript without CSE optimization
249
264
  gradient-script spring.gs --format javascript --no-cse
265
+
266
+ # Generate C# for Unity/Godot (float precision)
267
+ gradient-script spring.gs --format csharp
268
+
269
+ # Generate C# with double precision
270
+ gradient-script spring.gs --format csharp --csharp-float-type double
250
271
  ```
251
272
 
252
273
  ## Language Syntax
@@ -454,7 +475,7 @@ GradientScript includes a comprehensive test suite that validates all generated
454
475
  npm test
455
476
  ```
456
477
 
457
- Current status: **78 tests passing**
478
+ Current status: **129 tests passing**
458
479
 
459
480
  Test suite includes:
460
481
 
@@ -479,7 +500,7 @@ Test suite includes:
479
500
  - CSE optimization correctness
480
501
  - Operator precedence preservation
481
502
  - Power optimization (x*x vs Math.pow)
482
- - Multiple output formats (TypeScript, JavaScript, Python)
503
+ - Multiple output formats (TypeScript, JavaScript, Python, C#)
483
504
  - Algebraic simplification correctness
484
505
 
485
506
  **Key guarantee**: If a test passes, the generated gradient is correct to within numerical precision (~10 decimal places).
@@ -506,6 +527,28 @@ GradientScript is under active development. Contributions welcome!
506
527
  - Web playground for live gradient generation
507
528
  - Benchmarking suite
508
529
 
530
+ ## Examples
531
+
532
+ See the `examples/` directory for complete examples:
533
+
534
+ - **Physics Constraints**: [`examples/PHYSICS_EXAMPLES.md`](examples/PHYSICS_EXAMPLES.md) - Comprehensive guide to using structured types for XPBD constraints, rigid body dynamics, and more
535
+ - Raw (LLM-friendly): `https://raw.githubusercontent.com/mfagerlund/gradient-script/main/examples/PHYSICS_EXAMPLES.md`
536
+ - **XPBD Constraints**: `xpbd-rod-constraint.gs`, `xpbd-angle-constraint.gs`
537
+ - **Distance Functions**: `distance.gs`, `point-segment-distance.gs`
538
+ - **Geometry**: `triangle-area.gs`, `bearing.gs`, `circle-fit.gs`
539
+
540
+ **Try them:**
541
+ ```bash
542
+ # View physics examples guide
543
+ cat examples/PHYSICS_EXAMPLES.md
544
+
545
+ # Generate TypeScript from XPBD rod constraint
546
+ gradient-script examples/xpbd-rod-constraint.gs
547
+
548
+ # Generate C# for Unity/Godot
549
+ gradient-script examples/xpbd-angle-constraint.gs --format csharp
550
+ ```
551
+
509
552
  ## License
510
553
 
511
554
  MIT
package/dist/cli.js CHANGED
@@ -5,6 +5,69 @@ import { inferFunction } from './dsl/TypeInference.js';
5
5
  import { computeFunctionGradients } from './dsl/Differentiation.js';
6
6
  import { generateComplete } from './dsl/CodeGen.js';
7
7
  import { analyzeGuards, formatGuardWarnings } from './dsl/Guards.js';
8
+ import { ParseError, formatParseError } from './dsl/Errors.js';
9
+ import { GradientChecker, formatGradCheckResult } from './dsl/GradientChecker.js';
10
+ import { Types } from './dsl/Types.js';
11
+ /**
12
+ * Generate random test points for gradient verification.
13
+ * Uses multiple test points to catch errors at different values.
14
+ */
15
+ function generateTestPoints(func, env) {
16
+ const testPoints = [];
17
+ // Generate 3 different test points with varying scales
18
+ const scales = [1.0, 0.1, 10.0];
19
+ for (const scale of scales) {
20
+ const point = new Map();
21
+ for (const param of func.parameters) {
22
+ const paramType = env.getOrThrow(param.name);
23
+ if (Types.isScalar(paramType)) {
24
+ // Random scalar in range [-scale, scale], avoid zero
25
+ point.set(param.name, (Math.random() * 2 - 1) * scale + 0.1 * scale);
26
+ }
27
+ else {
28
+ // Structured type - get components
29
+ const struct = {};
30
+ for (const comp of paramType.components) {
31
+ struct[comp] = (Math.random() * 2 - 1) * scale + 0.1 * scale;
32
+ }
33
+ point.set(param.name, struct);
34
+ }
35
+ }
36
+ testPoints.push(point);
37
+ }
38
+ return testPoints;
39
+ }
40
+ /**
41
+ * Verify gradients for a function using numerical differentiation.
42
+ * Returns true if all gradients pass, false otherwise.
43
+ */
44
+ function verifyGradients(func, gradients, env) {
45
+ const checker = new GradientChecker(1e-5, 1e-4);
46
+ const testPoints = generateTestPoints(func, env);
47
+ let allPassed = true;
48
+ for (let i = 0; i < testPoints.length; i++) {
49
+ const result = checker.check(func, gradients, env, testPoints[i]);
50
+ if (!result.passed) {
51
+ if (allPassed) {
52
+ // First failure - print header (as comment for valid output)
53
+ console.error(`// Gradient verification FAILED for "${func.name}":`);
54
+ }
55
+ // Prefix each line with // so output remains valid code
56
+ const formattedResult = formatGradCheckResult(result, func.name)
57
+ .split('\n')
58
+ .map(line => '// ' + line)
59
+ .join('\n');
60
+ console.error(`// Test point ${i + 1}: ${formattedResult}`);
61
+ allPassed = false;
62
+ }
63
+ }
64
+ if (allPassed) {
65
+ const result = checker.check(func, gradients, env, testPoints[0]);
66
+ // Prefix with // so output is valid code
67
+ console.error('// ' + formatGradCheckResult(result, func.name));
68
+ }
69
+ return allPassed;
70
+ }
8
71
  function printUsage() {
9
72
  console.log(`
10
73
  GradientScript - Symbolic Differentiation for Structured Types
@@ -13,18 +76,20 @@ Usage:
13
76
  gradient-script <file.gs> [options]
14
77
 
15
78
  Options:
16
- --format <format> Output format: typescript (default), javascript, python
79
+ --format <format> Output format: typescript (default), javascript, python, csharp
17
80
  --no-simplify Disable gradient simplification
18
- --no-cse Disable common subexpression elimination
81
+ --no-cse Disable optimization (e-graph CSE)
19
82
  --no-comments Omit comments in generated code
20
83
  --guards Emit runtime guards for division by zero (experimental)
21
84
  --epsilon <value> Epsilon value for guards (default: 1e-10)
85
+ --csharp-float-type <type> C# float precision: float (default) or double
22
86
  --help, -h Show this help message
23
87
 
24
88
  Examples:
25
89
  gradient-script angle.gs
26
90
  gradient-script angle.gs --format python
27
91
  gradient-script angle.gs --format javascript --no-comments
92
+ gradient-script angle.gs --format csharp
28
93
 
29
94
  Input File Format (.gs):
30
95
  function name(param1∇: {x, y}, param2∇) {
@@ -35,6 +100,16 @@ Input File Format (.gs):
35
100
 
36
101
  The ∇ symbol marks parameters that need gradients computed.
37
102
  Type annotations like {x, y} specify structured types.
103
+ All functions in the file are processed automatically.
104
+
105
+ For more information and examples:
106
+ https://github.com/mfagerlund/gradient-script
107
+
108
+ README (raw, LLM-friendly):
109
+ https://raw.githubusercontent.com/mfagerlund/gradient-script/main/README.md
110
+
111
+ LLM Optimization Guide (for AI agents writing .gs files):
112
+ https://raw.githubusercontent.com/mfagerlund/gradient-script/main/docs/LLM-OPTIMIZATION-GUIDE.md
38
113
  `.trim());
39
114
  }
40
115
  function main() {
@@ -54,12 +129,17 @@ function main() {
54
129
  simplify: true,
55
130
  cse: true
56
131
  };
132
+ let skipVerify = false;
57
133
  for (let i = 1; i < args.length; i++) {
58
134
  const arg = args[i];
59
135
  if (arg === '--format') {
136
+ if (i + 1 >= args.length) {
137
+ console.error('Error: Missing value for --format');
138
+ process.exit(1);
139
+ }
60
140
  const format = args[++i];
61
- if (format !== 'typescript' && format !== 'javascript' && format !== 'python') {
62
- console.error(`Error: Invalid format "${format}". Must be: typescript, javascript, or python`);
141
+ if (format !== 'typescript' && format !== 'javascript' && format !== 'python' && format !== 'csharp') {
142
+ console.error(`Error: Invalid format "${format}". Must be: typescript, javascript, python, or csharp`);
63
143
  process.exit(1);
64
144
  }
65
145
  options.format = format;
@@ -77,13 +157,29 @@ function main() {
77
157
  options.emitGuards = true;
78
158
  }
79
159
  else if (arg === '--epsilon') {
160
+ if (i + 1 >= args.length) {
161
+ console.error('Error: Missing value for --epsilon');
162
+ process.exit(1);
163
+ }
80
164
  const epsilonValue = parseFloat(args[++i]);
81
165
  if (isNaN(epsilonValue) || epsilonValue <= 0) {
82
- console.error(`Error: Invalid epsilon value. Must be a positive number.`);
166
+ console.error('Error: Invalid epsilon value. Must be a positive number.');
83
167
  process.exit(1);
84
168
  }
85
169
  options.epsilon = epsilonValue;
86
170
  }
171
+ else if (arg === '--csharp-float-type') {
172
+ if (i + 1 >= args.length) {
173
+ console.error('Error: Missing value for --csharp-float-type');
174
+ process.exit(1);
175
+ }
176
+ const floatType = args[++i];
177
+ if (floatType !== 'float' && floatType !== 'double') {
178
+ console.error(`Error: Invalid C# float type "${floatType}". Must be: float or double`);
179
+ process.exit(1);
180
+ }
181
+ options.csharpFloatType = floatType;
182
+ }
87
183
  else {
88
184
  console.error(`Error: Unknown option "${arg}"`);
89
185
  printUsage();
@@ -107,23 +203,42 @@ function main() {
107
203
  console.error('Error: No functions found in input file');
108
204
  process.exit(1);
109
205
  }
110
- const func = program.functions[0];
111
- if (program.functions.length > 1) {
112
- console.warn(`Warning: Multiple functions found, processing only "${func.name}"`);
113
- }
114
- const env = inferFunction(func);
115
- const gradients = computeFunctionGradients(func, env);
116
- // Analyze for edge cases
117
- const guardAnalysis = analyzeGuards(func);
118
- if (guardAnalysis.hasIssues) {
119
- console.error(formatGuardWarnings(guardAnalysis));
206
+ const outputs = [];
207
+ let hasVerificationFailure = false;
208
+ program.functions.forEach((func, index) => {
209
+ const env = inferFunction(func);
210
+ const gradients = computeFunctionGradients(func, env);
211
+ // MANDATORY gradient verification
212
+ const verified = verifyGradients(func, gradients, env);
213
+ if (!verified) {
214
+ hasVerificationFailure = true;
215
+ }
216
+ const guardAnalysis = analyzeGuards(func);
217
+ if (guardAnalysis.hasIssues) {
218
+ // Format warnings as comments so output remains valid code even if stderr is captured
219
+ console.error('// Function "' + func.name + '" may have edge cases:');
220
+ console.error(formatGuardWarnings(guardAnalysis, true));
221
+ }
222
+ const perFunctionOptions = { ...options };
223
+ if (index > 0 && perFunctionOptions.includeComments !== false) {
224
+ perFunctionOptions.includeComments = false;
225
+ }
226
+ const code = generateComplete(func, gradients, env, perFunctionOptions);
227
+ outputs.push(code);
228
+ });
229
+ if (hasVerificationFailure) {
230
+ console.error('// ERROR: Gradient verification failed. Output may contain incorrect gradients!');
231
+ process.exit(1);
120
232
  }
121
- const code = generateComplete(func, gradients, env, options);
122
- console.log(code);
233
+ console.log(outputs.join('\n\n'));
123
234
  }
124
235
  catch (err) {
125
- console.error('Error: Failed to process input file');
126
- if (err instanceof Error) {
236
+ if (err instanceof ParseError) {
237
+ // Use formatted error message for parse errors (always verbose with stack trace)
238
+ console.error(formatParseError(err, input, true));
239
+ }
240
+ else if (err instanceof Error) {
241
+ console.error('Error: Failed to process input file');
127
242
  console.error(err.message);
128
243
  if (err.stack) {
129
244
  console.error('\nStack trace:');
package/dist/dsl/AST.d.ts CHANGED
@@ -3,11 +3,19 @@
3
3
  * Supports function definitions with structured types
4
4
  */
5
5
  import { Type } from './Types.js';
6
+ /**
7
+ * Source location in the input file
8
+ */
9
+ export interface SourceLocation {
10
+ line: number;
11
+ column: number;
12
+ }
6
13
  /**
7
14
  * Base AST node
8
15
  */
9
16
  export interface ASTNode {
10
17
  type?: Type;
18
+ loc?: SourceLocation;
11
19
  }
12
20
  /**
13
21
  * Program (top-level)
@@ -9,19 +9,22 @@ import { GradientResult } from './Differentiation.js';
9
9
  * Code generation options
10
10
  */
11
11
  export interface CodeGenOptions {
12
- format?: 'typescript' | 'javascript' | 'python';
12
+ format?: 'typescript' | 'javascript' | 'python' | 'csharp';
13
13
  includeComments?: boolean;
14
14
  simplify?: boolean;
15
15
  cse?: boolean;
16
16
  epsilon?: number;
17
17
  emitGuards?: boolean;
18
+ csharpFloatType?: 'float' | 'double';
19
+ csharpNamingConvention?: 'camelCase' | 'PascalCase';
18
20
  }
19
21
  /**
20
22
  * Code generator for expressions
21
23
  */
22
24
  export declare class ExpressionCodeGen {
23
25
  private format;
24
- constructor(format?: 'typescript' | 'javascript' | 'python');
26
+ private csharpFloatType;
27
+ constructor(format?: 'typescript' | 'javascript' | 'python' | 'csharp', csharpFloatType?: 'float' | 'double');
25
28
  /**
26
29
  * Generate code for an expression
27
30
  */
@@ -44,6 +47,8 @@ export declare class ExpressionCodeGen {
44
47
  private genUnary;
45
48
  private genCall;
46
49
  private genComponent;
50
+ private static readonly MATH_FUNCTIONS;
51
+ private static readonly PYTHON_BUILTINS;
47
52
  private mapFunctionName;
48
53
  }
49
54
  /**
@@ -51,7 +56,7 @@ export declare class ExpressionCodeGen {
51
56
  */
52
57
  export declare function generateGradientFunction(func: FunctionDef, gradients: GradientResult, env: TypeEnv, options?: CodeGenOptions): string;
53
58
  /**
54
- * Generate the original forward function
59
+ * Generate the original forward function (with optional e-graph optimization)
55
60
  */
56
61
  export declare function generateForwardFunction(func: FunctionDef, options?: CodeGenOptions): string;
57
62
  /**