Parameteridentifikation

Parameteridentifikation: ein klassisches Inverses Problem

Parameteridentifikation ist ein zentrales Thema in der Inversen Theorie. Dabei geht es um die Bestimmung einer unbekannten Funktion. Im Folgenden wird ein typisches Szenario behandelt: die Bestimmung einer unbekannten Parameterfunktion \( \lambda \) aus gegebenen Daten \( y \), die über einen Integraloperator \( \mathcal{A} \) mit \( \lambda \) verknüpft sind. Die zugrunde liegende Operatorgleichung ist linear, das numerische Verfahren basiert auf einer Diskretisierung mittels Trapezregel und der Lösung eines linearen Gleichungssystems.

Die Problemstellung geht auf einen Beitrag von Kirsch[1] im Sammelband Trends in Mathematical Optimization zurück und eignet sich gut, um grundlegende Phänomene inverser Probleme – insbesondere Instabilität, schlechte Konditionierung und die Notwendigkeit von Regularisierung – anschaulich darzustellen. Der Text baut auf einer früheren Version auf. Die vorliegende Fassung präzisiert die Darstellung der adjungierten Abbildung und ihrer konsistenten Diskretisierung und ergänzt sie um einen Vergleich äquivalenter Implementierungen in MATLAB, Python und Fortran hinsichtlich numerischer Übereinstimmung, Laufzeit und Threadskalierung.

Inhaltsverzeichnis

  1. Das Problem
  2. Numerische Lösung der Parameteridentifikation
  3. Zur Regularisierung
  4. Zusammenfassung
  5. Sage-Skript
  6. Implementierungen mit MATLAB, Python und Fortran
  7. Quellen

Das Problem

Gegeben sei

\[
(\mathcal{A}\lambda ) (t) = \int_0^1 \lambda(s) \, e^{ts} \, ds, \qquad t \in [0,1] \tag{1}
\]

Man kann zeigen, dass \(\mathcal A\) injektiv ist. Wenn sich \(y\) in der Form \(y=\mathcal A\lambda\) darstellen lässt, ist die Lösung \(\lambda\) daher eindeutig. Nicht jede beliebige Funktion \(y\in L^2(0,1)\) besitzt jedoch eine solche Darstellung. Der Operator ist somit nicht surjektiv auf \(L^2(0,1)\).

Als konkretes Beispiel sei \( \lambda(t) = e^t \). So ist

\[
y (t) := \mathcal{A} (e^t) = \frac{1}{t+1} \left( e^{t + 1} – 1 \right), \qquad t \in [0,1] \tag{2}
\]

Wir ändern nun den Blickwinkel und betrachten \( \lambda \) als „Parameterfunktion“, die es zu bestimmen gilt, wobei die Kenntnis der Funktion \( y \) aus (2) vorausgesetzt wird. Dazu sei das folgende numerische Verfahren gewählt:

Diskretisierung

  1. Ersetze das Integral (den Operator \( \mathcal{A} \)) durch die summierte Trapezregel (eine Diskretisierung des Problems).
  2. Die Unterteilung \( 0 = x_0 < x_1 < \ldots < x_{n-1} < x_n =1 \), \( n > 0 \) führt zu
    \[
    T_n(f, [0,1]) := \sum_{j=0}^{n-1} \frac{x_{j+1} – x_j }{2} \left( f(x_j) + f(x_{j+1}) \right)
    \]
    Mit \( x_i = i h \), \( h=1/n \) ist
    \[
    T_n(f, [0,1]) = \frac{1}{2n} \left( f(0) + f(1) \right) + \frac{1}{n} \sum_{j=1}^{n-1} f(x_j) \tag{3}
    \]
    und es gilt eine Fehlerabschätzung (\( f \in C^2[0,1] \)) der Form
    \[
    T_n(f, [0,1]) – \int_0^1 f(s) \, ds = \frac{h^2}{12} f^{(2)}(\xi), \quad \text{für ein } \xi \in (0,1)
    \]
  3. Die summierte Trapezregel für (1) lautet demnach
    \[
    \int_0^1 \lambda(s) \, e^{ts} \, ds \approx \frac{1}{2n} \left( \lambda(0) + \lambda(1) e^{t} \right) + \frac{1}{n} \sum_{j=1}^{n-1} \lambda(x_j) e^{t x_j} \tag{4}
    \]
Das diskrete Gleichungssystem

Gesucht sind daher die Funktionswerte von \( \lambda \) in den Stützstellen \( x_i \). Gegeben ist die Funktion \( y \). Zur Bestimmung der \( n+1 \) unbekannten Werte \( \lambda_i := \lambda(x_i) \) haben wir die (semi-diskrete) Gleichung

\[
\frac{1}{2n} \left( \lambda(x_0) + \lambda(x_n) e^{x_i} \right) + \frac{1}{n} \sum_{j=1}^{n-1} \lambda(x_j) e^{x_i x_j} = y(x_i), \quad i=0,\ldots,n
\]

bzw. als lineares Gleichungssystem geschrieben

\[
\mathbf{A}_n \boldsymbol{\alpha} = \mathbf{y} \tag{5}
\]

mit

