Einsatz von Klassenmodulen

Was ist die Newton-Interpolation ?

Bei der Newton-Interpolation sucht man zu n Funktionswerten (x, y) ein Polynom, welches genau durch diese Punkte geht. Dafür braucht man ein Polynom, welches höchstens vom Grad (n-1) ist.

Die Newton-Interpolation liefert ein Polynom, welches zu jedem der n x-Werte genau den passenden y-Wert liefert. Dies ist allerdings noch ohne Wert, denn was will man mit Werten, die man schon kennt?

Die Interpolation wird gerne eingesetzt, um Zwischenwerte aus Tabellen zu berechnen. Wer noch Logarithmentafeln aus seiner Schulzeit kennt, weiß, dass man Zwischenwerte, die nicht in der Tabelle standen, mit der linearen Interpolation berechnen mußte. Eine Schlangenlinie in der Tabelle deutete manchmal auch an, dass die lineare Interpolation nicht genau genug war.

Auch heute noch gibt es viele Zusammenhänge, die nicht ganz zu durchschauen sind, so dass man Tabellen verwendet, um Zwischenwerte daraus zu berechnen, so manche Steuerung (z.B. für Heizkessel, ESP) benutzt heimlich Tabellen und interpoliert dann die Zwischenwerte.

Die Newton-Interpolation ist relativ schnell und einfach durchzuführen, in mathematischen Lehrbüchern wird aber auch gerne das Lagrange-Verfahren zur Interpolation angegeben, welches aber rechnerisch viel aufwändiger ist.

Seit einigen Versionen bietet VBA Klassenmodule an. Dieses Beispiel soll zeigen, wie man Klassenmodule sinnvoll einsetzen kann.

Hier geht es darum, dass mit den gleichen Funktionen mehrere verschiedene Datenquellen bearbeiten möchte, wobei sich die Programme ihre Daten nicht gegenseitig überschreiben dürfen. Weil die Aufgabe und ihre Lösung nicht offensichtlich ist, habe ich die Newton-Interpolation als praktisches Beispiel vorbereitet.

Nehmen wir an, wir haben einen Versuch gemacht und eine Tabelle mit Messwerten vorliegen. Da wurde in unterschiedlichen Zeitabständen eine Kraft F gemessen, aber leider ist der formelmässige Zusammenhang zwischen Zeit und Kraft gegenwärtig unbekannt. Andererseits müssen Kräfte nachträglich auch für Zeitpunkte ermittelt werden, die nicht in der Tabelle stehen, also wo gar keine Kraft gemessen wurde.

Beispiel 1: Kraft-Zeit-Funktion
Zeit [s]1234
Kraft [N]14916

Nehmen wir dazu die links dargestellte, einfache Tabelle, in der die Kraft (F) und die Zeit (t) gegeben ist: Es ist offensichtlich, dass hinter diesem Beispiel die Formel F(t) = c · t2 steckt - wollen wir doch einmal sehen, ob das die Mathematik und der Computer auch feststellen. Die Aufgabe besteht nun darin, die Kraft F zum Zeitpunkt t = 1.5, also F(1.5) zu ermitteln. Dazu brauchen wir zunächst ein VBA-Programm, welches in einem (normalen) Standardmodul so aussehen würde:

Newton-Interpolation als normale Funktion in Excel, Access
' Die Funktion untersucht die übergebenen Arrays und ermittelt
' aus n Elementen von [Zeit] und [Werte] ein Polynom n-1 Grades,
' setzt [t] ein und gibt den interpolierten Wert zurück

Public Function Interpolation(Zeit, ByVal Werte, t As Double) As Double
    Dim i As Long, j As Long

    ' Interpolation nach Newton
    For i = LBound(Werte) To UBound(Werte)
        For j = UBound(Werte) To i + 1 Step -1
            Werte(j) = (Werte(j) - Werte(j - 1)) / (Zeit(j) - Zeit(j - i - 1))
        Next j
    Next i

     ' Hornerschema anwenden, um Interpolationspolynom auszuwerten
    Interpolation = 0
    For i = UBound(Werte) To LBound(Werte) Step -1
        Interpolation = Interpolation * (t - Zeit(i)) + Werte(i)
    Next i

End Function

Um das Programm zur Interpolation aus einem anderen Programm heraus aufzurufen, um den Wert von F(1.5) zu ermitteln, würde man so vorgehen:

Aufruf der Newton-Interpolation
Sub TestAufruf()
    Dim X, Y

    X = Array(1, 2, 3, 4)
    Y = Array(1, 4, 9, 16)

    MsgBox Interpolation(X, Y, 1.5)

End Sub

