Diskussion:Romberg-Integration

message: Hallo, zum Quellcode habe ich folgende Vorschläge:
* Man sollte erwähnen, dass es sich um die Sprache Java handelt
* In der deutschen Wikipedia dürfen die Kommentare im Quelltext doch ruhig deutsch sein, oder?
* Die Hilfsmethode pow irritiert doch nur. Warum nicht (int) Math.pow()?
* Es scheint naheliegender, computeAreaRomberg als statische Methode zu implementieren, vgl. Math-Klasse in Java. Für die Berechnung extra ein Objekt zu konstruieren, ist zumindest ungewöhnlich.
* Schließlich erscheint es mir sinnvoll, die eigentliche interessante computeAreaRomberg-Methode ganz an den Anfang zu stellen
JM2C, --Winne 12:52, 16. Jul 2006 (CEST)
thread_title:  Quellcode 

message: Ich habe beim Einfügen des Schemas die Indizierung aus den bisherigen Formeln übernommen. Allerdings kenne ich die Indizierung etwas anders und finde sie wesentlich logischer, z.B. so:
\begin{matrix}
I_{1,1} \\
&\searrow \\
I_{2,1} & \rightarrow & I_{2,2} \\
&\searrow & & \searrow \\
I_{3,1} & \rightarrow & I_{3,2} & \rightarrow & I_{3,3} \\
&\searrow & & \searrow & & \searrow \\
I_{4,1} & \rightarrow & I_{4,2} & \rightarrow & I_{4,3} & \rightarrow & I_{4,4} \\
&\searrow & & \searrow & & \searrow & & \searrow \\
I_{5,1} & \rightarrow & I_{5,2} & \rightarrow & I_{5,3} & \rightarrow & I_{5,4} & \rightarrow & I_{5,5} \\
\end{matrix}
Dadurch ist es möglich die Formel auf folgende Weise anzugeben, was die bereits bemängelte Rekursion mit den vielen verschiedenen Variablen n,k,s etwas vereinfachen würde.
 I_{i,j}=I_{i,j-1}+\frac{I_{i,j-1}-I_{i-1,j-1}}{4^{j-1}-1} 
Vgl, auch die Formel die im oben vorgeschlagenen Skript aus Bielefeld angegeben wird.
-- B-Navigator 17:04, 17. Aug 2006 (CEST)
replies: text: Nützlich für das Verständnis ist vor allem eine anschauliche Interpretation der Indizes. Ich gehe mal davon aus, dass I_{n,k} eine Annäherung mit Fehler O(h_n^k) bezeichnen soll. Habe ich das richtig verstanden? In dem Falle wäre die gewählte Indizierung durchaus sinnvoll, aber nur, wenn das auch irgendwo so steht. --Carsten Milkau 12:26, 14. Jul. 2009 (CEST)
thread_title:  Indizierung 

message: Der Absatz irritiert mich sehr. Wenn der Nenner in der Fehlerschranke 0 wird, sollte doch klar sein, dass das ein Problem ist. Damit ist doch nicht "die Fehlerschranke unterschritten", nur weil gleichzeitig der Zähler auch verschwindet. Stimmt die Formel für den Fehler überhaupt? Man könnte sich ja behelfen, in dem man eine Konstante c zur Funktion addiert. Alle Werte werden dann um c·(b-a) größer, aber im Zähler des Fehlers hebt sich das weg, im Nenner nicht, und der Fehler wird anscheinend kleiner. Seltsam.--LotharT (Diskussion) 10:15, 8. Jul. 2016 (CEST)
thread_title:  Anmerkungen 

message: Ich nutze zum Integrieren eines kleinen Intervalls auch gern die Romberg-Integration, deren rekursive Berechnung ich genau dann abbreche, wenn zwei aufeinander folgende berechnete Integralsummen (Si-1, Si+0) relativ dicht beieinander liegen. Wie dicht sie konkret beieinander liegen sollen, wird dabei über die geforderte Genauigkeit bestimmt (z.B. ForcedPrecision=+10-06 für ca. 6 korrekte Dezimalstellen), welche dem Romberg-Integrations-Algorithmus als Parameter mitzugegeben ist. Mathematisch ausgedrückt, breche ich die rekursive Romberg-Integration, wie fast jede andere konvergierende rekursive Berechnung meistens genau ab, wenn folgende logische Bedingung erfüllt ist:
    (-ForcedPrecision <= getRelativeDistance(Si-1, Si-0) <= +ForcedPrecision)
