next up previous contents
Next: Literatur Up: B. Simulator Previous: B.3 Beispiel für die

Unterabschnitte


B.4 Verwaltung physikalischer Einheiten

Bei jedem Simulator birgt die Schnittstelle zwischen dem Benutzer, der seine Problemstellung in Form von physikalischen Größen über die Eingabeparameter spezifiziert, und dem für die Auswertung verwendeten Rechenkern eine potenzielle Fehlerquelle. An die Schnittstelle werden folgende Anforderungen gestellt.

Um all diese Anforderungen zu erfüllen, wurde für den Simulator eigens ein Verwaltungssystem für die Einheiten geschaffen. Dazu bot sich das Konzept einer objektorientierten Sprache wie C++ an.

B.4.1 Konzept

Alle vom Benutzer verwendeten Einheiten lassen sich auf die sieben Basiseinheiten umwandeln. Hier wurde das SI-System als Ausgangspunkt gewählt und die Klasse SIrep definiert, welche die Exponenten der Zerlegung einer beliebigen Einheit in die SI-Einheiten Meter, Gramm, Ampere, Sekunden, Kelvin, Candela und Mol repräsentiert.

Um Rundungsfehler auszuschließen und eine praktische und genaue Darstellung zu erhalten, werden die Exponenten der Zerlegung in SI-Einheiten in Form von Brüchen in Instanzen der Klasse Bruch abgespeichert. Neben der Initialisierung über den Typ int und den arithmetischen Operatoren Addition, Subtraktion, Multiplikation und Division ermöglicht die Klasse Bruch auch die Vereinfachung auf den kleinsten gemeinsamen Nenner.

Unter Verwendung der Klasse Bruch wurde die Klasse Fnumber für die Speicherung reeller Zahlen in der Form

$\displaystyle f \cdot 10^{b} \qquad b=\frac{n}{z} \qquad f\in\Bbb{R}\quad n,z\in\Bbb{N}$ (B.1)

definiert. Dies ermöglicht eine konsequente Behandlung der exakten Brüche bei der Einheitenumwandlung und stellt einen erweiterten Zahlenbereich zur Verfügung.

Mit den bisher beschriebenen Klassen wird nun der neue Typ Basiseinheit - Basicunit - definiert, der sich aus einem Vorfaktor und einer Darstellung der SI-Einheiten zusammensetzt.

$\displaystyle \texttt{Basicunit} = \left\{ \texttt{Fnumber} , \texttt{SIrep} \right\}$ (B.2)

Für die Klasse Basicunit wurden ebenfalls alle wichtigen arithmetischen Operationen implementiert. Die Klasse wird verwendet, um die Information über zusammengesetzte Einheiten zu speichern und den Programmcode für die Einheitenumrechnung einfach und übersichtlich zu gestalten. Auch die Basiseinheiten, wie das Meter, sind als Instanz der Klasse Basicunit definiert. Eine wichtige Funktion dieser Klasse ist die Funktion iscompatibleto. Mit dieser Funktion kann festgestellt werden, ob zwei Einheiten dieselbe Darstellung in den Basiseinheiten haben.

Für den Entwickler des Simulators ist lediglich die von der Klasse Basicunit abgeleitete Klasse Unit von Interesse. Sie unterscheidet sich von Ihrer Basisklasse nur durch den Zuweisungsoperator, über den sie mittels einer Zeichenkette initialisiert werden kann. Die Trennung in Basisklasse Basicunit und Klasse Unit ist notwendig, um die Zerlegung zusammengesetzter Einheiten mit einem Parser vornehmen zu können. Beim Zerlegen der eingegebenen Einheit in die Basiseinheiten wird die Basisklasse für das Abspeichern der Zwischenresultate und die Umrechnungen benutzt.

B.4.2 Parser

In einer ersten Version wurden die populären Unix-Programme lex und bison zur Umsetzung einer LR-Grammatik in ein C-Programm verwendet. In der aktuellen Version wird der frei verfügbare Parsergenerator ANTLR [37] verwendet. Dieser generiert C++Programmcode. Weiters ist es möglich, die gesamte Zerlegung mittels der lexikalischen Analyse zu bewerkstelligen.

B.4.2.1 Lexikalische Analyse

In diesem Abschnitt ist ein Auszug aus der Definition der Grammatik in ANTLR angegeben und kommentiert.



Der Einstiegspunkt für die lexikalische Analyse prüft, ob die Eingabe syntaktisch korrekt ist, und liefert als Ergebns eine Instanz der Klasse Basicunit.

protected
EXPR returns [Basicunit u]
            : u=UNITMULT { (LA(1) == EOF ) }?
            ;

Zur Erkennung von ganzen und reellen Zahlen werden die Funktionen atoi und atof der Standard-C-Bibliothek verwendet.

protected
UINT returns [int i]
            : ('0'..'9')+ { i = atoi($getText.c_str()); }
            ;
protected
INT returns [int i]  
            : ('-')? ('0'..'9')+ 
                          { i = atoi($getText.c_str()); }
            ; 
protected
REAL returns [ double d]
            : 
            ('-')? (UINT)? '.' (UINT)? (('e'|'E') INT)?
            { d = atof($getText.c_str()); }
            ;