Die Dialogbox zeigt den korrekten Wert 2.25 an.

Soweit, so gut. Aber was macht man, wenn man auch noch F(2.5) und F(3.5) ermitteln will ? Bei jedem Aufruf von Interpolation() wird die ganze Berechnung komplett neu durchgeführt, obwohl die doppelte FOR-Schleife am Anfang des Programms nur einmal durchlaufen werden muß - dann hat man den größten Teil der Arbeit erledigt und kommt mit der letzten FOR-Schleife aus, weil nur noch ein neuer Wert ermittelt werden muss.

Offensichtlich wäre es also günstiger, zwei Programme zu schreiben: Das folgende Beispiel zeigt die Lösung mit den Programmen InitInterpolation(), Interpolation2() und den auf Modulebene als privat deklarierten Variablen X und Y.

Private X, Y

Public Sub InitInterpolation(Zeit, ByVal Werte)
    Dim i As Long, j As Long

    For i = LBound(Werte) To UBound(Werte)
        For j = UBound(Werte) To i + 1 Step -1
            Werte(j) = (Werte(j) - Werte(j - 1)) / (Zeit(j) - Zeit(j - i - 1))
        Next j
    Next i

    X = Zeit
    Y = Werte

End Sub

Public Function Interpolation2(t As Double) As Double
    Dim i As Long
    Interpolation2 = 0

    For i = UBound(Y) To LBound(Y) Step -1
        Interpolation2 = Interpolation2 * (t - X(i)) + Y(i)
    Next i

End Function

Der Aufruf von einem anderen Programm aus funktioniert dann so:

Sub TestAufruf()
    Dim X, Y

    X = Array(1, 2, 3, 4)
    Y = Array(1, 4, 9, 16)

    InitInterpolation X, Y

    MsgBox Interpolation2(2.5)
    MsgBox Interpolation2(3.5)

End Sub

Doch es bleibt immer noch ein Problem: Was passiert, wenn man gleichzeitig mit zwei unterschiedlichen Tabellen arbeiten muss? Nehmen wir den Fall an, daß wir eine zweite Tabelle haben, in der der Weg (s) und die Zeit (t) angegeben ist:

Beispiel 2: Weg-Zeit-Funktion
Zeit [s]23511
Weg [m]3824120

Auch hier ist offensichtlich, dass hinter diesem Beispiel die Formel s(t) = c ·( t2 - 1) steckt, aber als Beispiel reicht es aus. Was passiert, wenn wir abwechselnd eine Interpolation für F(t) und dann wieder für s(t) einen Wert berechnen will?

Nun bei jedem Wechsel von F(t) auf s(t) muß man InitInterpolation() neu aufrufen, der rechnerischer Aufwand bleibt hoch. Außerdem darf man keinen anderen Programmteil aufrufen, der die Interpolation zwischendurch zusätzlich für eigene Zwecke benutzen könnte, weil sonst die globalen Variablen X und Y geändert werden!

Das Klassenmodul

Die beste Lösung bietet hier ein Klassenmodul, welches als neues Modulblatt im VBA-Editor (der VBE) eingefügt wird. Ein Klassenmodul kann nämlich beliebig oft aufgerufen werden und jedesmal ist es so, als ob man die Funktionen zur Interpolation noch einmal auf einem weiteren Modulblatt programmiert hätte - die Instanzen (=  aufgerufenenKlassenmodule) stören sich nicht und verhindern den Zugriff auf ihre Daten von außen. < /P>

Zunächst muß man ein eigenes (Klassen-)Modulblatt anlegen und ganz für die Newton-Interpolation reservieren:

Option Explicit

Private IstBerechnet As Boolean
Private
X, Y

' Die Funktion untersucht die übergebenen Arrays und ermittelt
' aus n Elementen von [X] und [Y] ein Polynom n-1 Grades,
' setzt [t] ein und gibt den interpolierten Wert zurück

Public Function Interpolation(t As Double) As Double
    Dim i As Long, j As Long
    On Error GoTo BeiFehler

    If Not IstBerechnet Then

        ' Fehlerprüfung: Sind gleich viele x- wie y-Werte vorhanden ?
        If UBound(X) <> UBound(Y) Then
            Err.Raise vbObjectError, Titel, "Ungleiche Anzahl an X- und Y-Werten"
        End If

        ' Interpolation nach Newton
        For i = LBound(Y) To UBound(Y)
            For j = UBound(Y) To i + 1 Step -1
                Y(j) = (Y(j) - Y(j - 1)) / (X(j) - X(j - i - 1))
            Next j
        Next i

        IstBerechnet = True
    End If

    ' Hornerschema anwenden, um Interpolationspolynom auszuwerten
    Interpolation = 0
    For i = UBound(Y) To LBound(Y) Step -1
        Interpolation = Interpolation * (t - X(i)) + Y(i)
    Next i

