ATLAS Offline Software
Loading...
Searching...
No Matches
interpolate.py
Go to the documentation of this file.
1# Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
2
3#
4# File: interpolate.py
5# Created: sss, 2004.
6# Purpose: Polynomial interpolation in a table.
7#
8
9
10"""
11Polynomial interpolation in a table.
12This is a python version of CaloClusterCorrection/interpolate.
13"""
14
15# Helper: do a binary search in a table.
16# Note: these are slightly different than the standard python definition
17# of bisect, in that the comparison is done with <= rather than <.
18# This results in different behavior for the case that a key exactly
19# matching x is present in the table. As it's done here, it matches
20# the result of the C++ lower_bound function, used in the C++ implementation.
21def _bisect (a, x, lo=0, hi=None):
22 if hi is None:
23 hi = len(a)
24 while lo < hi:
25 mid = (lo+hi)//2
26 if x <= a[mid]:
27 hi = mid
28 else:
29 lo = mid+1
30 return lo
31
32# Helper: do a binary search in a 2D table.
33def _bisect2 (a, x, xcol, lo=0, hi=None):
34 if hi is None:
35 hi = len(a)
36 while lo < hi:
37 mid = (lo+hi)//2
38 if x <= a[mid][xcol]:
39 hi = mid
40 else:
41 lo = mid+1
42 return lo
43
44
45#
46# Polynomial interpolation in a table.
47#
48def interpolate (points, x, degree, ycol=1, regions=[], xcol=0):
49 """Polynomial interpolation in a table.
50
51 points Interpolation table.
52 The x values are in the first column (column 0),
53 and by default, the y values are in the second column (column 1).
54 The x values must be in ascending order, with no duplicates.
55 x The value to interpolate.
56 degree The degree of the interpolating polynomial.
57 ycol The column number of the y values. (0-based.)
58 regions Sometimes, you want to divide the range being interpolated
59 into several distinct regions, where the interpolated
60 function may be discontinuous at the boundaries
61 between regions. To do this, supply this argument,
62 which should contain a list (in ascending order)
63 of the x-coordinates of the region boundaries.
64 When the interpolation runs against a boundary,
65 the algorithm will add a copy of the last point
66 before the boundary, positioned at the boundary.
67 This helps to control runaway extrapolation
68 leading up to the boundary.
69 Returns the interpolated value.
70
71 The method used is Newtonian interpolation.
72 Based on the cernlib routine divdif.
73 """
74 if len (points) < 2 or degree < 1:
75 raise Exception ('bad args!')
76 degree = min (degree, len (points) - 1)
77
78
79 # START. FIND SUBSCRIPT IX OF X IN ARRAY.
80 #ix = _bisect (points, x, lambda x, p, xcol=xcol: cmp (x, p[xcol]))# - 1
81 ix = _bisect2 (points, x, xcol)
82
83 ir = _bisect (regions, x)
84
85 # Number of points to try for.
86 # Either degree+1 or degree+2, whichever is even,
87 # to give the same number of points on each side.
88 # If we run up against an edge or a boundary, we'll
89 # fall back to using just degree+1 points (or fewer if we can't
90 # even get that many).
91 # If we end up using degree+2 points, we'll do two interpolations
92 # of degree degree and average them.
93 npts = degree + 2 - (degree%2)
94 l = 0 # noqa: E741
95 t = []
96 d = []
97
98 # If we run up against the edge of a region boundary,
99 # we'll want to add a psuedopoint right at the boundary
100 # (copying the point closest to the boundary) instead of the
101 # point farthest away from it.
102 extralo = 0
103 extrahi = 0
104
105 # Starting point index, not considering edges or boundaries.
106 ilo = ix - npts // 2
107
108 # Make sure this point is within the array range and has not
109 # crossed a region boundary.
110 if ilo < 0:
111 ilo = 0
112 npts = degree+1
113 while ilo < len (points) and ir>0 and points[ilo][xcol] < regions[ir-1]:
114 ilo += 1
115 npts = degree+1
116 extralo = 1
117
118 # Same deal for the right hand edge.
119 # ihi is one past the last point to use.
120 himoved = 0
121 ihi = ilo + npts
122 if ihi > len (points):
123 ihi = len (points)
124 npts = degree+1
125 himoved = 1
126 while ihi > 0 and ir<len(regions) and points[ihi-1][xcol] >= regions[ir]:
127 ihi -= 1
128 npts = degree+1
129 extrahi = 1
130 himoved = 1
131
132 lomoved = 0
133 ilo = ihi - npts
134 if ilo < 0:
135 ilo = 0
136 lomoved = 1
137 while ilo < len (points) and ir>0 and points[ilo][xcol] < regions[ir-1]:
138 ilo += 1
139 extralo = 1
140 lomoved = 1
141
142 npts = ihi - ilo
143 t = []
144 d = []
145
146 if extralo and points[ilo][xcol] != regions[ir-1]:
147 if not himoved:
148 ihi -= 1
149 else:
150 npts += 1
151 t.append (regions[ir-1])
152 d.append (points[ilo][ycol])
153
154 if extrahi and points[ihi-1][xcol] != regions[ir]:
155 if not lomoved:
156 ilo += 1
157 else:
158 npts += 1
159 t.append (regions[ir])
160 d.append (points[ihi-1][ycol])
161
162 t += [points[i][xcol] for i in range (ilo, ihi)]
163 d += [points[i][ycol] for i in range (ilo, ihi)]
164
165 degree = min (degree, npts-1)
166 extra = npts != degree+1
167
168 if extra:
169 (t[0], t[npts-2]) = (t[npts-2], t[0])
170 (d[0], d[npts-2]) = (d[npts-2], d[0])
171
172 # REPLACE D BY THE LEADING DIAGONAL OF A DIVIDED-DIFFERENCE TABLE,
173 # SUPPLEMENTED BY AN EXTRA LINE IF *EXTRA* IS TRUE.
174 for l in range(0, degree):
175 if extra:
176 d[degree+1] = (d[degree+1]-d[degree-1]) / (t[degree+1]-t[degree-1-l])
177 for i in range (degree, l, -1):
178 d[i] = (d[i]-d[i-1]) / (t[i]-t[i-1-l])
179
180 # EVALUATE THE NEWTON INTERPOLATION FORMULA AT X, AVERAGING TWO VALUES
181 # OF LAST DIFFERENCE IF *EXTRA* IS TRUE.
182 sum = d[degree]
183 if extra:
184 sum=0.5*(sum+d[degree+1])
185 for j in range (degree-1, -1, -1):
186 sum = d[j] + (x - t[j]) * sum
187
188 return sum
189
190
191__test__ = {}
192__test__['tests'] = """
193>>> def is_different (a, b):
194... den = abs(a) + abs(b)
195... if den == 0: return 0
196... return abs((a-b)/den) > 1e-6
197>>> def testit (y2, table, x, degree, regions=[], ycol=1):
198... y1 = interpolate (table, x, degree, ycol, regions)
199... if is_different(y1, y2):
200... return "diff %f %f %f" % (x, y1, y2)
201>>> table = [
202... [0.05, 6.264762e-01],
203... [0.15, 6.671484e-01],
204... [0.25, 7.134157e-01],
205... [0.35, 8.211740e-01],
206... [0.45, 9.047978e-01],
207... [0.55, 9.087084e-01],
208... [0.65, 8.923957e-01],
209... [0.75, 9.584507e-01],
210... [0.85, -5.659607e-01],
211... [0.95, -6.995252e-01],
212... [1.05, -8.711259e-01],
213... [1.15, -1.038724e+00],
214... [1.25, -1.294152e+00],
215... [1.35, -1.170352e+00],
216... [1.45, -9.312256e-01],
217... ]
218>>> testit ( 0.912999, table, 0.5, 3);
219>>> testit ( 0.590771, table, 0.0, 3);
220>>> testit (-0.871126, table, 1.05, 3);
221>>> testit (-1.0011166, table, 1.13, 3);
222>>> testit (-1.04871, table, 1.4 , 3);
223>>> testit (-9.34768, table, 2.0 , 3);
224>>>
225>>> testit ( 0.91485268, table, 0.5, 4);
226>>> testit ( 0.55207348, table, 0.0, 4);
227>>> testit (-0.87112588, table, 1.05, 4);
228>>> testit (-0.99805647, table, 1.13, 4);
229>>> testit (-1.0201578, table, 1.4 , 4);
230>>> testit (-78.760239, table, 2.0 , 4);
231>>>
232>>> regions = [0.8, 1.05]
233>>> testit ( 0.65513462, table, 0.1, 4, regions);
234>>> testit ( 0.92242599, table, 0.7, 4, regions);
235>>> testit (-0.88620204, table, 1.1, 4, regions);
236>>> testit (-0.73959452, table, 1.0, 4, regions);
237>>> testit (-0.69952518, table, 1.05, 4, regions);
238>>> testit ( 0.95845068, table, 0.8, 4, regions);
239>>> testit (-1.2802936, table, 1.3, 4, regions);
240>>>
241>>> testit ( 0.64960641, table, 0.1, 3, regions);
242>>> testit ( 0.92791027, table, 0.7, 3, regions);
243>>> testit (-0.91475517, table, 1.1, 3, regions);
244>>> testit (-0.73959452, table, 1.0, 3, regions);
245>>> testit (-0.69952518, table, 1.05, 3, regions);
246>>> testit ( 0.95845068, table, 0.8, 3, regions);
247>>> testit (-1.2631618, table, 1.3, 3, regions);
248>>>
249>>> testit (0.1, table, 0.1, 3, regions, 0);
250>>>
251>>> regions2 = [0.2, 1.2]
252>>> testit ( 0.65359104, table, 0.1, 4, regions2);
253>>> testit (-1.2503984, table, 1.3, 4, regions2);
254"""
255
256
257if __name__ == "__main__":
258 print ('PyAnalysisUtils/interpolate.py test')
259 import doctest
260 doctest.testmod()
_bisect2(a, x, xcol, lo=0, hi=None)
_bisect(a, x, lo=0, hi=None)