[NumCS] Update/Add code examples

This commit is contained in:
RobinB27
2026-01-12 14:46:51 +01:00
parent 9da3a2c0d3
commit 0d2fc1f47e
7 changed files with 115 additions and 84 deletions

View File

@@ -30,10 +30,28 @@ $$
\end{bmatrix}
$$
\begin{code}{python}
def coeffs_monomial(x: np.ndarray, y: np.ndarray):
""" Solve Vandermonde matrix for monomial coeffs (very unstable) """
A = np.vander(x)
coeffs = np.linalg.solve(A, y)
return coeffs
\end{code}
Um $\alpha_i$ zu finden ist die Vandermonde Matrix unbrauchbar, da die Matrix schlecht konditioniert ist.
\newpage
Zur Auswertung von $p(x)$ kann man direkt die Matrix-darstellung nutzen, oder effizienter:
\fancydef{Horner Schema} $p(x) = (x \ldots x ( x (\alpha_n x + \alpha_{n-1}) + \ldots + \alpha_1) + \alpha_0)$
\begin{code}{python}
def eval_horner(coeffs: np.ndarray, vals: np.ndarray):
""" Evaluate polynomial using Horner scheme """
h = coeffs[0]
for i in range(1, len(coeffs)): h = vals * h + coeffs[i]
return h
\end{code}
\innumpy liefert \verb|polyfit| die direkte Auswertung, \verb|polyval| wertet Polynome via Horner-Schema aus. (Gemäss Script, in der Praxis sind diese Funktionen \verb|deprecated|)

View File

@@ -1,4 +1,3 @@
\newpage
\subsection{Newton Basis}
% Session: Herleitung unwichtig, konzentrieren auf Funktion/Eigenschaften von Newton/Lagrange.
@@ -91,8 +90,6 @@ Falls $x_j = x_0 + \underbrace{j \cdot h}_{:= \Delta^j}$ gilt vereinfacht sich e
wenn bspw. die Stützstellen nicht gut gewählt sind oder das Polynom einen zu hohen Grad hat.
Sie ist definiert durch $\displaystyle f(x) = \frac{1}{1 + x^2}$
\newpage
\begin{multicols}{2}
Matrixmultiplikation in $\mathcal{O}(n^3)$, Speicher $\mathcal{O}(n^2)$
@@ -171,7 +168,3 @@ Auswertung eines Newton-Polynoms funktioniert in $\mathcal{O}(n)$ durch ein modi
$$
\forall x \in [a,b]\ \exists \xi \in (a,b):\quad\quad \underbrace{f(x)-p(x)}_{\text{Fehler}} = \prod_{i=0}^{n}(x-x_i)\cdot\frac{f^{(n+1)}(\xi)}{(n+1)!}
$$
Man bemerke: Die Wahl der Stützpunkte hat direkten Einfluss auf den Fehler.

View File

@@ -51,37 +51,15 @@ Man berechnet die baryzentrischen Gewichte $\lambda_k$ folgendermassen:
\end{align*}
oder das ganze mithilfe von Numpy:
\begin{code}{python}
def barycentric_weights(x: np.ndarray) -> np.ndarray:
n = len(x)
# Initialize to zeros
barweight = np.ones(n)
for k in range(n):
# Vectorized differences between $x_k$ and all $x$s
differences = x[k] - x
# Remove the $k$-th element (and handle edge cases for $k = 0$ and $k = n - 1$)
if k < n - 1 and k > 0:
diff_processed = np.concatenate((differences[:k], differences[(k + 1) :]))
barweight[k] = 1 / np.prod(diff_processed)
elif k == 0:
barweight[k] = 1 / np.prod(differences[1:])
else:
barweight[k] = 1 / np.prod(differences[:k])
return barweight
def weights_barycentric(x: np.ndarray):
""" All x should be pairwise distinct, else zero-divison error. """
n = x.size
w = np.zeros(n)
for i in range(n):
w[i] = 1/( np.prod(x[i] - x[0:i]) * np.prod(x[i] - x[i+1:n]) )
return w
\end{code}
Gleiche funktion, etwas kürzer:
\begin{code}{python}
def barycentric_weights(x: np.ndarray) -> np.ndarray:
n = len(x)
w = np.ones(n) # = barweight
# Compute the (non-inverted) product, avoiding case (x[i] - x[i]) = 0
for i in range(0, n, 1):
if (i-1 > 0): w[0:(i-1)] *= (x[0:(i-1)] - x[i])
if (i+1 < n): w[i+1:n] *= (x[i+1:n] - x[i])
# Invert all at once
return 1/w
\end{code}
Mit dem können wir dann ein Polynom mit der baryzentrischen Interpolationsformel interpolieren:
\numberingOff
@@ -101,26 +79,20 @@ Mit anderen $\lambda_k$ eröffnet die baryzentrische Formel einen Weg zur Verall
Eine weitere Anwendung der Formel ist als Ausganspunkt für die Spektralmethode für Differenzialgleichungen.
\begin{code}{python}
def interp_barycentric(
data_point_x: np.ndarray,
data_point_y: np.ndarray,
barweight: np.ndarray,
x: np.ndarray
):
p_x = np.zeros_like(x)
n = data_point_x.shape[0]
for i in range(x.shape[0]):
# Separate sums to divide in the end
upper_sum = 0
lower_sum = 0
for k in range(n):
frac = barweight[k] / (x[i] - data_point_x[k])
upper_sum += frac * data_point_y[k]
lower_sum += frac
p_x[i] = upper_sum / lower_sum
return p_x
def eval_barycentric(w: np.ndarray, data: np.ndarray, y: np.ndarray, x: np.ndarray):
""" Sequentially calculate the barycentric formula """
n = x.size
tmp = np.ones(n)
for i in range(n): tmp[i] = eval_barycentric_scalar(w, data, y, x[i])
return tmp
def eval_barycentric_scalar(w: np.ndarray, data: np.ndarray, y: np.ndarray, x):
""" Barycentric interpolation formula for a single value x """
n = data.size;
bottom = np.sum( w / (x - data) )
top = np.sum( w / (x - data) * y)
return top / bottom
\end{code}

View File

@@ -139,20 +139,15 @@ Auf der nächsten Seite findet sich eine saubere, effiziente Implementation des
\newpage
\begin{code}{python}
def clenshaw(coeffs: np.ndarray, x: np.ndarray):
n = len(coeffs) - 1
# initialise temporary variables
d_prev_prev, d_prev, d_curr = (
np.zeros_like(x),
np.zeros_like(x),
np.zeros_like(x),
)
for k in range(n, -1, -1): # backward recursion
d_curr = coeffs[k] + 2 * x * d_prev - d_prev_prev
d_prev_prev, d_prev = d_prev, d_curr
return d_prev - x * d_prev_prev
def clenshaw(c: np.ndarray, x: np.ndarray):
""" Clenshaw algorithm to evaluate polynomial using chebyshev coeffs """
n = c.size; m = x.size
d = np.zeros((m, 3)) # Save vectors [curr, prev1, prev2] as matrix
for i in range(n-1, -1, -1):
d[:, 2] = d[:, 1]; d[:, 1] = d[:, 0]
d[:, 0] = c[i] + (2*x)*d[:, 1] - d[:, 2]
return d[:, 0] - x*d[:, 1]
\end{code}