7  Die schwache Form: von der Gleichung zur Federkette

Zwei Bilder liegen jetzt auf dem Tisch, und sie scheinen nichts miteinander zu tun zu haben. Das eine ist die Federkette aus Kapitel 3: eine Handvoll Knoten, an denen Federn ziehen, zusammengefasst zu einer Tabelle aus Zahlen, die wir in Kapitel 4 im Handumdrehen gelöst haben. Das andere ist die Wärmeleitungsgleichung aus Kapitel 6: eine einzige Gleichung, die nicht für fünf Knoten gilt, sondern für unendlich viele Punkte gleichzeitig — und deren Lösung keine Handvoll Zahlen ist, sondern eine ganze Kurve.

Dieses Kapitel schlägt die Brücke zwischen den beiden. Am Ende wirst du sehen: Die Wärmeleitung im Träger ist buchstäblich eine Federkette. Zwischen den Knoten sitzen Wärme-Federn, und ihre Steifigkeit hat sogar einen konkreten Wert — \(\lambda\,A/h\), aus Wärmeleitfähigkeit, Querschnitt und Knotenabstand. Ist die Brücke einmal geschlagen, rechnet die Federketten-Maschinerie aus Kapitel 3 und Kapitel 4 die Wärme genauso, wie sie die Verschiebung gerechnet hat: assemblieren, Randwerte einbauen, lösen.

Code
import matplotlib.pyplot as plt

def feder(ax, x0, x1, y, farbe="tab:blue", lw=1.6):
    px = [x0, x0 + 0.15 * (x1 - x0)]
    py = [y, y]
    a = x0 + 0.15 * (x1 - x0)
    b = x0 + 0.85 * (x1 - x0)
    for i in range(11):
        anteil = i / 10
        px.append(a + anteil * (b - a))
        py.append(y + 0.06 if i % 2 == 0 else y - 0.06)
    px.append(x1)
    py.append(y)
    ax.plot(px, py, color=farbe, lw=lw, solid_capstyle="round")

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10.5, 3.6))

# Links: die Federkette
ax1.add_patch(plt.Rectangle((-0.3, -0.35), 0.3, 0.7, hatch="///",
                            facecolor="lightsteelblue", edgecolor="black"))
knoten = [0.0, 1.0, 2.0, 3.0]
for i in range(3):
    feder(ax1, knoten[i], knoten[i + 1], 0.0)
for i in range(4):
    ax1.plot(knoten[i], 0.0, "o", ms=12, color="tab:blue",
             markeredgecolor="black", zorder=4)
ax1.annotate("", xy=(3.6, 0.0), xytext=(3.0, 0.0),
             arrowprops=dict(arrowstyle="-|>", color="tab:red", lw=2.2))
ax1.text(3.62, 0.0, "Kraft", color="tab:red", va="center", fontsize=10)
ax1.set_title("Kapitel 3: die Federkette\n(Knoten, Federn, Zahlen)",
              fontsize=10)
ax1.set_xlim(-0.5, 4.3)
ax1.set_ylim(-0.7, 0.7)
ax1.set_aspect("equal")
ax1.axis("off")

# Rechts: der Träger mit der Gleichung
ax2.add_patch(plt.Rectangle((0.0, -0.3), 3.0, 0.6, facecolor="#fde8d0",
                            edgecolor="black", lw=1.3))
for i in range(4):
    ax2.plot(i, 0.0, "o", ms=12, color="tab:orange", markeredgecolor="black",
             zorder=4)
ax2.fill([3.0, 3.25, 3.0], [-0.15, 0.0, 0.15], color="orangered")
ax2.text(3.3, 0.0, "Brandherd", color="orangered", va="center", fontsize=9)
ax2.text(1.5, 0.75, r"$\frac{d}{dx}\left(\lambda\frac{dT}{dx}\right)=0$",
         ha="center", fontsize=15)
ax2.set_title("Kapitel 6: der Träger\n(eine Gleichung für jeden Punkt)",
              fontsize=10)
ax2.set_xlim(-0.5, 4.3)
ax2.set_ylim(-0.7, 1.1)
ax2.set_aspect("equal")
ax2.axis("off")

fig.text(0.5, 0.5, "?", ha="center", va="center", fontsize=34,
         color="tab:green", weight="bold")
plt.tight_layout()
plt.show()
Abbildung 7.1: Die Brücke dieses Kapitels. Links die Federkette aus Kapitel 3: Knoten, Federn, eine Zugkraft — gelöst mit reiner Schulalgebra. Rechts der Träger aus Kapitel 6: dieselben fünf Knoten, aber dazwischen die Wärmeleitungsgleichung, die für jeden Punkt gilt. Die Frage des Kapitels steht in der Mitte: Wie wird aus der Gleichung rechts die Kette links?

Was man hier sieht: links das Vertraute, rechts das scheinbar Fremde, in der Mitte das Fragezeichen dieses Kapitels. Die Leitfrage stellen wir bewusst rückwärts: nicht „was ist das für ein Verfahren?“, sondern ganz handfest — „Wie komme ich von der Gleichung rechts zur Kette links?“ Sieben Schritte weit ist der Weg, und jeder einzelne benutzt nur Werkzeuge, die du schon hast: die Federketten-Maschinerie (Kapitel 3), den Löser (Kapitel 4), Steigung und Fläche (Kapitel 5), die Bilanz (Kapitel 6).

Lernziele

Nach diesem Kapitel kannst du …

  1. … erklären, warum man die Temperatur zwischen den Knoten durch Geradenstücke annähert, und wie Hütchenfunktionen das aufschreiben,
  2. … das Prinzip der gewichteten Reste in einem Satz sagen — die Bilanz muss nicht überall exakt stimmen, aber im gewichteten Mittel um jeden Knoten,
  3. … erklären, welches Problem die Umverteilung (partielle Integration) löst: Unsere Geradenstücke haben Knicke, zweimal ableiten geht nicht,
  4. … die 2×2-Elementmatrix der Wärmeleitung \(\frac{\lambda A}{h} \begin{pmatrix} 1 & -1\\ -1 & 1\end{pmatrix}\) selbst ausrechnen — nur mit Steigungen und Flächen aus Kapitel 5,
  5. … das 5×5-System des Trägers assemblieren und mit dem Thomas-Löser aus Kapitel 4 lösen — und begründen, warum die FEM-Lösung hier die exakte Gerade trifft.
WarnungNaheliegende Vermutung

Vermutung: „Eine Näherung aus groben Geradenstücken kann nie exakt sein. Wenn ich die krumme Wirklichkeit durch ein paar gerade Linien ersetze, handle ich mir immer einen Fehler ein.”

Warum sie naheliegt: Das ganze Kapitel handelt vom Annähern — die glatte Temperatur wird durch einen eckigen Streckenzug ersetzt. Eckig statt glatt klingt nach Kompromiss, nach „ungefähr”, nach unvermeidlichem Fehler.

Was stattdessen stimmt: Für unseren homogenen Träger trifft die FEM die Lösung an den Knoten exakt — auf die Nachkommastelle. Der Grund ist verblüffend: Die wahre Lösung ist selbst eine Gerade (Kapitel 6), und einen Geradenzug kann man mit Geradenstücken perfekt nachzeichnen. Der Fehler lebt nur dort, wo die Wirklichkeit krummer ist als der Ansatz — bei Wärmequellen, Materialsprüngen, in der Fläche. Wie groß er dann ist und wie man ihn misst, vermessen wir in Kapitel 12. Hier erleben wir zuerst den schönen Sonderfall, in dem die grobe Näherung gar keine Näherung ist.

7.1 Schritt 1 — Der Ansatz: Zuständigkeitsgebiete

Fangen wir mit der einzigen Entscheidung an, die die ganze Methode trägt: Wie beschreiben wir die Temperatur im Träger mit nur fünf Zahlen? Wir kennen (oder suchen) die Temperatur an fünf Knoten — bei \(0,\ 0{,}25,\ 0{,}5, \ 0{,}75,\ 1{,}0\ \mathrm{m}\). Aber die Temperatur gibt es an jeder Stelle dazwischen, nicht nur an den Knoten. Was setzen wir zwischen die Knoten?

Die Antwort haben wir in Kapitel 5 schon vorbereitet: Zwischen zwei Knoten spannen wir eine Gerade. Der Temperaturverlauf wird ein Streckenzug — als hätte jemand die Knotenwerte mit dem Lineal verbunden. Fünf Zahlen, vier gerade Stücke, fertig ist die ganze Kurve. Und weil jedes Stück eine Gerade ist, sind Steigung und Fläche darauf exakte Schulgeometrie (Dreieck und Trapez, Kapitel 5) — kein Grenzwert, kein Fehler beim Rechnen.

Jetzt kommt der Trick, der diesen Streckenzug in eine saubere Formel gießt. Wir fragen: Welchen Beitrag leistet ein einzelner Knoten zur ganzen Kurve? Denk an Knoten 3 in der Mitte. Sein Wert bestimmt die Kurve ganz an seinem eigenen Ort, ein bisschen weniger kurz davor und dahinter, und gar nicht mehr bei seinen Nachbarn. Sein Einfluss ist ein Hütchen (oder ein Zelt): Über dem Knoten steht es auf der Höhe 1, zu beiden Nachbarn fällt es geradlinig auf 0 ab, und weiter draußen ist es null. Abbildung 7.2 zeigt alle fünf.

Code
import matplotlib.pyplot as plt

knoten_x = [0.0, 0.25, 0.5, 0.75, 1.0]
farben = ["tab:blue", "tab:orange", "tab:green", "tab:red", "tab:purple"]
h = 0.25

fig, ax = plt.subplots(figsize=(9.0, 3.8))
for i, xk in enumerate(knoten_x):
    # Huetchen: 0 -> 1 -> 0 um den Knoten herum
    xs = [xk - h, xk, xk + h]
    ys = [0.0, 1.0, 0.0]
    # auf den Träger beschneiden
    xs_c, ys_c = [], []
    for x, y in zip(xs, ys):
        if x < 0.0:
            continue
        if x > 1.0:
            continue
        xs_c.append(x)
        ys_c.append(y)
    ax.plot(xs_c, ys_c, color=farben[i], lw=2.2)
    ax.fill_between(xs_c, 0, ys_c, color=farben[i], alpha=0.12)
    ax.plot(xk, 0.0, "o", color=farben[i], ms=10, markeredgecolor="black",
            zorder=5)
    ax.text(xk, 1.05, "Knoten %d" % (i + 1), ha="center", fontsize=9,
            color=farben[i])

