Skip to main content

performance tuning - Fast Spherical Linear Interpolation of list of quaternions


An accurate way to interpolate between two quaternions is to use Spherical Linear Interpolation (Slerp) because it preserves the unit length, whereas straightforward linear interpolation does not, as shown by this example:


Clear[slerp];
slerp[q1_, q2_, f_] := Module[{omega},

omega = ArcCos[Dot[Normalize@q1, Normalize@q2]]];
If[PossibleZeroQ[omega],
q1,
(Sin[(1 - f) omega] q1 + Sin[f omega] q2)/Sin[omega]
]
]

q1 = {1,0,0,0};
q2 = {0,1,0,0};
q3 = {0,0,1,0};


(* Linear interpolation using Interpolation *)
qint = Interpolation[{{0,q1},{1,q2}},InterpolationOrder->1];

Plot[{Norm@qint[t], Norm@slerp[{1, 0, 0, 0}, {0, 1, 0, 0}, t]}, {t, 0, 1},
AxesLabel -> {"time", "length"}, PlotRange -> {0.7, 1.01},
PlotStyle -> {Automatic, Dashed},
Epilog -> {Text["Linear Interpolation", {0.5, 0.75}],
Text["Spherical Linear Interpolation", {0.5, 0.98}]}
]


Mathematica graphics


What's nice thing about Interpolation, however, is that it is easy to give it a list of vectors to get an interpolating function valid over the specified range, and it works fast:


Interpolation[{{0,q1},{1,q2},{2,q3}},InterpolationOrder->1]


InterpolatingFunction[{{0,2}},<>]



How can I create something like an InterpolatingFunction that preserves the unit-length property?


This is what I came up with, but it seems kludgy and it is very slow (this code does not check input bounds):



Clear[slerpInterpolation]; 
slerpInterpolation[q_List] := Function[{t},
Module[{times, quaternions, pos, u, dt},
times = q[[All, 1]];
quaternions = q[[All, 2]];
pos = Last@Flatten@Position[t - times, x_ /; x >= 0];
dt = times[[pos + 1]] - times[[pos]];
u = (t - times[[pos]])/dt;
slerp[quaternions[[pos]], quaternions[[pos + 1]], u]
]

]

It is too slow in practice, giving about 10 evaluations per second on my laptop. Compare that with regular interpolation:


qlist = Transpose[{Range[100000] - 1, Normalize /@ RandomReal[{-1, 1}, {100000, 4}]}];
sint = slerpInterpolation[qlist];
lint = Interpolation[qlist, InterpolationOrder -> 1];

AbsoluteTiming[Do[sint[541.236], {10}]][[1]]/10



0.0967707



AbsoluteTiming[Do[lint[541.236], {10000}]][[1]]/10000


7.2522*10^-6




Answer



Here's a somewhat complete implementation of Shoemake's spherical linear interpolation that functions completely analogously to Interpolation[] and InterpolatingFunction[]. As already noted, much of the slowness is due to your use of a sequential search. In any event, if you prefer, you could use the built-in interpolation as suggested in the other answer, but I think using a bisection routine is a bit more instructive.


Anyway...



SphericalLinearInterpolation::inddp = "The point `1` is duplicated.";

SphericalLinearInterpolation[data_] :=
Module[{dtr = Transpose[SortBy[data, Composition[N, First]]], diffs, times},
SphericalInterpolatingFunction[data[[{1, -1}, 1]], dtr] /;
If[MemberQ[diffs = Chop[Differences[times = First[dtr]]], 0],
Message[SphericalLinearInterpolation::inddp,
First[Extract[times, Position[diffs, 0]]]]; False, True]]

SphericalInterpolatingFunction::dmval =

"Input value `1` lies outside the domain of the interpolating function.";

