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 +49 -8
- package/dist/cli.js +57 -19
- package/dist/dsl/AST.d.ts +8 -0
- package/dist/dsl/CSE.js +5 -31
- package/dist/dsl/CodeGen.d.ts +7 -2
- package/dist/dsl/CodeGen.js +259 -66
- package/dist/dsl/Errors.d.ts +6 -1
- package/dist/dsl/Errors.js +70 -1
- package/dist/dsl/Expander.js +5 -2
- package/dist/dsl/ExpressionUtils.d.ts +8 -0
- package/dist/dsl/ExpressionUtils.js +24 -0
- package/dist/dsl/Guards.d.ts +2 -0
- package/dist/dsl/Guards.js +78 -36
- package/dist/dsl/Inliner.js +3 -2
- package/dist/dsl/Lexer.js +3 -1
- package/dist/dsl/Parser.js +11 -5
- package/dist/dsl/Simplify.js +47 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# GradientScript
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/gradient-script)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://github.com/mfagerlund/gradient-script/releases)
|
|
6
|
+
[](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
|
|
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>
|
|
234
|
-
--no-simplify
|
|
235
|
-
--no-cse
|
|
236
|
-
--no-comments
|
|
237
|
-
--
|
|
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: **
|
|
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
|
|
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(
|
|
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
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
126
|
-
|
|
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
|
|
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
|
|
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
|
|
166
|
+
const transformer = new PatternSubstitutionTransformer(pattern, replacement, counter);
|
|
193
167
|
return transformer.transform(expr);
|
|
194
168
|
}
|
package/dist/dsl/CodeGen.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
/**
|