ax.set_xlabel("Ort entlang des Trägers (m)")
ax.set_ylabel("Anteil des Knotens")
ax.set_title("Hütchenfunktionen: jedes Knotens Zuständigkeitsgebiet")
ax.set_ylim(0, 1.25)
ax.set_xlim(-0.05, 1.05)
plt.tight_layout()
plt.show()
Abbildung 7.2: Die fünf Hütchenfunktionen über dem Träger — je eine pro Knoten. Jedes Hütchen steht über seinem eigenen Knoten auf 1 und fällt zu den Nachbarn geradlinig auf 0. Das ist das „Zuständigkeitsgebiet” des Knotens: Nur dort, wo sein Hütchen von null verschieden ist, hat er ein Wort mitzureden. An jeder Stelle überlappen sich höchstens zwei Hütchen — darum wird jeder Punkt von höchstens zwei Knoten „regiert”.

Was man hier sieht: fünf Hütchen, für jeden Knoten eines. Über seinem eigenen Ort trägt ein Knoten voll (Höhe 1), bei den Nachbarn gar nicht (Höhe 0). Wichtig ist die Überlappung: An jeder Stelle des Trägers sind höchstens zwei Hütchen von null verschieden — das linke und das rechte des Abschnitts, in dem man steht. Jeder Punkt wird also von höchstens zwei Knoten „regiert”, und ihre beiden Hütchen sagen, zu welchen Anteilen. Genau das ist Interpolation: die Temperatur an einer Zwischenstelle als Mischung der beiden zuständigen Knotenwerte.

Diese Beschreibung lässt sich Wort für Wort in reines Python gießen. Ein Hütchen ist eine denkbar einfache Funktion — 1 am eigenen Knoten, linear fallend, 0 ab dem Nachbarn:

Code
def huetchenwert(knoten_nummer, ort, knotenabstand):
    """Wert der Huetchenfunktion eines Knotens an einer Stelle.

    knoten_nummer: welcher Knoten (0, 1, 2, ...).
    ort: die Stelle auf dem Träger (Meter).
    knotenabstand: der gleiche Abstand h zwischen zwei Knoten (Meter).
    Rueckgabe: der Anteil des Knotens an dieser Stelle (zwischen 0 und 1).

    Das Huetchen steht ueber dem eigenen Knoten auf 1 und faellt zu beiden
    Nachbarn geradlinig auf 0; ab dem Nachbarn ist es null.
    """
    knoten_ort = knoten_nummer * knotenabstand
    abstand = abs(ort - knoten_ort)
    if abstand >= knotenabstand:
        return 0.0
    return 1.0 - abstand / knotenabstand


def interpoliere(temperaturen, ort, knotenabstand):
    """Temperatur an einer beliebigen Stelle aus den Knotenwerten.

    temperaturen: Liste der Knotentemperaturen (Grad Celsius).
    ort: die Stelle auf dem Träger (Meter).
    knotenabstand: der gleiche Abstand h zwischen zwei Knoten (Meter).
    Rueckgabe: die interpolierte Temperatur an dieser Stelle.

    Die Kurve ist die Summe: jeder Knotenwert mal seinem Huetchen. Weil an
    jeder Stelle nur zwei Huetchen ungleich null sind, mischt die Summe
    genau die zwei zustaendigen Knotenwerte.
    """
    wert = 0.0
    for knoten in range(len(temperaturen)):
        wert = wert + temperaturen[knoten] * huetchenwert(knoten, ort,
                                                          knotenabstand)
    return wert


# Ein Beispielprofil an den fuenf Knoten und ein paar Zwischenstellen.
KNOTENTEMPERATUREN = [20.0, 90.0, 160.0, 230.0, 300.0]
KNOTENABSTAND = 0.25

print("Ort (m) | interpolierte Temperatur")
print("--------+-------------------------")
for schritt in range(9):
    ort = schritt * 0.125
    print("  %.3f | %7.1f °C" % (ort, interpoliere(KNOTENTEMPERATUREN, ort,
                                                   KNOTENABSTAND)))
Ort (m) | interpolierte Temperatur
--------+-------------------------
  0.000 |    20.0 °C
  0.125 |    55.0 °C
  0.250 |    90.0 °C
  0.375 |   125.0 °C
  0.500 |   160.0 °C
  0.625 |   195.0 °C
  0.750 |   230.0 °C
  0.875 |   265.0 °C
  1.000 |   300.0 °C

Interpretation der Ausgabe: An den Knoten (0, 0,25, 0,5, …) kommen genau die Knotenwerte heraus — 20, 90, 160, … —, weil dort das eigene Hütchen 1 ist und alle anderen 0 sind. Genau zwischen zwei Knoten (etwa bei 0,125 m: 55 °C) liegt der Mittelwert der beiden Nachbarn, weil sich dort ihre beiden Hütchen zu je einem halben Anteil mischen. Die Zeile temperaturen[knoten] * huetchenwert(...), aufsummiert, ist die ganze Interpolation — und sie ist buchstäblich eine gewichtete Summe wie der Mittelwert aus Kapitel 2, nur dass die Gewichte die Hütchen sind.

7.1.1 Fünf Zahlen bestimmen die ganze Kurve

Der Streckenzug hat eine Eigenschaft, die man gesehen haben muss: Verschiebt man einen einzigen Knotenwert, folgt die Kurve — aber nur in der Umgebung dieses Knotens, weil sein Hütchen weiter draußen null ist. Die folgende Animation zieht die Knotenwerte auf und ab; achte darauf, wie der Geradenzug gehorcht und wie ruhig die entfernten Abschnitte bleiben.

Code
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML

knoten_x = [0.0, 0.25, 0.5, 0.75, 1.0]
grund = [20.0, 90.0, 160.0, 230.0, 300.0]
# Wie stark jeder innere Knoten pro Frame ausgelenkt wird (Raender fest).
amplitude = [0.0, 70.0, 100.0, 70.0, 0.0]

fig, ax = plt.subplots(figsize=(8.0, 4.0))

def zeichne(frame):
    ax.clear()
    phase = np.sin(2 * np.pi * frame / 24.0)
    werte = [grund[i] + amplitude[i] * phase for i in range(5)]
    ax.plot(knoten_x, werte, "-", color="tab:orange", lw=2.4, zorder=2)
    ax.plot(knoten_x, werte, "o", color="tab:blue", ms=12,
            markeredgecolor="black", zorder=4)
    for i in range(5):
        ax.text(knoten_x[i], werte[i] + 12, "%.0f" % werte[i], ha="center",
                fontsize=9)
    ax.set_xlim(-0.05, 1.05)
    ax.set_ylim(0, 440)
    ax.set_xlabel("Ort entlang des Trägers (m)")
    ax.set_ylabel("Temperatur (°C)")
    ax.set_title("Der Streckenzug folgt den fünf Knotenwerten")
    return []

ani = FuncAnimation(fig, zeichne, frames=24, interval=140, blit=False)
plt.close(fig)
HTML(ani.to_jshtml())
Abbildung 7.3: Fünf Zahlen bestimmen die ganze Kurve. Die Knotenwerte (Punkte) wandern auf und ab, der Streckenzug folgt ihnen. Verschiebt sich ein Knoten, verbiegt sich die Kurve nur in seiner Nachbarschaft — sein Hütchen reicht ja nur bis zu den Nachbarn. Die Ränder (Knoten 1 und 5) bleiben hier fest, wie später beim eingespannten Träger.

Was man sieht: Der Geradenzug ist völlig durch die fünf Knotenwerte festgelegt — kein Freiheitsgrad mehr dazwischen. Das ist die Kernidee des Ansatzes: Aus der unendlich langen Wunschliste „Temperatur an jedem Punkt” haben wir fünf Stellschrauben gemacht. Die Kunst des restlichen Kapitels ist, die richtigen fünf Zahlen zu finden.

7.2 Schritt 2 — Einsetzen und scheitern

Wir haben einen Ansatz mit fünf Stellschrauben. Der nächste Gedanke liegt auf der Hand: Setz den Streckenzug in die Wärmeleitungsgleichung ein und schau, welche Knotenwerte sie erfüllen. Die Gleichung aus Kapitel 6 lautet

\[ \frac{\mathrm{d}}{\mathrm{d}x} \left( \lambda \, \frac{\mathrm{d}T}{\mathrm{d}x} \right) = 0. \]

Sie verlangt zwei Ableitungen hintereinander: erst die Steigung des Profils \(\mathrm{d}T/\mathrm{d}x\), dann die Steigung davon. Und genau hier laufen wir gegen eine Wand. Sehen wir uns an, was mit den Ableitungen unseres Streckenzugs passiert.

Code
import matplotlib.pyplot as plt

knoten_x = [0.0, 0.25, 0.5, 0.75, 1.0]
knoten_T = [20.0, 140.0, 80.0, 250.0, 300.0]
h = 0.25
steigungen = [(knoten_T[i + 1] - knoten_T[i]) / h for i in range(4)]

fig, achsen = plt.subplots(3, 1, figsize=(8.5, 7.2), sharex=True)

# Oben: der Streckenzug mit Knicken
ax = achsen[0]
ax.plot(knoten_x, knoten_T, "-", color="tab:orange", lw=2.4)
ax.plot(knoten_x, knoten_T, "o", color="tab:blue", ms=10,
        markeredgecolor="black", zorder=5)
for xk in knoten_x[1:4]:
    tk = knoten_T[knoten_x.index(xk)]
    ax.annotate("Knick", xy=(xk, tk), xytext=(xk, tk + 55),
                ha="center", fontsize=9, color="tab:red",
                arrowprops=dict(arrowstyle="->", color="tab:red"))
ax.set_ylabel("Temperatur\nT (°C)")
ax.set_title("Der Streckenzug: gerade Stücke mit Knicken")
ax.set_ylim(0, 330)

# Mitte: die Steigung (Treppe)
ax = achsen[1]
for i in range(4):
    ax.plot([knoten_x[i], knoten_x[i + 1]], [steigungen[i], steigungen[i]],
            color="tab:green", lw=2.6)
for i in range(3):
    ax.plot([knoten_x[i + 1], knoten_x[i + 1]],
            [steigungen[i], steigungen[i + 1]], color="tab:green", lw=1.0,
            ls="--")