\[
a_{ij} =
\begin{cases}
\frac{1}{2n} e^{x_i x_j} & j = 0 \text{ oder } j = n \\\\
\frac{1}{n} e^{x_i x_j} & \text{sonst}
\end{cases}
\]

und

\[
\mathbf{y} = \left( y(x_i) \right)_{i=0}^n ,\qquad \boldsymbol{\alpha} = \left( \lambda(x_i) \right)_{i=0}^n
\]

Obwohl der Operator \( \mathcal{A} \) im kontinuierlichen Fall selbstadjungiert ist, ist die Matrix \( \mathbf{A}_n \) im Allgemeinen nicht symmetrisch. Grund dafür sind die unterschiedlichen Gewichte der Trapezregel an den Rändern. Zum Beispiel: \( a_{01} = \frac{1}{n} \), aber \( a_{10} = \frac{1}{2n} \).

Numerische Lösung der Parameteridentifikation

Für verschiedene \( n \) wurde die Kondition der Matrix \( A_n \) bestimmt, das Gleichungssystem (5) aufgestellt und gelöst. \( \alpha(t) \) sei die durch den Vektor \( \alpha \) gegebene stückweise lineare Funktion. Die Kondition, der absolute Fehler \( \| \alpha(t) – e^t \|_\infty \) und der relative Fehler \( \| \alpha(t) – e^t \|_\infty / \| e^t \|_\infty \) sind in Tabelle 1 aufgeführt.

Ergebnisse zum Gleichungssystem (5)
n cond(A) \( \| \alpha – e^t \|_\infty \) rel. Fehler
1 5.87 1.0 36.7 %
2 168.03 1.0388 38.2 %
4 910021.07 1.08964 40.0 %
8 7.85e+14 4.7194 173 %
16 7.87e+17 108.34 3985 %
32 2.79e+19 267.85 9853 %
Visualisierung der Parameteridentifikation – exakte und rekonstruiere Lösung
Starke Instabilität der Parameteridentifikation bei direkter Lösung: Die rekonstruierten Werte weichen stark von der exakten Funktion \(e^t\) ab – typisch für schlecht gestellte Probleme ohne Regularisierung.

Dieses Diagramm zeigt den Vergleich zwischen der exakten Lösung \( e^t \) (blaue Linie) und der numerisch rekonstruierten Funktion \( \alpha(t) \) (orange gestrichelt) bei direkter Lösung des Gleichungssystems \( \mathbf{A}_n \alpha = y \) mit \( n = 16 \). Aufgrund der schlechten Kondition der Matrix \( \mathbf{A}_n \) ist das Verfahren instabil: Die rekonstruierten Werte oszillieren stark und weichen erheblich vom exakten Verlauf ab. Das Beispiel illustriert die typische Problematik schlecht gestellter inverser Aufgaben und macht die Notwendigkeit einer Regularisierung deutlich.

Zur Regularisierung

Zur Regularisierung des schlecht gestellten Problems wird die Tikhonov-Regularisierung eingesetzt:

\[
(\beta I + \mathcal{A}^* \mathcal{A}) \,\lambda = \mathcal{A}^* y \tag{6}
\]

Dabei bezeichnet \( \mathcal{A}^* \) die adjungierte Abbildung des Operators \( \mathcal{A} \). Sie ist definiert durch die Bedingung:

\[
\langle \mathcal{A} \lambda, y \rangle = \langle \lambda, \mathcal{A}^* y \rangle
\quad\text{für alle } \lambda, y \in L^2(0,1).
\]

Wenn man die Definition von \( \mathcal{A} \) einsetzt, ergibt sich durch Vertauschen der Integrale (Fubini):

\[
(\mathcal{A}^* y)(s) = \int_0^1 e^{t\,s}\,y(t)\,\mathrm{d}t. \tag{7}
\]

Da die Darstellung von \( \mathcal{A}^* \) formal genau der von \( \mathcal{A} \) entspricht (bis auf die Benennung der Integrationsvariable), ist die Aufgabe selbstadjungiert, d.h. \( \mathcal{A}^* = \mathcal{A} \).

Bei der Diskretisierung mit der Trapezregel sind die Randgewichte halbiert, sodass die entstehende Matrix \( \mathbf{A}_n \) nicht symmetrisch ist. Folglich ist die Annahme \( \mathbf{A}_n^* = \mathbf{A}_n^T \) falsch und führt zu Fehlern. Korrekt ist vielmehr, die adjungierte Abbildung ebenso mit der Trapezregel zu diskretisieren, wodurch \( \mathbf{A}_n^* = \mathbf{A}_n \) gilt.

Diskretes Regularisierungssystem (konsistent)

Mit der korrekten adjungierten Matrix \(A_n^* = A_n\) lautet die diskrete Tikhonov-Gleichung

\[
\bigl(\beta\,\mathbf{I} + \mathbf{A}_n\,\mathbf{A}_n\bigr)\,\boldsymbol{\alpha}_\beta
\;=\;\mathbf{A}_n\,\mathbf{y}.
\tag{8a}
\]

