Programming Patterns: Typische Stolperfallen beim Programmieren in MATLAB und wie man sie vermeidet
Von Loren Shure, MathWorks
Genau wie in natürlichen Sprachen gibt es auch in Programmiersprachen Idiome. Das Beherrschen dieser syntaktischen oder inhaltlichen Nuancen ist wesentlich, um anvisierte Programmierziele zu erreichen – ganz gleich, ob ein Programm robust sein oder einfach die richtige Antwort geben soll. Hier sind einige Tipps und Best Practices zu sprachlichen Feinheiten in MATLAB.
Die Erwartete Antwort Erhalten
Computerprogramme machen genau das, was ihr Programmcode ihnen vorschreibt. Eine exakte Notation ist darum entscheidend. Hier einige Beispiele dafür.
Matrizen- und elementweise Multiplikation
Angenommen, Sie wollen die korrespondierenden Elemente zweier großer, quadratischer Matrizen A
und B
miteinander multiplizieren. Mit A*B
erhalten Sie zwar ein Ergebnis der erwarteten Dimensionen (denen von A
), aber die falschen Zahlen. Denn statt die korrespondierenden Elemente in A
und B
zu multiplizieren, haben Sie gerade die Matrizen multipliziert. Um nur die korrespondierenden Elemente zu multiplizieren müssen Sie A.*B
eingeben:
>> A = [1 2; 3 4]; B = [1 0; -1 1]; >> C = A.*B C = 1 0 -3 4 >> D = A*B D = -1 2 -1 4
Transponierte und konjugiert Transponierte
In MATLAB kann man mit reell- und komplexwertigen Arrays mit doppelter Genauigkeit arbeiten, ohne diese gesondert behandeln zu müssen. Trotzdem muss der Code dafür richtig formuliert sein.
Für reelle Matrizen ist das Ergebnis von A'
und A.'
identisch. Für Matrizenoperationen an komplexwertigen Arrays ist der Transponierungs-Operator ('
) oft der richtige. Aus komplexen Matrizen dagegen erzeugt er die konjugiert Transponierte. Um diese Konjugation zu vermeiden, verwendet man den nicht-konjugierten Transponierungs-Operator (.'
):
>> C= A+i*B C = 1.0000 + 1.0000i 2.0000 3.0000 - 1.0000i 4.0000 + 1.0000i >> E = C' E = 1.0000 - 1.0000i 3.0000 + 1.0000i 2.0000 4.0000 - 1.0000i >> F = C.' F = 1.0000 + 1.0000i 3.0000 - 1.0000i 2.0000 4.0000 + 1.0000i
Imaginäre Einheit i
oder j
MATLAB definiert die Konstanten i
und j
beide als sqrt(-1)
, die Imaginäre Einheit. Wenn Sie aber i
oder j
als Variablen in Ihrem Code verwenden und der nachfolgende Code ebenfalls eine dieser beiden Konstanten nutzt, kann dies zu unerwarteten Fehlern führen.
Um dieses Problem zu vermeiden, verwenden Sie andere Variablennamen oder löschen Sie die Variablen nach Gebrauch:
>> for i=1:3 M{i} = magic(i+2) end M = [3x3 double] M = [3x3 double] [4x4 double] M = [3x3 double] [4x4 double] [5x5 double] >> clear i
Befehls /Funktions-Dualität
Die korrekte Zeichensetzung kann in MATLAB wichtig sein, besonders in Klammerausdrücken. Die folgenden MATLAB-Anweisungen sind äquivalent:
foo baz 5 foo('baz','5')
Durch Weglassen der Klammern beim Aufruf einer Funktion ruft man die Funktion als Befehl auf. MATLAB behandelt dann alle Argumente nach dem Funktionsnamen als Strings. Um das Ergebnis eines Funktionsaufrufs einer Variablen zuzuweisen, verwendet man daher die Funktionsform, nicht die Befehlsform.
Vergleich von Fließkommazahlen
Der Standarddatentyp in MATLAB sind Fließkomma-Doubles. Da der Double-Datentyp Zahlen mit einer endlichen Zahl von Bits (64) darstellt (davon 53 Bits für die Mantisse, also etwa 16 Dezimalstellen), lassen sich viele Zahlen wie etwa 1/7 nicht exakt darstellen. Beim Vergleich von Rechenergebnissen empfiehlt es sich daher, nicht auf exakte Gleichheit von Fließkommazahlen zu prüfen. Mit eps(target)
erhält man dagegen ein Ergebnis, das dem Ziel mit angemessener Toleranz entspricht.
Im folgenden Beispiel wurde willkürlich das Zehnfache der kleinstmöglichen Toleranz gewählt:
>> A = 1/7 A = 0.142857142857143 >> B = 0.142857142857143 B = 0.142857142857143 >> dAB = A-B dAB = -1.387778780781446e-016 >> AapproxB = abs(A-B) < (10 * eps(B)) AapproxB = 1
ERSTELLUNG ROBUSTER PROGRAMME
Man sollte sich frühzeitig Gedanken machen, welche Probleme bei der Nutzung eines Programms durch den Endanwender auftreten könnten und so Fehler oder falsche Berechnungen vermeiden. Hier darum einige Tools zur Erzeugung robuster Programme.
Verwendung von MException
statt lasterror
Um sicherzustellen, dass ein Programm robust ist, muss man potenzielle Fehler frühzeitig abfangen und beheben. Nutzt man hierfür die ältere lasterror
-Methode, riskiert man allerdings, dass noch nicht abgearbeitete Fehler überschrieben werden. Mit dem in R2007b eingeführten MException
-Objekt vermeidet man dies:
Altes Codeschema
try doSomething; catch rethrow(lasterror) end
Neues Codeschema
try doSomething; catch myException rethrow(myException) % or throw or throwAsCaller end
Verwendung von try/catch Exception
statt der Zwei-Argumenten-Form von eval
Mit der Funktion eval
lässt sich praktisch jede gültige MATLAB-Anweisung ausführen. Da aber das Argument für eval
ein String ist, kann MATLAB nicht vorhersehen, was hier eigentlich ausgeführt wird und so eventuell bestimmte Optimierungen nicht nutzen. Das gleiche gilt für die Ausführung von eval mit zwei Argumenten, bei der die zweite Eingabe nur ausgewertet wird, wenn bei der Ausführung der ersten ein Fehler auftritt. Mit try/catch
wird der Code dagegen für MATLAB transparent. Das folgende Skript fügt einen Wert value
in die Struktur S
ein, sofern value
existiert; andernfalls bleibt das Strukturfeld leer:
Altes Codeschema
eval(['S.field = value'],'S.field = []')
Neues Codeschema
try S.field = value; catch myException S.field = []; end
Verwendung dynamischer Feldnamen statt eval
Um ein beliebiges Feld eines struct
-Arrays zu erzeugen, verwendet man statt eval
dynamische Feldnamen. Mit dynamischen Feldnamen lassen sich Felder eines struct
-Arrays ansteuern, ohne dass deren Name beim Schreiben des Programms explizit bekannt sein muss. Dynamische Feldnamen machen außerdem MATLAB-Code effizienter und sie erleichtern MATLAB die Programmanalyse. Die folgenden zwei Programme erzeugen eine Struktur S
mit den Feldnamen magic1, ..., magic5:
Altes Codeschema
n = 5; for ind = 1:n eval(['S.magic' int2str(ind) '= magic(ind+2)']); end
Neues Codeschema
n = 5; for ind = 1:n S.(['magic' int2str(ind)]) = magic(ind+2); end
Definition von Funktionen durch Function Handles statt Strings
Bei einigen MATLAB-Funktionen muss eine andere Funktion als Eingabeargument definiert werden. Die Funktion fzero
dient beispielsweise zur Auffindung von Nullstellen anderer Funktionen. Mit den folgenden Codebeispielen findet man den Wert in der Nähe von Punkten pts
, an denen die Funktion sin
gleich 0
ist.
Definiert man eine auszuwertende Funktion als String, dann muss MATLAB den Pfad nach ihr durchsuchen. Geschieht diese Berechnung innerhalb einer Schleife, dann sucht MATLAB die Zielfunktion bei jedem Durchlauf erneut, denn der Pfad könnte sich geändert haben und sich nun auf eine andere Zielfunktion beziehen. Ein Function Handle legt dagegen die Zielversion eindeutig fest und MATLAB kann die Zielfunktion aufrufen, ohne sie in jedem Durchlauf wieder suchen zu müssen.
Altes Codeschema
pts = 0:1000; x = zeros(size(pts)); fun = 'sin'; for npts = 1:length(pts) x(npts) = fzero(fun,pts(npts)); end
Neues Codeschema
pts = 0:1000; x = zeros(size(pts)); fun = @sin; for npts = 1:length(pts) x(npts) = fzero(fun,pts(npts)); end
Datentypen von Variablen in Programmen durchgängig beibehalten
MATLAB versucht durch Optimierung von Berechnungen Speicher zu sparen. Führt man ein M-File aus, dann untersucht MATLAB zunächst das File und versucht es dann in optimaler Weise auszuführen. Wenn diese Analyse feststellt, dass die Variable D
einen bestimmten Datentyp hat, etwa double
, und dass dieser durchgehend unverändert bleibt, kann MATLAB seine Optimierungen anbringen.
Altes Codeschema
D = 17.34; doSomething; D = single(D);
Neues Codeschema
D = 17.34; doSomething; S = single(D);
Ausgabeargumente bei load
verwenden
MATLAB-Funktionen können sich auf unerwartete Weise verhalten, wenn MATLAB nicht “sehen” kann, dass einige der im Programm verwendeten Namen Variablen sind. Um dies zu vermeiden, verwendet man eine explizite Ausgabevariable mit load
. MATLAB kann das Programm dann besser analysieren und erkennt zuverlässiger, welche Codeelemente Variablen sind. Eine Variable kann aber auch unvermittelt erzeugt werden, wenn man mit load
einen Datensatz lädt, ohne dessen Variablen zu definieren. Angenommen, der Datensatz mydat.mat
enthält die Variable var
und ein Programm soll diese anzeigen.
function mydisp load mydat disp(var)
Wenn man mydisp
ausführt, erhält man diesen Fehler:
>> mydisp ??? Input argument "x" is undefined. Error in ==> var at 55 if isinteger(x) Error in ==> mydisp at 3 disp(var)
Er tritt auf, weil var
auch der Name einer Funktion im MATLAB-Pfad ist und MATLAB dies bei seiner Analyse festgestellt hat, bevor es das Programm ausführt.
Zur Vermeidung dieses Fehlers gibt es zwei Möglichkeiten:
function mydisp1 load mydat3 var disp(var)
function mydisp2 S = load('mydat.mat;); disp(S.var)
In beiden Fällen versteht MATLAB, welche Elemente im Programm Variablen sind (var
und S
) und vermeidet so den Konflikt mit der im MATLAB-Pfad gefundenen Funktion.
Einfache Tipps
- Löschen Sie beim Debuggen mit
clear variables
(ab R2008a mitclearvars
) die Variablen aus dem Workspace.clear
allein löscht auch alle Breakpoints. - Verwenden Sie
{}
zur Extraktion von Zelleninhalten aus Cell-Arrays. Soll ein Cell-Array wie jedes andere Array behandelt werden, etwa für Umformungen, verwendet man dagegen()
. - Benutzen Sie Handles zu Grafikobjekten, die später aktualisiert werden sollen, anstatt sich vom jeweiligen Status abhängig zu machen. Erzeugen Sie beispielsweise eine Achse einmal mit
hAx = axes;
und verwenden Sie danach für diese Achse immerhAx
stattgca
. Die Verwendung dieser direkten Referenz ist robuster in Fällen, in denen der Fokus in MATLAB gerade auf etwas anderem liegt als auf diesem Grafikfenster oder dieser Achse.
Veröffentlicht 2008 - 91611v00