ax.axhline(0, color="0.7", lw=0.8)
ax.set_ylabel("Steigung\ndT/dx (°C/m)")
ax.set_title("Ihre Steigung: auf jedem Stück konstant, an den Knicken springt sie")

# Unten: die zweite Ableitung (Blitze)
ax = achsen[2]
for i in range(4):
    ax.plot([knoten_x[i], knoten_x[i + 1]], [0, 0], color="tab:purple", lw=2.6)
for xk in knoten_x[1:4]:
    ax.annotate("", xy=(xk, 1.0), xytext=(xk, 0.0),
                arrowprops=dict(arrowstyle="-|>", color="tab:red", lw=2.2))
    ax.text(xk, 1.05, "∞ ?", ha="center", color="tab:red", fontsize=11)
ax.set_ylabel("zweite\nAbleitung")
ax.set_ylim(-0.4, 1.4)
ax.set_yticks([])
ax.set_title("Die zweite Ableitung: null im Inneren, undefiniert an den Knicken")
ax.set_xlabel("Ort entlang des Trägers (m)")
plt.tight_layout()
plt.show()
Abbildung 7.4: Warum das Einsetzen scheitert. Oben ein Streckenzug (irgendein Kandidatenprofil) mit scharfen Knicken an den Knoten. In der Mitte seine Steigung dT/dx: auf jedem geraden Stück konstant, aber an den Knicken springt sie schlagartig — eine Treppe mit senkrechten Stufen. Unten die zweite Ableitung: innerhalb der Stücke null, aber an den Knicken müsste sie unendlich groß werden (die roten Blitze). Dort, wo die Wärmeleitungsgleichung ihre zweite Ableitung braucht, existiert sie nicht.

Was man hier sieht: eine ehrliche Krise. Der Streckenzug (oben) hat an jedem Knoten einen Knick. Seine Steigung (Mitte) ist auf jedem geraden Stück eine brave Konstante — aber an den Knicken springt sie schlagartig von einem Wert auf den nächsten, ohne Übergang. Und die zweite Ableitung (unten), die ja die Steigung der Steigung misst, ist innerhalb der Stücke null (eine Konstante hat keine Steigung), müsste aber an den Sprungstellen unendlich groß werden: eine endliche Stufe in null Breite. Sie existiert dort nicht.

Das ist der Haken. Die Wärmeleitungsgleichung fordert die zweite Ableitung an jeder Stelle — auch an den Knoten. Aber genau an den Knoten, wo unsere Stellschrauben sitzen, stirbt die zweite Ableitung. Wir können den Streckenzug schlicht nicht in die Gleichung einsetzen. Der direkte Weg ist versperrt.

HinweisWarum wir den Ansatz trotzdem behalten

Man könnte in Versuchung geraten, den Streckenzug wegzuwerfen und eine glatte Kurve zu suchen, die man beliebig oft ableiten kann. Aber der Streckenzug ist ein Geschenk: Auf ihm sind Steigung und Fläche exakte Schulgeometrie (Kapitel 5), und mit fünf Zahlen ist er computertauglich einfach. Statt den guten Ansatz aufzugeben, ändern wir lieber die Frage. Der nächste Schritt formuliert die Wärmeleitungsgleichung so um, dass sie mit der einen Ableitung auskommt, die unser Streckenzug hat — und die zweite gar nicht mehr braucht. Aus der Not wird die Methode geboren.

7.3 Schritt 3 — Gewichtete Reste: die Bilanz im Mittel

Kehren wir zurück zum Ursprung der Gleichung. Sie war nie ein Selbstzweck — sie war die Bilanz aus Kapitel 6: An jedem Punkt soll gleich viel Wärme herein- wie hinausströmen (rein = raus). Die Forderung „an jedem Punkt” ist das Problem: Es gibt unendlich viele Punkte, aber wir haben nur fünf Stellschrauben. Fünf Zahlen können unmöglich unendlich viele Bedingungen erfüllen.

Was tut man, wenn man eine Bedingung nicht überall erzwingen kann? Man prüft sie stichprobenartig, aber klug. Nicht an einzelnen Punkten (das wäre Willkür), sondern als gewichtetes Mittel: Man fragt nicht „stimmt die Bilanz genau hier?“, sondern „stimmt die Bilanz im Mittel um diesen Knoten herum?”. Und man stellt genau eine solche Frage pro Knoten — so viele Fragen, wie es Stellschrauben gibt. Fünf Knoten, fünf Bilanzfragen, fünf Gleichungen: Das passt genau zu einem lösbaren System.

Die entscheidende Wahl ist, mit welchem Gewicht man mittelt. Und hier kommt die eleganteste Idee der ganzen Methode: Wir nehmen als Gewicht dasselbe Hütchen. Das Hütchen von Knoten 3 ist ja gerade sein Zuständigkeitsgebiet — es ist groß an seinem Ort und null bei den Nachbarn. Als Gewicht benutzt, „befühlt” es die Bilanz genau dort, wo dieser Knoten etwas zu sagen hat, und ignoriert den Rest des Trägers.

Code
import matplotlib.pyplot as plt

knoten_x = [0.0, 0.25, 0.5, 0.75, 1.0]
h = 0.25
mitte = 0.5

fig, ax = plt.subplots(figsize=(9.0, 3.6))
# der Träger
ax.add_patch(plt.Rectangle((0.0, -0.12), 1.0, 0.12, facecolor="#fde8d0",
                           edgecolor="black", lw=1.0))
# alle Knoten
for i, xk in enumerate(knoten_x):
    farbe = "tab:green" if i == 2 else "0.6"
    ax.plot(xk, 0.0, "o", color=farbe, ms=11, markeredgecolor="black",
            zorder=5)
# Gewichts-Huetchen um Knoten 3
xs = [mitte - h, mitte, mitte + h]
ys = [0.0, 1.0, 0.0]
ax.plot(xs, ys, color="tab:green", lw=2.4)
ax.fill_between(xs, 0, ys, color="tab:green", alpha=0.18)
ax.text(mitte, 1.05, "Gewicht = Hütchen von Knoten 3", ha="center",
        color="tab:green", fontsize=10)
ax.annotate("hier zählt die Bilanz voll", xy=(mitte, 0.7),
            xytext=(0.66, 0.85), fontsize=9, color="tab:green",
            arrowprops=dict(arrowstyle="->", color="tab:green"))
ax.text(0.12, 0.15, "hier gar nicht", color="0.5", fontsize=9)
ax.text(0.88, 0.15, "hier gar nicht", color="0.5", fontsize=9, ha="right")
ax.set_xlim(-0.05, 1.05)
ax.set_ylim(-0.2, 1.25)
ax.set_yticks([])
ax.set_xlabel("Ort entlang des Trägers (m)")
ax.set_title("Eine Bilanzfrage pro Knoten, gewichtet mit seinem Hütchen")
plt.tight_layout()
plt.show()
Abbildung 7.5: Das Hütchen als Gewicht. Für die Bilanzfrage an Knoten 3 (grün) legen wir sein eigenes Hütchen als Wichtung über den Träger. Wo das Hütchen hoch ist (am Knoten), zählt die Bilanz voll; wo es abfällt (zu den Nachbarn), immer weniger; außerhalb (grau) gar nicht. So wird aus „Bilanz genau am Punkt” ein gewichtetes Mittel um den Knoten — eine Frage, die unser Streckenzug beantworten kann.

Was man hier sieht: die Bilanzfrage des mittleren Knotens, gewichtet mit seinem eigenen Hütchen. Die Bilanz wird nicht überall gleich streng genommen: Direkt am Knoten zählt sie voll, zu den Nachbarn hin immer weniger, außerhalb gar nicht. Jeder Knoten stellt so seine lokale Bilanzfrage — „stimmt die Wärmebilanz in meinem Einzugsbereich?” —, und die fünf Antworten sind die fünf Gleichungen des Systems. Aufgeschrieben lautet die Frage für Knoten \(i\): die mit dem Hütchen \(N_i\) gewichtete Bilanz über den ganzen Träger soll null sein,

\[ \int_0^L \underbrace{\frac{\mathrm{d}}{\mathrm{d}x} \left( \lambda \, \frac{\mathrm{d}T}{\mathrm{d}x} \right)}_{\text{Bilanz an der Stelle}} \cdot \; \underbrace{N_i(x)}_{\text{Gewicht}} \; \mathrm{d}x \;=\; 0 . \]

Das lange \(\int\) ist die Summe aus Kapitel 5 — hier summiert es die gewichtete Bilanz über alle Orte. Nur: In der Klammer steht immer noch die zweite Ableitung, die unser Streckenzug nicht hat. Diesen letzten Stein räumt der nächste Schritt weg.

7.4 Schritt 4 — Umverteilung: die Steigungslast auf beide Schultern

Jetzt kommt der technische Kunstgriff des Kapitels — und er ist harmloser, als sein späterer Name („partielle Integration”) klingt. In einer Dimension ist er nichts als eine Umverteilung von Arbeit.

Das Problem: In dem Integral hängen beide Ableitungen an der Temperatur \(T\) — erst die Steigung, dann die Steigung davon. Auf \(T\) lastet die ganze „Steigungslast”, und die zweite davon kann unser Streckenzug nicht tragen. Das Gewicht \(N_i\) dagegen steht unbeteiligt daneben und wird gar nicht abgeleitet.

Die Umverteilung nimmt \(T\) eine der beiden Ableitungen ab und legt sie dem Gewicht auf die Schulter: Statt \(T\) zweimal abzuleiten, leiten wir \(T\) einmal ab und das Gewicht einmal. Danach trägt jeder nur noch eine Steigung — und eine Steigung hat unser Streckenzug, das ist ja gerade seine brave Treppe aus Abbildung 7.4. Die unmögliche zweite Ableitung verschwindet.

Code
import matplotlib.pyplot as plt

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10.0, 3.8))

def schulter(ax, x, name, last, farbe, tragbar):
    # eine Figur als Kreis-Kopf plus Koerper, mit "Last" als Kasten obendrauf
    ax.add_patch(plt.Circle((x, 1.1), 0.12, facecolor=farbe,
                            edgecolor="black", lw=1.2, zorder=3))
    ax.plot([x, x], [0.2, 0.98], color=farbe, lw=6, solid_capstyle="round")
    ax.text(x, 0.02, name, ha="center", fontsize=12, weight="bold")
    for j in range(last):
        rand = "tab:red" if (not tragbar and j == last - 1) else "black"
        ax.add_patch(plt.Rectangle((x - 0.18, 1.42 + j * 0.28), 0.36, 0.24,
                                   facecolor="#ffe0b3", edgecolor=rand,
                                   lw=2.0 if rand == "tab:red" else 1.0,
                                   zorder=4))
        ax.text(x, 1.54 + j * 0.28, "Steigung", ha="center", fontsize=8,
                zorder=5)
    if not tragbar:
        ax.text(x, 1.42 + last * 0.28 + 0.12, "geht nicht!", ha="center",
                color="tab:red", fontsize=10, weight="bold")