Regularisierte Lösung gemäß Gleichung (8a), \( \beta = 10^{-3} \)
n \( \| \alpha – e^t \|_\infty \) rel. Fehler
1 0.99 36.6 %
2 0.60 22.0 %
4 0.234 8.6 %
8 0.1 3.6 %
16 0.063 2.3 %
32 0.054 1.9 %
Lösung der Parameteridentifikation mit n = 32 und konsistenter Diskretisierung, Vergleich von exakter und rekonstruierter Funktion e^t
Vergleich von exakter Lösung \( e^t \) und rekonstruiertem Ergebnis bei konsistenter Tikhonov-Regularisierung mit \( \beta = 10^{-3} \), \( n = 32 \)

Der Plot zeigt den Vergleich zwischen der exakten Lösung \( e^t \) (blaue Linie) und der numerisch rekonstruierten Funktion (orange gestrichelt) für \( n = 32 \) mit Tikhonov-Regularisierung bei konsistenter Diskretisierung der adjungierten Matrix. Die Annäherung ist bereits recht gut; der maximale Fehler beträgt etwa 2.3 %. Für eine höhere Genauigkeit – etwa unter 0.1 % – müsste \( n \) weiter erhöht (z. B. auf \( n = 128 \)) und der Regularisierungsparameter auf \( \beta = 10^{-6} \) reduziert werden.

Gleichung (8a), \( \beta = 10^{-6} \)
n \( \| \alpha – e^t \|_\infty \) rel. Fehler
1 1.0 36.7 %
2 1.0335 38.0 %
4 0.5639 20.7 %
8 0.202 7.4 %
16 0.057 2.12 %
32 0.016 0.6 %
128 0.0029 0.10 %

Diskretes Regularisierungssystem (inkonsistent)

Setzt man fälschlich \(A_n^* = A_n^T\), erhält man stattdessen

\[
\bigl(\beta\,\mathbf{I} + \mathbf{A}_n^T\,\mathbf{A}_n\bigr)\,\boldsymbol{\alpha}_\beta
\;=\;\mathbf{A}_n^T\,\mathbf{y}.
\tag{8b}
\]

Effekt einer nicht-konsistenten Diskretisierung (Gleichung (8b))
n \( \| \alpha – e^t \|_\infty \) rel. Fehler
1 0.99 36.6 %
2 0.96 35 %
4 1.0339 38 %
8 1.19 43.9 %
16 1.28 47.4 %
32 1.3371 49.18 %
Lösung der Parameteridentifikation mit n = 32 und inkonsistenter Diskretisierung, Vergleich von exakter und rekonstruierter Funktion e^t
Vergleich von exakter Lösung \( e^t\) und rekonstruiertem Ergebnis bei inkonsistenter Tikhonov-Regularisierung mit \( \beta = 10^{-3} , \; n = 32 \)

In dieser Darstellung wird die exakte Lösung \( e^t \) (blau) der rekonstruierten Funktion (orange gestrichelt) gegenübergestellt, wobei die Tikhonov-Regularisierung mit inkonsistenter Diskretisierung der adjungierten Matrix verwendet wurde. Trotz Regularisierung zeigen sich deutliche Abweichungen am Rand. Dies unterstreicht die Bedeutung einer konsistenten Diskretisierung bei der Parameteridentifikation.

Zusammenfassung

  • Die direkte Lösung des Gleichungssystems (5) ist numerisch instabil. Die Matrix \( \mathbf{A}_n \) ist schlecht konditioniert, was bei wachsendem \( n \) zu stark fehlerhaften Lösungen führt.
  • Die Tikhonov-Regularisierung gemäß Modell (8a) stabilisiert das Problem erfolgreich. In Abhängigkeit vom Regularisierungsparameter \( \beta \) und der Diskretisierung \( n \) lässt sich die exakte Parameterfunktion \( \lambda(t) = e^t \) mit hoher Genauigkeit rekonstruieren.
  • Eine konsistente Diskretisierung der adjungierten Abbildung \( \mathcal{A}^* \) ist entscheidend. Wird stattdessen fälschlich die transponierte Matrix \( \mathbf{A}_n^T \) verwendet (Modell (8b)), bleibt die Lösung zwar beschränkt, konvergiert jedoch nicht gegen die exakte Funktion \( e^t \).

Sage-Skript

Das Sage-Skript führt die numerische Untersuchung des Modellproblems Parameteridentifikation mit Tikhonov-Regularisierung automatisiert durch. Es erledigt dabei folgende Schritte:

  • Aufbau der Diskretisierungsmatrix \(A_n\)
    Verwendung der summierten Trapezregel auf \([0,1]\) mit \(n+1\) Stützstellen;
    Bildung der Matrixeinträge \(a_{ij}=w_j\,e^{x_i x_j}\).
  • Berechnung der Konditionszahl
    Bestimmung der größten und kleinsten Singulärwerte mittels NumPy;
    Kennzahl \(\kappa(A_n)=\sigma_{\max}/\sigma_{\min}\).
  • Direkte Lösung
    Lösung des linearen Systems \(A_n\,\alpha=y\) für \(\alpha\);
    Berechnung des absoluten und relativen Fehlers gegenüber \(\lambda(t)=e^t\).
  • Tikhonov-Regularisierung
    Lösen von \((\beta I + A_n^*A_n)\,\alpha_\beta = A_n^*y\) mit
    konsistent: \(A_n^*=A_n\)
    inkonsistent: \(A_n^*=A_n^T\)
    Vergleich der Regularisierungsergebnisse für \(\beta=10^{-3}\) (Modell 1) und \(\beta=10^{-6}\) (Modell 2).
  • Ausgabe
    Fünf einheitliche Tabellen im ASCII-Format:

    • Direkte Lösung (ohne Regularisierung)
    • Modell 1 (konsistent / inkonsistent)
    • Modell 2 (konsistent / inkonsistent)


