gradient-script 0.1.0 → 0.2.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 CHANGED
@@ -1,5 +1,13 @@
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.
@@ -10,7 +18,7 @@ GradientScript is a source-to-source compiler that automatically generates gradi
10
18
  - **Verified correctness**: Every gradient automatically checked against numerical differentiation
11
19
  - **Structured types**: Work with vectors `{x, y}` and custom structures, not just scalars
12
20
  - **Zero runtime overhead**: No tape, no graph - just pure gradient functions
13
- - **Multiple output languages**: TypeScript, JavaScript, or Python
21
+ - **Multiple output languages**: TypeScript, JavaScript, Python, or C#
14
22
  - **Readable output**: Human-reviewable formulas with automatic optimization
15
23
 
16
24
  ## Installation
@@ -230,13 +238,18 @@ function angle_between_grad(u, v) {
230
238
  gradient-script <file.gs> [options]
231
239
 
232
240
  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
241
+ --format <format> typescript (default), javascript, python, csharp
242
+ --no-simplify Disable gradient simplification
243
+ --no-cse Disable common subexpression elimination
244
+ --no-comments Omit comments in generated code
245
+ --guards Emit runtime guards for potential singularities
246
+ --epsilon <value> Epsilon value for guards (default: 1e-10)
247
+ --csharp-float-type <type> C# float precision: float (default) or double
248
+ --help, -h Show help message
238
249
  ```
239
250
 
251
+ GradientScript automatically generates gradient functions for all functions in your `.gs` file.
252
+
240
253
  **Examples:**
241
254
  ```bash
242
255
  # Generate TypeScript (default)
@@ -247,6 +260,12 @@ gradient-script spring.gs --format python
247
260
 
248
261
  # Generate JavaScript without CSE optimization
249
262
  gradient-script spring.gs --format javascript --no-cse
263
+
264
+ # Generate C# for Unity/Godot (float precision)
265
+ gradient-script spring.gs --format csharp
266
+
267
+ # Generate C# with double precision
268
+ gradient-script spring.gs --format csharp --csharp-float-type double
250
269
  ```
251
270
 
252
271
  ## Language Syntax
@@ -454,7 +473,7 @@ GradientScript includes a comprehensive test suite that validates all generated
454
473
  npm test
455
474
  ```
456
475
 
457
- Current status: **78 tests passing**
476
+ Current status: **129 tests passing**
458
477
 
459
478
  Test suite includes:
460
479
 
@@ -479,7 +498,7 @@ Test suite includes:
479
498
  - CSE optimization correctness
480
499
  - Operator precedence preservation
481
500
  - Power optimization (x*x vs Math.pow)
482
- - Multiple output formats (TypeScript, JavaScript, Python)
501
+ - Multiple output formats (TypeScript, JavaScript, Python, C#)
483
502
  - Algebraic simplification correctness
484
503
 
485
504
  **Key guarantee**: If a test passes, the generated gradient is correct to within numerical precision (~10 decimal places).
@@ -506,6 +525,28 @@ GradientScript is under active development. Contributions welcome!
506
525
  - Web playground for live gradient generation
507
526
  - Benchmarking suite
508
527
 
528
+ ## Examples
529
+
530
+ See the `examples/` directory for complete examples:
531
+
532
+ - **Physics Constraints**: [`examples/PHYSICS_EXAMPLES.md`](examples/PHYSICS_EXAMPLES.md) - Comprehensive guide to using structured types for XPBD constraints, rigid body dynamics, and more
533
+ - Raw (LLM-friendly): `https://raw.githubusercontent.com/mfagerlund/gradient-script/main/examples/PHYSICS_EXAMPLES.md`
534
+ - **XPBD Constraints**: `xpbd-rod-constraint.gs`, `xpbd-angle-constraint.gs`
535
+ - **Distance Functions**: `distance.gs`, `point-segment-distance.gs`
536
+ - **Geometry**: `triangle-area.gs`, `bearing.gs`, `circle-fit.gs`
537
+
538
+ **Try them:**
539
+ ```bash
540
+ # View physics examples guide
541
+ cat examples/PHYSICS_EXAMPLES.md
542
+
543
+ # Generate TypeScript from XPBD rod constraint
544
+ gradient-script examples/xpbd-rod-constraint.gs
545
+
546
+ # Generate C# for Unity/Godot
547
+ gradient-script examples/xpbd-angle-constraint.gs --format csharp
548
+ ```
549
+
509
550
  ## License
510
551
 
511
552
  MIT
package/dist/cli.js CHANGED
@@ -5,6 +5,7 @@ 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';
8
9
  function printUsage() {
9
10
  console.log(`
10
11
  GradientScript - Symbolic Differentiation for Structured Types
@@ -13,18 +14,20 @@ Usage:
13
14
  gradient-script <file.gs> [options]
14
15
 
15
16
  Options:
16
- --format <format> Output format: typescript (default), javascript, python
17
+ --format <format> Output format: typescript (default), javascript, python, csharp
17
18
  --no-simplify Disable gradient simplification
18
19
  --no-cse Disable common subexpression elimination
19
20
  --no-comments Omit comments in generated code
20
21
  --guards Emit runtime guards for division by zero (experimental)
21
22
  --epsilon <value> Epsilon value for guards (default: 1e-10)
23
+ --csharp-float-type <type> C# float precision: float (default) or double
22
24
  --help, -h Show this help message
23
25
 
24
26
  Examples:
25
27
  gradient-script angle.gs
26
28
  gradient-script angle.gs --format python
27
29
  gradient-script angle.gs --format javascript --no-comments
30
+ gradient-script angle.gs --format csharp
28
31
 
29
32
  Input File Format (.gs):
30
33
  function name(param1∇: {x, y}, param2∇) {
@@ -35,6 +38,13 @@ Input File Format (.gs):
35
38
 
36
39
  The ∇ symbol marks parameters that need gradients computed.
37
40
  Type annotations like {x, y} specify structured types.
41
+ All functions in the file are processed automatically.
42
+
43
+ For more information and examples:
44
+ https://github.com/mfagerlund/gradient-script
45
+
46
+ README (raw, LLM-friendly):
47
+ https://raw.githubusercontent.com/mfagerlund/gradient-script/main/README.md
38
48
  `.trim());
39
49
  }
40
50
  function main() {
@@ -57,9 +67,13 @@ function main() {
57
67
  for (let i = 1; i < args.length; i++) {
58
68
  const arg = args[i];
59
69
  if (arg === '--format') {
70
+ if (i + 1 >= args.length) {
71
+ console.error('Error: Missing value for --format');
72
+ process.exit(1);
73
+ }
60
74
  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`);
75
+ if (format !== 'typescript' && format !== 'javascript' && format !== 'python' && format !== 'csharp') {
76
+ console.error(`Error: Invalid format "${format}". Must be: typescript, javascript, python, or csharp`);
63
77
  process.exit(1);
64
78
  }
65
79
  options.format = format;
@@ -77,13 +91,29 @@ function main() {
77
91
  options.emitGuards = true;
78
92
  }
79
93
  else if (arg === '--epsilon') {
94
+ if (i + 1 >= args.length) {
95
+ console.error('Error: Missing value for --epsilon');
96
+ process.exit(1);
97
+ }
80
98
  const epsilonValue = parseFloat(args[++i]);
81
99
  if (isNaN(epsilonValue) || epsilonValue <= 0) {
82
- console.error(`Error: Invalid epsilon value. Must be a positive number.`);
100
+ console.error('Error: Invalid epsilon value. Must be a positive number.');
83
101
  process.exit(1);
84
102
  }
85
103
  options.epsilon = epsilonValue;
86
104
  }
105
+ else if (arg === '--csharp-float-type') {
106
+ if (i + 1 >= args.length) {
107
+ console.error('Error: Missing value for --csharp-float-type');
108
+ process.exit(1);
109
+ }
110
+ const floatType = args[++i];
111
+ if (floatType !== 'float' && floatType !== 'double') {
112
+ console.error(`Error: Invalid C# float type "${floatType}". Must be: float or double`);
113
+ process.exit(1);
114
+ }
115
+ options.csharpFloatType = floatType;
116
+ }
87
117
  else {
88
118
  console.error(`Error: Unknown option "${arg}"`);
89
119
  printUsage();
@@ -107,23 +137,31 @@ function main() {
107
137
  console.error('Error: No functions found in input file');
108
138
  process.exit(1);
109
139
  }
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));
120
- }
121
- const code = generateComplete(func, gradients, env, options);
122
- console.log(code);
140
+ const outputs = [];
141
+ program.functions.forEach((func, index) => {
142
+ const env = inferFunction(func);
143
+ const gradients = computeFunctionGradients(func, env);
144
+ const guardAnalysis = analyzeGuards(func);
145
+ if (guardAnalysis.hasIssues) {
146
+ console.error('Function "' + func.name + '" may have edge cases:');
147
+ console.error(formatGuardWarnings(guardAnalysis));
148
+ }
149
+ const perFunctionOptions = { ...options };
150
+ if (index > 0 && perFunctionOptions.includeComments !== false) {
151
+ perFunctionOptions.includeComments = false;
152
+ }
153
+ const code = generateComplete(func, gradients, env, perFunctionOptions);
154
+ outputs.push(code);
155
+ });
156
+ console.log(outputs.join('\n\n'));
123
157
  }
124
158
  catch (err) {
125
- console.error('Error: Failed to process input file');
126
- if (err instanceof Error) {
159
+ if (err instanceof ParseError) {
160
+ // Use formatted error message for parse errors (always verbose with stack trace)
161
+ console.error(formatParseError(err, input, true));
162
+ }
163
+ else if (err instanceof Error) {
164
+ console.error('Error: Failed to process input file');
127
165
  console.error(err.message);
128
166
  if (err.stack) {
129
167
  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)
package/dist/dsl/CSE.js CHANGED
@@ -3,33 +3,7 @@
3
3
  * Identifies repeated expressions and factors them out
4
4
  */
5
5
  import { ExpressionTransformer } from './ExpressionTransformer.js';
6
- /**
7
- * Serializes expressions to canonical string form for comparison
8
- * This is a dedicated serializer that doesn't abuse the type system
9
- */
10
- class ExpressionSerializer {
11
- serialize(expr) {
12
- switch (expr.kind) {
13
- case 'number':
14
- return `num(${expr.value})`;
15
- case 'variable':
16
- return `var(${expr.name})`;
17
- case 'binary':
18
- const left = this.serialize(expr.left);
19
- const right = this.serialize(expr.right);
20
- return `bin(${expr.operator},${left},${right})`;
21
- case 'unary':
22
- const operand = this.serialize(expr.operand);
23
- return `un(${expr.operator},${operand})`;
24
- case 'call':
25
- const args = expr.args.map(arg => this.serialize(arg)).join(',');
26
- return `call(${expr.name},${args})`;
27
- case 'component':
28
- const object = this.serialize(expr.object);
29
- return `comp(${object},${expr.component})`;
30
- }
31
- }
32
- }
6
+ import { serializeExpression } from './ExpressionUtils.js';
33
7
  /**
34
8
  * Perform CSE on an expression
35
9
  */
@@ -105,12 +79,11 @@ function shouldExtract(expr) {
105
79
  class ExpressionCounter extends ExpressionTransformer {
106
80
  counts = new Map();
107
81
  expressions = new Map();
108
- serializer = new ExpressionSerializer();
109
82
  count(expr) {
110
83
  this.transform(expr);
111
84
  }
112
85
  serialize(expr) {
113
- return this.serializer.serialize(expr);
86
+ return serializeExpression(expr);
114
87
  }
115
88
  recordExpression(expr) {
116
89
  const key = this.serialize(expr);
@@ -167,8 +140,9 @@ function substituteExpressions(expr, subexprMap, counter) {
167
140
  }
168
141
  /**
169
142
  * Transformer that substitutes a pattern with a replacement expression
143
+ * Used for CSE optimization to replace repeated subexpressions with intermediate variables
170
144
  */
171
- class SubstitutionTransformer extends ExpressionTransformer {
145
+ class PatternSubstitutionTransformer extends ExpressionTransformer {
172
146
  pattern;
173
147
  replacement;
174
148
  counter;
@@ -189,6 +163,6 @@ class SubstitutionTransformer extends ExpressionTransformer {
189
163
  * Helper to substitute an expression pattern with a replacement
190
164
  */
191
165
  function substituteInExpression(expr, pattern, replacement, counter) {
192
- const transformer = new SubstitutionTransformer(pattern, replacement, counter);
166
+ const transformer = new PatternSubstitutionTransformer(pattern, replacement, counter);
193
167
  return transformer.transform(expr);
194
168
  }
@@ -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
  /**