Hier werden die bekannten Präfixe und Basiseinheiten analysiert und in passender Form zwischengespeichert. Die angegebenen Beispiele sind nur ein kleiner Auszug aus den implementierten Präfixes und Basiseinheiten.
protected
PREF returns [Bruch p]
            :
            | 'm' { p =  -3; }
            | 'c' { p =  -2; }
            | 'k' { p =   3; }
            ;
protected
UNIT    returns [Basicunit u]
            : "m"     { u=UNIT_METER; }
            | "g"     { u=UNIT_GRAMM; }
            | "inch"  { u=
              Basicunit(Fnumber(2.54,-2 ),
                        1 , 0, 0, 0, 0, 0, 0);}
            ;

Nun müssen arithmetische Ausdrücke richtig aufgelöst werden. Dabei ist die Wahrung der Prioritätsregeln von Bedeutung.

// lowest priority for addition and subtraction
protected
UNITMULT returns [Basicunit u]
            { Basicunit tmp;}
            :   
            u=UNITADD 
            (   
                   '+' tmp=UNITADD { u += tmp; }
                |  
                   '-' tmp=UNITADD { u -= tmp; }
            )* 
            ;

// second lowest priority for multiplication and division
protected
UNITADD returns [Basicunit u]
            { Basicunit tmp;}
            :   
            u=UNITEXP 
            (   
                   '*' tmp=UNITEXP { u *= tmp; }
                |  
                   '/' tmp=UNITEXP { u /= tmp; }
            )* 
            ;

// middle priority for exponent
protected
UNITEXP   returns [Basicunit u]
            { Bruch b; }
            :
            u=SINGLEUNIT ( '^' b=BRUCH { u^=b; } )? 
            ;

// highest priority for explicit parenthesis
protected
SINGLEUNIT returns [Basicunit u]
            { Bruch p;double d;int i;}
            :   
            (REAL) => d=REAL { u=Basicunit(d,0,0,0,0,0,0,0);} 
            |
            (INT)  => i=INT  { u=Basicunit(i,0,0,0,0,0,0,0);}
            |
            (UNIT) => u=UNIT
            |
            (PREF UNIT) => p=PREF u=UNIT { u.adjust_pref(p);}
            |   
            '(' u=UNITMULT ')'
            ;

Eine eigene Regel bewerkstelligt das Erkennen von ganzzahligen Brüchen.

protected
BRUCH returns [Bruch b]
            { int i1,i2; }
            :   
            b=INT
            |   
            '(' i1=INT '/' i2=UINT ')' { b=Bruch(i1,i2); }
            ;

Die Flexibilität der Methode liegt in der Möglichkeit des Lexers eine aus Präfix und Basiseinheit zusammengesetzte Einheit zu erkennen. Dabei werden alle möglichen Kombinationen aus den definierten Basiseinheiten und Präfixen überprüft. In der Definition des Lexers wird dies durch eine einzige Zeile definiert, die als einzige Bedingung die Eindeutigkeit von Präfix und Basiseinheiten erfordert. Eine neue Basiseinheit mm ist also nicht zulässig, da sie dann sowohl als Präfix milli und Basiseinheit Meter als auch als die neue Basiseinheit mm interpretiert werden könnte.

Im Prinzip verfolgt die hier implementierte Methode genau jenem Vorgang den ein Benutzer bei der händischen Umwandlung der Einheit ausführen würde. Die Fülle der zusammengesetzten Einheiten, die in der aktuellen Version erkannt werden, ist recht beachtlich, und kann bei Bedarf leicht erweitert werden. Dies bezieht sich nicht nur auf die vorgegebenen Präfixe und Basiseinheiten, sondern auch auf die erkannten arithmetischen Verknüpfungen. Hier ein paar Beispiele aus dem Selbsttest des Einheitenpakets: $ 0*m$, $ 1.2^{2/3}$, $ fm$, $ pg$, $ nA$, $ us$, $ kK$, $ ccd$, $ mmol$, $ minch$, $ 12.0^{-2}*m^{3/2}$, $ year/(365.242*day)$, $ 12.0E-2*m^{3/2}$, $ um+2.2*nm$, $ 1000*mC-A*s$, $ mol/cd*m/A^{3/2}/s^{1/2}/C/s$

Eine weitere Klasse LabelUnit dient in erster Linie der Ausgabe und der Konsistenz. Die Klasse muss mit einer als Referenz verwendeten Einheit initialisiert werden. Bei weiteren Zuweisungen wird dann immer geprüft, ob es sich um eine Einheit mit gleicher Darstellung in SI-Einheiten handelt. Eine Ausgabe einer Instanz dieser Klasse erfolgt automatisch in der ursprünglichen Einheit. Hier ein Beispiel.

    LabelUnit L("2*mm");
    LabelUnit L1("km");
    L = "1*m";
    cout << "1*m" << " = " << L << endl;
    L1 = L;
    cout << "= " << L1 << endl;
erzeugt die folgende Ausgabe:
    1*m = 0.5 * 10^(3) * 2mm
    = 1 * 10^(-3) * km


next up previous contents
Next: Literatur Up: B. Simulator Previous: B.3 Beispiel für die

C. Troger: Modellierung von Quantisierungseffekten in Feldeffekttransistoren