# Links: T traegt beide
ax1.set_title("vorher: T trägt beide Steigungen", fontsize=10)
schulter(ax1, 0.5, "T", 2, "tab:orange", tragbar=False)
ax1.set_xlim(0, 1)
ax1.set_ylim(0, 2.6)
ax1.axis("off")

# Rechts: aufgeteilt
ax2.set_title("nachher: jeder trägt eine", fontsize=10)
schulter(ax2, 0.32, "T", 1, "tab:orange", tragbar=True)
schulter(ax2, 0.72, "N", 1, "tab:green", tragbar=True)
ax2.annotate("", xy=(0.62, 1.55), xytext=(0.42, 1.55),
             arrowprops=dict(arrowstyle="-|>", color="0.4", lw=2.0))
ax2.set_xlim(0, 1)
ax2.set_ylim(0, 2.6)
ax2.axis("off")
plt.tight_layout()
plt.show()
Abbildung 7.6: Die Umverteilung als Lastentausch. Links trägt die Temperatur T beide Steigungen allein (die zweite kann der Streckenzug nicht stemmen — rotes Kreuz). Rechts nach der Umverteilung: T trägt eine Steigung, das Gewicht N die andere. Jeder trägt nur noch eine — und eine Steigung hat der Streckenzug. Die Last ist auf zwei Schultern verteilt.

Was man hier sieht: links die Überlastung — die Temperatur soll zwei Steigungen zugleich tragen, die obere geht nicht (rotes Kreuz). Rechts der Ausgleich: eine Steigung bleibt bei \(T\), die andere wandert zum Gewicht \(N\). Beide tragen jetzt eine tragbare Last. Genau das leistet die Umverteilung im Integral. Aus der gewichteten Bilanz wird

\[ \int_0^L \frac{\mathrm{d}}{\mathrm{d}x} \left( \lambda \frac{\mathrm{d}T}{\mathrm{d}x} \right) N_i \, \mathrm{d}x \;=\; \underbrace{\left[ N_i \, \lambda \frac{\mathrm{d}T}{\mathrm{d}x} \right]_0^L}_{\text{Randterm — geparkt}} \;-\; \int_0^L \lambda \, \frac{\mathrm{d}N_i}{\mathrm{d}x}\, \frac{\mathrm{d}T}{\mathrm{d}x} \, \mathrm{d}x . \]

Lies das rechte Integral: Jetzt steht dort \(\frac{\mathrm{d}N_i}{\mathrm{d}x} \cdot \frac{\mathrm{d}T}{\mathrm{d}x}\) — die Steigung des Gewichts mal der Steigung der Temperatur, beide nur einfach abgeleitet. Kein zweites Mal, kein Sterben an den Knicken. Die Umverteilung hat die Gleichung genau auf das Werkzeug zugeschnitten, das unser Streckenzug bietet.

HinweisDer Randterm — hier geparkt, in Kapitel 8 abgeholt

Der eckige Klammerausdruck \([N_i\,\lambda\,\mathrm{d}T/\mathrm{d}x]_0^L\) entsteht bei der Umverteilung immer — er zählt, was an den beiden Enden des Trägers (\(x=0\) und \(x=L\)) passiert. Für einen inneren Knoten ist er harmlos: Dessen Hütchen \(N_i\) ist an beiden Trägerenden null (es reicht ja nur bis zu den Nachbarn), also ist die ganze Klammer null, und der Term verschwindet. Nur für die beiden Randknoten bleibt er stehen — und dort steckt genau die Information über den Wärmestrom, der am Rand ein- oder austritt. Das ist der Stoff von Kapitel 8 („Randbedingungen”). Wir zeigen den Term hier ehrlich und parken ihn: Für die inneren Knoten, um die es in diesem Kapitel geht, ist er null.

Setzt man den Randterm für die inneren Knoten auf null, bleibt die schlichte Forderung

\[ \int_0^L \lambda \, \frac{\mathrm{d}N_i}{\mathrm{d}x}\, \frac{\mathrm{d}T}{\mathrm{d}x} \, \mathrm{d}x \;=\; 0 . \]

Und jetzt kommt eine schöne Überraschung. Rechnet man dieses Integral für das Hütchen eines inneren Knotens aus (das tun wir gleich, Schritt für Schritt), so ergibt sich Zeile für Zeile genau die Bilanzgleichung aus Kapitel 6: „Was durch das linke Element hereinfließt, gleicht, was durch das rechte hinausfließt.” Der ganze Apparat — Ansatz, gewichtete Reste, Umverteilung — hat die Bilanz nicht ersetzt, sondern sie systematisch hergeleitet, wo wir sie in Kapitel 6 nur raten konnten. Der Bilanz-Prüfer von damals hat schon genau diese Gleichung geprüft.

7.5 Schritt 5 — Die Elementmatrix, und der Gänsehaut-Moment

Jetzt rechnen wir das Integral tatsächlich aus — und zwar so, wie es die FEM immer tut: ein Element nach dem anderen. Wir zoomen auf ein einzelnes Element, das zwischen einem linken Knoten \(a\) und einem rechten Knoten \(b\) liegt, im Abstand \(h = 0{,}25\ \mathrm{m}\). Auf diesem Element leben nur zwei Hütchen: das von \(a\) (fällt über das Element von 1 auf 0) und das von \(b\) (steigt von 0 auf 1). Alle anderen Hütchen sind hier null.

Ihre Steigungen sind das Herz der Rechnung, und sie sind wieder reine Schulgeometrie (Kapitel 5):

  • Das Hütchen von \(a\) fällt über die Breite \(h\) um 1: Steigung \(\dfrac{0-1}{h} = -\dfrac{1}{h} = -\dfrac{1}{0{,}25} = -4\ \tfrac{1}{\mathrm{m}}\).
  • Das Hütchen von \(b\) steigt über die Breite \(h\) um 1: Steigung \(\dfrac{1-0}{h} = +\dfrac{1}{h} = +4\ \tfrac{1}{\mathrm{m}}\).
Code
import matplotlib.pyplot as plt

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10.5, 4.0),
                               gridspec_kw={"width_ratios": [1.4, 1]})

# Links: die zwei Huetchen ueber dem Element
ax1.plot([0.0, 0.25], [1.0, 0.0], color="tab:blue", lw=2.6,
         label="Hütchen von a")
ax1.plot([0.0, 0.25], [0.0, 1.0], color="tab:orange", lw=2.6,
         label="Hütchen von b")
ax1.plot([0.0, 0.25], [0.0, 0.0], color="black", lw=1.0)
ax1.plot(0.0, 0.0, "o", color="tab:blue", ms=11, markeredgecolor="black",
         zorder=5)
ax1.plot(0.25, 0.0, "o", color="tab:orange", ms=11, markeredgecolor="black",
         zorder=5)
ax1.text(0.0, -0.12, "Knoten a", ha="center", fontsize=10, color="tab:blue")
ax1.text(0.25, -0.12, "Knoten b", ha="center", fontsize=10, color="tab:orange")
ax1.text(0.125, -0.24, "Breite h = 0,25 m", ha="center", fontsize=9)
ax1.text(0.045, 0.55, "−4 1/m", color="tab:blue", fontsize=10, rotation=-52)
ax1.text(0.20, 0.55, "+4 1/m", color="tab:orange", fontsize=10, rotation=52)
ax1.set_xlim(-0.05, 0.30)
ax1.set_ylim(-0.35, 1.2)
ax1.legend(loc="upper center", fontsize=9)
ax1.set_title("ein Element, zwei Hütchen", fontsize=11)
ax1.axis("off")

# Rechts: die fertige 2x2-Matrix
werte = [[2, -2], [-2, 2]]
namen = ["a", "b"]
for r in range(2):
    for c in range(2):
        diag = (r == c)
        farbe = "#d9f0d3" if diag else "#e7dcf3"
        ax2.add_patch(plt.Rectangle((c, 1 - r), 1, 1, facecolor=farbe,
                                    edgecolor="black", lw=1.4))
        ax2.text(c + 0.5, 1 - r + 0.5, "%+d" % werte[r][c], ha="center",
                 va="center", fontsize=20, weight="bold")
for i in range(2):
    ax2.text(-0.12, 1 - i + 0.5, namen[i], ha="right", va="center",
             fontsize=12, color="tab:blue", weight="bold")
    ax2.text(i + 0.5, 2.12, namen[i], ha="center", fontsize=12,
             color="tab:orange", weight="bold")
ax2.text(1.0, -0.28, "k = λA/h = 2 W/K", ha="center", fontsize=11,
         color="black")
ax2.set_xlim(-0.5, 2.2)
ax2.set_ylim(-0.5, 2.4)
ax2.set_aspect("equal")
ax2.set_title("die 2×2-Elementmatrix (W/K)", fontsize=11)
ax2.axis("off")
plt.tight_layout()
plt.show()
Abbildung 7.7: Das Element-Rechenblatt. Auf einem Element (Breite h = 0,25 m) leben zwei Hütchen: das des linken Knotens a fällt von 1 auf 0 (Steigung −4 1/m), das des rechten Knotens b steigt von 0 auf 1 (Steigung +4 1/m). Aus diesen beiden Steigungen und der Breite baut sich die 2×2-Elementmatrix unten auf — jeder Eintrag ist Steigung × Steigung × λA × Breite.

Was man hier sieht: links die zwei Hütchen mit ihren Steigungen \(\mp 4\ \tfrac{1}{\mathrm{m}}\), rechts das Ergebnis, das wir jetzt Eintrag für Eintrag ausrechnen. Jeder Eintrag der 2×2-Matrix ist das Integral \(\int \lambda A\, \frac{\mathrm{d}N_r}{\mathrm{d}x}\frac{\mathrm{d}N_c}{\mathrm{d}x}\, \mathrm{d}x\) über das Element — und weil beide Steigungen über das ganze Element konstant sind, ist auch ihr Produkt konstant. Das Integral einer Konstanten ist aber die einfachste Fläche überhaupt: Rechteck = Höhe mal Breite (Kapitel 5, ganz ohne Streifenstapel). Höhe ist hier \(\lambda A \cdot (\text{Steigung} \times \text{Steigung})\), Breite ist die Elementlänge \(h\).

