human
Solve the following python programming problem given the following code:
# Numpy уже имеет класс для полиномов: numpy.poly1d.
# Для полноты изложения мы реализуем свой класс.
class Poly():
    def __init__(self, pn):
        """
        Создает многочлен с коэффициентами pn:
            self(x) = sum_n pn[n] * x**n.
        Коэффициенты pn перечисляются в порядке возрастания степени одночлена.
        """
        self.pn = pn
    def __call__(self, x):
        """
        Вычисляет многочлен на векторе значений x.
        """
        a = 1. # Здесь мы накапливаем степени x**n.
        p = 0. # Сюда мы помещаем сумму одночленов.
        for pn in self.pn:
            p += a*pn # Учитываем очередной одночлен.
            a *= x # Повышаем степень одночлена
        return p
    
# Вспомогательная функция для счета матрицы Вандермонда.
def vandermonde(xn):
    return np.power(xn[:,None], np.arange(len(xn))[None,:])

# Напишем функцию, которая будет находить интерполяционных многочлен через решение системы.
def interp_naive(xn, fn):
    """
    Возвращает интерполяционный многочлен, принимающий в точках xp значение fp.
    """
    M = vandermonde(xn)
    # Мы используем функцию numpy для решения линейных систем. 
    # Методы решения линейных систем обсуждаются в другой лабораторной работе.
    pn = np.linalg.solve(M, fn)
    return Poly(pn)

# Возьмем логарифмическую решетку на интервале [1E-6,1] и посмотрим, насколько точно мы восстанавливаем многочлен.
N = 8 # Число узлов интерполяции.
x = np.logspace(-6,0,N) # Точки равномерной решетки
# Будет интерполировать многочлен степени N-1, который зададим случайными коэффициентами.
f = Poly(np.random.randn(N))
y = f(x) # Значения многочлена на решетке.
p = interp_naive(x, y) # Строим интерполяционный многочлен.
z = p(x) # Значения интерполяционного многочлена на решетке.
print("Absolute error of values", np.linalg.norm(z-y))
print("Absolute error of coefficients", np.linalg.norm(f.pn-p.pn))
Given the above code, generate some markdown instructions for it.
assistant
#Here is the Python program you requested.

Интерполяция многочленами
Допустим мы знаем значения $f_k=f(x_k)$ некоторой функции $f(x)$ только на некотором множестве аргументов $x_k\in\mathbb R$, $k=1..K$.
Мы хотим вычислять $f$ в точках $x$ лежащих между узлами интерполяции $x_k$, для чего мы строим интерполирующую функцию $p(x)$, определенную при всех значениях $x$ и совпадающую с $f$ в узлах интерполяции $f(x)=p(x)$, $k=1..K$.
Распространенным (но не единственным) выбором для функции $p$ является многочлен степени $K-1$:
$$p(x)=\sum_{n=0}^{K-1} p_n x^n,$$
число коэффициентов которого равно число известных значений функции, что позволяет однозначно найти эти коэффициенты.
Формально коэффициенты находятся из системы уравнений:
$$p(x_k)=f_k=\sum_{n=0}^{K-1} p_n x_k^n,$$
или в матричном виде $MP=F$, где 
$$
F=\begin{pmatrix}f_1\\vdots\f_K\end{pmatrix},\quad
P=\begin{pmatrix}p_0\\vdots\p_{K-1}\end{pmatrix},\quad
M=\begin{pmatrix}
x_1^0 & \cdots & x_1^{K-1} \
\vdots & \ddots & \vdots \
x_K^0 & \cdots & x_K^{K-1} \
\end{pmatrix}.
$$
Матрица $M$ называется матрицей Вандермонда.
Попробуем построить интерполяционный многочлен таким способом.