Lately, we had this thread about interpolation where J. M. linked two interpolation methods. The background for my question is that I estimated a parameter in polar coordinates with dependence on the angle. Therefore, I have a set of values where I know that the values are periodic.
The question is: Is there an easy procedure to interpolate a set of (only a few) points so that the interpolant is monotonic, continuous up to the 1st order derivative, and where the continuous property holds between the start and the end of the interval?
I tried to tweak Interpolation
, so if you are interested, please see the following section.
Example and Investigation
Let's define a simple dataset and use Mathematica's Interpolation
with PeriodicInterpolation -> True
:
data = {{0, 0.2}, {1/3, 0.9}, {2/3, 0.15}, {1, 0.2}};
ip = Interpolation[data, PeriodicInterpolation -> True]
Show[{
ListLinePlot[data, Mesh -> Full,
MeshStyle -> Directive[Red, PointSize[0.02]]],
Plot[ip[x], {x, 0, 1}, PlotStyle -> ColorData[1, 2]]
}]
This does not look so bad at a first glance, but the minimum between the last two points violate the monotonicity (which was expected since the interpolation in Mathematica never claimed such a property). Looking at the interpolation graph only and over the interval-boundary shows
Plot[ip[x], {x, 0.2, 1.2}]
that the derivative is not continuous. Now, my idea was that by using PeriodicInterpolation -> True
, I basically tell Mathematica that it can use the values periodically to pad the sides if required. Therefore, a higher interpolation order or maybe using Method -> "Spline"
should be able to make the derivatives continuous. When I try to use
Interpolation[data, PeriodicInterpolation -> True, Method -> "Spline"]
I get
Interpolation::mspl: The Spline method could not be used because the data could not be coerced to machine real numbers. >>
which I don't really understand. If I use InterpolationOrder
and the default "Hermite"
method, I need a value of 20 to get an almost continuous derivative.
Therefore, I tried to do the padding by myself and use "Spline"
and a low interpolation order (to reduce the overshooting):
ip2 = Interpolation[
Join @@ Table[Plus[{i, 0}, #] & /@ Most[data], {i, 0, 10}],
InterpolationOrder -> 2, Method -> "Spline"]
Plot[{ip2[x]}, {x, 5, 6.5},
Epilog -> {Red, PointSize[0.02], Point[{5, 0} + # & /@ data]}]
which is of course not monotonic, but at least the derivative is continuous:
Plot[ip2'[x], {x, 5, 6.5}]
Final solution
I'm accepting the Steffen interpolation in J.M.'s answer as solution for the following reason:
It shows nicely how one can supply derivative values and not only the interpolation values to Interpolation
. It therefore does not implement a whole new interpolation but it only calculates adjusted derivatives and uses the internal Interpolation
of Mathematica.
Note, that I made some changes in his function. First, the following part
pp = Apply[Dot, Transpose[MapAt[Map[Reverse, #] &,
Map[Partition[#, 2, 1, {-1, 1}] &, {h, del}], 1]], 1]/
ListConvolve[{1, 1}, h, {1, -1}];
can be (IMO) slightly simplified to
pp = Dot @@@ Transpose[ MapAt[Reverse,
Map[Partition[#, 2, 1, {-1, 1}] &, {h, del}], {1, All}]]/
ListConvolve[{1, 1}, h, {1, -1}];
which saves a whole pure function and a Map
. Otherwise, I combined the periodic and the non-periodic interpolation and added a pattern so that data in the form {y1,y2,...}
can be interpolated in the usual way. (Please change the function name. It's only called like that since I included it in a package)
Options[IPCUMonotonicInterpolation] := {
PeriodicInterpolation -> False
}
steffenEnds[{{h1_, h2_}, {d1_, d2_}}] :=
With[{p = d1 + h1 (d1 - d2)/(h1 + h2)}, (Sign[p] + Sign[d1]) Min[
Abs[p]/2, Abs[d1]]]
IPCUMonotonicInterpolation[data_?(VectorQ[#, NumericQ] &), opts___?OptionQ] :=
IPCUMonotonicInterpolation[Transpose[{Range[Length[data]], data}], opts];
IPCUMonotonicInterpolation[data_?MatrixQ, OptionsPattern[]] :=
Module[{dTrans = Transpose[data], del, h, m, pp, optPeriodic, overhangs},
optPeriodic = OptionValue[PeriodicInterpolation];
h = Differences[First[dTrans]];
del = Differences[Last[dTrans]]/h;
overhangs = If[optPeriodic === False, {1, -1}, {-1, 1}];
(* Note that overhangs in Partition and ListConvolve are defined differently*)
pp = Dot @@@ Transpose[MapAt[Reverse,
Map[Partition[#, 2, 1, overhangs] &, {h, del}], {1, All}]]/
ListConvolve[{1, 1}, h, -1*overhangs];
If[optPeriodic === True,
del = ArrayPad[del, 1, "Periodic"]
];
m = ListConvolve[{1, 1}, 2 UnitStep[del] - 1] *
MapThread[Min, {Partition[Abs[del], 2, 1], Abs[pp]/2}];
Interpolation[
{{#1}, ##2} & @@@ Transpose[Append[dTrans,
If[optPeriodic === True,
m,
Flatten[{
steffenEnds[#[[{1, 2}]] & /@ {h, del}],
m,
steffenEnds[#[[{-1, -2}]] & /@ {h, del}]
}]
]
]],
PeriodicInterpolation -> optPeriodic]
]
Apply[Dot, Transpose[MapAt[Map[Reverse, #] &, Map[Partition[#, 2, 1, {-1, 1}] &, {Table[Subscript[h, k], {k, 5}], Table[Subscript[Δ, k], {k, 5}]}], 1]], 1]
andDot @@@ Transpose[Map[Partition[Reverse[#], 2, 1, {-1, 1}] &, {Table[Subscript[h, k], {k, 5}], Table[Subscript[Δ, k], {k, 5}]}]]
. $\endgroup$