Rechnen wir alle vier Einträge mit \(\lambda = 50\ \mathrm{W/(m\,K)}\), \(A = 0{,}01\ \mathrm{m^2}\) (also \(\lambda A = 0{,}5\ \mathrm{W\,m/K}\)) und \(h = 0{,}25\ \mathrm{m}\):

\[ \begin{aligned} K_{aa} &= \lambda A \cdot \Bigl(-\tfrac{1}{h}\Bigr)\Bigl(-\tfrac{1}{h}\Bigr) \cdot h = 0{,}5 \cdot (-4)(-4) \cdot 0{,}25 = 0{,}5 \cdot 16 \cdot 0{,}25 = +2\ \tfrac{\mathrm{W}}{\mathrm{K}},\\[2pt] K_{ab} &= \lambda A \cdot \Bigl(-\tfrac{1}{h}\Bigr)\Bigl(+\tfrac{1}{h}\Bigr) \cdot h = 0{,}5 \cdot (-4)(+4) \cdot 0{,}25 = 0{,}5 \cdot (-16) \cdot 0{,}25 = -2\ \tfrac{\mathrm{W}}{\mathrm{K}},\\[2pt] K_{ba} &= \lambda A \cdot \Bigl(+\tfrac{1}{h}\Bigr)\Bigl(-\tfrac{1}{h}\Bigr) \cdot h = -2\ \tfrac{\mathrm{W}}{\mathrm{K}},\\[2pt] K_{bb} &= \lambda A \cdot \Bigl(+\tfrac{1}{h}\Bigr)\Bigl(+\tfrac{1}{h}\Bigr) \cdot h = +2\ \tfrac{\mathrm{W}}{\mathrm{K}}. \end{aligned} \]

Alle vier Zahlen zusammengeschrieben ergeben die Elementmatrix der Wärmeleitung:

\[ \mathbf{K}_{\text{Element}} \;=\; \frac{\lambda A}{h} \begin{pmatrix} 1 & -1 \\ -1 & 1 \end{pmatrix} \;=\; \begin{pmatrix} 2 & -2 \\ -2 & 2 \end{pmatrix}\ \tfrac{\mathrm{W}}{\mathrm{K}}, \qquad k \;=\; \frac{\lambda A}{h} \;=\; \frac{50 \cdot 0{,}01}{0{,}25} \;=\; 2\ \tfrac{\mathrm{W}}{\mathrm{K}} . \]

Und jetzt halt inne und sieh genau hin.

TippDer Gänsehaut-Moment

Vergleiche die eben ausgerechnete Wärme-Elementmatrix mit dem Steckbrief der Feder aus Kapitel 3:

\[ \underbrace{\begin{pmatrix} k & -k \\ -k & k \end{pmatrix}}_{\text{Feder (Kap. 3)}} \qquad\text{gegen}\qquad \underbrace{\frac{\lambda A}{h}\begin{pmatrix} 1 & -1 \\ -1 & 1 \end{pmatrix}}_{\text{Wärme (Kap. 7)}} \]

Es ist dieselbe Matrix. Zahl für Zahl, Vorzeichen für Vorzeichen. Die Wärmeleitung in einem Trägerstück ist eine Feder — nur heißt ihre Steifigkeit nicht mehr „Federhärte”, sondern \(k = \lambda A / h\): viel Wärmeleitfähigkeit, dicker Querschnitt und kurzer Abstand machen eine „steife” Wärme-Feder. Zwischen unseren fünf Knoten sitzen also vier Wärme-Federn mit \(k = 2\ \mathrm{W/K}\), und der ganze Rest des Kapitels ist Kapitel 3, noch einmal gespielt — mit Temperatur statt Verschiebung und Wärmestrom statt Kraft.

Diese Verwandtschaft ist kein Zufall und keine bloße Analogie — es ist buchstäblich dasselbe Gleichungssystem. Abbildung 7.8 stellt die beiden Ketten nebeneinander.

Code
import matplotlib.pyplot as plt

def feder(ax, x0, x1, y, farbe, lw=1.8):
    px = [x0, x0 + 0.14 * (x1 - x0)]
    py = [y, y]
    a = x0 + 0.14 * (x1 - x0)
    b = x0 + 0.86 * (x1 - x0)
    for i in range(13):
        anteil = i / 12
        px.append(a + anteil * (b - a))
        py.append(y + 0.09 if i % 2 == 0 else y - 0.09)
    px.append(x1)
    py.append(y)
    ax.plot(px, py, color=farbe, lw=lw, solid_capstyle="round")

fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(9.0, 4.6))

# Oben: mechanische Feder
feder(ax1, 0.0, 2.0, 0.0, "tab:blue")
for x, name in ((0.0, "u₁"), (2.0, "u₂")):
    ax1.plot(x, 0.0, "o", color="tab:blue", ms=14, markeredgecolor="black",
             zorder=4)
    ax1.text(x, 0.32, name, ha="center", fontsize=12)
ax1.text(1.0, -0.42, "Steifigkeit k = 100 N/mm — verknüpft Verschiebungen über die Kraft",
         ha="center", fontsize=10, color="tab:blue")
ax1.text(2.6, 0.0, "[  k   −k ]\n[ −k    k ]", va="center", fontsize=13,
         family="monospace")
ax1.set_title("Kapitel 3: mechanische Feder", fontsize=10)
ax1.set_xlim(-0.4, 3.7)
ax1.set_ylim(-0.6, 0.6)
ax1.set_aspect("equal")
ax1.axis("off")

# Unten: Waerme-Feder
feder(ax2, 0.0, 2.0, 0.0, "tab:red")
for x, name in ((0.0, "T₁"), (2.0, "T₂")):
    ax2.plot(x, 0.0, "o", color="tab:red", ms=14, markeredgecolor="black",
             zorder=4)
    ax2.text(x, 0.32, name, ha="center", fontsize=12)
ax2.text(1.0, -0.42, "Steifigkeit k = λA/h = 2 W/K — verknüpft Temperaturen über den Wärmestrom",
         ha="center", fontsize=10, color="tab:red")
ax2.text(2.6, 0.0, "λA/h ·\n[  1   −1 ]\n[ −1    1 ]", va="center",
         fontsize=13, family="monospace")
ax2.set_title("Kapitel 7: Wärme-Feder", fontsize=10)
ax2.set_xlim(-0.4, 3.7)
ax2.set_ylim(-0.6, 0.6)
ax2.set_aspect("equal")
ax2.axis("off")
plt.tight_layout()
plt.show()
Abbildung 7.8: Federkette und Wärmekette sind dieselbe Kette. Oben die mechanische Feder: Steifigkeit k, sie verknüpft Verschiebungen u über die Kraft. Unten die Wärme-Feder: Steifigkeit k = λA/h, sie verknüpft Temperaturen T über den Wärmestrom. Beide haben denselben 2×2-Steckbrief; nur die Namen der Größen sind ausgetauscht.

Was man hier sieht: oben und unten dieselbe Feder, nur anders beschriftet. Die mechanische verknüpft Verschiebungen \(u\) über die Kraft, die thermische verknüpft Temperaturen \(T\) über den Wärmestrom — aber ihr Steckbrief ist identisch. Damit ist die Brücke aus dem Aufhänger geschlagen: Ab hier rechnet die Federketten-Maschinerie die Wärme.

7.6 Schritt 6 — Assemblieren, einspannen, lösen

Vier Wärme-Federn mit \(k = 2\ \mathrm{W/K}\), fünf Knoten — das ist genau die Ausgangslage von Kapitel 3, nur mit neuen Zahlen. Also spielen wir dasselbe Schiebespiel: Jeder Element-Steckbrief rutscht an die Stelle seiner beiden Knoten in die große 5×5-Tabelle, und wo sich zwei Federn einen Knoten teilen, addieren sich ihre \(k\) auf der Diagonale zu \(2k = 4\ \mathrm{W/K}\). Die folgende Animation ist bewusst dieselbe wie in Kapitel 3 — nur stehen jetzt 2 und 4 und −2 in den Feldern statt 100 und 200 und −100. Das Wiedererkennen ist die halbe Botschaft.

Code
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML

n = 5
k = 2
elemente = [(0, 1), (1, 2), (2, 3), (3, 4)]

def matrix_nach(anzahl):
    m = [[0 for _ in range(n)] for _ in range(n)]
    for e in range(anzahl):
        a, b = elemente[e]
        m[a][a] += k
        m[b][b] += k
        m[a][b] -= k
        m[b][a] -= k
    return m

fig, ax = plt.subplots(figsize=(6.2, 6.4))

def zeichne(schritt):
    ax.clear()
    ax.set_xlim(-0.7, n + 0.2)
    ax.set_ylim(-0.9, n + 0.6)
    ax.set_aspect("equal")
    ax.axis("off")
    m = matrix_nach(schritt)
    aktiv = elemente[schritt - 1] if schritt >= 1 else None
    for r in range(n):
        for c in range(n):
            gehoert = aktiv is not None and r in aktiv and c in aktiv
            flaeche = "#ffe0b3" if gehoert else "white"
            ax.add_patch(plt.Rectangle((c, n - 1 - r), 1, 1, facecolor=flaeche,
                                       edgecolor="0.75", lw=1.0))
            if m[r][c] != 0:
                betont = gehoert and r == c and m[r][c] == 2 * k
                ax.text(c + 0.5, n - 1 - r + 0.5, "%d" % m[r][c], ha="center",
                        va="center", fontsize=12,
                        weight="bold" if betont else "normal",
                        color="tab:red" if betont else "black")
            else:
                ax.text(c + 0.5, n - 1 - r + 0.5, ".", ha="center",
                        va="center", fontsize=11, color="0.7")
    for i in range(n):
        ax.text(-0.2, n - 1 - i + 0.5, "K%d" % (i + 1), ha="right",
                va="center", fontsize=9, color="tab:blue", weight="bold")
        ax.text(i + 0.5, n + 0.12, "K%d" % (i + 1), ha="center", va="bottom",
                fontsize=9, color="tab:red", weight="bold")
    if schritt == 0:
        titel = "Leere Systemmatrix (5 Knoten)"
    else:
        titel = "Wärme-Feder %d eingebaut (Knoten %d%d)" % (
            schritt, aktiv[0] + 1, aktiv[1] + 1)
    ax.text(n / 2, -0.6, titel, ha="center", fontsize=11)
    return []

