@danielsimonjr/mathts-autograd 0.1.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.
@@ -0,0 +1,168 @@
1
+ import { Tensor } from '@danielsimonjr/mathts-tensor';
2
+
3
+ /**
4
+ * DualTensor — a Tensor + per-element tangent component for forward-mode AD.
5
+ * Storage: two Float64Arrays of equal length (primal + tangent), shape from
6
+ * the wrapped Tensor. Arithmetic follows the dual-number rules:
7
+ * (a, a') + (b, b') = (a+b, a'+b')
8
+ * (a, a') * (b, b') = (a·b, a·b' + a'·b)
9
+ * (a, a') / (b, b') = (a/b, (a'·b − a·b')/b²)
10
+ * scale((a, a'), k) = (k·a, k·a')
11
+ * Elementwise unless noted. Reductions and contractions add their own rules.
12
+ *
13
+ * The dual-number framework: tracking ε such that ε²=0; (a + a'·ε)(b + b'·ε)
14
+ * = ab + (ab'+a'b)ε, so the tangent is the linearized first-order response.
15
+ *
16
+ * @packageDocumentation
17
+ */
18
+
19
+ declare class DualTensor {
20
+ readonly shape: ReadonlyArray<number>;
21
+ readonly primal: Float64Array;
22
+ readonly tangent: Float64Array;
23
+ constructor(shape: ReadonlyArray<number>, primal: Float64Array, tangent: Float64Array);
24
+ /**
25
+ * S5 fix: existing engine ops (e.g. lower, pderiv, contract) reach into
26
+ * `.data`. The getter returns the primal so those ops still work when a
27
+ * DualTensor flows through them as a structurally-compatible Tensor.
28
+ * (AD-aware ops branch on `'tangent' in arg` before reaching here.)
29
+ */
30
+ get data(): Float64Array;
31
+ /** Lift a Tensor to a DualTensor with zero tangent. */
32
+ static fromTensor(t: Tensor): DualTensor;
33
+ /** Lift a Tensor to a DualTensor with a unit tangent at flat-index `i`. */
34
+ static unitAt(t: Tensor, i: number): DualTensor;
35
+ /** Extract the primal as a plain Tensor. */
36
+ toPrimalTensor(): Tensor;
37
+ /** Extract the tangent as a plain Tensor. */
38
+ toTangentTensor(): Tensor;
39
+ /** Elementwise addition following dual-number rule: (a+b, a'+b'). */
40
+ add(other: DualTensor): DualTensor;
41
+ /** Elementwise subtraction following dual-number rule: (a-b, a'-b'). */
42
+ sub(other: DualTensor): DualTensor;
43
+ /**
44
+ * Elementwise multiplication following dual-number rule: (a·b, a·b' + a'·b).
45
+ *
46
+ * I3 fix: explicit alias check. When `this === other` (self-multiplication),
47
+ * use the specialised rule (a·a)' = 2·a·a' directly. The general rule also
48
+ * degenerates correctly, but the explicit branch is more readable and aligns
49
+ * the forward-mode implementation with the reverse-mode alias fix.
50
+ */
51
+ mul(other: DualTensor): DualTensor;
52
+ /** Scalar multiplication following dual-number rule: (k·a, k·a'). */
53
+ scale(k: number): DualTensor;
54
+ private checkSameShape;
55
+ }
56
+
57
+ /**
58
+ * Forward-mode AD via dual numbers. Returns the value and the full Jacobian
59
+ * of fn at x, shape [...value.shape, ...x.shape] (row-major flatten).
60
+ *
61
+ * Implementation: for each flat-index k in x, build a DualTensor with a unit
62
+ * tangent at position k, run fn (which operates on Tensors — see "interop"
63
+ * below), extract the tangent of the result. The tangent is the k-th column
64
+ * of the Jacobian (in row-major-of-output-flattened form).
65
+ *
66
+ * Interop with the Tensor type: fn is typed `(x: Tensor) => Tensor`, so the
67
+ * tracing requires fn's body to be implementable on either Tensor OR
68
+ * DualTensor. v0.1.0 ships a documented "trace mode" — if fn uses only the
69
+ * core ops that DualTensor implements (add/sub/mul/scale), the
70
+ * implementation auto-traces by detecting DualTensor inputs via a thin
71
+ * wrapper. The actual mechanism in v0.1.0: caller writes fn as if Tensor's
72
+ * methods accept either Tensor or DualTensor (TypeScript's structural typing
73
+ * makes this work for the implemented ops). v0.2.0 may broaden via decorator
74
+ * instrumentation.
75
+ *
76
+ * For UPT v0.4.0's call-sites (metric functions in christoffel lowering),
77
+ * fn uses only add/sub/mul/scale → safe.
78
+ *
79
+ * @packageDocumentation
80
+ */
81
+
82
+ /**
83
+ * Compute the value and full Jacobian of `fn` at point `x` using forward-mode
84
+ * automatic differentiation (dual numbers).
85
+ *
86
+ * @param fn - Pure function operating on Tensors via add/sub/mul/scale.
87
+ * Must be traceable: at runtime it receives a DualTensor (structurally
88
+ * compatible with Tensor) and must return a DualTensor.
89
+ * @param x - Input tensor at which to differentiate.
90
+ * @returns `{ value, jacobian }` where:
91
+ * - `value` has the output shape of fn applied to x
92
+ * - `jacobian` has shape `[...value.shape, ...x.shape]` row-major.
93
+ * `jacobian.data[kY * xSize + kX]` = ∂y[kY] / ∂x[kX].
94
+ * Verified by Adam+Eve review 2026-05-15 (E13): kY outer, kX inner,
95
+ * stride xSize between successive y-rows IS row-major [...y.shape, ...x.shape].
96
+ */
97
+ declare function forwardGrad(fn: (x: Tensor) => Tensor, x: Tensor): {
98
+ value: Tensor;
99
+ jacobian: Tensor;
100
+ };
101
+
102
+ /**
103
+ * Tape — records the sequence of ops during a forward pass so we can
104
+ * replay them in reverse to compute the vector-Jacobian product.
105
+ *
106
+ * Each TapedTensor wraps a primal Tensor + a tape-node id. Ops on
107
+ * TapedTensors record their backward closures into a shared Tape.
108
+ * After the forward pass, calling Tape.backward(outputGrad) walks the
109
+ * tape in reverse, accumulating gradients into each input slot.
110
+ *
111
+ * v0.1.0 supports the same ops as DualTensor: add, sub, mul, scale.
112
+ */
113
+
114
+ type BackwardFn = (outputGrad: Float64Array) => void;
115
+ declare class Tape {
116
+ private nodes;
117
+ private inputGradSlots;
118
+ private nextOpId;
119
+ private nextInputId;
120
+ /** Allocate a fresh id for an input or intermediate. */
121
+ allocate(size: number): {
122
+ id: number;
123
+ gradSlot: Float64Array;
124
+ };
125
+ record(inputIds: ReadonlyArray<number>, outputSize: number, backward: BackwardFn): {
126
+ id: number;
127
+ gradSlot: Float64Array;
128
+ };
129
+ /** Seed the final output's gradient slot, then replay in reverse. */
130
+ backward(outputId: number, outputGrad: Float64Array): void;
131
+ getInputGrad(id: number): Float64Array | undefined;
132
+ }
133
+ declare class TapedTensor {
134
+ readonly shape: ReadonlyArray<number>;
135
+ readonly primal: Float64Array;
136
+ readonly tape: Tape;
137
+ readonly id: number;
138
+ constructor(shape: ReadonlyArray<number>, primal: Float64Array, tape: Tape, id: number);
139
+ /**
140
+ * S5 fix: existing engine ops (e.g. lower, pderiv, contract) reach into
141
+ * `.data`. The getter returns the primal so those ops still work when a
142
+ * TapedTensor flows through them as a structurally-compatible Tensor.
143
+ * (AD-aware ops branch on `'tape' in arg` before reaching here.)
144
+ */
145
+ get data(): Float64Array;
146
+ static fromTensorAsInput(t: Tensor, tape: Tape): TapedTensor;
147
+ toPrimalTensor(): Tensor;
148
+ add(other: TapedTensor): TapedTensor;
149
+ sub(other: TapedTensor): TapedTensor;
150
+ mul(other: TapedTensor): TapedTensor;
151
+ scale(k: number): TapedTensor;
152
+ private checkSameShape;
153
+ }
154
+
155
+ /**
156
+ * Reverse-mode AD via tape. Returns the value and the vector-Jacobian product
157
+ * (gradient = ∂(cotangent · value) / ∂x). gradient.shape = x.shape.
158
+ *
159
+ * Default cotangent: ones-like(value) — required for scalar outputs, useful
160
+ * for VJP defaults. For non-scalar value, cotangent's shape must match value.shape.
161
+ */
162
+
163
+ declare function reverseGrad(fn: (x: Tensor) => Tensor, x: Tensor, cotangent?: Tensor): {
164
+ value: Tensor;
165
+ gradient: Tensor;
166
+ };
167
+
168
+ export { DualTensor, Tape, TapedTensor, forwardGrad, reverseGrad };
package/dist/index.js ADDED
@@ -0,0 +1,305 @@
1
+ // src/dual-tensor.ts
2
+ import { Tensor } from "@danielsimonjr/mathts-tensor";
3
+ var DualTensor = class _DualTensor {
4
+ shape;
5
+ primal;
6
+ tangent;
7
+ constructor(shape, primal, tangent) {
8
+ if (primal.length !== tangent.length) {
9
+ throw new Error(`DualTensor: primal length ${primal.length} != tangent length ${tangent.length}`);
10
+ }
11
+ this.shape = shape;
12
+ this.primal = primal;
13
+ this.tangent = tangent;
14
+ }
15
+ /**
16
+ * S5 fix: existing engine ops (e.g. lower, pderiv, contract) reach into
17
+ * `.data`. The getter returns the primal so those ops still work when a
18
+ * DualTensor flows through them as a structurally-compatible Tensor.
19
+ * (AD-aware ops branch on `'tangent' in arg` before reaching here.)
20
+ */
21
+ get data() {
22
+ return this.primal;
23
+ }
24
+ /** Lift a Tensor to a DualTensor with zero tangent. */
25
+ static fromTensor(t) {
26
+ return new _DualTensor(t.shape, new Float64Array(t.data), new Float64Array(t.data.length));
27
+ }
28
+ /** Lift a Tensor to a DualTensor with a unit tangent at flat-index `i`. */
29
+ static unitAt(t, i) {
30
+ const tan = new Float64Array(t.data.length);
31
+ tan[i] = 1;
32
+ return new _DualTensor(t.shape, new Float64Array(t.data), tan);
33
+ }
34
+ /** Extract the primal as a plain Tensor. */
35
+ toPrimalTensor() {
36
+ return new Tensor(this.shape, new Float64Array(this.primal));
37
+ }
38
+ /** Extract the tangent as a plain Tensor. */
39
+ toTangentTensor() {
40
+ return new Tensor(this.shape, new Float64Array(this.tangent));
41
+ }
42
+ /** Elementwise addition following dual-number rule: (a+b, a'+b'). */
43
+ add(other) {
44
+ this.checkSameShape(other, "add");
45
+ const p = new Float64Array(this.primal.length);
46
+ const t = new Float64Array(this.tangent.length);
47
+ for (let i = 0; i < p.length; i++) {
48
+ p[i] = this.primal[i] + other.primal[i];
49
+ t[i] = this.tangent[i] + other.tangent[i];
50
+ }
51
+ return new _DualTensor(this.shape, p, t);
52
+ }
53
+ /** Elementwise subtraction following dual-number rule: (a-b, a'-b'). */
54
+ sub(other) {
55
+ this.checkSameShape(other, "sub");
56
+ const p = new Float64Array(this.primal.length);
57
+ const t = new Float64Array(this.tangent.length);
58
+ for (let i = 0; i < p.length; i++) {
59
+ p[i] = this.primal[i] - other.primal[i];
60
+ t[i] = this.tangent[i] - other.tangent[i];
61
+ }
62
+ return new _DualTensor(this.shape, p, t);
63
+ }
64
+ /**
65
+ * Elementwise multiplication following dual-number rule: (a·b, a·b' + a'·b).
66
+ *
67
+ * I3 fix: explicit alias check. When `this === other` (self-multiplication),
68
+ * use the specialised rule (a·a)' = 2·a·a' directly. The general rule also
69
+ * degenerates correctly, but the explicit branch is more readable and aligns
70
+ * the forward-mode implementation with the reverse-mode alias fix.
71
+ */
72
+ mul(other) {
73
+ this.checkSameShape(other, "mul");
74
+ const p = new Float64Array(this.primal.length);
75
+ const t = new Float64Array(this.tangent.length);
76
+ if (this === other) {
77
+ for (let i = 0; i < p.length; i++) {
78
+ p[i] = this.primal[i] * this.primal[i];
79
+ t[i] = 2 * this.primal[i] * this.tangent[i];
80
+ }
81
+ } else {
82
+ for (let i = 0; i < p.length; i++) {
83
+ p[i] = this.primal[i] * other.primal[i];
84
+ t[i] = this.tangent[i] * other.primal[i] + this.primal[i] * other.tangent[i];
85
+ }
86
+ }
87
+ return new _DualTensor(this.shape, p, t);
88
+ }
89
+ /** Scalar multiplication following dual-number rule: (k·a, k·a'). */
90
+ scale(k) {
91
+ const p = new Float64Array(this.primal.length);
92
+ const t = new Float64Array(this.tangent.length);
93
+ for (let i = 0; i < p.length; i++) {
94
+ p[i] = this.primal[i] * k;
95
+ t[i] = this.tangent[i] * k;
96
+ }
97
+ return new _DualTensor(this.shape, p, t);
98
+ }
99
+ checkSameShape(other, op) {
100
+ if (this.shape.length !== other.shape.length || !this.shape.every((v, i) => v === other.shape[i])) {
101
+ throw new Error(`DualTensor.${op}: shape mismatch [${this.shape}] vs [${other.shape}]`);
102
+ }
103
+ }
104
+ };
105
+
106
+ // src/forward-grad.ts
107
+ import { Tensor as Tensor2 } from "@danielsimonjr/mathts-tensor";
108
+ function forwardGrad(fn, x) {
109
+ const xDualZero = DualTensor.fromTensor(x);
110
+ const yProbeRaw = fn(xDualZero);
111
+ if (!(yProbeRaw instanceof DualTensor)) {
112
+ throw new Error(
113
+ "forwardGrad: fn must be AD-traceable \u2014 its return must propagate through DualTensor arithmetic (use add/sub/mul/scale on the argument). A fresh Tensor return loses the tangent and silently corrupts the Jacobian."
114
+ );
115
+ }
116
+ const yPrimal = yProbeRaw.toPrimalTensor();
117
+ const jacobianShape = [...yPrimal.shape, ...x.shape];
118
+ const jacobianSize = jacobianShape.reduce((a, b) => a * b, 1);
119
+ const jacobianData = new Float64Array(jacobianSize);
120
+ const xSize = x.data.length;
121
+ const ySize = yPrimal.data.length;
122
+ for (let kX = 0; kX < xSize; kX++) {
123
+ const xDualUnit = DualTensor.unitAt(x, kX);
124
+ const yDualRaw = fn(xDualUnit);
125
+ if (!(yDualRaw instanceof DualTensor)) {
126
+ throw new Error("forwardGrad: fn lost AD trace mid-sweep (returned non-DualTensor)");
127
+ }
128
+ const yDual = yDualRaw;
129
+ for (let kY = 0; kY < ySize; kY++) {
130
+ jacobianData[kY * xSize + kX] = yDual.tangent[kY];
131
+ }
132
+ }
133
+ return {
134
+ value: yPrimal,
135
+ jacobian: new Tensor2(jacobianShape, jacobianData)
136
+ };
137
+ }
138
+
139
+ // src/tape.ts
140
+ import { Tensor as Tensor3 } from "@danielsimonjr/mathts-tensor";
141
+ var Tape = class {
142
+ nodes = [];
143
+ inputGradSlots = /* @__PURE__ */ new Map();
144
+ // S3 fix: disjoint ID namespaces. Inputs use negative IDs, ops use
145
+ // non-negative IDs. Eliminates the v0.4.0-review collision where
146
+ // id = nodes.length + 1000 would collide with op ids past 1000 entries.
147
+ nextOpId = 0;
148
+ nextInputId = -1;
149
+ // negatives = inputs; non-negatives = ops
150
+ /** Allocate a fresh id for an input or intermediate. */
151
+ allocate(size) {
152
+ const id = this.nextInputId--;
153
+ const gradSlot = new Float64Array(size);
154
+ this.inputGradSlots.set(id, gradSlot);
155
+ return { id, gradSlot };
156
+ }
157
+ record(inputIds, outputSize, backward) {
158
+ const outputGradSlot = new Float64Array(outputSize);
159
+ const id = this.nextOpId++;
160
+ this.nodes.push({ inputIds, backward, outputGradSlot });
161
+ this.inputGradSlots.set(id, outputGradSlot);
162
+ return { id, gradSlot: outputGradSlot };
163
+ }
164
+ /** Seed the final output's gradient slot, then replay in reverse. */
165
+ backward(outputId, outputGrad) {
166
+ const slot = this.inputGradSlots.get(outputId);
167
+ if (!slot) throw new Error(`Tape.backward: unknown outputId ${outputId}`);
168
+ slot.set(outputGrad);
169
+ for (let n = this.nodes.length - 1; n >= 0; n--) {
170
+ this.nodes[n].backward(this.nodes[n].outputGradSlot);
171
+ }
172
+ }
173
+ getInputGrad(id) {
174
+ return this.inputGradSlots.get(id);
175
+ }
176
+ };
177
+ var TapedTensor = class _TapedTensor {
178
+ constructor(shape, primal, tape, id) {
179
+ this.shape = shape;
180
+ this.primal = primal;
181
+ this.tape = tape;
182
+ this.id = id;
183
+ }
184
+ /**
185
+ * S5 fix: existing engine ops (e.g. lower, pderiv, contract) reach into
186
+ * `.data`. The getter returns the primal so those ops still work when a
187
+ * TapedTensor flows through them as a structurally-compatible Tensor.
188
+ * (AD-aware ops branch on `'tape' in arg` before reaching here.)
189
+ */
190
+ get data() {
191
+ return this.primal;
192
+ }
193
+ static fromTensorAsInput(t, tape) {
194
+ const { id } = tape.allocate(t.data.length);
195
+ return new _TapedTensor(t.shape, new Float64Array(t.data), tape, id);
196
+ }
197
+ toPrimalTensor() {
198
+ return new Tensor3(this.shape, new Float64Array(this.primal));
199
+ }
200
+ add(other) {
201
+ this.checkSameShape(other, "add");
202
+ const out = new Float64Array(this.primal.length);
203
+ for (let i = 0; i < out.length; i++) out[i] = this.primal[i] + other.primal[i];
204
+ const thisGradSlot = this.tape.getInputGrad(this.id);
205
+ const otherGradSlot = this.tape.getInputGrad(other.id);
206
+ const { id } = this.tape.record([this.id, other.id], out.length, (outputGrad) => {
207
+ for (let i = 0; i < outputGrad.length; i++) {
208
+ thisGradSlot[i] += outputGrad[i];
209
+ otherGradSlot[i] += outputGrad[i];
210
+ }
211
+ });
212
+ return new _TapedTensor(this.shape, out, this.tape, id);
213
+ }
214
+ sub(other) {
215
+ this.checkSameShape(other, "sub");
216
+ const out = new Float64Array(this.primal.length);
217
+ for (let i = 0; i < out.length; i++) out[i] = this.primal[i] - other.primal[i];
218
+ const thisGradSlot = this.tape.getInputGrad(this.id);
219
+ const otherGradSlot = this.tape.getInputGrad(other.id);
220
+ const { id } = this.tape.record([this.id, other.id], out.length, (outputGrad) => {
221
+ for (let i = 0; i < outputGrad.length; i++) {
222
+ thisGradSlot[i] += outputGrad[i];
223
+ otherGradSlot[i] -= outputGrad[i];
224
+ }
225
+ });
226
+ return new _TapedTensor(this.shape, out, this.tape, id);
227
+ }
228
+ mul(other) {
229
+ this.checkSameShape(other, "mul");
230
+ const out = new Float64Array(this.primal.length);
231
+ for (let i = 0; i < out.length; i++) out[i] = this.primal[i] * other.primal[i];
232
+ const thisPrimal = this.primal;
233
+ const otherPrimal = other.primal;
234
+ const thisGradSlot = this.tape.getInputGrad(this.id);
235
+ const otherGradSlot = this.tape.getInputGrad(other.id);
236
+ const isAliased = this === other;
237
+ const { id } = this.tape.record([this.id, other.id], out.length, (outputGrad) => {
238
+ for (let i = 0; i < outputGrad.length; i++) {
239
+ if (isAliased) {
240
+ thisGradSlot[i] += 2 * outputGrad[i] * thisPrimal[i];
241
+ } else {
242
+ thisGradSlot[i] += outputGrad[i] * otherPrimal[i];
243
+ otherGradSlot[i] += outputGrad[i] * thisPrimal[i];
244
+ }
245
+ }
246
+ });
247
+ return new _TapedTensor(this.shape, out, this.tape, id);
248
+ }
249
+ scale(k) {
250
+ const out = new Float64Array(this.primal.length);
251
+ for (let i = 0; i < out.length; i++) out[i] = this.primal[i] * k;
252
+ const thisGradSlot = this.tape.getInputGrad(this.id);
253
+ const { id } = this.tape.record([this.id], out.length, (outputGrad) => {
254
+ for (let i = 0; i < outputGrad.length; i++) {
255
+ thisGradSlot[i] += outputGrad[i] * k;
256
+ }
257
+ });
258
+ return new _TapedTensor(this.shape, out, this.tape, id);
259
+ }
260
+ checkSameShape(other, op) {
261
+ if (this.shape.length !== other.shape.length || !this.shape.every((v, i) => v === other.shape[i])) {
262
+ throw new Error(`TapedTensor.${op}: shape mismatch [${this.shape}] vs [${other.shape}]`);
263
+ }
264
+ }
265
+ };
266
+
267
+ // src/reverse-grad.ts
268
+ import { Tensor as Tensor4 } from "@danielsimonjr/mathts-tensor";
269
+ function reverseGrad(fn, x, cotangent) {
270
+ const tape = new Tape();
271
+ const xTaped = TapedTensor.fromTensorAsInput(x, tape);
272
+ const yRaw = fn(xTaped);
273
+ if (!(yRaw instanceof TapedTensor)) {
274
+ throw new Error(
275
+ "reverseGrad: fn must be AD-traceable \u2014 its return must propagate through TapedTensor arithmetic (use add/sub/mul/scale on the argument). A fresh Tensor return loses the tape and silently corrupts the gradient."
276
+ );
277
+ }
278
+ const yTaped = yRaw;
279
+ const value = yTaped.toPrimalTensor();
280
+ let ct;
281
+ if (cotangent === void 0) {
282
+ const data = new Float64Array(value.data.length).fill(1);
283
+ ct = new Tensor4(value.shape, data);
284
+ } else {
285
+ if (cotangent.shape.length !== value.shape.length || !cotangent.shape.every((v, i) => v === value.shape[i])) {
286
+ throw new Error(
287
+ `reverseGrad: cotangent shape [${cotangent.shape}] != value shape [${value.shape}]`
288
+ );
289
+ }
290
+ ct = cotangent;
291
+ }
292
+ tape.backward(yTaped.id, new Float64Array(ct.data));
293
+ const xGradSlot = tape.getInputGrad(xTaped.id);
294
+ return {
295
+ value,
296
+ gradient: new Tensor4(x.shape, new Float64Array(xGradSlot))
297
+ };
298
+ }
299
+ export {
300
+ DualTensor,
301
+ Tape,
302
+ TapedTensor,
303
+ forwardGrad,
304
+ reverseGrad
305
+ };
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@danielsimonjr/mathts-autograd",
3
+ "version": "0.1.0",
4
+ "description": "Forward + reverse-mode automatic differentiation for MathTS rank-N Tensor",
5
+ "author": "Daniel Simon Jr.",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "main": "./dist/index.js",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "README.md"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsup src/index.ts --format esm --dts --clean",
23
+ "dev": "tsup src/index.ts --format esm --dts --watch",
24
+ "test": "vitest run",
25
+ "test:watch": "vitest",
26
+ "test:coverage": "vitest run --coverage",
27
+ "typecheck": "tsc --noEmit",
28
+ "lint": "eslint src --ext .ts",
29
+ "lint:fix": "eslint src --ext .ts --fix",
30
+ "clean": "rm -rf dist",
31
+ "build:prod": "tsup src/index.ts --format esm --dts --clean --minify --treeshake"
32
+ },
33
+ "dependencies": {
34
+ "@danielsimonjr/mathts-core": "*",
35
+ "@danielsimonjr/mathts-tensor": "*"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^25.5.2",
39
+ "tsup": "^8.0.0",
40
+ "typescript": "^5.3.0",
41
+ "vitest": "^4.1.5"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "https://github.com/danielsimonjr/mathts",
49
+ "directory": "autograd"
50
+ },
51
+ "keywords": [
52
+ "math",
53
+ "autograd",
54
+ "automatic-differentiation",
55
+ "tensor",
56
+ "typescript"
57
+ ]
58
+ }