Exit Function

BeiFehler:
    MsgBox Err.Description, vbCritical + vbOKOnly, Titel
End Function

Public Property Let StützStellen(X_Werte)
    If Right(TypeName(X_Werte), 2) <> "()" Then
        Err.Raise vbObjectError + 1, Titel, "X-Werte müssen als Array() übergeben werden"
    Else
        X = X_Werte
    End If

    IstBerechnet = False
End Property

Public Property Let StützWerte(Y_Werte)
    If Right(TypeName(Y_Werte), 2) <> "()" Then
        Err.Raise vbObjectError + 1, Titel, "Y-Werte müssen als Array() übergeben werden"
    Else
        Y = Y_Werte
    End If

    IstBerechnet = False
End Property

Wichtig ist auch, dass das Klassenmodul einen vernünftigen Namen bekommt, denn unter dem wird es aus anderen Anwendungen heraus angesprochen. Hier soll 'NewtonInterpolation' gewählt werden. Über die Property Let-Prozeduren nimmt das Klassenmodul die Arrays() mit den x- und y-Werten entgegen und speichert sie intern in den Variablen X und Y auf (Klassen-)Modulebene. Da es keine Property Get-Prozeduren gibt, kommt man später auch nicht mehr an die Inhalte von X und Y heran - das ist der Unterschied zwischen Eigenschaften (den Variablen von Klassenmodulen) und echten Variablen: Nach dem Rückruf können die Daten völlig anders aussehen (oder gar nicht mehr gesehen werden), als vor dem Speichern.

Um festzustellen, ob die Hauptarbeit der Interpolation für die Daten schon einmal ausgeführt wurden, benutzt das Klassenmodul die Variable IstBerechnet, die über True/False die Berechnung steuert.

Die Klasse im Einsatz

Bleibt die Aufgabe, die Fähigkeiten des Klassenmoduls aus anderen Programmen heraus zu nutzen.

Will man nur eine einzige Interpolation ausführen, hilft die With-Anweisung mit dem Schlüsselwort New:

Public Sub TestKlasse()

    With New NewtonInterpolation
        .StützStellen = Array(1, 2, 3, 4)
        .StützWerte = Array(1, 4, 9, 16)
        MsgBox .Interpolation(1.5)
    End With

End Sub

An dem Beispiel sieht man, warum ein aussagefähiger Name für das Klasenmodul wichtig ist. Bei der Eingabe merkt man ausserdem, daß 'IntelliSense' bei der Eingabe hilft und die Namen der Eigenschaften und Methoden der Klasse NewtonInterpolation kennt.

Will man mehrere Interpolationen durchführen, empfiehlt sich die Verwendung einer Objekt-Variablen, die vom Typ Object oder besser NewtonInterpolation ist.

Public Sub TestKlasse()
    Dim Tabelle As NewtonInterpolation

    Set Tabelle = New NewtonInterpolation
    Tabelle.StützStellen = Array(1, 2, 3, 4)
    Tabelle.StützWerte = Array(1, 4, 9, 16)

    MsgBox Tabelle.Interpolation(2.5)
    MsgBox Tabelle.Interpolation(3.5)

End Sub

Will man mehrere verschiedene Interpolationen verwenden, so kann man auch mehrere Variable mit unterschiedlichen Instanzen des Klassenmoduls erzeugen:

Public Sub TestKlasse()
    Dim Tabelle1 As New NewtonInterpolation
    Dim Tabelle2 As New NewtonInterpolation

    Tabelle1.StützStellen = Array(1, 2, 3, 4)
    Tabelle1.StützWerte = Array(1, 4, 9, 16)

    Tabelle2.StützStellen = Array(2, 3, 5, 11)
    Tabelle2.StützWerte = Array(3, 8, 24, 120)

    MsgBox Tabelle1.Interpolation(2.5)
    MsgBox Tabelle2.Interpolation(7.2)
    MsgBox Tabelle1.Interpolation(3.5)

End Sub

Wie man sieht, kann man mit Tabelle1 und Tabelle2 zwei Berechnungen beliebig durcheinander laufen lassen - ohne dass sich die Instanzen des Klassenmoduls gegenseitig die Daten überschreiben.

Das Beispiel zeigt auch eine zweite Variante, Variable zu deklarieren: Wird das Schlüsselwort New bereits in der Dim-Anweisung verwendet, kann man sich das spätere Set ersparen.