ani = FuncAnimation(fig, zeichne, frames=5, interval=1300, blit=False)
plt.close(fig)
HTML(ani.to_jshtml())
Abbildung 7.9: Assemblierung der Wärmekette, Element für Element — dasselbe Schiebespiel wie bei den Federn in Kapitel 3, nur mit den Wärmezahlen. Jede Wärme-Feder legt ihren Steckbrief (2 auf die Diagonale, −2 auf die Kreuzfelder) an die Stelle ihrer beiden Knoten. Auf den geteilten Diagonalfeldern (Knoten 2, 3, 4) springt die Zahl von 2 auf 4, wenn die zweite Feder ihren Beitrag dazulegt.

Was man sieht: dasselbe Wachsen wie bei den Federn, mit den Wärmezahlen. Nach vier Wärme-Federn steht die vollständige Systemmatrix — und sie trägt genau die Bandstruktur, die wir schon kennen: die Diagonale und je ein Feld links und rechts, alles andere leer.

7.6.1 Das vollständige 5×5-System

Schreiben wir es einmal ganz hin. Fünf Knoten, fünf Kraft-Gleichungen — Verzeihung, Wärmestrom-Gleichungen, eine pro Knoten. Auf der linken Seite die assemblierte Matrix (in W/K), auf der rechten die von außen zugeführten Wärmeströme (in W):

\[ \begin{pmatrix} 2 & -2 & 0 & 0 & 0\\ -2 & \textcolor{orange}{4} & -2 & 0 & 0\\ 0 & -2 & \textcolor{orange}{4} & -2 & 0\\ 0 & 0 & -2 & \textcolor{orange}{4} & -2\\ 0 & 0 & 0 & -2 & 2 \end{pmatrix} \begin{pmatrix} T_1\\ T_2\\ T_3\\ T_4\\ T_5 \end{pmatrix} = \begin{pmatrix} Q_1\\ 0\\ 0\\ 0\\ Q_5 \end{pmatrix} \]

Die Diagonale \(2 / 4 / 4 / 4 / 2\) erzählt dieselbe Geschichte wie bei den Federn: Die inneren Knoten 2, 3, 4 tragen je zwei Wärme-Federn (darum \(4 = 2 + 2\)), die Randknoten 1 und 5 nur eine (darum \(2\)). Auf der rechten Seite steht bei den inneren Knoten null — dort speist niemand von außen Wärme ein (keine Quelle, das ist die reine Leitung). Bei den Randknoten stehen \(Q_1\) und \(Q_5\): die noch unbekannten Wärmeströme, die das kühle Auflager und der Brandherd abführen und zuführen — das genaue Gegenstück zur unbekannten Auflagerkraft \(F_1\) der Federkette.

7.6.2 Die Randwerte einbauen

Wie bei der Feder ist das System so noch nicht lösbar: An den Rändern stehen unbekannte Ströme, und die Temperatur könnte als Ganzes nach oben oder unten rutschen. Was fehlt, ist die Information, die wir noch nicht benutzt haben: Knoten 1 wird auf \(20\ °\mathrm{C}\) gehalten, Knoten 5 auf \(300\ °\mathrm{C}\). Das sind die beiden Randwerte.

Wir bauen sie mit dem Streich-Rezept aus Kapitel 3 ein: Die beiden Randknoten sind bekannt, also streichen wir ihre Zeilen und Spalten und lösen nur noch für die drei inneren Knoten. Ein feiner Unterschied zur Feder: Dort war der feste Wert null (\(u_1 = 0\)), und eine gestrichene Spalte verschwand spurlos. Hier sind die Werte nicht null (20 und 300) — darum hinterlässt die gestrichene Spalte einen Beitrag auf der rechten Seite: der bekannte Randwert mal seinem Spalteneintrag wandert dorthin. Aus Zeile 2 zum Beispiel wird mit \(T_1 = 20\):

\[ -2 \cdot \underbrace{20}_{T_1} + 4\,T_2 - 2\,T_3 = 0 \quad\Longrightarrow\quad 4\,T_2 - 2\,T_3 = 2 \cdot 20 = 40 . \]

Ebenso wandert bei Zeile 4 der Term \(-2 \cdot 300 = -600\) nach rechts und wird zu \(+600\). Übrig bleibt ein sauberes 3×3-System für die drei inneren Knoten:

\[ \begin{pmatrix} 4 & -2 & 0\\ -2 & 4 & -2\\ 0 & -2 & 4 \end{pmatrix} \begin{pmatrix} T_2\\ T_3\\ T_4 \end{pmatrix} = \begin{pmatrix} 40\\ 0\\ 600 \end{pmatrix} \]

(Warum das Streichen und Verschieben genau die richtige Behandlung fester Randwerte ist — und was man mit Rändern noch alles anstellen kann —, ist so wichtig, dass Kapitel 8 ein ganzes Kapitel daraus macht. Hier benutzen wir das Rezept, die Begründung kommt dort.)

WichtigVorhersage-Punkt

Bevor wir lösen: In Kapitel 6 haben wir mit reiner Bilanz-Physik begründet, dass das eingeschwungene Profil eine Gerade von 20 auf 300 °C ist — mit den Knotenwerten 20 / 90 / 160 / 230 / 300 °C. Gleich löst der Thomas-Algorithmus das 3×3-System der FEM. Was glaubst du: Trifft die FEM diese Gerade — und wenn ja, ungefähr oder ganz genau? Leg dich fest.

7.6.3 Lösen mit dem Thomas-Algorithmus

Das reduzierte System ist tridiagonal — genau die Bandform, für die wir in Kapitel 4 den Thomas-Algorithmus gebaut haben. Er läuft als Vorwärts- und Rückwärtswelle einmal durch das Band. Wir füttern ihn mit den drei Diagonalen und der rechten Seite:

Code
import os
import sys
sys.path.insert(0, os.path.join("..", "programme", "gemeinsam"))
from loeser import thomas

# Das reduzierte 3x3-System der inneren Knoten (in W/K bzw. W).
unter = [0.0, -2.0, -2.0]        # Nebendiagonale links (unter[0] unbenutzt)
diagonale = [4.0, 4.0, 4.0]      # Hauptdiagonale
ober = [-2.0, -2.0, 0.0]         # Nebendiagonale rechts (ober[2] unbenutzt)
rechte_seite = [40.0, 0.0, 600.0]

innere = thomas(unter, diagonale, ober, rechte_seite)
temperaturen = [20.0] + innere + [300.0]

print("Knoten |   Ort  | FEM-Temperatur | exakte Gerade")
print("-------+--------+----------------+--------------")
for i in range(5):
    gerade = 20.0 + (300.0 - 20.0) * i / 4.0
    print("  %d    | %.2f m |   %6.1f °C    |   %6.1f °C"
          % (i + 1, i * 0.25, temperaturen[i], gerade))
Knoten |   Ort  | FEM-Temperatur | exakte Gerade
-------+--------+----------------+--------------
  1    | 0.00 m |     20.0 °C    |     20.0 °C
  2    | 0.25 m |     90.0 °C    |     90.0 °C
  3    | 0.50 m |    160.0 °C    |    160.0 °C
  4    | 0.75 m |    230.0 °C    |    230.0 °C
  5    | 1.00 m |    300.0 °C    |    300.0 °C

Interpretation der Ausgabe: \(20 / 90 / 160 / 230 / 300\ °\mathrm{C}\) — die FEM trifft die Gerade aus Kapitel 6 nicht ungefähr, sondern exakt, Knoten für Knoten. Damit ist die Vermutung vom Kapitelanfang widerlegt.

WarnungVermutung aufgelöst

Die Vermutung war: „Eine Näherung aus groben Geradenstücken kann nie exakt sein.” Und doch trifft die FEM hier die Wahrheit auf die Nachkommastelle. Der Grund: Die wahre Lösung ist selbst eine Gerade (Kapitel 6), und eine Gerade lässt sich durch Geradenstücke perfekt darstellen — der Streckenzug ist dann die exakte Kurve, keine Näherung. Egal, ob wir 5 oder 500 Knoten nehmen: An den Knoten stimmt es immer exakt. Der Fehler taucht erst auf, wenn die Wirklichkeit krummer ist als unser Geradenzug — bei der Wärmequelle (Übung 7.3), beim Materialsprung (Übung 7.2), in der Fläche (Kapitel 10). Wie groß er dann ist, misst Kapitel 12.

Und die Physik dahinter ist dieselbe wie bei der Federkette. Rechne aus den Knotentemperaturen den Wärmestrom durch jedes Element zurück (\(k \cdot \text{Temperatursprung}\)): Durch jede der vier Wärme-Federn fließen \(2 \cdot 70 = 140\ \mathrm{W}\) — exakt die 140 W aus Kapitel 6. So wie in Kapitel 3 jede mechanische Feder dieselbe Kraft von 50 N trug und sich gleich weit dehnte, trägt hier jede Wärme-Feder denselben Strom von 140 W und lässt die Temperatur um dieselben 70 K steigen. Die Treppe \(20 / 90 / 160 / 230 / 300\) ist die Wärme-Version der Verschiebungstreppe \(0 / 0{,}5 / 1{,}0 / 1{,}5\).

7.7 Die interaktive Einheit: die Wärme-Federkette

Jetzt bist du dran. Das folgende Programm ist die Wärme-Federkette als Ganzes: Du gibst die Zahl der Knoten vor (zwischen 3 und 20), und es baut den Element-Steckbrief, assembliert das System, baut die Randwerte ein und löst mit dem Thomas-Algorithmus — alle Schritte dieses Kapitels an einem Stück. Jede Codezeile trägt die Nummer des Papier-Schritts, dem sie entspricht.

WichtigVorhersage-Punkt

Bevor du ausführst: Das Programm startet mit 5 Knoten. Ändere KNOTENZAHL gleich auf 20 und lass es erneut laufen. Was glaubst du: Ändern sich die Temperaturen an den Knoten, wenn du von 5 auf 20 Knoten verfeinerst — werden sie „genauer”? Oder bleiben die Knotenwerte, wo sie sind? Leg dich fest, bevor du es probierst.

