diff --git a/lib/apl/runtime.sx b/lib/apl/runtime.sx index 4ee5f295..9f0d9028 100644 --- a/lib/apl/runtime.sx +++ b/lib/apl/runtime.sx @@ -1066,3 +1066,20 @@ (define apl-compose (fn (f g x) (f (g x)))) (define apl-compose-dyadic (fn (f g x y) (f x (g y)))) + +(define + apl-power + (fn (f n x) (reduce (fn (acc i) (f acc)) x (range 0 n)))) + +(define + apl-power-fixed + (fn + (f x) + (let + ((next (f x))) + (if + (and + (equal? (get next :shape) (get x :shape)) + (equal? (get next :ravel) (get x :ravel))) + x + (apl-power-fixed f next))))) diff --git a/lib/apl/tests/operators.sx b/lib/apl/tests/operators.sx index 43d67fa2..532acf50 100644 --- a/lib/apl/tests/operators.sx +++ b/lib/apl/tests/operators.sx @@ -579,4 +579,65 @@ apl-neg-m (make-array (list 2 3) (list 1 2 3 4 5 6)) (make-array (list 2 3) (list 1 1 1 1 1 1)))) - (list 2 3)) \ No newline at end of file + (list 2 3)) + +(apl-test + "power n=0 identity" + (rv (apl-power (fn (a) (apl-add a (apl-scalar 1))) 0 (apl-scalar 5))) + (list 5)) + +(apl-test + "power increment by 3" + (rv (apl-power (fn (a) (apl-add a (apl-scalar 1))) 3 (apl-scalar 0))) + (list 3)) + +(apl-test + "power double 4 times = 16" + (rv (apl-power (fn (a) (apl-mul a (apl-scalar 2))) 4 (apl-scalar 1))) + (list 16)) + +(apl-test + "power on vector +5" + (rv + (apl-power + (fn (a) (apl-add a (apl-scalar 1))) + 5 + (make-array (list 3) (list 1 2 3)))) + (list 6 7 8)) + +(apl-test + "power on vector preserves shape" + (sh + (apl-power + (fn (a) (apl-add a (apl-scalar 1))) + 5 + (make-array (list 3) (list 1 2 3)))) + (list 3)) + +(apl-test + "power on matrix" + (rv + (apl-power + (fn (a) (apl-mul a (apl-scalar 3))) + 2 + (make-array (list 2 2) (list 1 2 3 4)))) + (list 9 18 27 36)) + +(apl-test + "power-fixed identity stops immediately" + (rv (apl-power-fixed (fn (a) a) (make-array (list 3) (list 1 2 3)))) + (list 1 2 3)) + +(apl-test + "power-fixed floor half scalar to 0" + (rv + (apl-power-fixed + (fn (a) (apl-floor (apl-div a (apl-scalar 2)))) + (apl-scalar 100))) + (list 0)) + +(apl-test + "power-fixed shape preserved" + (sh + (apl-power-fixed (fn (a) a) (make-array (list 2 2) (list 1 2 3 4)))) + (list 2 2)) \ No newline at end of file diff --git a/plans/apl-on-sx.md b/plans/apl-on-sx.md index 95f10e62..8b218fd0 100644 --- a/plans/apl-on-sx.md +++ b/plans/apl-on-sx.md @@ -80,7 +80,7 @@ Core mapping: - [x] Inner product `f.g` — `+.×` is matrix multiply - [x] Commute `f⍨` — `f⍨ x` ↔ `x f x`, `x f⍨ y` ↔ `y f x` - [x] Compose `f∘g` — applies `g` first then `f` -- [ ] Power `f⍣n` — apply f n times; `f⍣≡` until fixed point +- [x] Power `f⍣n` — apply f n times; `f⍣≡` until fixed point - [ ] Rank `f⍤k` — apply f at sub-rank k - [ ] At `@` — selective replace - [ ] 40+ tests in `lib/apl/tests/operators.sx` @@ -118,6 +118,7 @@ data; format for string templating. _Newest first._ +- 2026-05-06: Phase 4 step 8 — power f⍣n (apl-power) + fixed-point f⍣≡ (apl-power-fixed); 191/191 tests - 2026-05-06: Phase 4 step 7 — compose f∘g (apl-compose monadic f∘g x, apl-compose-dyadic dyadic f x (g y)); 182/182 tests - 2026-05-06: Phase 4 step 6 — commute f⍨ (apl-commute monadic dup, apl-commute-dyadic swap); 173/173 tests - 2026-05-06: Phase 4 step 5 — inner product f.g (apl-inner); +.× matrix multiply, ∧.= equal-vectors; 163/163 tests