Implementierungen mit MATLAB, Python und Fortran

Motivation

Den Anstoß für diese Erweiterung gab eine praktische Frage aus dem wissenschaftlichen Rechnen: Wie lässt sich ein vorhandenes numerisches Modell zwischen MATLAB, Python und Fortran übertragen, ohne seine mathematische Aussage oder seine numerische Qualität zu verändern?

Eine solche Portierung ist keine reine Sprachfrage. Zuerst muss geklärt werden, ob beide Programme wirklich dasselbe mathematische Modell, dieselbe Diskretisierung und dieselben Rand- beziehungsweise Quadraturbehandlungen verwenden. Erst danach sind Aussagen über numerische Übereinstimmung, Wartbarkeit oder Geschwindigkeit sinnvoll.

Das Modellproblem dieses Beitrags bietet dafür einen überschaubaren Referenzfall. Ausgehend vom vorangehenden Sage-Skript diskretisieren die MATLAB-, Python- und Fortran-Fassungen denselben Integraloperator und lösen dieselben direkten beziehungsweise regularisierten Gleichungssysteme. Der Vergleich konzentriert sich auf zwei Fragen: Stimmen die numerischen Ergebnisse überein, und wie unterscheiden sich Laufzeit und Speicherbedarf der konkreten Implementierungen?

Für kleine und mittlere Diskretisierungen steht die mathematische Validierung im Vordergrund. Große Matrizen werden ausschließlich als Performance- und Skalierungstest verwendet, da die direkte inverse Aufgabe mit wachsendem n extrem schlecht konditioniert ist.

Gemeinsame diskrete Referenz

Alle drei Implementierungen verwenden die äquidistanten Stützstellen \(x_i=i/n\), \(i=0,\ldots,n\), und die Gewichte der summierten Trapezregel. Mit

\[
K_{ij}=\exp(x_i x_j),
\qquad
W=\operatorname{diag}(w_0,\ldots,w_n),
\qquad
A=KW
\]

lautet die direkte diskrete Aufgabe

\[
A\boldsymbol{\alpha}=\boldsymbol{y}.
\]

Die konsistent diskretisierte Tikhonov-Gleichung wird in allen Programmen in derselben Form gelöst:

\[
(\beta I+A A)\boldsymbol{\alpha}_{\beta}=A\boldsymbol{y}.
\]

Damit vergleichen wir tatsächlich dieselbe mathematische Aufgabe. Insbesondere darf die diskrete adjungierte Abbildung hier nicht stillschweigend durch die gewöhnliche transponierte Matrix ersetzt werden; die Quadraturgewichte müssen konsistent berücksichtigt bleiben.

MATLAB-Implementierung

Die MATLAB-Fassung bildet Stützstellen, Quadraturgewichte und Matrix vektorisiert und verwendet die lineare Algebra des installierten MATLAB-Backends. Direkte und regularisierte Lösung werden getrennt ausgewertet, damit Konditionierung, Rekonstruktionsfehler und Laufzeit nicht miteinander vermischt werden.

% Diskretisierung und konsistente Tikhonov-Regularisierung
n = 32;
beta = 1.0e-6;

x = (0:n).'/n;
weights = ones(n+1, 1)/n;
weights([1, end]) = 1/(2*n);

