codexnewton-interpolation-node-order.py
newton-interpolation-node-order.py
"""Show how reversing nodes changes Newton prefixes, but not the full fit.
Tested with Python 3.14, matplotlib 3.10.7, numpy 2.3.5.
Run from _media; writes this script's basename.png to the current directory.
"""
from fractions import Fraction
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def coefficients(nodes):
values = [Fraction(x**3-2*x+1) for x in nodes]
result = [values[0]]
for k in range(1, len(nodes)):
values = [(values[i+1]-values[i])/(nodes[i+k]-nodes[i])
for i in range(len(nodes)-k)]
result.append(values[0])
return result
def main():
assert coefficients([-1, 0, 2, 3]) == [2, -1, 1, 1]
assert coefficients([3, 2, 0, -1]) == [22, 17, 5, 1]
blue, orange, green = '#1967a5', '#b55a16', '#167343'
plt.rcParams.update({'font.size': 12, 'figure.facecolor': 'white'})
fig = plt.figure(figsize=(9.6, 6.8), layout='constrained')
gs = fig.add_gridspec(2, 2, height_ratios=[0.9, 1.6])
fig.suptitle('Different Newton paths, the same final polynomial', fontsize=16)
for col, color, title, lines in [
(0, blue, r'Order A: $(-1,0,2,3)$',
[r'Coefficients: $(2,-1,1,\mathbf{1})$',
r'$p_0=2$', r'$p_1=2-(x+1)$',
r'$p_2=2-(x+1)+(x+1)x$',
r'$p_3=p_2+(x+1)x(x-2)$']),
(1, orange, r'Order B: $(3,2,0,-1)$',
[r'Coefficients: $(22,17,5,\mathbf{1})$',
r'$q_0=22$', r'$q_1=22+17(x-3)$',
r'$q_2=22+17(x-3)+5(x-3)(x-2)$',
r'$q_3=q_2+(x-3)(x-2)x$'])]:
top = fig.add_subplot(gs[0, col])
top.axis('off')
top.set_title(title, color=color, fontsize=13)
for i, line in enumerate(lines):
top.text(0.03, 0.87-i*0.18, line, color=color, transform=top.transAxes,
va='center', fontsize=11.5)
ax = fig.add_subplot(gs[1, :])
x = np.linspace(-1.25, 3.25, 450)
ax.plot(x, x*x+1, color=blue, lw=2, label=r'$p_2=x^2+1$ (fits $-1,0,2$)')
ax.plot(x, 5*x*x-8*x+1, color=orange, lw=2, ls='--',
label=r'$q_2=5x^2-8x+1$ (fits $3,2,0$)')
ax.plot(x, x**3-2*x+1, color=green, lw=2.6,
label=r'$p_3=q_3=x^3-2x+1$ (fits all four)')
nodes = np.array([-1, 0, 2, 3])
ax.scatter(nodes, nodes**3-2*nodes+1, color=green, edgecolor='white', s=65, zorder=5)
ax.scatter([3], [10], facecolor='white', edgecolor=blue, s=60, lw=1.6, zorder=5)
ax.scatter([-1], [14], facecolor='white', edgecolor=orange, s=60, lw=1.6, zorder=5)
for node, before, after, label, color, labelx in [
(3, 10, 22, '+12', blue, 2.66), (-1, 14, 2, '−12', orange, -0.73)]:
ax.annotate('', xy=(node, after), xytext=(node, before),
arrowprops={'arrowstyle': '->', 'lw': 1.8, 'color': color})
ax.text(labelx, (before+after)/2, label, color=color, fontsize=12,
va='center', ha='center', bbox={'facecolor':'white', 'edgecolor':'none', 'pad':2})
ax.set(xlim=(-1.25, 3.25), ylim=(-4, 29), xlabel='x', ylabel='Interpolant value',
xticks=[-1, 0, 1, 2, 3])
ax.grid(alpha=0.18)
ax.legend(loc='upper center', fontsize=10.5, framealpha=0.96)
ax.spines[['top', 'right']].set_visible(False)
fig.savefig(Path(__file__).stem+'.png', dpi=120, facecolor='white', transparent=False)
plt.close(fig)
if __name__ == '__main__':
main()