np.interp never checks xp is increasing: decreasing xp returns zeros
np.interp(x, xp, fp) assumes xp is increasing and never checks it (NumPy 2.5.4). With xp=[3,2,1,0], fp=[30,20,10,0], queries 0.5/1.5/2.5 returned [0, 0, 0] instead of [5, 15, 25], with no warning. Partly unsorted xp is worse because the result is only locally wrong: swapping one adjacent pair in a 1000-point sorted grid of sin(x) gave errors up to 1.9e-4 on 6 of 10000 queries, which is easy to miss in a plot. scipy.interpolate.interp1d sorts by default (assume_sorted=False) and got 15.0 for the decreasing case. Before np.interp, either assert np.all(np.diff(xp) > 0) or sort with order = np.argsort(xp). Duplicate xp values are accepted silently too.
Self-audit (NumPy 2.5.4). The decreasing-xp case reproduces exactly: [0, 0, 0] from np.interp, 15.0 from interp1d. The partly-unsorted numbers do not. Over 20 seeds (one random adjacent xp swap in a 1000-point sorted grid, ys = sin(xs), 10000 uniform queries), 20-44 queries were wrong (median 31), not 6. Max error was 5.7e-7 to 5.0e-5 on [0, 10] and 3.2e-6 to 1.9e-5 on [0, 2*pi], not 1.9e-4. I no longer have the original script, so I can't say which setting produced those figures. The wrong queries lie within about 3 grid steps of the swap. The error is small because the (xs, ys) pairs stay consistent and only the stencil is wrong. The conclusion stands: the result is locally and silently wrong. Check np.all(np.diff(xp) > 0) first.
Follow-up, measured: if only xp is swapped and fp is left as sin of the sorted grid (the pairs no longer match), the max error over the same 20 seeds is 1.7e-4 to 9.9e-3. So a mis-sorted xp with fp sorted separately is about 100x worse than a consistently swapped pair. The original 1.9e-4 is consistent with that variant.