A = exp(x*x.').*weights.';
y = expm1(x+1)./(x+1);
lambdaExact = exp(x);

conditionA = cond(A, 2);
alphaDirect = A\y;
directError = max(abs(alphaDirect-lambdaExact));

lhs = beta*eye(n+1) + A*A;
rhs = A*y;
alphaRegularized = lhs\rhs;
regularizedError = max(abs(alphaRegularized-lambdaExact));

Beim Matrixaufbau bezeichnet .* die elementweise Multiplikation des Kerns mit den Quadraturgewichten. Die Produkte A*A und A*y sind dagegen gewöhnliche Matrixprodukte.

Der vollständige MATLAB-Quellcode mit Tests und Benchmarkprogrammen ist auf GitHub verfügbar.

Python-/NumPy-Implementierung

Die Python-Fassung verwendet NumPy und folgt derselben Reihenfolge von Matrixaufbau, direkter Lösung und Tikhonov-Regularisierung. Für einen fairen Vergleich werden keine sprachspezifischen Vereinfachungen verwendet, die das mathematische Verfahren verändern würden.

# Diskretisierung und konsistente Tikhonov-Regularisierung
import numpy as np

n = 32
beta = 1.0e-6

x = np.arange(n + 1, dtype=np.float64) / n
weights = np.full(n + 1, 1.0 / n, dtype=np.float64)
weights[[0, -1]] = 1.0 / (2.0 * n)

A = np.exp(np.outer(x, x)) * weights[np.newaxis, :]
y = np.expm1(x + 1.0) / (x + 1.0)
lambda_exact = np.exp(x)

condition_A = np.linalg.cond(A)
alpha_direct = np.linalg.solve(A, y)
direct_error = np.max(np.abs(alpha_direct - lambda_exact))

lhs = beta * np.eye(n + 1) + A @ A
rhs = A @ y
alpha_regularized = np.linalg.solve(lhs, rhs)
regularized_error = np.max(np.abs(alpha_regularized - lambda_exact))

NumPy verwendet * für die elementweise Multiplikation und @ für Matrixprodukte. Abgesehen von diesen syntaktischen Unterschieden führen beide Listings dieselben Rechenschritte in derselben Reihenfolge aus.

Der vollständige Python-/NumPy-Quellcode mit Tests und Benchmarkprogrammen ist auf GitHub verfügbar.

Fortran-90/95-Implementierung

Die Fortran-Fassung ist eine eigenständige Referenzimplementierung desselben Modells. Der numerische Kern ist in Module für Präzision, Modellaufbau, lineare Algebra und Auswertung gegliedert und wird mit strengem Fortran 95 übersetzt. DGESV, DGESVD, DGEMM und DGEMV binden die etablierten BLAS-/LAPACK-Routinen direkt ein. Nur das getrennte Benchmark-Modul nutzt für die hochauflösende Wandzeitmessung eine dokumentierte neuere SYSTEM_CLOCK-Erweiterung von gfortran.

Der folgende Ausschnitt stammt aus dem tatsächlichen Modellmodul. Er zeigt die konkrete Datenstruktur, ihre explizite Speicherverwaltung und den Aufbau der diskreten Aufgabe mit Fortran-95-Mitteln:

module gwr_model
    use gwr_precision, only: dp
    implicit none

    type, public :: problem_data
        integer :: n = 0
        real(dp), pointer :: x(:)            => null()
        real(dp), pointer :: weights(:)      => null()
        real(dp), pointer :: matrix(:, :)    => null()
        real(dp), pointer :: data(:)         => null()
        real(dp), pointer :: exact_lambda(:) => null()
    end type problem_data

contains

    subroutine build_problem(n, problem, info)
        integer, intent(in) :: n
        type(problem_data), intent(out) :: problem
        integer, intent(out) :: info
        integer :: i, j, stat

        info = 0
        nullify(problem%x, problem%weights, problem%matrix, &
                problem%data, problem%exact_lambda)
        if (n < 1) then
            info = -1
            return
        end if
        problem%n = n
        allocate(problem%x(0:n), problem%weights(0:n), &
                 problem%matrix(0:n,0:n), problem%data(0:n), &
                 problem%exact_lambda(0:n), stat=stat)
        if (stat /= 0) then
            info = stat
            call destroy_problem(problem)
            return
        end if

        do i = 0, n
            problem%x(i) = real(i,dp) / real(n,dp)
            problem%weights(i) = 1.0_dp / real(n,dp)
        end do
        problem%weights(0) = 1.0_dp / (2.0_dp*real(n,dp))
        problem%weights(n) = problem%weights(0)

        do j = 0, n
            do i = 0, n
                problem%matrix(i,j) = problem%weights(j) * &
                    exp(problem%x(i)*problem%x(j))
            end do
        end do

        do i = 0, n
            problem%data(i) = (exp(problem%x(i)+1.0_dp)-1.0_dp) / &
                              (problem%x(i)+1.0_dp)
            problem%exact_lambda(i) = exp(problem%x(i))
        end do
    end subroutine build_problem
end module gwr_model

Darauf baut der fachliche Ablauf mit Konditionsanalyse, direkter Lösung und konsistenter Regularisierung auf:

! Derselbe Ablauf wie in MATLAB und Python
type(problem_data) :: problem
real(dp) :: lhs(n+1,n+1), rhs(n+1)
real(dp) :: alpha_direct(n+1), alpha_regularized(n+1)
real(dp) :: condition_a, direct_error, regularized_error

call build_problem(n, problem, info)
call condition_number_2(problem%matrix, condition_a, info)

! Direkte Lösung A alpha = y
call solve_linear_system(problem%matrix, problem%data, &
                         alpha_direct, info)
direct_error = maxval(abs(alpha_direct - problem%exact_lambda))

! Konsistente Regularisierung:
! (beta I + A A) alpha_beta = A y
call build_tikhonov_system(problem%matrix, problem%data, beta, &
                           adjoint_weighted, lhs, rhs, info)
call solve_linear_system(lhs, rhs, alpha_regularized, info)
regularized_error = maxval(abs(alpha_regularized - &
                               problem%exact_lambda))

call destroy_problem(problem)

Die Kommentare im vollständigen Quellcode verweisen auf die jeweils entsprechende Python-Funktion. CMake und CTest prüfen Modellaufbau, stabile Referenzfälle, Regularisierung und beide Adjungierten-Konventionen. Ein unabhängiger Python-Vergleich umfasst 29 Ergebniswerte; die größte relative Differenz beträgt 4.818e-10. Ein zusätzlicher Prüflauf mit Speicher- und Laufzeitdiagnostik blieb ebenfalls fehlerfrei.

Der vollständige Fortran-90/95-Quellcode mit CMake und CTest sowie das gemeinsame Referenz-Repository sind auf GitHub verfügbar.

Als Referenz dienen die Ergebnisse des vorangehenden Sage-Abschnitts. Die neue Gegenüberstellung prüft, ob MATLAB, Python und Fortran diese Referenzfälle reproduzieren.

Numerische Übereinstimmung

Zunächst werden kleine und mittlere Werte von n untersucht. Bei der direkten Lösung sind nur die noch ausreichend stabilen Fälle bis \(n=4\) für einen Vergleich der Implementierungen geeignet. Für feinere Gitter werden die regularisierten Rekonstruktionsfehler gegenüber \(\lambda(t)=\exp(t)\) verglichen.

Verfahren n Sage MATLAB/Python/Fortran
direkt 1 1.0 1.0000
direkt 2 1.0388 1.0388
direkt 4 1.08964 1.0896
\(\beta=10^{-3}\) 4 0.234 0.23438
\(\beta=10^{-3}\) 8 0.1 0.10021
\(\beta=10^{-3}\) 16 0.063 0.063559
\(\beta=10^{-3}\) 32 0.054 0.054207
\(\beta=10^{-6}\) 4 0.5639 0.56395
\(\beta=10^{-6}\) 8 0.202 0.20214
\(\beta=10^{-6}\) 16 0.057 0.057867
\(\beta=10^{-6}\) 32 0.016 0.016412
\(\beta=10^{-6}\) 128 0.0029 0.0029506
Absoluter Maximalfehler der direkten beziehungsweise konsistent regularisierten Rekonstruktion. Die Sage-Werte entsprechen der bisherigen Darstellung, die gemeinsamen MATLAB-/Python-/Fortran-Werte sind auf fünf signifikante Stellen gerundet.

MATLAB, Python und Fortran reproduzieren die stabilen direkten Fälle und die veröffentlichten regularisierten Sage-Werte. In allen aufgeführten Fällen liefern sie auf die dargestellten fünf signifikanten Stellen dieselben Maximalfehler. Beim feinsten gezeigten Fall mit \(n=128\) und \(\beta=10^{-6}\) liegen auch die ungerundeten Ergebnisse eng beieinander.

Bei der direkten Lösung steigt die Konditionszahl dagegen bereits von ungefähr \(9.1\cdot10^5\) für \(n=4\) auf etwa \(7.9\cdot10^{14}\) für \(n=8\). Ab diesem Bereich können verschiedene BLAS-/LAPACK-Backends deutlich unterschiedliche Lösungsvektoren liefern, obwohl sie dieselbe diskrete Matrix verarbeiten. Diese Abweichung ist ein Symptom der Schlechtgestelltheit und kein belastbarer Geschwindigkeits- oder Qualitätsvergleich zwischen den Programmiersprachen.

Laufzeitvergleich

Für den Laufzeitvergleich werden Matrixaufbau, direkte Lösung, Aufbau des Tikhonov-Systems und regularisierte Lösung getrennt gemessen. Die Gesamtzeit ist der Median der Summe dieser vier Phasen pro Lauf. Nicht enthalten sind Programmstart, Paketinstallation und Datei-Ein-/Ausgabe.

n N=n+1 MATLAB 14.08. [ms] Python 14.10. [ms] Fortran 14.10. [ms]
64 65 0.260 ± 0.005 0.108 ± 0.001 0.108 ± 0.000
128 129 0.640 ± 0.002 0.385 ± 0.001 0.455 ± 0.014
256 257 2.480 ± 0.005 2.561 ± 0.013 2.104 ± 0.026
512 513 12.806 ± 0.013 14.260 ± 0.056 11.389 ± 0.114
1024 1025 73.044 ± 0.083 77.379 ± 0.641 69.412 ± 0.061
Vollständiger dichter Arbeitsablauf in Millisekunden, angegeben als Median ± MAD aus fünf unabhängigen Läufen. MATLAB stammt aus der Messreihe vom 14. August 2026; Python und Fortran wurden am 14. Oktober 2026 gemessen.

Die veröffentlichten MATLAB-Werte vom 14. August bleiben als historische Referenz stehen. Python wurde am 14. Oktober erneut gemessen und dient als Brücke zwischen den Terminen: Bei \(n=1024\) liegt der frische Python-Wert nur 0.88 % über dem historischen Wert. Fortran ist dort 10.3 % schneller als das frisch gemessene Python und 5.0 % schneller als der historische MATLAB-Wert. Bei kleinen Matrizen dominieren dagegen Aufruf- und Verwaltungsanteile; insbesondere bei \(n=128\) liegt Fortran hinter Python.

Laufzeiten der einzelnen Rechenphasen
Phase bei n=1024 MATLAB 14.08. [ms] Python 14.10. [ms] Fortran 14.10. [ms]
Matrixaufbau 7.265 4.829 2.604
direkte Lösung 20.203 21.570 21.309
Aufbau des Tikhonov-Systems 26.521 30.365 24.696
regularisierte Lösung 19.251 20.725 20.793
Median der einzeln gemessenen Phasen für die größte untersuchte Matrix mit \(N=1025\). Die Messtermine entsprechen der vorangehenden Tabelle.

Die Phasenmessung zeigt ein differenziertes Bild: Fortran baut die Matrix mit kompilierten Schleifen besonders schnell auf. Bei den drei Operationen der dichten linearen Algebra liegen die Implementierungen enger zusammen, weil ein wesentlicher Teil der Rechenzeit in optimierten BLAS- und LAPACK-Bibliotheken anfällt. Die Messwerte vergleichen deshalb nicht abstrakt Programmiersprachen, sondern stets die getesteten Programme zusammen mit Compiler und numerischem Backend.

Reproduzierbarkeit

  • identische Problemdefinition und identische Eingabedaten,
  • Matrixdimension \(N=n+1\) ausdrücklich angegeben,
  • MATLAB-, Python-, NumPy-, Fortran-Compiler- und Sage-Version dokumentiert,
  • BLAS-/LAPACK-Backend dokumentiert,
  • Threadzahl und CPU-Affinität kontrolliert,
  • Warm-up vor der Messung,
  • mehrere Wiederholungen mit Median und Streuung,
  • Validierungs- und Performancefälle getrennt ausgewiesen.
Messdaten MATLAB: 14. August 2026; Python/Fortran: 14. Oktober 2026
Host turing
CPU 2 × Intel Xeon Silver 4316, je 20 Kerne, 80 logische CPUs
Betriebssystem Linux 5.14.0, x86-64, glibc 2.34
MATLAB R2026a Update 4
MATLAB-Backend Intel oneAPI MKL 2025.0.1, LAPACK 3.11.0
Python CPython 3.13.14
NumPy 2.5.2
NumPy-Backend OpenBLAS 0.3.34, ILP64, Haswell/AVX-512
Fortran GNU Fortran 11.5.0, Release-Build
Fortran-Backend OpenBLAS 0.3.34, LP64, pthread; benutzerlokaler Build
Threadmodus ein Rechenthread, Prozess an logische CPU 0 gebunden
Wiederholungen Einthreadvergleich: fünf unabhängige Läufe, Python/Fortran intern jeweils neun Messungen nach einem Warm-up

MATLAB wurde mit -singleCompThread gestartet. Für Python und Fortran waren OPENBLAS_NUM_THREADS=1, OMP_NUM_THREADS=1 und MKL_NUM_THREADS=1 gesetzt. Alle Prozesse liefen mit taskset -c 0 auf demselben logischen CPU-Kern.

Der Einthreadmodus wurde bewusst als klar definierte Vergleichsbasis gewählt. Dadurch dominieren unterschiedliche automatische Threadstrategien von MKL und OpenBLAS die Messung nicht. Eine separate Messreihe untersucht im Folgenden die Parallelskalierung mit identischer Threadzahl und kontrollierter CPU-Affinität.

Threadskalierung auf physischen Kernen

Für die Threadmessung wird die dichte Performanceaufgabe auf \(n=8192\), also \(N=8193\), vergrößert. Gemessen werden 1, 2, 4, 8, 16, 20 und 40 Rechenthreads. Für MATLAB und Python liegen jeweils fünf unabhängige Läufe vom 14. August vor; Fortran wurde am 14. Oktober in drei unabhängigen Läufen gemessen. Bis 20 Threads liegen alle verwendeten physischen Kerne auf dem ersten CPU-Sockel. Der 40-Thread-Punkt verwendet die 40 physischen Kerne beider Sockel. Hyperthreading bleibt vollständig ausgeschlossen.

Die Parallelisierung wird nicht im Modellcode mit parfor, multiprocessing oder OpenMP programmiert. MATLAB begrenzt seine Rechenthreads mit maxNumCompThreads; bei Python und Fortran steuert OPENBLAS_NUM_THREADS das OpenBLAS-Backend. taskset bindet die Prozesse an dieselben physischen CPUs. Matrixprodukte und lineare Löser werden damit innerhalb von MKL beziehungsweise OpenBLAS parallel ausgeführt.

MATLAB, Beispiel mit 16 Threads:
taskset -c 0-15 matlab -batch "maxNumCompThreads(16); ..."

Python/NumPy, Beispiel mit 16 Threads:
OPENBLAS_NUM_THREADS=16 OMP_NUM_THREADS=16 MKL_NUM_THREADS=16 \
taskset -c 0-15 python -m gwr_parameteridentifikation.benchmark ...

Fortran/OpenBLAS, Beispiel mit 16 Threads:
OPENBLAS_NUM_THREADS=16 OMP_NUM_THREADS=16 \
taskset -c 0-15 ./gwr_thread_benchmark_16
Threads MATLAB 14.08. [s] Python 14.08. [s] Fortran 14.10. [s]
1 24.598 ± 0.005 23.099 ± 0.008 22.512 ± 0.003
2 12.778 ± 0.015 12.348 ± 0.005 11.849 ± 0.007
4 7.451 ± 0.035 6.917 ± 0.009 6.440 ± 0.004
8 4.709 ± 0.006 4.640 ± 0.009 4.210 ± 0.001
16 4.016 ± 0.011 4.493 ± 0.006 3.992 ± 0.007
20 4.070 ± 0.017 4.630 ± 0.016 4.130 ± 0.001
40 3.932 ± 0.058 6.770 ± 0.095 7.027 ± 0.012
Vollständiger dichter Arbeitsablauf bei \(n=8192\), Median ± MAD. MATLAB und Python: fünf unabhängige Läufe vom 14. August; Fortran: drei unabhängige Läufe vom 14. Oktober. Ausschließlich physische Kerne, ohne Hyperthreading.
Ergebnisse der Threadskalierung

Alle drei Implementierungen erreichen bis acht Threads ungefähr den fünffachen Durchsatz. Fortran erzielt mit 16 Threads eine Gesamtzeit von 3.992 s und damit einen Speedup von 5.64. Dies ist zugleich der beste Fortran-Messpunkt; bei 20 Threads steigt die Zeit geringfügig auf 4.130 s.

Die Fortran-Phasenmessung zeigt die Wirkung des aktuellen Backends: Bei 16 Threads beschleunigt sich der mit DGEMM berechnete Aufbau des Tikhonov-Systems um den Faktor 7.71. Die direkte und die regularisierte DGESV-Lösung erreichen Speedups von 4.53 beziehungsweise 4.76. Der explizite Matrixaufbau bleibt einthreadig, macht mit rund 0.23 s jedoch nur einen kleinen Teil der Gesamtlaufzeit aus.

Eine Kontrollmessung mit dem zuvor installierten OpenBLAS 0.3.29 hatte für DGESV nahezu konstante Zeiten ergeben. Ursache war ein inzwischen korrigierter Schwellwertfehler dieser Version: Bei einer rechten Seite wurde die Parallelisierung anhand von N × NRHS statt anhand der quadratischen Matrixgröße aktiviert. Mit OpenBLAS 0.3.34 skaliert derselbe unveränderte Fortran-95-Code erwartungsgemäß.

Auch der Matrixaufbau reagiert unterschiedlich: MATLAB parallelisiert den vektorisierten Aufbau in dieser Konfiguration sichtbar, während NumPys outer/exp-Folge und die expliziten Fortran-Schleifen einthreadig bleiben. Der 40-Kern-Fall lief unter der Standard-NUMA-Policy von Linux und ist als eigener Zwei-Sockel-Messpunkt zu lesen. Die Skalierungskurve bestätigt damit, dass die konkrete Backend- und Speicherarchitektur wichtiger ist als die bloße Angabe einer maximalen Threadzahl.

Was der Vergleich zeigt

MATLAB, Python und Fortran können dieselbe Parameteridentifikation mit praktisch übereinstimmenden Resultaten abbilden. Entscheidend für die fachliche Qualität sind jedoch eine konsistente Diskretisierung, eine geeignete Regularisierung und eine sachgerechte Beurteilung der Kondition des diskretisierten Problems. Die Wahl der Programmiersprache beeinflusst Bedienung und konkrete Laufzeit, ändert aber nichts an der mathematischen Schlechtgestelltheit.

Bei großen Diskretisierungen zeigt sich außerdem, dass die Wahl des Algorithmus wesentlich mehr bewirken kann als der Wechsel zwischen MATLAB, Python und Fortran. Der Kern \(\exp(x_i x_j)\) besitzt eine separierbare Entwicklung. Dadurch lässt sich die vollständige Kernmatrix durch voll besetzte Faktormatrizen mit nur wenigen Spalten darstellen.

Ausblick: Niedrigrangrechnung ohne vollständige Matrix

Die daraus entstehende Niedrigrangrechnung mit separierbaren Kernen ist ein eigenständiges Thema. Im vorliegenden Beispiel ersetzen voll besetzte Faktormatrizen mit nur wenigen Spalten die vollständige Kernmatrix. Damit folgt die Rechnung derselben strukturorientierten Grundidee, die auch hierarchischen Matrizen und dem Panel Clustering zugrunde liegt: Die Darstellung speichert nicht alle Matrixeinträge einzeln, sondern bildet die Operatorwirkung aus kompakten Faktoren.

Hinweis: Die ergänzten Abschnitte zu Sage, MATLAB, Python und Fortran sowie zur Threadskalierung wurden mit Unterstützung KI-gestützter Werkzeuge ausgearbeitet. Mathematische Aussagen, Implementierungen und Messergebnisse wurden fachlich geprüft und durch reproduzierbare Rechnungen validiert.


Quellen

  1. Andreas Kirsch (1988): Inverse Problems. In: Hoffmann, K. H.; Hoppe, R. H. W. (Hrsg.): Trends in Mathematical Optimization, S. 117–137, Birkhäuser, Basel, 1988.

Erstellt von Dr. Frank Liebau, Erstfassung: 17. März 2001. Übersetzt in modernes HTML/MathJax 2025.