@danielsimonjr/mathts-autograd 0.2.0 → 0.3.1
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/dist/index.d.ts +60 -6
- package/dist/index.js +101 -28
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -391,14 +391,21 @@ declare class TapedTensor {
|
|
|
391
391
|
*/
|
|
392
392
|
square(): TapedTensor;
|
|
393
393
|
/**
|
|
394
|
-
* Elementwise
|
|
394
|
+
* Elementwise power: x^k.
|
|
395
395
|
*
|
|
396
|
-
*
|
|
397
|
-
*
|
|
396
|
+
* `k` may be a fixed `number` (constant exponent) or a `TapedTensor`
|
|
397
|
+
* (variable exponent, both inputs on the tape).
|
|
398
398
|
*
|
|
399
|
-
*
|
|
399
|
+
* Adjoints, for y = a^b:
|
|
400
|
+
* - fixed exponent k: dA = dY · k · a^(k−1)
|
|
401
|
+
* - variable exponent: dA = dY · b · a^(b−1), dB = dY · a^b · ln(a)
|
|
402
|
+
* - aliased a^a: dA = dY · a^a · (ln a + 1)
|
|
403
|
+
*
|
|
404
|
+
* The dB term needs ln(a); for a ≤ 0 it is mathematically undefined and
|
|
405
|
+
* yields NaN/−Inf, honestly signalling non-differentiability w.r.t. the
|
|
406
|
+
* exponent rather than masking it.
|
|
400
407
|
*/
|
|
401
|
-
pow(k: number): TapedTensor;
|
|
408
|
+
pow(k: number | TapedTensor): TapedTensor;
|
|
402
409
|
/**
|
|
403
410
|
* Elementwise reciprocal: 1 / x.
|
|
404
411
|
*
|
|
@@ -594,4 +601,51 @@ declare function reverseGrad(fn: (x: Tensor) => Tensor, x: Tensor, cotangent?: T
|
|
|
594
601
|
gradient: Tensor;
|
|
595
602
|
};
|
|
596
603
|
|
|
597
|
-
|
|
604
|
+
/**
|
|
605
|
+
* GC15 — ergonomic, number-friendly autodiff bridge.
|
|
606
|
+
*
|
|
607
|
+
* The low-level building blocks (`Tape`, `TapedTensor`, `reverseGrad`) require
|
|
608
|
+
* manual tape management and `Tensor` marshalling. This is the JAX-style
|
|
609
|
+
* convenience layer: write a function of a single `TapedTensor` (the AD-aware
|
|
610
|
+
* surface — `.sin()`, `.mul()`, `.sum()`, `.matmul()`, … all record adjoints)
|
|
611
|
+
* and differentiate it against plain JS numbers / arrays.
|
|
612
|
+
*
|
|
613
|
+
* grad((x) => x.sin())(1) // [cos(1)]
|
|
614
|
+
* valueAndGrad((x) => x.pow(2).sum())([1, 2, 3]) // { value: 14, grad: [2,4,6] }
|
|
615
|
+
* derivative((x) => x.sin().mul(x), 2) // sin(2) + 2cos(2)
|
|
616
|
+
*
|
|
617
|
+
* `f` must use TapedTensor operations (not the plain `functions/` ops, which are
|
|
618
|
+
* not AD-aware). Differentiating arbitrary `functions/` ops would require
|
|
619
|
+
* overloading them onto dual numbers / the tape — a separate, larger effort.
|
|
620
|
+
*/
|
|
621
|
+
|
|
622
|
+
type ScalarFn = (x: TapedTensor) => TapedTensor;
|
|
623
|
+
type NumericInput = number | readonly number[] | Float64Array;
|
|
624
|
+
/**
|
|
625
|
+
* Forward value and reverse-mode gradient of a scalar-valued function.
|
|
626
|
+
*
|
|
627
|
+
* @param fn maps a TapedTensor (shape `[n]`) to a scalar TapedTensor (length 1).
|
|
628
|
+
* @param x the point — a number, number[], or Float64Array.
|
|
629
|
+
* @returns `{ value, grad }` where `grad` has the same length as `x`.
|
|
630
|
+
* @throws if `fn` does not return a length-1 (scalar) result.
|
|
631
|
+
*/
|
|
632
|
+
declare function valueAndGrad(fn: ScalarFn, x: NumericInput): {
|
|
633
|
+
value: number;
|
|
634
|
+
grad: Float64Array;
|
|
635
|
+
};
|
|
636
|
+
/**
|
|
637
|
+
* Gradient function of a scalar-valued function: `grad(f)(x)` returns ∇f(x).
|
|
638
|
+
*/
|
|
639
|
+
declare function grad(fn: ScalarFn): (x: NumericInput) => Float64Array;
|
|
640
|
+
/**
|
|
641
|
+
* Scalar convenience: derivative of `f: ℝ → ℝ` at `x0`.
|
|
642
|
+
*/
|
|
643
|
+
declare function derivative(fn: ScalarFn, x0: number): number;
|
|
644
|
+
/**
|
|
645
|
+
* Jacobian of a vector-valued function `f: ℝⁿ → ℝᵐ`, one reverse pass per output
|
|
646
|
+
* row. Returns an `m × n` array. (For tall outputs forward-mode is cheaper; this
|
|
647
|
+
* favours wide-input / few-output cases, the common ML gradient shape.)
|
|
648
|
+
*/
|
|
649
|
+
declare function jacobian(fn: ScalarFn, x: NumericInput): number[][];
|
|
650
|
+
|
|
651
|
+
export { DualTensor, type NumericInput, type ScalarFn, Tape, TapedTensor, derivative, forwardGrad, grad, jacobian, reverseGrad, valueAndGrad };
|
package/dist/index.js
CHANGED
|
@@ -658,8 +658,8 @@ var TapedTensor = class _TapedTensor {
|
|
|
658
658
|
const thisGradSlot = this.tape.getInputGrad(this.id);
|
|
659
659
|
const { id } = this.tape.record([this.id], outT.data.length, (outputGrad) => {
|
|
660
660
|
const outShape = [...outT.shape];
|
|
661
|
-
const outStrides =
|
|
662
|
-
const inStrides =
|
|
661
|
+
const outStrides = Tensor3.rowMajorStrides(outShape);
|
|
662
|
+
const inStrides = Tensor3.rowMajorStrides(inputShape);
|
|
663
663
|
const inCoords = new Array(inputShape.length).fill(0);
|
|
664
664
|
for (let n = 0; n < inputSize; n++) {
|
|
665
665
|
let outFlat = 0;
|
|
@@ -702,7 +702,7 @@ var TapedTensor = class _TapedTensor {
|
|
|
702
702
|
const thisGradSlot = this.tape.getInputGrad(this.id);
|
|
703
703
|
const { id } = this.tape.record([this.id], outT.data.length, (outputGrad) => {
|
|
704
704
|
const outShape = [...outT.shape];
|
|
705
|
-
const outStrides =
|
|
705
|
+
const outStrides = Tensor3.rowMajorStrides(outShape);
|
|
706
706
|
const inCoords = new Array(inputShape.length).fill(0);
|
|
707
707
|
for (let n = 0; n < inputSize; n++) {
|
|
708
708
|
let outFlat = 0;
|
|
@@ -750,8 +750,8 @@ var TapedTensor = class _TapedTensor {
|
|
|
750
750
|
const thisGradSlot = this.tape.getInputGrad(this.id);
|
|
751
751
|
const { id } = this.tape.record([this.id], outT.data.length, (outputGrad) => {
|
|
752
752
|
const outShape = [...outT.shape];
|
|
753
|
-
const outStrides =
|
|
754
|
-
const inStrides =
|
|
753
|
+
const outStrides = Tensor3.rowMajorStrides(outShape);
|
|
754
|
+
const inStrides = Tensor3.rowMajorStrides(inputShape);
|
|
755
755
|
const groups = /* @__PURE__ */ new Map();
|
|
756
756
|
const inCoords = new Array(inputShape.length).fill(0);
|
|
757
757
|
for (let n = 0; n < primalData.length; n++) {
|
|
@@ -815,7 +815,7 @@ var TapedTensor = class _TapedTensor {
|
|
|
815
815
|
const thisGradSlot = this.tape.getInputGrad(this.id);
|
|
816
816
|
const { id } = this.tape.record([this.id], outT.data.length, (outputGrad) => {
|
|
817
817
|
const outShape = [...outT.shape];
|
|
818
|
-
const outStrides =
|
|
818
|
+
const outStrides = Tensor3.rowMajorStrides(outShape);
|
|
819
819
|
const argmaxSlot = new Int32Array(outT.data.length).fill(-1);
|
|
820
820
|
const inCoords = new Array(inputShape.length).fill(0);
|
|
821
821
|
for (let n = 0; n < primalData.length; n++) {
|
|
@@ -864,7 +864,7 @@ var TapedTensor = class _TapedTensor {
|
|
|
864
864
|
const thisGradSlot = this.tape.getInputGrad(this.id);
|
|
865
865
|
const { id } = this.tape.record([this.id], outT.data.length, (outputGrad) => {
|
|
866
866
|
const outShape = [...outT.shape];
|
|
867
|
-
const outStrides =
|
|
867
|
+
const outStrides = Tensor3.rowMajorStrides(outShape);
|
|
868
868
|
const argminSlot = new Int32Array(outT.data.length).fill(-1);
|
|
869
869
|
const inCoords = new Array(inputShape.length).fill(0);
|
|
870
870
|
for (let n = 0; n < primalData.length; n++) {
|
|
@@ -922,7 +922,7 @@ var TapedTensor = class _TapedTensor {
|
|
|
922
922
|
const normData = new Float64Array(outT.data);
|
|
923
923
|
const { id } = this.tape.record([this.id], outT.data.length, (outputGrad) => {
|
|
924
924
|
const outShape = [...outT.shape];
|
|
925
|
-
const outStrides =
|
|
925
|
+
const outStrides = Tensor3.rowMajorStrides(outShape);
|
|
926
926
|
const inCoords = new Array(inputShape.length).fill(0);
|
|
927
927
|
for (let n = 0; n < primalData.length; n++) {
|
|
928
928
|
let outFlat = 0;
|
|
@@ -950,7 +950,7 @@ var TapedTensor = class _TapedTensor {
|
|
|
950
950
|
if (p === 1) {
|
|
951
951
|
const { id } = this.tape.record([this.id], outT.data.length, (outputGrad) => {
|
|
952
952
|
const outShape = [...outT.shape];
|
|
953
|
-
const outStrides =
|
|
953
|
+
const outStrides = Tensor3.rowMajorStrides(outShape);
|
|
954
954
|
const inCoords = new Array(inputShape.length).fill(0);
|
|
955
955
|
for (let n = 0; n < primalData.length; n++) {
|
|
956
956
|
let outFlat = 0;
|
|
@@ -977,7 +977,7 @@ var TapedTensor = class _TapedTensor {
|
|
|
977
977
|
{
|
|
978
978
|
const { id } = this.tape.record([this.id], outT.data.length, (outputGrad) => {
|
|
979
979
|
const outShape = [...outT.shape];
|
|
980
|
-
const outStrides =
|
|
980
|
+
const outStrides = Tensor3.rowMajorStrides(outShape);
|
|
981
981
|
const argmaxAbsSlot = new Int32Array(outT.data.length).fill(-1);
|
|
982
982
|
const inCoords = new Array(inputShape.length).fill(0);
|
|
983
983
|
for (let n = 0; n < primalData.length; n++) {
|
|
@@ -1145,21 +1145,55 @@ var TapedTensor = class _TapedTensor {
|
|
|
1145
1145
|
return new _TapedTensor(this.shape, out, this.tape, id);
|
|
1146
1146
|
}
|
|
1147
1147
|
/**
|
|
1148
|
-
* Elementwise
|
|
1148
|
+
* Elementwise power: x^k.
|
|
1149
1149
|
*
|
|
1150
|
-
*
|
|
1151
|
-
*
|
|
1150
|
+
* `k` may be a fixed `number` (constant exponent) or a `TapedTensor`
|
|
1151
|
+
* (variable exponent, both inputs on the tape).
|
|
1152
1152
|
*
|
|
1153
|
-
*
|
|
1153
|
+
* Adjoints, for y = a^b:
|
|
1154
|
+
* - fixed exponent k: dA = dY · k · a^(k−1)
|
|
1155
|
+
* - variable exponent: dA = dY · b · a^(b−1), dB = dY · a^b · ln(a)
|
|
1156
|
+
* - aliased a^a: dA = dY · a^a · (ln a + 1)
|
|
1157
|
+
*
|
|
1158
|
+
* The dB term needs ln(a); for a ≤ 0 it is mathematically undefined and
|
|
1159
|
+
* yields NaN/−Inf, honestly signalling non-differentiability w.r.t. the
|
|
1160
|
+
* exponent rather than masking it.
|
|
1154
1161
|
*/
|
|
1155
1162
|
pow(k) {
|
|
1163
|
+
if (typeof k === "number") {
|
|
1164
|
+
const out2 = new Float64Array(this.primal.length);
|
|
1165
|
+
for (let i = 0; i < out2.length; i++) out2[i] = Math.pow(this.primal[i], k);
|
|
1166
|
+
const primalData = new Float64Array(this.primal);
|
|
1167
|
+
const thisGradSlot2 = this.tape.getInputGrad(this.id);
|
|
1168
|
+
const { id: id2 } = this.tape.record([this.id], out2.length, (outputGrad) => {
|
|
1169
|
+
for (let i = 0; i < outputGrad.length; i++) {
|
|
1170
|
+
thisGradSlot2[i] += outputGrad[i] * k * Math.pow(primalData[i], k - 1);
|
|
1171
|
+
}
|
|
1172
|
+
});
|
|
1173
|
+
return new _TapedTensor(this.shape, out2, this.tape, id2);
|
|
1174
|
+
}
|
|
1175
|
+
const other = k;
|
|
1176
|
+
this.checkSameShape(other, "pow");
|
|
1156
1177
|
const out = new Float64Array(this.primal.length);
|
|
1157
|
-
for (let i = 0; i < out.length; i++) out[i] = Math.pow(this.primal[i],
|
|
1158
|
-
const
|
|
1178
|
+
for (let i = 0; i < out.length; i++) out[i] = Math.pow(this.primal[i], other.primal[i]);
|
|
1179
|
+
const aPrimal = new Float64Array(this.primal);
|
|
1180
|
+
const bPrimal = new Float64Array(other.primal);
|
|
1181
|
+
const yPrimal = new Float64Array(out);
|
|
1159
1182
|
const thisGradSlot = this.tape.getInputGrad(this.id);
|
|
1160
|
-
const
|
|
1183
|
+
const otherGradSlot = this.tape.getInputGrad(other.id);
|
|
1184
|
+
const isAliased = this === other;
|
|
1185
|
+
const { id } = this.tape.record([this.id, other.id], out.length, (outputGrad) => {
|
|
1161
1186
|
for (let i = 0; i < outputGrad.length; i++) {
|
|
1162
|
-
|
|
1187
|
+
const a = aPrimal[i];
|
|
1188
|
+
const b = bPrimal[i];
|
|
1189
|
+
const y = yPrimal[i];
|
|
1190
|
+
const g = outputGrad[i];
|
|
1191
|
+
if (isAliased) {
|
|
1192
|
+
thisGradSlot[i] += g * y * (Math.log(a) + 1);
|
|
1193
|
+
} else {
|
|
1194
|
+
thisGradSlot[i] += g * b * Math.pow(a, b - 1);
|
|
1195
|
+
otherGradSlot[i] += g * y * Math.log(a);
|
|
1196
|
+
}
|
|
1163
1197
|
}
|
|
1164
1198
|
});
|
|
1165
1199
|
return new _TapedTensor(this.shape, out, this.tape, id);
|
|
@@ -1939,15 +1973,6 @@ function _resolveAxes(axis, rank) {
|
|
|
1939
1973
|
}
|
|
1940
1974
|
return [...axis].sort((a, b) => a - b);
|
|
1941
1975
|
}
|
|
1942
|
-
function _rowMajorStrides(shape) {
|
|
1943
|
-
const strides = new Array(shape.length);
|
|
1944
|
-
let acc = 1;
|
|
1945
|
-
for (let k = shape.length - 1; k >= 0; k--) {
|
|
1946
|
-
strides[k] = acc;
|
|
1947
|
-
acc *= shape[k];
|
|
1948
|
-
}
|
|
1949
|
-
return strides;
|
|
1950
|
-
}
|
|
1951
1976
|
function _scatterToOriginalAxes(tensor, targetLabels) {
|
|
1952
1977
|
const currentLabels = tensor.axisLabels;
|
|
1953
1978
|
const rank = targetLabels.length;
|
|
@@ -2076,10 +2101,58 @@ function reverseGrad(fn, x, cotangent) {
|
|
|
2076
2101
|
gradient: new Tensor4(x.shape, new Float64Array(xGradSlot))
|
|
2077
2102
|
};
|
|
2078
2103
|
}
|
|
2104
|
+
|
|
2105
|
+
// src/grad.ts
|
|
2106
|
+
function toFloat64(x) {
|
|
2107
|
+
if (typeof x === "number") return Float64Array.of(x);
|
|
2108
|
+
return x instanceof Float64Array ? new Float64Array(x) : Float64Array.from(x);
|
|
2109
|
+
}
|
|
2110
|
+
function valueAndGrad(fn, x) {
|
|
2111
|
+
const xs = toFloat64(x);
|
|
2112
|
+
const tape = new Tape();
|
|
2113
|
+
const { id } = tape.allocate(xs.length);
|
|
2114
|
+
const leaf = new TapedTensor([xs.length], xs, tape, id);
|
|
2115
|
+
const out = fn(leaf);
|
|
2116
|
+
if (out.primal.length !== 1) {
|
|
2117
|
+
throw new Error(
|
|
2118
|
+
`valueAndGrad: fn must return a scalar (length 1); got length ${out.primal.length}. Reduce with .sum()/.mean(), or use jacobian() for vector outputs.`
|
|
2119
|
+
);
|
|
2120
|
+
}
|
|
2121
|
+
tape.backward(out.id, Float64Array.of(1));
|
|
2122
|
+
return { value: out.primal[0], grad: new Float64Array(tape.getInputGrad(id)) };
|
|
2123
|
+
}
|
|
2124
|
+
function grad(fn) {
|
|
2125
|
+
return (x) => valueAndGrad(fn, x).grad;
|
|
2126
|
+
}
|
|
2127
|
+
function derivative(fn, x0) {
|
|
2128
|
+
return valueAndGrad(fn, x0).grad[0];
|
|
2129
|
+
}
|
|
2130
|
+
function jacobian(fn, x) {
|
|
2131
|
+
const xs = toFloat64(x);
|
|
2132
|
+
const probeTape = new Tape();
|
|
2133
|
+
const { id: probeId } = probeTape.allocate(xs.length);
|
|
2134
|
+
const probeOut = fn(new TapedTensor([xs.length], new Float64Array(xs), probeTape, probeId));
|
|
2135
|
+
const m = probeOut.primal.length;
|
|
2136
|
+
const rows = [];
|
|
2137
|
+
for (let r = 0; r < m; r++) {
|
|
2138
|
+
const tape = new Tape();
|
|
2139
|
+
const { id } = tape.allocate(xs.length);
|
|
2140
|
+
const out = fn(new TapedTensor([xs.length], new Float64Array(xs), tape, id));
|
|
2141
|
+
const seed = new Float64Array(m);
|
|
2142
|
+
seed[r] = 1;
|
|
2143
|
+
tape.backward(out.id, seed);
|
|
2144
|
+
rows.push(Array.from(tape.getInputGrad(id)));
|
|
2145
|
+
}
|
|
2146
|
+
return rows;
|
|
2147
|
+
}
|
|
2079
2148
|
export {
|
|
2080
2149
|
DualTensor,
|
|
2081
2150
|
Tape,
|
|
2082
2151
|
TapedTensor,
|
|
2152
|
+
derivative,
|
|
2083
2153
|
forwardGrad,
|
|
2084
|
-
|
|
2154
|
+
grad,
|
|
2155
|
+
jacobian,
|
|
2156
|
+
reverseGrad,
|
|
2157
|
+
valueAndGrad
|
|
2085
2158
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danielsimonjr/mathts-autograd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Forward + reverse-mode automatic differentiation for MathTS rank-N Tensor",
|
|
5
5
|
"author": "Daniel Simon Jr.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"build:prod": "tsup src/index.ts --format esm --dts --clean --minify --treeshake"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@danielsimonjr/mathts-core": "^0.
|
|
35
|
-
"@danielsimonjr/mathts-tensor": "^0.1
|
|
34
|
+
"@danielsimonjr/mathts-core": "^0.2.0",
|
|
35
|
+
"@danielsimonjr/mathts-tensor": "^0.2.1"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^25.5.2",
|