Abbildung 7.10: Vorgerenderte Fassung der Wärme-Federkette mit 5 Knoten: die FEM-Knotenwerte (Punkte) liegen exakt auf der Geraden aus Kapitel 6. Im Browser ersetzt dein eigenes Ergebnis dieses Bild, sobald du die Zelle darüber ausführst — stell KNOTENZAHL auf 20 und sieh, dass die Punkte auf derselben Geraden bleiben.

Was man sieht: Mit 5 Knoten liegen die Punkte auf der Geraden. Stellst du KNOTENZAHL = 20, ändert sich die Element-Steifigkeit \(k\) (sie wächst, weil \(h\) kleiner wird) und damit jede Zahl in der Matrix — aber die Temperaturen an den Knoten bleiben exakt auf derselben Geraden. Genau das war die Pointe des Vorhersage-Punkts: Verfeinern macht die Lösung hier nicht „genauer”, denn sie war schon exakt. Fünf Knoten genügen für die Gerade; mehr Knoten braucht man erst, wenn die Kurve sich biegt.

7.8 Das Kapitel-Programm

Das vollständige, eigenständig lauffähige Skript liegt in programme/kap07/kap07_waerme_fem.py. Es ist der Kern des späteren Finales: dieselben vier Funktionen, die das ganze restliche Buch nur noch größer macht. baue_elementmatrix liefert den Steckbrief \(\lambda A / h\, [[1,-1],[-1,1]]\), assembliere schiebt die Bausteine zusammen (mit dem Matrix-Drucker aus Kapitel 3 für die Bandstruktur), baue_randwerte_ein setzt die festen Temperaturen nach dem Streich-Rezept ein, und gelöst wird mit dem importierten Thomas-Löser aus Kapitel 4. Das Skript rechnet das 5-Knoten-Handbeispiel vor, weist nach, dass Thomas und die volle Gauß-Elimination dasselbe liefern, und zeigt, dass durch jede Wärme-Feder dieselben 140 W fließen. Führe es mit python kap07_waerme_fem.py aus.

7.9 Rückblick auf die Landkarte

Erinnerst du dich an die Landkarte aus Kapitel 1 — die fünf Bausteine jeder FEM-Rechnung? In diesem Kapitel sind gleich mehrere Kästen mit Leben gefüllt worden. Die Physik (die Wärmeleitungsgleichung, Kapitel 6) ist über Ansatz, gewichtete Reste und Umverteilung in ein Gleichungssystem übersetzt worden; assembliert wie in Kapitel 3; die Randbedingungen sind (vorläufig) eingebaut; und der Löser aus Kapitel 4 hat es geknackt.

Code
import matplotlib.pyplot as plt

bausteine = [
    ("Geometrie\n& Netz", "Kap. 10–11", False),
    ("Physik", "Kap. 5, 6, 9", True),
    ("Rand-\nbedingungen", "Kap. 8", True),
    ("Gleichungs-\nsystem", "Kap. 3, 7", True),
    ("Löser", "Kap. 4", True),
]

fig, ax = plt.subplots(figsize=(9.0, 3.0))
breite, luecke = 1.5, 0.4
for k, (name, kapitel, fertig) in enumerate(bausteine):
    x0 = k * (breite + luecke)
    farbe = "#d9f0d3" if fertig else "#eeeeee"
    rand = "tab:green" if fertig else "0.7"
    ax.add_patch(plt.Rectangle((x0, 0), breite, 1.0, facecolor=farbe,
                               edgecolor=rand, lw=2.0 if fertig else 1.0))
    ax.text(x0 + breite / 2, 0.62, name, ha="center", va="center",
            fontsize=10, weight="bold",
            color="black" if fertig else "0.5")
    ax.text(x0 + breite / 2, 0.24, kapitel, ha="center", va="center",
            fontsize=9, color="black" if fertig else "0.5")
    if fertig:
        ax.text(x0 + breite - 0.16, 0.85, "✓", ha="center", va="center",
                fontsize=15, color="tab:green", weight="bold")
    if k < len(bausteine) - 1:
        ax.annotate("", xy=(x0 + breite + luecke, 0.5),
                    xytext=(x0 + breite, 0.5),
                    arrowprops=dict(arrowstyle="-|>", color="black", lw=1.3))
ax.text((len(bausteine) * (breite + luecke) - luecke) / 2, 1.4,
        "Nach Kapitel 7: die 1D-FEM-Kette ist komplett — Physik ⟶ System ⟶ Löser",
        ha="center", va="center", fontsize=10, style="italic",
        color="tab:green")
ax.set_xlim(-0.2, len(bausteine) * (breite + luecke) - luecke + 0.2)
ax.set_ylim(-0.2, 1.7)
ax.axis("off")
plt.tight_layout()
plt.show()
Abbildung 7.11: Die Landkarte aus Kapitel 1, mit dem Stand nach Kapitel 7. Grün und mit Häkchen: die Bausteine, die jetzt gefüllt sind — Physik, Randbedingungen (vorläufig), Gleichungssystem und Löser. Noch offen (blass): Geometrie und Netz für die Fläche, das kommt in Teil IV. In der 1D-Welt ist die FEM-Kette damit komplett.

Was man hier sieht: Vier der fünf Kästen tragen jetzt ein Häkchen. In einer Dimension ist die FEM damit vollständig: Wir können die Physik eines Trägers in ein Gleichungssystem gießen und es lösen. Offen bleibt vor allem der erste Kasten — Geometrie und Netz —, sobald der Träger die Linie verlässt und in die Fläche geht. Dann werden aus den Hütchen auf der Linie Zelte über Dreiecken (Kapitel 10) — aber das Rezept bleibt: Steckbrief, assemblieren, Randwerte, lösen.

HinweisFDM: fast dieselbe Matrix, ein anderer Weg dorthin

Es gibt einen zweiten, älteren Weg, die Wärmeleitungsgleichung in ein Gleichungssystem zu verwandeln: die Finite-Differenzen-Methode (FDM). Sie geht nicht über Hütchen und gewichtete Reste, sondern ersetzt die Ableitungen direkt durch Steigungsdreiecke aus Kapitel 5 — die zweite Ableitung an einem Knoten als „Steigung rechts minus Steigung links, geteilt durch \(h\)“. Rechnet man das für unseren gleichmäßigen Träger aus, kommt für jeden inneren Knoten die Zeile \(-T_{i-1} + 2\,T_i - T_{i+1} = 0\) heraus — dasselbe Muster wie unsere assemblierte Matrix (die \(\textcolor{orange}{4} = 2 \cdot 2\) auf der Diagonale, die \(-2\) daneben, alles durch \(k\) geteilt). Auf dem gleichmäßigen 1D-Gitter liefern FDM und FEM also fast Zahl für Zahl dasselbe. Der Unterschied zeigt sich erst, wenn die Ränder kniffliger werden (Kapitel 8) und wenn der Träger in die Fläche geht: Dort spielt die FEM ihre Stärke aus — beliebige Netze und Ränder, die sich „von selbst” ergeben. In 1D auf gleichem Gitter sind die beiden Methoden fast Zwillinge.

7.10 Die Namensschilder

Wir haben die ganze Methode gebaut, ohne ihre Fachwörter zu benutzen — mit Absicht. Jetzt, wo die Sache steht, kleben wir die Namensschilder an. Keiner dieser Begriffe ist mehr als ein Etikett für etwas, das du schon verstanden hast.

TippDie Fachwörter, jetzt nachgereicht
  • Ansatzfunktion (auch Formfunktion): unser Hütchen \(N_i\). Der „Ansatz” ist die Entscheidung, die Lösung als Summe \(T(x) = \sum_j T_j N_j(x)\) anzusetzen — Knotenwerte mal Hütchen.
  • Schwache Form: die Gleichung nach der Umverteilung, in der nur noch einfache Ableitungen vorkommen. „Schwach”, weil sie weniger verlangt als die ursprüngliche („starke”) Gleichung — statt einer zweiten Ableitung überall nur noch ein gewichtetes Mittel. Genau deshalb kommt der eckige Streckenzug mit ihr zurecht.
  • Methode der gewichteten Reste: die Idee, die Gleichung nicht überall, sondern nur im gewichteten Mittel zu erfüllen. Der „Rest” (oder das Residuum) ist der Fehlbetrag der Gleichung; wir zwingen ihn, im Mittel gegen jedes Gewicht null zu sein.
  • Testfunktion (oder Gewichtsfunktion): das Gewicht, mit dem gemittelt wird. Weil wir dafür dieselben Hütchen nehmen wie für den Ansatz, trägt das Verfahren einen eigenen Namen: Galerkin-Verfahren (nach dem russischen Ingenieur Boris Galjorkin, 1871–1945, der es 1915 an Stäben und Platten entwickelte (Galerkin 1915)). „Ansatz und Test aus demselben Baukasten” — das ist die Galerkin-Wahl.
  • Partielle Integration: der mathematische Name für unsere Umverteilung — die Regel, die eine Ableitung von einem Faktor auf den anderen schiebt und dabei den geparkten Randterm erzeugt.
  • Elementintegral / Elementmatrix: das über ein Element gerechnete Integral \(\int \lambda A\, N_r' N_c'\,\mathrm{d}x\) und die 2×2-Tabelle, die daraus entsteht — der Steckbrief, den wir in Schritt 5 von Hand ausgerechnet haben.

Aus der assemblierten Systemmatrix wird, ganz wie bei den Federn, die Steifigkeitsmatrix genannt — bei der Wärme auch Leitfähigkeitsmatrix. Alle diese Wörter stehen im Glossar; hier haben wir zuerst die Sache gebaut und dann das Schild geklebt.

TippMerkkasten
  • Der Ansatz beschreibt die Temperatur als Streckenzug: fünf Knotenwerte, dazwischen Geraden. Aufgeschrieben mit Hütchenfunktionen — jedes Knotens Zuständigkeitsgebiet.
  • Der Streckenzug hat Knicke, seine zweite Ableitung stirbt — man kann ihn nicht direkt in die Wärmeleitungsgleichung einsetzen.
  • Ausweg: gewichtete Reste (Bilanz nur im Mittel, ein Gewicht = Hütchen pro Knoten) plus Umverteilung (partielle Integration), die \(T\) eine Ableitung abnimmt. Danach braucht man nur noch eine Steigung — die hat der Streckenzug.
  • Die Elementmatrix der Wärmeleitung ist \(\frac{\lambda A}{h} \begin{pmatrix} 1 & -1\\ -1 & 1\end{pmatrix}\)Zahl für Zahl die Federmatrix aus Kapitel 3, mit \(k = \lambda A/h = 2\ \mathrm{W/K}\).
  • Assemblieren, Randwerte einbauen, mit Thomas lösen liefert \(20/90/160/230/300\ °\mathrm{C}\) — die exakte Gerade aus Kapitel 6. Durch jede Wärme-Feder fließen dieselben 140 W.
  • Für den homogenen Träger ist die grobe Näherung keine Näherung: Weil die Wahrheit selbst eine Gerade ist, trifft die FEM sie an den Knoten exakt.