MakeBoxes[SphericalInterpolatingFunction[range_, rest__], _] ^:=
InterpretationBox[RowBox[
{"SphericalInterpolatingFunction", "[", "{", #1, ",", #2, "}", ",", "\"<>\"", "]"}],
SphericalInterpolatingFunction[range, rest]] & @@ Map[ToBoxes, range]

SphericalInterpolatingFunction[stuff__][l_List] :=
SphericalInterpolatingFunction[stuff] /@ l


SphericalInterpolatingFunction[range_, __]["Domain"] := range

SphericalInterpolatingFunction[{r_, s_}, data_][t_?NumericQ] :=
(Message[SphericalInterpolatingFunction::dmval, t]; $Failed) /; ! (r <= t <= s)

slerp = Compile[{{q1, _Real, 1}, {q2, _Real, 1}, {f, _Real}},
Module[{n1 = Norm[q1], n2 = Norm[q2], omega, so},
(* vector angle formula by Velvel Kahan *)
omega = 2 ArcTan[Norm[q1 n2 + n1 q2], Norm[q1 n2 - n1 q2]];
If[Chop[so = Sin[omega]] == 0, q1, Sin[{1 - f, f} omega].{q1, q2}/so]]];


SphericalInterpolatingFunction[range_, data_][t_?NumericQ] := Module[{times, quats, k},
{times, quats} = data;
k = GeometricFunctions`BinarySearch[times, t];
slerp[quats[[k]], quats[[k + 1]], Rescale[t, times[[{k, k + 1}]], {0, 1}]]]



See J. Blow's article on why spherical linear interpolation might not always be the best method for interpolating quaternions.


Comments

Popular posts from this blog

front end - keyboard shortcut to invoke Insert new matrix

I frequently need to type in some matrices, and the menu command Insert > Table/Matrix > New... allows matrices with lines drawn between columns and rows, which is very helpful. I would like to make a keyboard shortcut for it, but cannot find the relevant frontend token command (4209405) for it. Since the FullForm[] and InputForm[] of matrices with lines drawn between rows and columns is the same as those without lines, it's hard to do this via 3rd party system-wide text expanders (e.g. autohotkey or atext on mac). How does one assign a keyboard shortcut for the menu item Insert > Table/Matrix > New... , preferably using only mathematica? Thanks! Answer In the MenuSetup.tr (for linux located in the $InstallationDirectory/SystemFiles/FrontEnd/TextResources/X/ directory), I changed the line MenuItem["&New...", "CreateGridBoxDialog"] to read MenuItem["&New...", "CreateGridBoxDialog", MenuKey["m", Modifiers-...

How to thread a list

I have data in format data = {{a1, a2}, {b1, b2}, {c1, c2}, {d1, d2}} Tableform: I want to thread it to : tdata = {{{a1, b1}, {a2, b2}}, {{a1, c1}, {a2, c2}}, {{a1, d1}, {a2, d2}}} Tableform: And I would like to do better then pseudofunction[n_] := Transpose[{data2[[1]], data2[[n]]}]; SetAttributes[pseudofunction, Listable]; Range[2, 4] // pseudofunction Here is my benchmark data, where data3 is normal sample of real data. data3 = Drop[ExcelWorkBook[[Column1 ;; Column4]], None, 1]; data2 = {a #, b #, c #, d #} & /@ Range[1, 10^5]; data = RandomReal[{0, 1}, {10^6, 4}]; Here is my benchmark code kptnw[list_] := Transpose[{Table[First@#, {Length@# - 1}], Rest@#}, {3, 1, 2}] &@list kptnw2[list_] := Transpose[{ConstantArray[First@#, Length@# - 1], Rest@#}, {3, 1, 2}] &@list OleksandrR[list_] := Flatten[Outer[List, List@First[list], Rest[list], 1], {{2}, {1, 4}}] paradox2[list_] := Partition[Riffle[list[[1]], #], 2] & /@ Drop[list, 1] RM[list_] := FoldList[Transpose[{First@li...

plotting - Magnifying Glass on a Plot

Although there is a trick in TEX magnifying glass but I want to know is there any function to magnifying glass on a plot with Mathematica ? For example for a function as Sin[x] and at x=Pi/6 Below, this is just a picture desired from the cited site. the image got huge unfortunately I don't know how can I change the size of an image here! Answer Insetting a magnified part of the original Plot A) by adding a new Plot of the specified range xPos = Pi/6; range = 0.2; f = Sin; xyMinMax = {{xPos - range, xPos + range}, {f[xPos] - range*GoldenRatio^-1, f[xPos] + range*GoldenRatio^-1}}; Plot[f[x], {x, 0, 5}, Epilog -> {Transparent, EdgeForm[Thick], Rectangle[Sequence @@ Transpose[xyMinMax]], Inset[Plot[f[x], {x, xPos - range, xPos + range}, Frame -> True, Axes -> False, PlotRange -> xyMinMax, ImageSize -> 270], {4., 0.5}]}, ImageSize -> 700] B) by adding a new Plot within a Circle mf = RegionMember[Disk[{xPos, f[xPos]}, {range, range/GoldenRatio}]] Show...