This namespace contains an implementation of two data types:
Series, which represents a generic infinite series of arbitrary values, and
PowerSeries, a series that represents a power series in a single variable; in other words, a series where the nth entry is interpreted as the coefficient of Loading...:
[a b c d ...] Loading...
We'll proceed by building up implementations of the arithmetic operations +, -, *, / and a few others on bare Clojure lazy sequences, and then introduce deftype wrappers so that we can install these types into the generic dispatch system.
Okay, let's proceed, in roughly the same order as the paper.
Sequence Operations
A 'series' is an infinite sequence of numbers, represented by Clojure's lazy sequence. First, a function ->series that takes some existing sequence, finite or infinite, and coerces it to an infinite seq by concatenating it with an infinite sequence of zeros. (We use g/zero-like so that everything plays nicely with generic arithmetic.)
(defn->series
"Form the infinite sequence starting with the supplied values. The
remainder of the series will be filled with the zero-value
corresponding to the first of the given values."
[xs]
(lazy-cat xs (repeat (g/zero-like (first xs)))))
#object[emmy.series.impl$__GT_series0x2b8802b4"
emmy.series.impl$__GT_series@2b8802b4"
]
This works as expected:
(->clerk-only
(->series [1234]))
(1234000000000000000010+ more elided)
The core observation we'll use in the following definitions (courtesy of McIlroy) is that a power series Loading... in a variable Loading...:
Loading...
Decomposes into a head element Loading... plus a tail series, multiplied by Loading...:
Loading...
We'll use this observation to derive the more complicated sequence operations below.
Negation
To negate a series, negate each element:
(defnnegate [xs]
(map g/negate xs))
#object[emmy.series.impl$negate0x1dc16244"
emmy.series.impl$negate@1dc16244"
]
(->clerk-only
(let [xs [1234]]
(negate (->series xs))))
(-1-2-3-4000000000000000010+ more elided)
Addition
We can derive series addition by expanding the series Loading... and Loading... into head and tail and rearranging terms:
Loading...
This is particularly straightforward in Clojure, where map already merges sequences elementwise:
A constant is a series with its first element populated, all zeros otherwise. To add a constant to another series we only need add it to the first element. Here are two versions, constant-on-left vs constant-on-right:
(defnc+seq [c f]
(lazy-seq
(cons (g/+ c (first f)) (rest f))))
#object[emmy.series.impl$c_PLUS_seq0x5c2e97c4"
emmy.series.impl$c_PLUS_seq@5c2e97c4"
]
(defnseq+c [f c]
(lazy-seq
(cons (g/+ (first f) c) (rest f))))
#object[emmy.series.impl$seq_PLUS_c0x3758a357"
emmy.series.impl$seq_PLUS_c@3758a357"
]
(->clerk-only
(let [series (->series [1234])]
[(take6 (seq+c series 10))
(take6 (c+seq10 series))]))
[(1123400)(1123400)]
Subtraction
Subtraction comes for free from the two definitions above:
(defnseq:- [f g]
(map g/- f g))
#object[emmy.series.impl$seq_COLON__0x219d24ba"
emmy.series.impl$seq_COLON__@219d24ba"
]
(->clerk-only
(take5 (seq:- (range) (range))))
(00000)
Subtract a constant from a sequence by subtracting it from the first element:
(defnseq-c [f c]
(lazy-seq
(cons (g/- (first f) c) (rest f))))
#object[emmy.series.impl$seq_c0x1495ea9a"
emmy.series.impl$seq_c@1495ea9a"
]
(->clerk-only
(take5 (seq-c (range) 10)))
(-101234)
To subtract a sequence from a constant, subtract the first element as before, but negate the tail of the sequence:
(defnc-seq [c f]
(lazy-seq
(cons (g/- c (first f)) (negate (rest f)))))
#object[emmy.series.impl$c_seq0x3c2ddf7b"
emmy.series.impl$c_seq@3c2ddf7b"
]
(->clerk-only
(take5 (c-seq10 (range))))
(10-1-2-3-4)
Multiplication
What does it mean to multiply two infinite sequences? As McIlroy notes, multiplication is where the lazy-sequence-based approach really comes into its own.
First, the simple cases of multiplication by a scalar on either side of a sequence:
(defnseq*c [f c] (map #(g/mul % c) f))
#object[emmy.series.impl$seq_STAR_c0x4e1d91b2"
emmy.series.impl$seq_STAR_c@4e1d91b2"
]
(defnc*seq [c f] (map #(g/mul c %) f))
#object[emmy.series.impl$c_STAR_seq0x6edc43f"
emmy.series.impl$c_STAR_seq@6edc43f"
]
To multiply sequences, first recall from above that we can decompose each sequence Loading... and Loading... into a head and tail.
Mutiply the expanded representations out and rearrange terms:
Loading...
Loading... appears on the left and the right, so use an inner function that closes over Loading... to simplify matters, and rewrite the above definition in Clojure:
NOTE This is also called the "Cauchy Product" of the two sequences: https://en.wikipedia.org/wiki/Cauchy_product The description on the Wikipedia page has complicated index tracking that simply doesn't come in to play with the stream-based approach. Amazing!
Division
The quotient Loading... of Loading... and Loading... should satisfy:
Loading...
From McIlroy, first expand out Loading..., Loading... and one instance of Loading...:
Loading...
Look at just the constant terms and note that Loading....
Consider the terms multiplied by Loading... and solve for Loading...:
Loading...
There are two special cases to consider:
If Loading..., Loading... can only succeed if Loading...; in this case, $Q = {F_1 \over G1}$, from the larger formula above.
If Loading..., Loading...
Encoded in Clojure:
(defndiv [f g]
(lazy-seq
(let [f0 (first f) fs (rest f)
g0 (first g) gs (rest g)]
(cond (and (g/zero? f0) (g/zero? g0))
(div fs gs)
(g/zero? f0)
(cons f0 (div fs g))
(g/zero? g0)
(u/arithmetic-ex"ERROR: denominator has a zero constant term")
:else (let [q (g/div f0 g0)]
(cons q (-> (seq:- fs (c*seq q gs))
(div g))))))))
#object[emmy.series.impl$div0x6f13e99c"
emmy.series.impl$div@6f13e99c"
]
A simple example shows success:
(->clerk-only
(let [series (->series [0004321])]
(take5 (div series series))))
(10000)
Reciprocal
We could generate the reciprocal of Loading... by dividing Loading... by Loading.... Page 21 of an earlier paper by McIlroy gives us a more direct formula.
We want Loading... such that Loading.... Expand Loading...:
Loading...
Solve for R:
Loading...
A recursive definition is no problem in the stream abstraction:
(defninvert [f]
(lazy-seq
(let [finv (g/invert (first f))
F1*Finv (seq:* (rest f) (invert f))
tail (c*seq finv (negate F1*Finv))]
(cons finv tail))))
#object[emmy.series.impl$invert0x4da175cd"
emmy.series.impl$invert@4da175cd"
]
This definition of invert matches the more straightforward division implementation:
(->clerk-only
(let [series (iterate inc 3)]
(= (take5 (invert series))
(take5 (div (->series [1]) series)))))
true
An example:
(->clerk-only
(let [series (iterate inc 3)]
[(take5 (seq:* series (invert series)))
(take5 (div series series))]))
[(1N0N0N0N0N)(10000)]
Division of a constant by a series comes easily from our previous multiplication definitions and invert:
(defnc-div-seq [c f]
(c*seq c (invert f)))
#object[emmy.series.impl$c_div_seq0x25858c7a"
emmy.series.impl$c_div_seq@25858c7a"
]
It's not obvious that this works:
(->clerk-only
(let [nats (iterate inc 1)]
(take6 (c-div-seq4 nats))))
(4-84000)
But we can recover the initial series:
(->clerk-only
(let [nats (iterate inc 1)
divided (c-div-seq4 nats)
seq-over-4 (invert divided)
original (seq*c seq-over-4 4)]
[(take5 nats)
(take5 original)]))
[(12345)(1N2N3N4N5N)]
To divide a series by a constant, divide each element of the series:
(defnseq-div-c [f c]
(map #(g// % c) f))
#object[emmy.series.impl$seq_div_c0x21eccab7"
emmy.series.impl$seq_div_c@21eccab7"
]
Division by a constant undoes multiplication by a constant:
(->clerk-only
(let [nats (iterate inc 1)]
(take5 (seq-div-c (seq*c nats 2) 2))))
(12345)
Functional Composition
To compose two series Loading... and Loading... means to create a new series Loading.... Derive this by substituting Loading... for Loading... in the expansion of Loading...:
Loading...
For the stream-based calculation to work, we need to be able to calculate the head element and attach it to an infinite tail; unless Loading... above the head element depends on Loading..., an infinite sequence.
If Loading... the calculation simplifies:
Loading...
In Clojure, using an inner function that captures Loading...:
(defncompose [f g]
(letfn [(step [f]
(lazy-seq
;; TODO I don't understand why we get a StackOverflow if I move
;; this assertion out of the `letfn`.
(assert (g/zero? (first g)))
(let [[f0 & fs] f
gs (rest g)
tail (seq:* gs (step fs))]
(cons f0 tail))))]
(step f)))
#object[emmy.series.impl$compose0x5143881f"
emmy.series.impl$compose@5143881f"
]
Composing Loading... should square all Loading...s, and give us a sequence of only even powers:
(->clerk-only
(take10 (compose (repeat1)
(->series [001]))))
(1010101010)
Reversion
The functional inverse of a power series Loading... is a series Loading... that satisfies Loading....
Following McIlroy, we expand Loading... (substituting Loading... for Loading...) and one occurrence of Loading...:
Loading...
Just like in the composition derivation, in the general case the head term depends on an infinite sequence. Set Loading... to address this:
Loading...
For this to work, the constant Loading... must be 0 as well, hence
Loading...
This works as an implementation because Loading.... Loading... is allowed to reference Loading... thanks to the stream-based approach:
(defnrevert [f]
{:pre [(g/zero? (first f))]}
(letfn [(step [f]
(lazy-seq
(let [F1 (rest f)
R (step f)]
(cons0 (invert
(compose F1 R))))))]
(step f)))
#object[emmy.series.impl$revert0x170a90e7"
emmy.series.impl$revert@170a90e7"
]
An example, inverting a series starting with 0:
(->clerk-only
(let [f (cons0 (iterate inc 1))]
(take5 (compose f (revert f)))))
(01000)
Series Calculus
Derivatives of power series are simple and mechanical:
Loading...
Implies that all entries shift left by 1, and each new entry gets multiplied by its former index (i.e., its new index plus 1).
(defnderiv [f]
(map g/* (rest f) (iterate inc 1)))
#object[emmy.series.impl$deriv0x1e652820"
emmy.series.impl$deriv@1e652820"
]
(->clerk-only
;; 1 + 2x + 3x^2 + ...
(take6 (deriv (repeat1))))
(123456)
The definite integral Loading... is similar. To take the anti-derivative of each term, move it to the right by appending a constant term onto the sequence and divide each element by its new position:
(defnintegral
([s] (integral s 0))
([s constant-term]
(cons constant-term
(map g/div s (iterate inc 1)))))
#object[emmy.series.impl$integral0x591bf029"
emmy.series.impl$integral@591bf029"
]
With a custom constant term:
(->clerk-only
(take6 (integral (iterate inc 1) 5)))
(511111)
By default, the constant term is 0:
(->clerk-only
(take6 (integral (iterate inc 1))))
(011111)
Exponentiation
Exponentiation of a power series by some integer is simply repeated multiplication. The implementation here is more efficient the iterating seq:*, and handles negative exponent terms by inverting the original sequence.
(defnexpt [s e]
(letfn [(expt [base pow]
(loop [n pow
y (->series [1])
z base]
(let [t (even? n)
n (quot n 2)]
(cond
t (recur n y (seq:* z z))
(zero? n) (seq:* z y)
:else (recur n (seq:* z y) (seq:* z z))))))]
(cond (pos? e) (expt s e)
(zero? e) (->series [1])
:else (invert (expt s (g/negate e))))))
#object[emmy.series.impl$expt0x14558865"
emmy.series.impl$expt@14558865"
]
We can use expt to verify that Loading... expands to Loading...:
(->clerk-only
(take5 (expt (->series [11]) 3)))
(13310)
Square Root of a Series
The square root of a series Loading... is a series Loading... such that Loading.... We can find this using our calculus methods from above:
Loading...
or
Loading...
When the head term of Loading... is nonzero, i.e., Loading..., the head of Loading... must be Loading... for the multiplication to work out.
Integrate both sides:
Loading...
One optimization appears if the first two terms of Loading... vanish, i.e., Loading.... In this case Loading....
Here it is in Clojure:
(defnsqrt [[f1 & [f2 & fs] :as f]]
(if (and (g/zero? f1)
(g/zero? f2))
(cons f1 (sqrt fs))
(let [const (g/sqrt f1)
step (fn step [g]
(lazy-seq
(-> (div (deriv g)
(c*seq2 (step g)))
(integral const))))]
(step f))))
#object[emmy.series.impl$sqrt0x579bafa0"
emmy.series.impl$sqrt@579bafa0"
]
And a test that we can recover the naturals:
(->clerk-only
(let [xs (iterate inc 1)]
(take6 (seq:* (sqrt xs)
(sqrt xs)))))
(123456)
We can maintain precision of the first element is the square of a rational number:
(->clerk-only
(let [xs (iterate inc 9)]
(take6 (seq:* (sqrt xs)
(sqrt xs)))))
(910N11N12N13N14N)
We get a correct result if the sequence starts with 0, 0:
(let [xs (concat [00] (iterate inc 9))]
(->clerk-only
(take6 (seq:* (sqrt xs)
(sqrt xs)))))
(00910N11N12N)
Examples
Power series computations can encode polynomial computations. Encoding Loading... as a power series returns the correct result:
(->clerk-only
(take10 (expt (->series [10-2]) 3)))
(10-60120-8000)
Encoding Loading... returns the power series Loading... which sums to that value in its region of convergence:
(->clerk-only
(take10 (div (->series [1])
(->series [1-1]))))
(1111111111)
Loading... is the derivative of the above series:
(->clerk-only
(take10 (div (->series [1])
(-> (->series [1-1])
(expt2)))))
(12345678910)
Various Power Series
With the above primitives we can define a number of series with somewhat astonishing brevity.
Loading... is its own derivative, so Loading...:
(defexpx
(lazy-seq
(integral expx 1)))
(111/21/61/241/1201/7201/50401/403201/3628801/36288001/399168001/4790016001/62270208001/871782912001/13076743680001/209227898880001/3556874280960001/64023737057280001/12164510040883200010+ more elided)
This bare definition is enough to generate the power series for Loading...:
(->clerk-only
(take10 expx))
(111/21/61/241/1201/7201/50401/403201/362880)
Loading... and Loading... afford recursive definitions. Loading... and $D(cos) = -sin$, so (with appropriate constant terms added) on:
(declare cosx)
(10-1/201/240-1/72001/403200-1/362880001/4790016000-1/8717829120001/209227898880000-1/6402373705728000010+ more elided)
(defsinx (lazy-seq (integral cosx)))
(010-1/601/1200-1/504001/3628800-1/3991680001/62270208000-1/130767436800001/3556874280960000-1/12164510040883200010+ more elided)
(defcosx (lazy-seq (c-seq1 (integral sinx))))
(10-1/201/240-1/72001/403200-1/362880001/4790016000-1/8717829120001/209227898880000-1/6402373705728000010+ more elided)
tangent and secant come easily from these:
(deftanx (div sinx cosx))
(0101/302/15017/315062/283501382/155925021844/60810750929569/63851287506404582/108547188750443861162/185615692762510+ more elided)
(defsecx (invert cosx))
(101/205/24061/7200277/8064050521/36288000540553/958003200199360981/8717829120003878302429/418455797760002404879675441/6402373705728000010+ more elided)
Reversion lets us define arcsine from sine:
(defasinx (revert sinx))
(0101/603/4005/112035/1152063/28160231/133120143/1024006435/557056012155/124518410+ more elided)
(defatanx (integral (cycle [10-10])))
(010-1/301/50-1/701/90-1/1101/130-1/1501/170-1/1910+ more elided)
These two are less elegant, perhaps:
(defacosx (c-seq (/ Math/PI 2) asinx))
(1.5707963267948966-10-1/60-3/400-5/1120-35/11520-63/28160-231/133120-143/102400-6435/5570560-12155/124518410+ more elided)
(defacotx (c-seq (/ Math/PI 2) atanx))
(1.5707963267948966-101/30-1/501/70-1/901/110-1/1301/150-1/1701/1910+ more elided)
The hyperbolic trig functions are defined in a similar way:
(declare sinhx)
(0101/601/12001/504001/36288001/3991680001/622702080001/130767436800001/35568742809600001/12164510040883200010+ more elided)
(defcoshx (lazy-seq (integral sinhx 1)))
(101/201/2401/72001/4032001/362880001/47900160001/8717829120001/2092278988800001/6402373705728000010+ more elided)
(defsinhx (lazy-seq (integral coshx)))
(0101/601/12001/504001/36288001/3991680001/622702080001/130767436800001/35568742809600001/12164510040883200010+ more elided)
(deftanhx (div sinhx coshx))
(010-1/302/150-17/315062/28350-1382/155925021844/60810750-929569/63851287506404582/108547188750-443861162/185615692762510+ more elided)
(defasinhx (revert sinhx))
(010-1/603/400-5/112035/11520-63/28160231/133120-143/1024006435/5570560-12155/124518410+ more elided)
(defatanhx (revert tanhx))
(0101/301/501/701/901/1101/1301/1501/1701/1910+ more elided)
(deflog1-x
(integral (repeat-1)))
(0-1-1/2-1/3-1/4-1/5-1/6-1/7-1/8-1/9-1/10-1/11-1/12-1/13-1/14-1/15-1/16-1/17-1/18-1/1910+ more elided)
(01-1/21/3-1/41/5-1/61/7-1/81/9-1/101/11-1/121/13-1/141/15-1/161/17-1/181/1910+ more elided)
Generating Functions
Catalan numbers
These are a few more examples from McIlroy's "Power Serious" paper, presented here without context. (If you have the energy to write about these, please feel free and send us a PR!)
(defcatalan
(lazy-cat [1] (seq:* catalan catalan)))
(112514421324291430486216796587862080127429002674440969484535357670129644790477638700176726319010+ more elided)
ordered trees...
(declare tree' forest' list')
(011251442132429143048621679658786208012742900267444096948453535767012964479047763870010+ more elided)
(deftree' (lazy-cat [0] forest'))
(011251442132429143048621679658786208012742900267444096948453535767012964479047763870010+ more elided)
(deflist' (lazy-cat [1] list'))
(1111111111111111111110+ more elided)
(defforest' (compose list' tree'))
(112514421324291430486216796587862080127429002674440969484535357670129644790477638700176726319010+ more elided)
The catalan numbers again!
(deffib (lazy-cat [01] (map + fib (rest fib))))
(0112358132134558914423337761098715972584418110+ more elided)
numbers](https://en.wikipedia.org/wiki/Harmonic_number), starting from n=1."
(reductions
g/+ (map g// (iterate inc 1))))
(13/211/625/12137/6049/20363/140761/2807129/25207381/252083711/2772086021/277201145993/3603601171733/3603601195757/3603602436559/72072042142223/1225224014274301/4084080275295799/7759752055835135/1551950410+ more elided)
(defbell
"The sequence of [Bell numbers](https://en.wikipedia.org/wiki/Bell_number),
starting from n=1."
(map sf/bell (iterate inc 1)))
(12515522038774140211471159756785704213597276444371908993221382958545104801421478286486980468207680615958327422050575172415823537210+ more elided)