Roter Faden

Zurück: Dieses Kapitel war der Knoten, in dem alle Fäden zusammenliefen. Der Ansatz aus Geradenstücken ist die Streckenzug-Idee aus Kapitel 5; die Steigungen der Hütchen (\(\pm 1/h\)) und die Rechteckflächen der konstanten Integranden sind Schulgeometrie von dort. Die Elementmatrix, das Assemblieren und das Streich-Rezept für Randwerte sind die Federketten-Maschinerie aus Kapitel 3; gelöst hat der Thomas-Algorithmus aus Kapitel 4. Und die Bilanz aus Kapitel 6 ist genau das, was die gewichteten Reste systematisch herleiten — die 140 W und die Gerade kehren Zahl für Zahl wieder.

Vor: Der Randterm, den wir geparkt haben, wird in Kapitel 8 abgeholt — dort entscheidet sich, was „fest gehalten”, „beflammt” oder „isoliert” im Gleichungssystem bedeutet. Der Zeitterm (das Aufheizen aus der Animation in Kapitel 6) kommt in Kapitel 9 dazu und bringt die Massenmatrix, die nach derselben Halbe-Halbe-Logik gebaut wird wie die Wärmequelle in Übung 7.3. Die Hütchen auf der Linie werden in Kapitel 10 zu Zelten über Dreiecken, wenn der Träger in die Fläche geht. Und die Exaktheits-Frage — wie groß der Fehler ist, wenn die Wahrheit krumm ist — steht im Zentrum von Kapitel 12.

Übungen

Ü 7.1 (Verstehen). Rechne die Elementmatrix für ein Edelstahl-Element von Hand aus: Länge \(h = 0{,}5\ \mathrm{m}\), Wärmeleitfähigkeit \(\lambda = 15\ \mathrm{W/(m\,K)}\), gleicher Querschnitt \(A = 0{,}01\ \mathrm{m^2}\). Wie groß ist die Wärme-Steifigkeit \(k = \lambda A / h\), und wie sieht die 2×2-Matrix aus? Vergleiche mit einer Baustahl-Feder des Trägers (\(k = 2\ \mathrm{W/K}\)): steifer oder weicher?

\[ k = \frac{\lambda A}{h} = \frac{15 \cdot 0{,}01}{0{,}5} = \frac{0{,}15}{0{,}5} = 0{,}3\ \tfrac{\mathrm{W}}{\mathrm{K}}, \qquad \mathbf{K}_{\text{Element}} = 0{,}3\begin{pmatrix} 1 & -1\\ -1 & 1\end{pmatrix}\ \tfrac{\mathrm{W}}{\mathrm{K}}. \]

Mit \(k = 0{,}3\ \mathrm{W/K}\) ist diese Wärme-Feder rund sechseinhalbmal weicher als eine Baustahl-Feder des Trägers (\(k = 2\)). Zwei Gründe wirken zusammen: Edelstahl leitet gut dreimal schlechter als Baustahl (\(\lambda = 15\) statt \(50\)), und das doppelt so lange Element (\(h = 0{,}5\) statt \(0{,}25\ \mathrm{m}\)) halbiert \(k\) noch einmal. Eine „weiche” Wärme-Feder trägt unter demselben Strom ein größeres Temperaturgefälle: Sie ist ein schlechter Leiter, genau wie es sein soll.

Ü 7.2 (Verändern). Ersetze im 5-Knoten-Träger die beiden rechten Elemente (zwischen den Knoten 3 und 5) — die dem Brandherd zugewandte Hälfte — durch rostfreien Edelstahl (\(\lambda = 15\)); die beiden linken Elemente bleiben Baustahl (\(\lambda = 50\)). Randwerte wie immer, 20 und 300 °C. Sage zuerst vorher: Wo im Profil entsteht ein Knick, und in welche Richtung? Assembliere dann von Hand oder mit dem Skript und deute das Ergebnis. Das Lösungsskript loesungen/kap07_ue2.py rechnet es vor.

Die Wärme-Steifigkeiten sind jetzt \(2,\ 2,\ 0{,}6,\ 0{,}6\ \mathrm{W/K}\) (die beiden Edelstahl-Elemente haben \(k = 15 \cdot 0{,}01 / 0{,}25 = 0{,}6\)). Assembliert und gelöst ergibt sich das Profil

\[ [\,20,\ 52{,}3,\ 84{,}6,\ 192{,}3,\ 300\,]\ °\mathrm{C}. \]

Durch alle Elemente fließt derselbe Strom von rund \(64{,}6\ \mathrm{W}\) — es ist eine Reihenschaltung, genau wie bei den Federn. Die Edelstahl-Elemente leiten gut dreimal schlechter (\(k = 0{,}6\) statt \(2\)), tragen also unter demselben Strom je das gut dreifache Gefälle: über die kühlere Baustahl-Hälfte fallen zusammen nur \(64{,}6\ \mathrm{K}\) ab (20 → 84,6 °C), über die heiße Edelstahl-Hälfte dagegen \(215{,}4\ \mathrm{K}\) (84,6 → 300 °C). Genau an der Materialgrenze bei Knoten 3 knickt das Profil scharf nach oben. Das ist der Knick aus Übung 6.3 — dort noch von Hand über die Stromkonstanz erschlossen (Reihenwiderstand \(4{,}33\ \mathrm{K/W}\), Übergangstemperatur 84,6 °C), hier von der FEM-Maschinerie ausgerechnet. (Dass der Strom hier nur 64,6 W beträgt und nicht die 140 W des reinen Baustahl-Trägers, liegt am zusätzlichen Widerstand des Edelstahls: Die Kette ist als Ganzes „weicher” geworden.)

Ü 7.3 (Übertragen). Bau eine Wärmequelle ein — einen Heizdraht, der über die ganze Länge gleichmäßig \(P = 160\ \mathrm{W/m}\) erzeugt. Auf ein Element der Länge \(h = 0{,}25\ \mathrm{m}\) entfallen damit \(P \cdot h = 40\ \mathrm{W}\). Das Rezept für die rechte Seite: Verteile die Leistung jedes Elements hälftig auf seine beiden Knoten (je 20 W). Ein innerer Knoten sammelt so von seinen zwei Elementen \(40\ \mathrm{W}\) ein; diese Knotenleistungen sind die rechte Seite (bisher war sie null). Löse und vergleiche mit der Geraden. Das Skript loesungen/kap07_ue3.py macht es vor.

Die rechte Seite ist jetzt \([\,20,\ 40,\ 40,\ 40,\ 20\,]\ \mathrm{W}\) (die Randwerte 20/300 °C werden ohnehin festgehalten, ihre 20 W spielen keine Rolle). Assembliert und gelöst ergibt sich

\[ [\,20,\ 120,\ 200,\ 260,\ 300\,]\ °\mathrm{C}. \]

Das ist keine Gerade mehr! Verglichen mit der quellfreien Geraden \([\,20, 90, 160, 230, 300\,]\) wölbt sich das Profil zu einer Parabel über die Gerade — in der Mitte am stärksten (\(+40\) °C bei Knoten 3), zu den festgehaltenen Rändern hin auf null. So sieht es der Kasten in Kapitel 6 vorher: Eine Wärmequelle macht aus der Geraden eine gewölbte Kurve, weil die in der Mitte erzeugte Wärme nach beiden Seiten den weitesten Weg zum Rand hat und sich dort staut.

HinweisWarum genau hälftig auf die zwei Knoten?

Das Hütchen eines Knotens fällt über sein Element von 1 auf 0 — im Mittel „gehört” ihm also die halbe Elementlänge und damit die halbe darin erzeugte Leistung. Rechnet man das Elementintegral \(\int P\,N_i\,\mathrm{d}x\) sauber aus (eine Dreiecksfläche aus Kapitel 5: halbe Höhe mal Breite), kommt exakt „die Hälfte an jeden Knoten” heraus. Genau dieselbe Halbe-Halbe-Logik trifft dich in Kapitel 9 wieder, wenn die Massenmatrix den Zeitterm auf die Knoten verteilt.

Das Kleingedruckte

Drei ehrliche Feinheiten zum Schluss.

Erstens: Wir haben die Umverteilung (partielle Integration) benutzt, aber nicht bewiesen. Sie ist eine Rechenregel der Analysis, die immer gilt; wir haben sie hier nur angewandt und an einem Elementpaar plausibel gemacht, statt sie herzuleiten. Wer die Regel kennt, erkennt sie wieder; wer nicht, darf ihr vertrauen — das Ergebnis (die Elementmatrix) haben wir mit reiner Schulgeometrie nachgerechnet, ganz ohne die Regel.

Zweitens: Der Randterm ist echt und wichtig, wir haben ihn nur für die inneren Knoten auf null gesetzt. Für die Randknoten trägt er die Information über den ein- und austretenden Wärmestrom — und genau daraus werden in Kapitel 8 die verschiedenen Arten von Randbedingungen. Wir haben ihn also nicht unter den Teppich gekehrt, sondern für ein eigenes Kapitel aufgehoben.

Drittens: Die schöne Exaktheit dieses Kapitels — FEM trifft die Wahrheit auf die Nachkommastelle — ist der gutmütige Sonderfall, kein Naturgesetz. Sie gilt, weil die wahre Lösung selbst im Ansatzraum liegt (eine Gerade unter Geradenstücken). Sobald die Wahrheit krumm ist (Wärmequelle, Materialsprung, gebogene Geometrie), trifft die FEM nur noch näherungsweise, und die Näherung wird mit feinerem Netz besser. Diese ehrliche Fehler-Frage — „wie genau ist mein buntes Bild wirklich?” — ist so zentral, dass ihr Kapitel 12 gewidmet ist.

Galerkin, Boris G. 1915. „Sterzhni i plastinki. Ryady v nekotorykh voprosakh uprugogo ravnovesiya sterzhney i plastinok“. Vestnik Inzhenerov i Tekhnikov 19: 897–908.