[math-fun] Oscillation and the perils of numerical pessimism
The Minsky recurrence handily generates long sequences of a*sin(n*ω+φ). One can initialize the general case minsksin[ω_, φ_: 0, ε_: 2 Sin[ω/2], a_: 1] := {a*Sin[φ], -(a/2) ε Cos[φ + ω/2] Csc[ω/2], (4 Sin[ω/2]^2)/ε, ε} where ε is arbitrary. E.g., In[138]:= minsksin[ω, φ, ε, a] Out[138]= {a Sin[φ], -(1/2) a ε Cos[φ + ω/2] Csc[ω/2], (4 Sin[ω/2]^2)/ε, ε} The general Minsky step is stepminsk[{x_, y_, δ_, ε_}] := {#, y + ε*#, δ, ε} &[x - δ*y] E.g., running three steps, In[139]:= Simplify[Nest[stepminsk, %, 3]] Out[139]= {a Sin[φ + 3 ω], -(1/2) a ε Cos[φ + (7 ω)/2] Csc[ω/2], 4 Sin[ω/2]^2/ε, ε} (The 3rd and 4th positions are just along for the ride.) Nice choices of ε are 2 Sin[ω/2] and 1, where the latter saves a multiply in the stepper: stepm1nsk[{x_, y_, δ_}] := {#, y + #, δ}&[x - δ*y] E.g., for amplitude 1, phase 0, In[140]:= Drop[minsksin[w, 0, 1], -1] Out[140]= {0, (-(1/2))*Cot[w/2], 4*Sin[w/2]^2} Run four steps: In[144]:= FullSimplify[Nest[stepm1nsk, Out[140], 4]] Out[144]= {Sin[4*w], (-(1/2))*Cos[(9*w)/2]*Csc[w/2], 2 - 2*Cos[w]} But instead of Minsky, we could use good old Chebychyov*: In[37]:= chebsin[ω_, φ_: 0] := {Sin[φ], Sin[φ - ω], 2*Cos[ω]} whose stepper is stepcheb[{x_, y_, δ_}] := {δ*x - y, x, δ} E.g., In[146]:= FullSimplify[Nest[stepcheb, chebsin[a], 3]] Out[146]= {Sin[3 a], Sin[2 a], 2 Cos[a]} So let's run cheb vs minsky a few million and see who's better. Using ω := π φ (which will figure in a later email): In[148]:= chebsin[N[π*GoldenRatio]] Out[148]= {0, 0.932032, 0.72475} In[149]:= Timing[Nest[stepcheb, %, 9999999]] Out[149]= {22.9417, {0.748998, 0.888957, 0.72475}} In[150]:= minsksin[N[π*GoldenRatio], 0, 1] Out[150]= {0, 0.730862, 1.27525, 1} In[151]:= Timing[Nest[stepm1nsk, Drop[%, -1], 9999999]] Out[151]= {40.471, {0.748998, 0.858748, 1.27525}} Well, cheb is faster, but which is better? In[152]:= Sin[9999999*π*GoldenRatio] - %149[[2, 1]] Out[152]= 3.01301*10^-9 In[153]:= Sin[9999999*π*GoldenRatio] - %151[[2, 1]] Out[153]= 3.01292*10^-9 They're the same! But wait, this is a trap. You can't accurately compute sin(huge) with single precision! sin(huge) is like asking which way your valve stem will point after you drive across the country. In[154]:= Sin[9999999*π*GoldenRatio] - SetPrecision[%149[[2, 1]], 22] Out[154]= 1.9186385802598*10^-9 Significantly different, but In[155]:= Sin[9999999*π*GoldenRatio] - SetPrecision[%151[[2, 1]], 22] Out[155]= 1.9185395483660*10^-9 They're *still* the same! Something else is fishy. MachinePrecision is ~16, so these values are ~ 10^6 LSBs, not 10^(6/2). Let's drive ten times further. In[156]:= Timing[Nest[stepcheb, %149[[2]], 90000000]] Out[156]= {214.079, {-0.722388, -0.906264, 0.72475}} and now we're off by In[159]:= Sin[99999999*π*GoldenRatio] - SetPrecision[%156[[2, 1]], 22] Out[159]= -2.00240499632334*10^-8 Ten times as far! Whereas with Minsky In[157]:= Timing[Nest[stepm1nsk, %151[[2]], 90000000]] Out[157]= {382.448, {-0.722388, -0.866577, 1.27525}} In[160]:= Sin[99999999*π*GoldenRatio] - SetPrecision[%157[[2, 1]], 22] Out[160]= -2.00242976539902*10^-8 The same! The explanation: These recurrences don't compute Sin[n*π*GoldenRatio], but rather Sin[n*N[π*GoldenRatio]], which is off by In[161]:= SetPrecision[N[π*GoldenRatio], 22] - π*GoldenRatio Out[161]= -3.34860*10^-16 We are doomed to linearly creeping error. But hey, we can tack on a dozen extra digits and promise not to drive past a trillion. But first, let's drive just 999 to see how long it will take. (This is where it hits the fan.) In[166]:= Nest[stepcheb, chebsin[N[π*GoldenRatio, 33]], 999] Out[166]= {0.*10^121, 0.*10^121, 0.72474978016096023991729327494997} WAH? In[167]:= Nest[stepm1nsk, Drop[minsksin[N[π*GoldenRatio, 33], 0, 1], -1], 999] Out[167]= {0.*10^434, 0.*10^434, 1.27525021983903976008270672505003} WHA?? This is too gross: In[168]:= Nest[stepm1nsk, Drop[minsksin[N[π*GoldenRatio, 288], 0, 1], -1], 999] Out[168]= {0.*10^179, 0.*10^179, 1.27525021983903976008270672505002620127826891198880290709968642251975\ 2987505102052895607541471314178683631575021864167072493028631467693604\ 7459492180699315909580232204640529914388966962377507409450195194836649\ 3221557106208837998002035152821718325478473977678977255669689503145798\ 805151318} "Precision Tracking" (precision trashing, interval arithmetic) *totally* destroys the computation. Whereas the actual loss during the fixed machine precision iteration was negligible. --rwg *(The standard counterexample to the Continuum Hypothesis: The cardinal between ℵ0 and C is the number of ways to spell Chebychyov.)
On Sun, May 6, 2012 at 6:55 PM, Bill Gosper <billgosper@gmail.com> wrote:
The Minsky recurrence handily generates long sequences of a*sin(n*ω+φ). [...]
So let's run cheb vs minsky a few million and see who's better. Using ω := π φ (which will figure in a later email):
[linearly growing misbehavior for both]
The explanation: These recurrences don't compute Sin[n*π*GoldenRatio], but rather Sin[n*N[π*GoldenRatio]], which is off by
In[161]:= SetPrecision[N[π*GoldenRatio], 22] - π*GoldenRatio
Out[161]= -3.34860*10^-16
For cheb, this gets intractable for ω near a multiple of 2π. E.g., for the vertices of a (near) megagon: In[218]:= Timing[Nest[stepcheb, chebsin[N[π/999999], 0], 999999]] Out[218]= {2.29889, {-5.83135*10^-6, -2.68975*10^-6, 2.}} (Loss: 10^10 lsb) whereas In[217]:= Timing[ Nest[stepm1nsk, Drop[minsksin[N[π/9999999], 0, 1], -1], 9999999]] Out[217]= {40.412, {-7.25373*10^-14, 3.1831*10^6, 9.86961*10^-14}} (100 lsb) We are doomed to linearly creeping error. But hey, we can tack on a dozen
extra digits and promise not to drive past a trillion. But first, let's drive just 999 to see how long it will take. (This is where it hits the fan.)
In[166]:= Nest[stepcheb, chebsin[N[π*GoldenRatio, 33]], 999]
Out[166]= {0.*10^121, 0.*10^121, 0.72474978016096023991729327494997}
WAH?
In[167]:= Nest[stepm1nsk, Drop[minsksin[N[π*GoldenRatio, 33], 0, 1], -1], 999]
Out[167]= {0.*10^434, 0.*10^434, 1.27525021983903976008270672505003}
WHA?? This is too gross:
In[168]:= Nest[stepm1nsk, Drop[minsksin[N[π*GoldenRatio, 288], 0, 1], -1], 999]
Out[168]= {0.*10^179, 0.*10^179, 1.27525021983903976008270672505002620127826891198880290709968642251975\ 2987505102052895607541471314178683631575021864167072493028631467693604\ 7459492180699315909580232204640529914388966962377507409450195194836649\ 3221557106208837998002035152821718325478473977678977255669689503145798\ 805151318}
"Precision Tracking" (precision trashing, interval arithmetic) *totally* destroys the computation. Whereas the actual loss during the fixed machine precision iteration was negligible. --rwg
Julian to the rescue: On Mon, May 7, 2012 at 12:50 PM, Julian Ziegler Hunts <julianj.zh@gmail.com>wrote:
You probably know this, but in case you haven't gotten around to it yet:
And, as you suggested,
minsksinint[ω_, ϕ_: 0, ∂_: 2 Sin[ω/2], a_: 1] := {Round[a*Sin[ϕ]], Round[-(a/2) ∂ Cos[ϕ + ω/2] Csc[ω/2]], (4 Sin[ω/2]^2)/∂, ∂}
stepminskint[{x_, y_, δ_, ∂_}] := {Round[#], Round[y + ∂*#], δ, ∂} &[x - δ*y]
stepm1nskint[{x_, y_, δ_}] := {Round[#], Round[y + #], δ} &[x - δ*y]
chebsinint[ω_, ϕ_: 0, a_: 1] := {Round[a*Sin[ϕ]], Round[a*Sin[ϕ - ω]], 2*Cos[ω]}
stepchebint[{x_, y_, δ_}] := {Round[δ*x - y], x, δ}
rwg>You madperson. Why did you change ε to the other flavor of δ (i.e. ∂)??
j>works much better [...] I.e., choose a large integer amplitude a and use big integers (like in the "good" old days) instead of floats. E.g., for floats: In[175]:= Timing[ Nest[stepm1nsk, Drop[minsksin[N[π*GoldenRatio], 0, 1], -1], 999999]] Out[175]= {4.08617, {0.918646, 0.748074, 1.27525}} In[176]:= Sin[999999*π*GoldenRatio] - SetPrecision[%[[2, 1]], 33] Out[176]= 1.14393260781121554602005*10^-10 Loss=~10^6 lsb Integers, with a:=10^69: In[184]:= Timing[ Nest[stepm1nskint, Drop[minsksinint[N[π*GoldenRatio, 69], 0, 1, 10^69], -1], 999999]] Out[184]= {6.74437, \ {918645523252150530219574122157946569212458324866848819204746126477690, 748073906145249981381508759485701971803310022978410476968586624220216, 1.27525021983903976008270672505002620127826891198880290709968642251975}} In[185]:= N[Round[Sin[999999*π*GoldenRatio]*10^69 - %[[2, 1]]]] Out[185]= -214. I.e., loss= 214 lsb. Way more accurate. Less than twice as slow. But the error still scales (almost) linearly: In[186]:= Timing[Nest[stepm1nskint, Drop[minsksinint[N[π*GoldenRatio, 69], 0, 1, 10^69], -1],9999999]] Out[186]= {68.2078, \ {748998425544230349596759154767033168207054569581106778235375691401471, 858748004778120235089047360826898766127851223417284925293705386758193, 1.27525021983903976008270672505002620127826891198880290709968642251975}} In[187]:= N[Round[Sin[9999999*π*GoldenRatio]*10^69 - %[[2, 1]]]] Out[187]= -1220. But with integers, we avoid the deadly precision trashing; the dozen digit cushion idea works fine. --rwg
On Tue, May 8, 2012 at 6:26 PM, Bill Gosper <billgosper@gmail.com> wrote:
On Sun, May 6, 2012 at 6:55 PM, Bill Gosper <billgosper@gmail.com> wrote:
The Minsky recurrence handily generates long sequences of a*sin(n*ω+φ). [...]
So let's run cheb vs minsky a few million and see who's better. Using ω := π φ (which will figure in a later email):
[linearly growing misbehavior for both]
The explanation: These recurrences don't compute Sin[n*π*GoldenRatio], but rather Sin[n*N[π*GoldenRatio]], which is off by
In[161]:= SetPrecision[N[π*GoldenRatio], 22] - π*GoldenRatio
Out[161]= -3.34860*10^-16
For cheb, this gets intractable for ω near a multiple of 2π. E.g., for the vertices of a (near) megagon:
In[218]:= Timing[Nest[stepcheb, chebsin[N[π/999999], 0], 999999]]
Out[218]= {2.29889, {-5.83135*10^-6, -2.68975*10^-6, 2.}}
(Loss: 10^10 lsb) whereas
In[217]:= Timing[ Nest[stepm1nsk, Drop[minsksin[N[π/9999999], 0, 1], -1], 9999999]]
Out[217]= {40.412, {-7.25373*10^-14, 3.1831*10^6, 9.86961*10^-14}}
(100 lsb)
We are doomed to linearly creeping error. But hey, we can tack on a dozen
extra digits and promise not to drive past a trillion. But first, let's drive just 999 to see how long it will take. (This is where it hits the fan.)
In[166]:= Nest[stepcheb, chebsin[N[π*GoldenRatio, 33]], 999]
Out[166]= {0.*10^121, 0.*10^121, 0.72474978016096023991729327494997}
WAH?
In[167]:= Nest[stepm1nsk, Drop[minsksin[N[π*GoldenRatio, 33], 0, 1], -1], 999]
Out[167]= {0.*10^434, 0.*10^434, 1.27525021983903976008270672505003}
WHA?? This is too gross:
In[168]:= Nest[stepm1nsk, Drop[minsksin[N[π*GoldenRatio, 288], 0, 1], -1], 999]
Out[168]= {0.*10^179, 0.*10^179, 1.27525021983903976008270672505002620127826891198880290709968642251975\ 2987505102052895607541471314178683631575021864167072493028631467693604\ 7459492180699315909580232204640529914388966962377507409450195194836649\ 3221557106208837998002035152821718325478473977678977255669689503145798\ 805151318}
"Precision Tracking" (precision trashing, interval arithmetic) *totally* destroys the computation. Whereas the actual loss during the fixed machine precision iteration was negligible. --rwg
Julian to the rescue:
On Mon, May 7, 2012 at 12:50 PM, Julian Ziegler Hunts < julianj.zh@gmail.com> wrote:
You probably know this, but in case you haven't gotten around to it yet:
And, as you suggested,
minsksinint[ω_, ϕ_: 0, ∂_: 2 Sin[ω/2], a_: 1] := {Round[a*Sin[ϕ]], Round[-(a/2) ∂ Cos[ϕ + ω/2] Csc[ω/2]], (4 Sin[ω/2]^2)/∂, ∂}
stepminskint[{x_, y_, δ_, ∂_}] := {Round[#], Round[y + ∂*#], δ, ∂} &[x - δ*y]
stepm1nskint[{x_, y_, δ_}] := {Round[#], Round[y + #], δ} &[x - δ*y]
chebsinint[ω_, ϕ_: 0, a_: 1] := {Round[a*Sin[ϕ]], Round[a*Sin[ϕ - ω]], 2*Cos[ω]}
stepchebint[{x_, y_, δ_}] := {Round[δ*x - y], x, δ}
rwg>You madperson. Why did you change ε to the other flavor of δ (i.e. ∂)??
j> It's not a delta. There's no agreed-upon name for ∂<http://en.wikipedia.org/wiki/%E2%88%82> .
j>works much better [...]
I.e., choose a large integer amplitude a and use big integers (like in the "good" old days) instead of floats. E.g., for floats:
In[175]:= Timing[ Nest[stepm1nsk, Drop[minsksin[N[π*GoldenRatio], 0, 1], -1], 999999]]
Out[175]= {4.08617, {0.918646, 0.748074, 1.27525}}
In[176]:= Sin[999999*π*GoldenRatio] - SetPrecision[%[[2, 1]], 33]
Out[176]= 1.14393260781121554602005*10^-10
Loss=~10^6 lsb
Integers, with a:=10^69:
In[184]:= Timing[ Nest[stepm1nskint, Drop[minsksinint[N[π*GoldenRatio, 69], 0, 1, 10^69], -1], 999999]]
Out[184]= {6.74437, \ {918645523252150530219574122157946569212458324866848819204746126477690, 748073906145249981381508759485701971803310022978410476968586624220216, 1.27525021983903976008270672505002620127826891198880290709968642251975}}
In[185]:= N[Round[Sin[999999*π*GoldenRatio]*10^69 - %[[2, 1]]]]
Out[185]= -214.
I.e., loss= 214 lsb.
Way more accurate. Less than twice as slow. But the error still scales (almost) linearly:
In[186]:= Timing[Nest[stepm1nskint, Drop[minsksinint[N[π*GoldenRatio, 69], 0, 1, 10^69], -1],9999999]]
Out[186]= {68.2078, \ {748998425544230349596759154767033168207054569581106778235375691401471, 858748004778120235089047360826898766127851223417284925293705386758193, 1.27525021983903976008270672505002620127826891198880290709968642251975}}
In[187]:= N[Round[Sin[9999999*π*GoldenRatio]*10^69 - %[[2, 1]]]]
Out[187]= -1220.
This is egregious rubbish. Julian actually said j>works much better–the error is sqrt or less, and (with a=10^60, and 100 digits of precision on the multipliers) it takes less than twice as long as normal with machine precision to compute. which I censored in disbelief, too stupid to see how reducing the amplitude accuracy could actually suppress the linear phase drift, and mistaking a random walk for linear growth. He later explained j>Yes, the loss is still linear, but the linear component is the result of the angle being approximate. Give the angle 40 extra digits (without changing the amplitude) and this goes away until past the point where it's impossible to compute, leaving us with sqrt-ish error. Julian It turns out, you need nothing like 40 digits of cushion. Changing 100 to 69 in In[86]:= Table[(Print[#]; #) &[ Round[10^60*Sin[\[Pi]*GoldenRatio*10^n]] - Nest[stepchebint, chebsinint[N[\[Pi]*GoldenRatio, 100], 0, 10^60], 10^n][[1]]], {n, 9}] Out[86]= {1, -1, -9, -2, 98, 38, 1248, 410, 11484} makes ZERO difference! But with integers, we avoid the deadly precision trashing; the dozen digit
cushion idea works fine.
And way better than I thought. I.e., good for ~ 10^24 steps, not 10^12.
--rwg
I don't actually recall a case where deliberately reducing accuracy in one part of a calculation significantly enhances the overall accuracy. And I'm surprised that (cheaper) cheb competes with minsky except for ω near 2 n π. --rwg
participants (1)
-
Bill Gosper