oder logisch äquivalent
    (abs(getRelativeDistance(Si-1, Si-0)) <= +ForcedPrecision)
Dabei hat die von mir verwendete Mini-Funktion getRelativeDistance(a,b) folgende, wie ich finde, positive mathematische Eigenschaften:
(0) (getRelativeDistance(a,b) == getRelativeDistance(x,y)) gdw ((x = c * a) und (y = c * b) mit (c # 0))  // größenunabhängig eben relativ
(1) (-2.0 <= getRelativeDistance(a,b) <= +2.0) // nach oben und unten begrenzt
(2) (a > b) gdw (getRelativeDistance(a,b) > 0) // kleiner werdende Summenfolge
(3) (a < b) gdw (getRelativeDistance(a,b) < 0) // groeßer werdende Summenfolge
(4) (abs(getRelativeDistance(a,b)) = 2.0) gdw (sgn(a) # sgn(b)) und (abs(a) = abs(b)) // verschiedene Vorzeichen, gleiche Beträge ==> maximale relative Entfernung
(5) (abs(getRelativeDistance(a,b)) > 1.0) gdw (sgn(a) # sgn(b)) und (abs(a) # abs(b)) // verschiedene Vorzeichen, verschiedene Beträge ==> große relative Entfernung
(6) (abs(getRelativeDistance(a,b)) = 1.0) gdw ((a # b) und ((a = 0) oder (b = 0)))    // genau ein Wert ist 0 ==> mittlere relative Entfernung
(7) (abs(getRelativeDistance(a,b)) > 0.0) gdw (sgn(a) = sgn(b)) und (abs(a) # abs(b)) // gleiche Vorzeichen, verschiedene Beträge ==> kleine relative Entfernung
(8) (abs(getRelativeDistance(a,b)) = 0.0) gdw (sgn(a) = sgn(b)) und (abs(a) = abs(b)) // gleiche Vorzeichen, gleiche Beträge  ==> gar keine relative Entfernung
Wird ein Integrationsergebnis mit dem Wert 0 erwartet (z.B. Integral(sin(von 0*PI bis 2*PI)), dann kann es sein, dass die rekursiv berechneten Integrationssummen zwar betragsmäßig immer kleiner werden, aber stets wechselnde Vorzeichen haben, also von beiden Seiten gegen den Wert 0 konvergieren (z.B. Si+0=+1/1; Si+1=-1/2; Si+2=+1/4; Si+3=-1/8; ...), womit immer Eigenschaft (5) zuschlagen und somit zu einer Endlosrekursion führen würde. Dem kann aber ganz einfach dadurch abgeholfen werden, in dem man einen ausreichend großen konstanten positiven Wert (hier z.B. C=+1) auf die jeweils berechneten Integrationssummen aufaddiert (also Si+0=C+1/1; Si+1=C-1/2; Si+2=C+1/4; Si+3=C-1/8; ...), so dass die berechneten Integrationssummen nun stets das gleiche Vorzeichen haben und sich dem Wert C#0, statt dem Wert 0 von beiden Seiten annähern, welchen man dann natürlich aber hinterher vom zurückgebenen Integrationsergebnis wieder abziehen muss.
In der derzeitigen 64Bit-Computer-Realität, und somit fernab von jeder theoretischen Mathematik zeigt sich jedoch, dass die Eigenschaft (6) auch dann zu schlägt, wenn keiner der beiden betrachteten Werte gleich 0 aber der absolute Größenunterschied der beiden Werte a und b dafür sehr groß ist. Mathematisch völlig unerwartet und mathematisch falsch aber trotzdem Realität ist, dass z.B. bei den Werten a=+10+30 b=+10+10 oder a=10+01 und b=10-15 die Differenz (a - b) = a ist, so als wäre der Wert (b # 0) gar nicht von dem Wert (a >> b) abgezogen worden. Das liegt daran, dass bei einem 64 Bit langen Fließkommawert vom Typ double nur 48 Bit Platz für die Mantisse vorgesehen sind und damit meist nur 14, maximal 15, aber jedenfalls nicht endlos viele Dezimalstellen zur Verfügung stehen. Wenn sich die beiden Werte a und b aber erst nach der 15ten Dezimalstelle unterscheiden, entspricht sowohl die Differenz (a-b) als auch die Summe (a+b) dem Wert mit dem größeren Betrag und der betragsmäßig kleinere Wert wird so behandelt, als wäre er gleich 0. Dieser durchaus praxisrelevante Fall dürfte aber bei der rekursiven Romberg-Integration kleiner Intervalle nicht auftreten, da sich zwei aufeinander folgende rekursiv berechnete Intergalsummen Si-1 und Si-0 keinesfalls so massiv voneinander unterscheiden sollten.
Wenn die geforderte Genauigkeit einen sinnvollen Wert z.B. ((MaximalPrecision=+10-14) <= ForcedPrecision <= (MinimalPrecision=+10-01)) hat, sind eigentlich nur noch die Eigenschaften (7) und (8) von besonderem Interesse. Was nun noch zu zeigen bleibt, ist der PseudoCode der von mir verwendeten Mini-Funktion getRelativeDistance:
double getRelativeDistance(double Si-1, double Si-0)
{
    if(Si-1 = Si-0) then
        return(0);
    else
        return((Si-1 - Si-0) / max(abs(Si-1), abs(Si-0)));
    end;
}
Ich würde mich freuen, wenn ich mit der Mini-Funktion getRelativeDistance(a,b), wenigstens das Problem, wann die rekursive Romberg-Integration eines kleinen Intervalls am sinnvollsten abzubrechen ist, weitestgehend gelöst hätte. --Aragorn321 (Diskussion) 12:27, 15. Nov. 2016 (CET)
thread_title:  RelativeDistance(a,b) 

message: Der originale Romberg-Integrations-Algorithmus hat noch mehrere versteckte, teilweise schon angesprochene Probleme, die seine Praxistauglichkeit auch im Computerzeitalter stark beeinträchtigen.
Eines davon sind Funktionen, deren Anstiege sich in einzelnen Punkten schlagartig ändern. Man stelle sich dazu nur einmal vor, man möchte mit dem Romberg-Integrations-Algorithmus das Integral von a bis b einer Funktion berechnen, die von (a < c) bis c linear ansteigt und von c bis (b > c) linear abfällt, also quasi ein Dreieck mit dem Scheitelpunkt bei c. Da sich lineare Funktionen mit dem Romberg-Integrations-Algorithmus eigentlich sehr leicht und schnell integrieren lassen, weil hier schon (S2 = S1) ist und deshalb die Rekursion bereits bei Rekursionstiefe 2 abgebrochen wird, erwartet man eigentlich keine Probleme. Das stimmt auch, aber nur, wenn man das gesamte Intervall [a,b] vorher vollständig in die beiden disjunkte Teilintervalle [a,c] und [c,b] zerlegt und auf beide Teilintervalle den rekursiven Romberg-Integrations-Algorithmus separat ansetzt und beide erhaltenen Ergebnisse hinterher addiert. Kennt man den Wert von c (also die versteckte Tretmine) aber nicht, was in der Praxis leider oft der Fall ist, kann es durchaus sein, dass der Romberg-Integrations-Algorithmus statt der tatsächlich erforderlichen handvoll von Funktionswerten in Wirklichkeit aber Milliarden von Funktionswerten berechnet, weil er locker Rekursionstiefen von 30 und mehr erreicht, nur um die geforderte Genauigkeit zu erreichen. 
Hinweis: 
210 = 1.024 > 1 Tausend;   
220 = 1.048.576 > 1 Million;   
230 = 1.073.741.824 > 1 Milliarde;
Es gibt aber auch massive Probleme bei stetigen Funktionen, deren Anstiege sich also nicht schlagartig ändern. Man betrachte dazu nur mal die Funktion y=f(x1/100) also die 100ste Wurzel von x im Bereich von (a=0) bis (b=1000). Im Teilintervall [0,1] steigt die Funktion erst sehr stark an um danach im zweiten Teilintervall [1,1000] immer flacher zu werden. Das erste steilere Teilintervall läßt sich äußerst schlecht integrieren und es werden große Rekursionstiefen erreicht, wohingegen das zweite flachere Teilintervall wesentlich leichter integrierbar ist und deshalb auch nur kleine Rekursionstiefen erreicht werden. Dieses Prinzip der vollständigen Zerlegung eines großen Gesamtintervalls in mehrere kleinere und disjunkte Teilintervalle unterschiedlicher Länge läßt sich zwar immer mehr auf die Spitze treiben: z.B. (x0=0, x1=2-30, x2=2-29, x3=2-28, ..., x30=2-1, x31=20, x32=2+1... x41=2+10) wo das j-te Teilintervall Tj der Bereich zwischen xj-1 und xj-0 ist. Natürlich muss hier für alle j gelten (xj-1 < xj-0). Trotzdem macht das Teilintervall mit dem sich am stärksten ändernden Anstieg, also in unserem Fall das erste Teilintervall, oft noch die meisten Probleme beim Integrieren. 
Dies ändert sich schlagartig, wenn man nicht mehr "spaltenweise" vorgeht, also die einzelnen Teilintervalle zuerst separat mit dem originalen Romberg-Integrations-Algorithmus und der geforderten Genauigkeit berechnet und danach erst die erhaltenen Teilergebnisse aufaddiert, sondern wenn man quasi genau anders herum also "zeilenweise" vorgeht. Man berechnet für alle Teilintervalle Tj zuerst nur die Integrationssumme S1,j , also jeweils nur den ersten Rekursionsschritt, wo pro Teilintervall nur ein einziges Trapez berechnet wird. Alle so erhaltenen Teilsummen S1,j addiert man zu der ersten Gesamtsumme oder Zeilensumme S1=Σ(S1,j). Im zweiten Schritt errechnet man für alle Teilintervalle Tj die Integrationssumme S2,j , also jeweils nur den zweiten Rekursionsschritt, wo jetzt schon zwei Trapeze (also allgemein doppelt soviel, wie im vorherigen Rekursionsschritt) pro Teilintervall berechnet werden. Daraus generiert man nun die zweite Gesamtsumme oder Zeilensumme S2=Σ(S2,j). So macht man "zeilenweise" weiter. Man läßt jetzt aber sinnvollerweise den gesamten Algorithmus bereits schon dann terminieren, wenn (abs(getRelativeDistance(Si-1, Si-0) <= +ForcedPrecision) ist. Es sind also nicht mehr die Teilsummen Si,j der einzelnen Teilintervalle Tj, die das Abbruchkriterium bestimmen, sondern nur noch die berechneten Gesamt- oder Zeilensummen Si.
Dieses "zeilengetriebe" Vorgehen spart schon sehr viele überflüssige Funktionswertberechnungen ein. Aber richtig effektiv wird dieses optimierte Romberg-Integrations-Verfahren erst dann, wenn man sich in einem IgnoriereMichFlag pro Teilintervall aufhebt, ob es überhaupt noch einen Sinn macht, für dieses Teilintervall Tj noch weitere Rekursionsstufen zu berechnen. Es könnte ja durchaus sein, dass die aktuelle Teilsumme Si,j oder die Differenz zweier aufeinander folgender  Teilsummen DSi,j = (Si-0,j - Si-1,j) relativ zur aktuellen Gesamtsumme Si-0 inzwischen schon so klein geworden ist, dass deren Addition auf einem 64 Bit großen Fließkommawert (also in der heutigen 64-Bit-Realität) keinerlei Effekt mehr hat, und somit die Eigenschaft (6) der obigen Mini-Funktion (getRelativeDistance(Si , Si,j) <= ForcedPrecision) z.B. für die 2 Teilintervalle [-2-10,-2-09] , [+2-09,+2-10] der Standard-Gauß-Funktion, oder (getRelativeDistance(Si , DSi,j) <= ForcedPrecision) z.B. für die 3 Teilintervalle [-2-19,-2-20] , [-2-20,+2-20] , [+2-20,+2-19] der Standard-Gauß-Funktion ab sofort vermutlich ständig zuschlägt. Das werden pro Rekursionsschritt in der Regel immer mehr Teilintervalle, deren Neuberechnungen absofort ignoriert werden können.
Man kann in der Praxis daher oft, ohne irgendwelche Genauigkeitsverluste hinnehmen zu müssen, durch eine geschickte integrationsunterstützende und vollständige Zerlegung des Gesamtintervalls in unterschiedlich lange diskunkte Teilintervalle, das originale Romberg-Integrations-Verfahren durchaus um den Faktor 10+06 und mehr beschleunigen, wenn man schwierig zu integrierende Abschnitte möglichst klein und leicht zu integrierende Abschnitte dagegen möglichst groß wählt. Man würde also nicht mehr zig Milliarden von Funktionswerten berechnen, von denen die allermeisten absolut überflüssig sind, sondern man würde nur noch wenige Tausende Funktionswerte berechnen, die aber nun fast alle erforderlich sind. 
Über weitere inhaltliche Verbesserungsvorschläge bzgl. des Romberg-Integrations-Verfahrens würde ich mich freuen.
--Aragorn321 (Diskussion) 16:30, 15. Nov. 2016 (CET)
thread_title:  Optimiertes versus Originales Romberg-Integrations-Verfahren