# Source code for refl1d.lib_numba.convolve_sampled

```import numba

@numba.njit(cache=True)
def convolve_point_sampled(Nin, xin, yin, Np, xp, yp, xo, dx, _in):
# Walk the theory spline and the resolution spline together, computing
# the integral of the pairs of line segments.  Since the spline knots
# do not necessarily correspond, we need to treat each individual segment
# in both curves piece-wise, integrating from knot to knot in the union
# of the two knot sets.

# Need an undefined value in case the algorithm is broken and we don't
# initialize the initial lines.  This would ideal be NaN, but that isn't
# available in MSVC, so set it to something very large.  In order to avoid
# normalizing that very large value to 1., only set the theory line to
# undefined, and set the resolution line to 0.
# const double undefined = 0./0.;
undefined = 1e308
m1 = undefined
b1 = undefined
m2 = 0.
b2 = 0.
sum = 0.
norm = 0.
# size_t p;
# double delta,delta2,delta3;
# double x, next_x, next_xin, next_xp;

# Set the target start of the integral to xin[_in].  This may or may
# not be beyond the end of the resolution function, depending on whether
# we are at the left edge of the data, or somewhere inside.
next_xin = xin[_in]

# Set p to the point just before next_xin
for p in range(1, Np):
if (xo + dx*xp[p] > next_xin):
break

p -= 1
next_xp = xo + dx*xp[p]

# Choose the larger of next_xp and next_xin as the starting point of the
# integral.  This will force us to step both xin and xp in the first
# iteration of the loop, computing new slope/intercepts for both lines.
# If the theory line extends beyond the resolution function, then we will
# be called with xin[_in] before the first resolution point, and next_xp
# will be bigger than next_xin.  If the resolution extends beyond the
# end of the theory, then the next_xp will be set before next_xin, and
# next_xin will be the start of the integral.  The integral ends when
# either the resolution or the theory runs out.
#
# We are tracking the area under the resolution as well as the area
# under the product of theory and resolution so that we can properly
# normalize the data when less than the full resolution is included.
# This means that we do not need to provide a resolution function
# normalized to a total area of 1 as our input.
x = next_xp if next_xp > next_xin else next_xin
# printf("  point xo:%g dx:%g x:%g, p:%ld, in:%ld, xp:%g, xin:%g\n",
#       xo,dx,x,p,in,next_xp,next_xin);
while True:
# Step xin if we are at the next theory point
if (next_xin <= x):
_in += 1
if (_in >= Nin):
break  # At the right edge of the data
next_xin = xin[_in]
m1 = (yin[_in] - yin[_in-1]) / (xin[_in] - xin[_in-1])
b1 = yin[_in] - m1*xin[_in]

# Step xp if we are at the next resolution point
if (next_xp <= x):
p += 1
if (p >= Np):
break  # At the right edge of the resolution
next_xp = xo + dx*xp[p]
m2 = (yp[p] - yp[p-1]) / (xp[p] - xp[p-1]) / dx
b2 = yp[p] - m2*next_xp

# Find the next node
next_x = next_xin if next_xin < next_xp else next_xp
# Compute the convolution and norm between the current and next node
delta = next_x - x
delta2 = next_x*next_x - x*x
delta3 = next_x*next_x*next_x - x*x*x
norm += 0.5*m2*delta2 + b2*delta
sum += m1*m2/3.0*delta3 + 0.5*(m1*b2+m2*b1)*delta2 + b1*b2*delta
# printf("  delta:%g delta2:%g delta3:%g norm:%g sum:%g m1:%g b1:%g m2:%g b2:%g x:%g nx:%g ni:%g np:%g\n",
#  delta, delta2, delta3, norm, sum, m1, b1, m2, b2, x, next_x, next_xin, next_xp);
# Move to the next node
x = next_x

return sum/norm

[docs]
@numba.njit(cache=True)
def convolve_sampled(xin, yin, xp, yp, x, dx, y):
Nin = len(xin)
Np = len(xp)
N = len(x)

# /* FIXME fails if xin are not sorted
# * slow if x not sorted */
if not Nin > 1:
raise ValueError("Nin must be > 1")

# /* Scan through all x values to be calculated */
# /* Re: omp, each thread is going through the entire input array,
# * independently, computing the resolution from the neighbourhood
# * around its individual output points.  The firstprivate( in )
# * clause sets each thread to keep its own copy of in, initialized
# * at in's initial value of zero.  The "schedule(static,1)" clause
# * puts neighbouring points in separate threads, which is a benefit
# * since there will be less backtracking if resolution width increases
# * from point to point.  Because the schedule is static, this does not
# * significantly increase the parallelization overhead.  Because the
# * threads are operating on interleaved points, there should be fewer cache
# * misses than if each thread were given different stretches of x to
# * convolve.
# */

_in = 0

# ifdef _OPENMP
# pragma omp parallel for firstprivate(in) schedule(static,1)
# endif
for _out in range(N):
# /* width of resolution window for x is w = 2 dx ^ 2. */
limit = -dx[_out]*xp[0]
xo = x[_out]

# /* Line up the left edge of the convolution window */
# /* It is probably forward from the current position, */
# /* but if the next dx is a lot higher than the current */
# /* dx or if the x are not sorted, then it may be before */
# /* the current position. * /
# /* FIXME verify that the convolution window is just right */
while (_in < Nin-1 and xin[_in] < xo-limit):
_in += 1
while (_in > 0 and xin[_in] > xo-limit):
_in -= 1

# /* Special handling to avoid 0/0 for w = 0. */
if (dx[_out] > 0.):
# printf("convolve in:%ld out:%ld, xo:%g dx:%g\n",in,out,xo,dx[out])
y[_out] = convolve_point_sampled(
Nin, xin, yin, Np, xp, yp, xo, dx[_out], _in)
elif (_in < Nin-1):
# /* Linear interpolation */
m = (yin[_in + 1] - yin[_in]) / (xin[_in + 1] - xin[_in])
b = yin[_in] - m*xin[_in]
y[_out] = m*xo + b
elif (_in > 0):
# /* Linear extrapolation */
m = (yin[_in]-yin[_in - 1])/(xin[_in]-xin[_in - 1])
b = yin[_in] - m*xin[_in]
y[_out] = m*xo + b
else:
# /* Can't happen because there is more than one point in xin. */
if not Nin > 1:
raise ValueError("should be more than one point in Nin")

```