Levenberg-Marquardt-Algorithmus - welche Implementierung für C++
-
Hi,
eine Implementierung ist Teil der Gnu Scientific Library, aber man findet kaum etwas dazu. Außerdem kommen viele Funktionen vor, die an C erinnern, printf() und Speicherfreigaben. Ebenso wenn ich nach den spezifischen Funktionen der Bibliothek google, finde ich sehr wenig. Ich glaube also, es gibt eine andere verbreitetere Umsetzung für C++-Programme, bei denen ggf. mehr Tutorials und Doku zu finden ist. Danke für den ein oder anderen Tipp. Hier noch der Link zur GSL-Variante:
http://www.gnu.org/software/gsl/manual/html_node/Example-programs-for-Nonlinear-Least_002dSquares-Fitting.html
-
Ich fürchte in der Numerik kommst du um C (oder gar Fortran!) Schnittstellen nicht so leicht herum. Wenn jemand viel Arbeit in eine allgemein anwendbare Bibliothek steckt, dann macht er das Interface so, dass es von möglichst vielen Sprachen benutzt werden kann und C ist meistens der kleinste gemeinsame Nenner. Ebenso natürlich die Beispiele.
Meine Empfehlung wäre, das einfach so zu schlucken und sich des C-Interfaces zu bedienen (es hindert dich ja auch nichts, z.B. einen vector anstatt eines dynamischen Arrays zu nehmen, dann übergibst du eben die Adresse des ersten Elements als Parameter). Nur wenn du es wirklich an sehr vielen Stellen sehr oft brauchst, dann würde ich mir selber einen C++-Wrapper schreiben oder mal schauen ob es schon einen fertigen gibt. Mit diesem Stichwort findest du gewiss was bei Google.
-
Mein Tip:
selber machen wenn es performant sein soll (Ist auch nichts anderes als Gauss-Newton). Sonst kannst du dir ja mal die C++ Implementierung von LevMar anschauen
http://www.ics.forth.gr/~lourakis/levmar/
-
Shit, ich suche nun schon seit Tagen ... einmal fand ich etwas in Numerical Recipes, da ist es so, dass dort wiederum die Verteilung nicht implementiert ist. Außerdem ist die Variante langsam und es werden wohl sehr viele Berechnungen durchgeführt, es muss also schon schnell sein. In der Gnu Scientific Library-Variante ist sehr viel anzugeben und die Doku ist sehr gering, da blick ich kaum durch. Tutorials oder Beispiele zur GSL sind rar. Und levmar bekomme ich hier auf meiner Maschine (Ubuntu 10.04) nicht ans Laufen ... dazu muss der Code auf einer Maschine laufen, die ich nicht beeinflussen kann, also da kann ich nicht mal rumprobieren, um lange etwas zu installieren - geht mir das auf den Sack mittlerweile! Das gibts doch nicht, dass das hier noch keiner lösen musste oder dass das so schwierig ist.
So - nächster Versuch: http://devernay.free.fr/hacks/cminpack/cminpack.html
-
Sodala,
ich versuche mich gerade dran das GSL-Beispiel umzubiegen. Im Endeffekt kann das ja nicht so schwierig sein und da ich die GSL ohnehin einsetze. Ich habe die Daten im Code dazu einige Fragen eingestellt und die Links zu den Formeln der Gumbel-Verteilung mit den Parametern mue und beta. Vielleicht findet ja einer den Thread der vorher schon etwas ähnliches gemacht hat und kann mir den ein oder anderen Tipp geben. gslCurveFit() ist die main-Funktion.
Hier ist der Link zum Beispiel und darunter mein Code:
http://www.gnu.org/software/gsl/manual/html_node/Example-programs-for-Nonlinear-Least_002dSquares-Fitting.html// user-provided-functions: // Gumbel-densitiy: y = exp( ( -1 * ( x - mue ) ) / beta ) * exp( -1 * ( exp( ( -1 * ( x - mue ) ) / beta )) ) / beta ; // Gumbel-cumulative: y = exp( - exp( - ( x - mue ) / beta ) ); // Gumbel-Vals uniform-dice: y = mue - beta * ln( -1 * ln ( x ) ); struct curvefitdata // data-container { size_t n; // observations double * y; // y-val f(x) double * mue; // mue, location of distribution double * beta; // beta, scale of distribution }; // function stores f(x)-vals to incoming x-vals. int curvefit_f( const gsl_vector * x, void *data, gsl_vector * f ) { // Attention: value x comes from parameter 'data', GSL-x represents variable to look for, often the y in math! // params: // x = current position, that means observation // TODO is this right?! // f = current value at current position // lots of // data = current-data-container, see struct curvefitdata size_t n = ((struct curvefitdata *) data)->n; double *y = ((struct curvefitdata *) data)->y; double *mue = ((struct curvefitdata *) data)->mue; double *beta = ((struct curvefitdata *) data)->beta; // pick datacontainer x, the vector // seems this are the weights and above are the params to fit double w1 = gsl_vector_get(x, 0); double w2 = gsl_vector_get(x, 1); size_t i; // TODO Are those number of obervations? for (i = 0; i < n; i++) { // Modell: // y = exp( ( -1 * ( x - mue ) ) / beta ) * exp( -1 * ( exp( ( -1 * ( x - mue ) ) / beta )) ) / beta ; // TODO Check the meaning of this part of the function double t = i; // TODO is t for time and should this be implicit type casting? // double Yi = exp( ( -1 * ( x - mue ) ) / beta ) * exp( -1 * ( exp( ( -1 * ( x - mue ) ) / beta )) ) / beta ; double Yi = 3; // TODO change to formula gsl_vector_set(f, i, (Yi - y[i]) / beta[i]); // } return GSL_SUCCESS; } // function stores n-by-p-values of matrix int curvefit_df( const gsl_vector * x, void *data, gsl_matrix * J) { size_t n = ((struct curvefitdata *) data)->n; double *y = ((struct curvefitdata *) data)->y; double *mue = ((struct curvefitdata *) data)->mue; double *beta = ((struct curvefitdata *) data)->beta; // TODO see ref double A = gsl_vector_get(x, 0); double lambda = gsl_vector_get(x, 1); size_t i; for (i = 0; i < n; i++) { // TODO, see ref //double t = i; //double s = mue[i]; //double e = exp(-lambda * t); //gsl_matrix_set(J, i, 0, e / s); //gsl_matrix_set(J, i, 1, -t * A * e / s); //gsl_matrix_set(J, i, 2, 1 / s); } return GSL_SUCCESS; } // function calls curvefit_f and curvefit_df simultanously int curvefit_fdf( const gsl_vector * x, void *data, gsl_vector * f, gsl_matrix * J) { curvefit_f(x, data, f); curvefit_df(x, data, J); return GSL_SUCCESS; } void curveFitPrintState(size_t iter, gsl_multifit_fdfsolver * s) { printf("iter: %3u x = % 15.8f % 15.8f % 15.8f |f(x)| = %g\n", iter, gsl_vector_get(s->x, 0), gsl_vector_get(s->x, 1), gsl_vector_get(s->x, 2), gsl_blas_dnrm2(s->f)); } void gslCurveFit() { // IDE-Properties: // Netbeans-Linker-Additional-Options: -lgsl -lgslcblas -lm // References: // Doc: gnu.org/software/gsl/manual/html_node/index.html#Top // Gumbel-Distribution: en.wikipedia.org/wiki/Gumbel_distribution // Threads: lists.gnu.org/archive/html/help-gsl/2007-12/msg00004.html; // lists.gnu.org/archive/html/help-gsl/2005-12/msg00049.html cout << "GSL Curve Fitting-Example \n" << endl; // data-vector, 30 vals, maybe gumbel-distributed: vector<double> uvalsRealVector; uvalsRealVector.push_back(0.0603461); uvalsRealVector.push_back(0.1436600); uvalsRealVector.push_back(0.1091960); uvalsRealVector.push_back(0.1441190); uvalsRealVector.push_back(0.1383480); uvalsRealVector.push_back(0.0634344); uvalsRealVector.push_back(0.0964405); uvalsRealVector.push_back(0.1011820); uvalsRealVector.push_back(0.1142970); uvalsRealVector.push_back(0.1221080); uvalsRealVector.push_back(0.1118600); uvalsRealVector.push_back(0.0852936); uvalsRealVector.push_back(0.1518220); uvalsRealVector.push_back(0.0780212); uvalsRealVector.push_back(0.0929122); uvalsRealVector.push_back(0.1219700); uvalsRealVector.push_back(0.1128130); uvalsRealVector.push_back(0.1478180); uvalsRealVector.push_back(0.0774807); uvalsRealVector.push_back(0.0975139); uvalsRealVector.push_back(0.0904746); uvalsRealVector.push_back(0.0898072); uvalsRealVector.push_back(0.1010630); uvalsRealVector.push_back(0.0739358); uvalsRealVector.push_back(0.0525509); uvalsRealVector.push_back(0.1371950); uvalsRealVector.push_back(0.1028160); uvalsRealVector.push_back(0.1035900); uvalsRealVector.push_back(0.1361230); uvalsRealVector.push_back(0.0845199); gsl_vector * uvalsReal = gsl_vector_alloc(30); // change data to GSL-expected data type for( int i = 0; i < 30; i++ ) { gsl_vector_set( uvalsReal, i, uvalsRealVector.at( i ) ); } // functions-components // struct curvefitdata - parameters and number of observations // 3 corefunctions x_f, x_df and x_fdf, got hooked to solver // solver attributes are // vektor x current position, // vektor f current value at current position; // vektor dx difference after one step down to local minimum // matrix J Jacobi-Matrix at current position // main() // solver get initialized, data and starting guesses get to it as well. // Curvefitting to Gumbel-Dist, Levenberg-Marquardt int status; // solver-status int i; // index int iter = 0; // counter for steps down to minimum int constn = 30; // number of observations, 'N' in Documentation const size_t n = constn; const size_t p = 2; // parameters, mue and beta const gsl_multifit_fdfsolver_type * T = gsl_multifit_fdfsolver_lmder; // solver-creation part 1 gsl_multifit_fdfsolver * s = gsl_multifit_fdfsolver_alloc(T, constn, p); // solver-creation part 2 //printf("s is a '%s' solver\n", gsl_multifit_fdfsolver_name(s)); // point to solver, solver-name gsl_multifit_function_fdf f; // function double x_init[3] = {0.2, 0.002}; // starting guesses gsl_matrix *covar = gsl_matrix_alloc(p, p); // matrix for functions // data types, functions for curve-fitting-procedure (struct, f, df, fdf) double y[constn]; double mue[constn]; double beta[constn]; struct curvefitdata d = {n, y, mue, beta}; f.f = &curvefit_f; // function f f.df = &curvefit_df; // function df f.fdf = &curvefit_fdf; // function fdf f.n = n; // observations f.p = p; // parameters f.params = &d; // struct T = gsl_multifit_fdfsolver_lmsder; // Levenberg-Marquardt-Solver type s = gsl_multifit_fdfsolver_alloc(T, n, p); // Levenberg-Marquardt-Solver object gsl_vector_view x = gsl_vector_view_array(x_init, p); gsl_multifit_fdfsolver_set(s, &f, &x.vector); // solver-collection curveFitPrintState(iter, s); // solving ... first run do { // more runs iter++; status = gsl_multifit_fdfsolver_iterate(s); printf("solver-status = %s\n", gsl_strerror(status)); curveFitPrintState(iter, s); if (status) { break; } // break if minimum is achieved status = gsl_multifit_test_delta(s->dx, s->x, 1e-4, 1e-4); // Solver verwaltet die Abweichungen } while (status == GSL_CONTINUE && iter < 500); // Print Result of solver gsl_multifit_covar (s->J, 0.0, covar); #define FIT(i) gsl_vector_get(s->x, i) #define ERR(i) sqrt(gsl_matrix_get(covar,i,i)) { double chi = gsl_blas_dnrm2(s->f); double dof = n - p; double c = GSL_MAX_DBL(1, chi / sqrt(dof)); printf("chisq/dof = %g\n", pow(chi, 2.0) / dof); printf("mue = % .5f + / - % .5f\n", FIT(0), c * ERR(0)); printf("beta = %.5f +/- %.5f\n", FIT(1), c * ERR(1)); } printf ("status = %s\n", gsl_strerror (status)); // free memory gsl_multifit_fdfsolver_free(s); gsl_matrix_free( covar); gsl_vector_free( uvalsReal ); //gsl_rng_free(r); cout << " ... GSL-Curve-Fitting-End \n"; }
-
Nächste Levenberg-Marquardt-Umsetzung, diese erfordert wirklich nur die Angabe einer Funktion die dann minimiert wird, mal schauen, ob ich damit klar komme: http://www.quantcode.com/modules/mydownloads/singlefile.php?lid=436
-
So habe es nun zusammengehämmert, da der Code sonst super-umfassend ist, habe ich Quelltextkommentare rausgenommen, bitte bedenken, einiges an Code ist von Fremprojekten. Es werden 1000 Werte aus einem Experiment eingelesen, daraus wird mittels GSL dann arithmetisches Mittel und Standardabweichung ermittelt. Es wird angenommen, dass es sich um eine Gumbel-Verteilung handelt. Daher können direkt mit den bekannten Größen die Parameter mue und beta berechnet werden. Okay, dann werden mittels GSL Histogramme angelegt und die Bins und Höhen kann man nutzen um die Parameter der kumulierten Verteilung via Quantlib-C-Minpack-Implementierung zu berechnen. Dazu kommt noch ein Test, ob die Gumbel-Verteilung zutrifft oder nicht, hier nehme ich den KS-Test aus Numerical Recipes, da gibts aber noch Problemchen. Da da heut ein Riesengefrickel war, poste ich hier mal den Code, vielleicht hilft die Vorgehensweise ja jemand.
#include <algorithm> #include <cmath> #include <cstdio> #include <fstream> #include <gsl/gsl_blas.h> #include <gsl/gsl_histogram.h> #include <gsl/gsl_multifit_nlin.h> #include <gsl/gsl_randist.h> #include <gsl/gsl_rng.h> #include <gsl/gsl_sf_bessel.h> #include <gsl/gsl_sort.h> #include <gsl/gsl_statistics.h> #include <gsl/gsl_vector.h> #include <levmar.h> #include <math.h> #include <iostream> #include <sstream> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <time.h> #include <vector> #include "nr3.h" #include "sort.h" using namespace std; // Quantlib-globale Variablen double* independent_variables; double* oberved_values; // XXXXXXXXXXXXXXX Start Quantlib-Header lmdif XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX /************************lmdif*************************/ namespace QuantLib { namespace MINPACK { #define BUG 0 /* resolution of arithmetic */ double MACHEP = 1.2e-16; /* smallest nonzero number */ double DWARF = 1.0e-38; double enorm(int n,double* x) { int i; double agiant, floatn, s1, s2, s3, xabs, x1max, x3max; double ans, temp; static double rdwarf = 3.834e-20; static double rgiant = 1.304e19; static double zero = 0.0; static double one = 1.0; s1 = zero; s2 = zero; s3 = zero; x1max = zero; x3max = zero; floatn = n; agiant = rgiant/floatn; for( i=0; i<n; i++ ) { xabs = std::fabs(x[i]); if( (xabs > rdwarf) && (xabs < agiant) ) { s2 += xabs*xabs; continue; } if(xabs > rdwarf) { if(xabs > x1max) { temp = x1max/xabs; s1 = one + s1*temp*temp; x1max = xabs; } else { temp = xabs/x1max; s1 += temp*temp; } continue; } if(xabs > x3max) { temp = x3max/xabs; s3 = one + s3*temp*temp; x3max = xabs; } else { if(xabs != zero) { temp = xabs/x3max; s3 += temp*temp; } } } if(s1 != zero) { temp = s1 + (s2/x1max)/x1max; ans = x1max*std::sqrt(temp); return(ans); } if(s2 != zero) { if(s2 >= x3max) temp = s2*(one+(x3max/s2)*(x3max*s3)); else temp = x3max*((s2/x3max)+(x3max*s3)); ans = std::sqrt(temp); } else { ans = x3max*std::sqrt(s3); } return(ans); } /************************lmmisc.c*************************/ double dmax1(double a,double b) { if( a >= b ) return(a); else return(b); } double dmin1(double a,double b) { if( a <= b ) return(a); else return(b); } int min0(int a,int b) { if( a <= b ) return(a); else return(b); } int mod( int k, int m ) { return( k % m ); } #if BUG void pmat( int m, int n, double* y ) { int i, j, k; k = 0; for( i=0; i<m; i++ ) { for( j=0; j<n; j++ ) { printf( "%.5e ", y[k] ); k += 1; } printf( "\n" ); } } #endif // XXXXXXX Fitting-Model XXXXXXXXXX /***********Sample of user supplied function**************** * m = number of functions * n = number of variables * x = vector of function arguments * fvec = vector of function values * iflag = error return variable */ void fcn(int m,int n, double* x, double* fvec,int *iflag) { double a=x[0]; double b=x[1]; for (int i=0;i<m;i++) { double xi=independent_variables[i]; double observed_value=oberved_values[i]; //double fitted_value=a*xi*xi*xi + b*xi*xi + c; // Tut-Code //double ydist = exp( - exp( - ( x - gumbelmue ) / gumbelbeta ) ); // cumulated //double ydist = exp( ( -1 * ( x - gumbelmue ) ) / gumbelbeta ) * exp( -1 * ( exp( ( -1 * ( x - gumbelmue ) ) / gumbelbeta )) ) / gumbelbeta ; //double gumbelDouble = ydist * ( elements / bins ); double fitted_value=exp( - exp( - ( xi - a ) / b ) ); fvec[i]=fitted_value-observed_value; } } void fdjac2(int m,int n,double* x,double* fvec,double* fjac,int, int* iflag,double epsfcn,double* wa) { int i,j,ij; double eps,h,temp; static double zero = 0.0; temp = dmax1(epsfcn,MACHEP); eps = std::sqrt(temp); #if BUG printf( "fdjac2\n" ); #endif ij = 0; for( j=0; j<n; j++ ) { temp = x[j]; h = eps * std::fabs(temp); if(h == zero) h = eps; x[j] = temp + h; fcn(m,n,x,wa,iflag); if( *iflag < 0) return; x[j] = temp; for( i=0; i<m; i++ ) { fjac[ij] = (wa[i] - fvec[i])/h; ij += 1; /* fjac[i+m*j] */ } } #if BUG pmat( m, n, fjac ); #endif } /************************qrfac.c*************************/ void qrfac(int m,int n,double* a,int,int pivot,int* ipvt, int,double* rdiag,double* acnorm,double* wa) { int i,ij,jj,j,jp1,k,kmax,minmn; double ajnorm,sum,temp; static double zero = 0.0; static double one = 1.0; static double p05 = 0.05; ij = 0; for( j=0; j<n; j++ ) { acnorm[j] = enorm(m,&a[ij]); rdiag[j] = acnorm[j]; wa[j] = rdiag[j]; if(pivot != 0) ipvt[j] = j; ij += m; /* m*j */ } #if BUG printf( "qrfac\n" ); #endif minmn = min0(m,n); for( j=0; j<minmn; j++ ) { if(pivot == 0) goto L40; kmax = j; for( k=j; k<n; k++ ) { if(rdiag[k] > rdiag[kmax]) kmax = k; } if(kmax == j) goto L40; ij = m * j; jj = m * kmax; for( i=0; i<m; i++ ) { temp = a[ij]; /* [i+m*j] */ a[ij] = a[jj]; /* [i+m*kmax] */ a[jj] = temp; ij += 1; jj += 1; } rdiag[kmax] = rdiag[j]; wa[kmax] = wa[j]; k = ipvt[j]; ipvt[j] = ipvt[kmax]; ipvt[kmax] = k; L40: jj = j + m*j; ajnorm = enorm(m-j,&a[jj]); if(ajnorm == zero) goto L100; if(a[jj] < zero) ajnorm = -ajnorm; ij = jj; for( i=j; i<m; i++ ) { a[ij] /= ajnorm; ij += 1; /* [i+m*j] */ } a[jj] += one; jp1 = j + 1; if(jp1 < n ) { for( k=jp1; k<n; k++ ) { sum = zero; ij = j + m*k; jj = j + m*j; for( i=j; i<m; i++ ) { sum += a[jj]*a[ij]; ij += 1; /* [i+m*k] */ jj += 1; /* [i+m*j] */ } temp = sum/a[j+m*j]; ij = j + m*k; jj = j + m*j; for( i=j; i<m; i++ ) { a[ij] -= temp*a[jj]; ij += 1; /* [i+m*k] */ jj += 1; /* [i+m*j] */ } if( (pivot != 0) && (rdiag[k] != zero) ) { temp = a[j+m*k]/rdiag[k]; temp = dmax1( zero, one-temp*temp ); rdiag[k] *= std::sqrt(temp); temp = rdiag[k]/wa[k]; if( (p05*temp*temp) <= MACHEP) { rdiag[k] = enorm(m-j-1,&a[jp1+m*k]); wa[k] = rdiag[k]; } } } } L100: rdiag[j] = -ajnorm; } } /************************qrsolv.c*************************/ void qrsolv(int n,double* r,int ldr,int* ipvt,double* diag,double* qtb, double* x,double* sdiag,double* wa) { int i,ij,ik,kk,j,jp1,k,kp1,l,nsing; double cos,cotan,qtbpj,sin,sum,tan,temp; static double zero = 0.0; static double p25 = 0.25; static double p5 = 0.5; kk = 0; for( j=0; j<n; j++ ) { ij = kk; ik = kk; for( i=j; i<n; i++ ) { r[ij] = r[ik]; ij += 1; /* [i+ldr*j] */ ik += ldr; /* [j+ldr*i] */ } x[j] = r[kk]; wa[j] = qtb[j]; kk += ldr+1; /* j+ldr*j */ } #if BUG printf( "qrsolv\n" ); #endif for( j=0; j<n; j++ ) { l = ipvt[j]; if(diag[l] == zero) goto L90; for( k=j; k<n; k++ ) sdiag[k] = zero; sdiag[j] = diag[l]; qtbpj = zero; for( k=j; k<n; k++ ) { if(sdiag[k] == zero) continue; kk = k + ldr * k; if(std::fabs(r[kk]) < std::fabs(sdiag[k])) { cotan = r[kk]/sdiag[k]; sin = p5/std::sqrt(p25+p25*cotan*cotan); cos = sin*cotan; } else { tan = sdiag[k]/r[kk]; cos = p5/std::sqrt(p25+p25*tan*tan); sin = cos*tan; } r[kk] = cos*r[kk] + sin*sdiag[k]; temp = cos*wa[k] + sin*qtbpj; qtbpj = -sin*wa[k] + cos*qtbpj; wa[k] = temp; kp1 = k + 1; if( n > kp1 ) { ik = kk + 1; for( i=kp1; i<n; i++ ) { temp = cos*r[ik] + sin*sdiag[i]; sdiag[i] = -sin*r[ik] + cos*sdiag[i]; r[ik] = temp; ik += 1; /* [i+ldr*k] */ } } } L90: kk = j + ldr*j; sdiag[j] = r[kk]; r[kk] = x[j]; } nsing = n; for( j=0; j<n; j++ ) { if( (sdiag[j] == zero) && (nsing == n) ) nsing = j; if(nsing < n) wa[j] = zero; } if(nsing < 1) goto L150; for( k=0; k<nsing; k++ ) { j = nsing - k - 1; sum = zero; jp1 = j + 1; if(nsing > jp1) { ij = jp1 + ldr * j; for( i=jp1; i<nsing; i++ ) { sum += r[ij]*wa[i]; ij += 1; /* [i+ldr*j] */ } } wa[j] = (wa[j] - sum)/sdiag[j]; } L150: for( j=0; j<n; j++ ) { l = ipvt[j]; x[l] = wa[j]; } } /************************lmpar.c*************************/ void lmpar(int n,double* r,int ldr,int* ipvt,double* diag, double* qtb,double delta,double* par,double* x,double* sdiag, double* wa1,double* wa2) { int i,iter,ij,jj,j,jm1,jp1,k,l,nsing; double dxnorm,fp,gnorm,parc,parl,paru; double sum,temp; static double zero = 0.0; static double p1 = 0.1; static double p001 = 0.001; extern double DWARF; #if BUG printf( "lmpar\n" ); #endif nsing = n; jj = 0; for( j=0; j<n; j++ ) { wa1[j] = qtb[j]; if( (r[jj] == zero) && (nsing == n) ) nsing = j; if(nsing < n) wa1[j] = zero; jj += ldr+1; /* [j+ldr*j] */ } #if BUG printf( "nsing %d ", nsing ); #endif if(nsing >= 1) { for( k=0; k<nsing; k++ ) { j = nsing - k - 1; wa1[j] = wa1[j]/r[j+ldr*j]; temp = wa1[j]; jm1 = j - 1; if(jm1 >= 0) { ij = ldr * j; for( i=0; i<=jm1; i++ ) { wa1[i] -= r[ij]*temp; ij += 1; } } } } for( j=0; j<n; j++ ) { l = ipvt[j]; x[l] = wa1[j]; } iter = 0; for( j=0; j<n; j++ ) wa2[j] = diag[j]*x[j]; dxnorm = enorm(n,wa2); fp = dxnorm - delta; if(fp <= p1*delta) { #if BUG printf( "going to L220\n" ); #endif goto L220; } parl = zero; if(nsing >= n) { for( j=0; j<n; j++ ) { l = ipvt[j]; wa1[j] = diag[l]*(wa2[l]/dxnorm); } jj = 0; for( j=0; j<n; j++ ) { sum = zero; jm1 = j - 1; if(jm1 >= 0) { ij = jj; for( i=0; i<=jm1; i++ ) { sum += r[ij]*wa1[i]; ij += 1; } } wa1[j] = (wa1[j] - sum)/r[j+ldr*j]; jj += ldr; /* [i+ldr*j] */ } temp = enorm(n,wa1); parl = ((fp/delta)/temp)/temp; } jj = 0; for( j=0; j<n; j++ ) { sum = zero; ij = jj; for( i=0; i<=j; i++ ) { sum += r[ij]*qtb[i]; ij += 1; } l = ipvt[j]; wa1[j] = sum/diag[l]; jj += ldr; /* [i+ldr*j] */ } gnorm = enorm(n,wa1); paru = gnorm/delta; if(paru == zero) paru = DWARF/dmin1(delta,p1); *par = dmax1( *par,parl); *par = dmin1( *par,paru); if( *par == zero) *par = gnorm/dxnorm; #if BUG printf( "parl %.4e par %.4e paru %.4e\n", parl, *par, paru ); #endif L150: iter += 1; if( *par == zero) *par = dmax1(DWARF,p001*paru); temp = std::sqrt( *par ); for( j=0; j<n; j++ ) wa1[j] = temp*diag[j]; qrsolv(n,r,ldr,ipvt,wa1,qtb,x,sdiag,wa2); for( j=0; j<n; j++ ) wa2[j] = diag[j]*x[j]; dxnorm = enorm(n,wa2); temp = fp; fp = dxnorm - delta; if( (std::fabs(fp) <= p1*delta) || ((parl == zero) && (fp <= temp) && (temp < zero)) || (iter == 10) ) goto L220; for( j=0; j<n; j++ ) { l = ipvt[j]; wa1[j] = diag[l]*(wa2[l]/dxnorm); } jj = 0; for( j=0; j<n; j++ ) { wa1[j] = wa1[j]/sdiag[j]; temp = wa1[j]; jp1 = j + 1; if(jp1 < n) { ij = jp1 + jj; for( i=jp1; i<n; i++ ) { wa1[i] -= r[ij]*temp; ij += 1; /* [i+ldr*j] */ } } jj += ldr; /* ldr*j */ } temp = enorm(n,wa1); parc = ((fp/delta)/temp)/temp; if(fp > zero) parl = dmax1(parl, *par); if(fp < zero) paru = dmin1(paru, *par); *par = dmax1(parl, *par + parc); goto L150; L220: if(iter == 0) *par = zero; } /*********************** lmdif.c ****************************/ void lmdif(int m,int n,double* x,double* fvec,double ftol, double xtol,double gtol,int maxfev,double epsfcn, double* diag, int mode, double factor, int nprint, int* info,int* nfev,double* fjac, int ldfjac,int* ipvt,double* qtf, double* wa1,double* wa2,double* wa3,double* wa4) { int i,iflag,ij,jj,iter,j,l; double actred,delta=0,dirder,fnorm,fnorm1,gnorm; double par,pnorm,prered,ratio; double sum,temp,temp1,temp2,temp3,xnorm=0; static double one = 1.0; static double p1 = 0.1; static double p5 = 0.5; static double p25 = 0.25; static double p75 = 0.75; static double p0001 = 1.0e-4; static double zero = 0.0; *info = 0; iflag = 0; *nfev = 0; if( (n <= 0) || (m < n) || (ldfjac < m) || (ftol < zero) || (xtol < zero) || (gtol < zero) || (maxfev <= 0) || (factor <= zero) ) goto L300; if( mode == 2 ) { /* scaling by diag[] */ for( j=0; j<n; j++ ) { if( diag[j] <= 0.0 ) goto L300; } } #if BUG printf( "lmdif\n" ); #endif iflag = 1; fcn(m,n,x,fvec,&iflag); *nfev = 1; if(iflag < 0) goto L300; fnorm = enorm(m,fvec); par = zero; iter = 1; L30: iflag = 2; fdjac2(m,n,x,fvec,fjac,ldfjac,&iflag,epsfcn,wa4); *nfev += n; if(iflag < 0) goto L300; if( nprint > 0 ) { iflag = 0; if(mod(iter-1,nprint) == 0) { fcn(m,n,x,fvec,&iflag); if(iflag < 0) goto L300; #if BUG printf( "fnorm %.15e\n", enorm(m,fvec) ); #endif } } qrfac(m,n,fjac,ldfjac,1,ipvt,n,wa1,wa2,wa3); if(iter == 1) { if(mode != 2) { for( j=0; j<n; j++ ) { diag[j] = wa2[j]; if( wa2[j] == zero ) diag[j] = one; } } for( j=0; j<n; j++ ) wa3[j] = diag[j] * x[j]; xnorm = enorm(n,wa3); delta = factor*xnorm; if(delta == zero) delta = factor; } for( i=0; i<m; i++ ) wa4[i] = fvec[i]; jj = 0; for( j=0; j<n; j++ ) { temp3 = fjac[jj]; if(temp3 != zero) { sum = zero; ij = jj; for( i=j; i<m; i++ ) { sum += fjac[ij] * wa4[i]; ij += 1; /* fjac[i+m*j] */ } temp = -sum / temp3; ij = jj; for( i=j; i<m; i++ ) { wa4[i] += fjac[ij] * temp; ij += 1; /* fjac[i+m*j] */ } } fjac[jj] = wa1[j]; jj += m+1; /* fjac[j+m*j] */ qtf[j] = wa4[j]; } gnorm = zero; if(fnorm != zero) { jj = 0; for( j=0; j<n; j++ ) { l = ipvt[j]; if(wa2[l] != zero) { sum = zero; ij = jj; for( i=0; i<=j; i++ ) { sum += fjac[ij]*(qtf[i]/fnorm); ij += 1; /* fjac[i+m*j] */ } gnorm = dmax1(gnorm,std::fabs(sum/wa2[l])); } jj += m; } } if(gnorm <= gtol) *info = 4; if( *info != 0) goto L300; if(mode != 2) { for( j=0; j<n; j++ ) diag[j] = dmax1(diag[j],wa2[j]); } L200: lmpar(n,fjac,ldfjac,ipvt,diag,qtf,delta,&par,wa1,wa2,wa3,wa4); for( j=0; j<n; j++ ) { wa1[j] = -wa1[j]; wa2[j] = x[j] + wa1[j]; wa3[j] = diag[j]*wa1[j]; } pnorm = enorm(n,wa3); if(iter == 1) delta = dmin1(delta,pnorm); iflag = 1; fcn(m,n,wa2,wa4,&iflag); *nfev += 1; if(iflag < 0) goto L300; fnorm1 = enorm(m,wa4); #if BUG printf( "pnorm %.10e fnorm1 %.10e\n", pnorm, fnorm1 ); #endif actred = -one; if( (p1*fnorm1) < fnorm) { temp = fnorm1/fnorm; actred = one - temp * temp; } jj = 0; for( j=0; j<n; j++ ) { wa3[j] = zero; l = ipvt[j]; temp = wa1[l]; ij = jj; for( i=0; i<=j; i++ ) { wa3[i] += fjac[ij]*temp; ij += 1; /* fjac[i+m*j] */ } jj += m; } temp1 = enorm(n,wa3)/fnorm; temp2 = (std::sqrt(par)*pnorm)/fnorm; prered = temp1*temp1 + (temp2*temp2)/p5; dirder = -(temp1*temp1 + temp2*temp2); ratio = zero; if(prered != zero) ratio = actred/prered; if(ratio <= p25) { if(actred >= zero) temp = p5; else temp = p5*dirder/(dirder + p5*actred); if( ((p1*fnorm1) >= fnorm) || (temp < p1) ) temp = p1; delta = temp*dmin1(delta,pnorm/p1); par = par/temp; } else { if( (par == zero) || (ratio >= p75) ) { delta = pnorm/p5; par = p5*par; } } if(ratio >= p0001) { for( j=0; j<n; j++ ) { x[j] = wa2[j]; wa2[j] = diag[j]*x[j]; } for( i=0; i<m; i++ ) fvec[i] = wa4[i]; xnorm = enorm(n,wa2); fnorm = fnorm1; iter += 1; } if( (std::fabs(actred) <= ftol) && (prered <= ftol) && (p5*ratio <= one) ) *info = 1; if(delta <= xtol*xnorm) *info = 2; if( (std::fabs(actred) <= ftol) && (prered <= ftol) && (p5*ratio <= one) && ( *info == 2) ) *info = 3; if( *info != 0) goto L300; if( *nfev >= maxfev) *info = 5; if( (std::fabs(actred) <= MACHEP) && (prered <= MACHEP) && (p5*ratio <= one) ) *info = 6; if(delta <= MACHEP*xnorm) *info = 7; if(gnorm <= MACHEP) *info = 8; if( *info != 0) goto L300; if(ratio < p0001) goto L200; goto L30; L300: if(iflag < 0) *info = iflag; iflag = 0; if(nprint > 0) fcn(m,n,x,fvec,&iflag); }}} // XXXXXXXXXXXXXXXXXXXXXXXXXX ENDE Quantlib-Header lmdif XXXXXXXXXXXXXXXXXXXXXXX // XXXXXXXXXXXXXXXXXXXXX Start Numerical Recipe KS-Test XXXXXXXXXXXXXXXXXXXXXXXX // NR-Struktur fuer KS-Verteilung struct KSdist { // Kap 14.3.3. S.737 + Kap 6.14.56 S. 334 double pks(double z) { // Return cumulative distribution function. if (z < 0.) throw ("bad z in KSdist"); if (z == 0.) return 0.; if (z < 1.18) { double y = exp(-1.23370055013616983 / SQR(z)); return 2.25675833419102515 * sqrt(-log(y))*(y + pow(y, 9) + pow(y, 25) + pow(y, 49)); } else { double x = exp(-2. * SQR(z)); return 1. - 2. * (x - pow(x, 4) + pow(x, 9)); } } // Return complementary cumulative distribution function. double qks(double z) { if (z < 0.) throw ("bad z in KSdist"); if (z == 0.) return 1.; if (z < 1.18) return 1. - pks(z); double x = exp(-2. * (z * z)); return 2. * (x - pow(x, 4) + pow(x, 9)); } // more code in the book ... }; // NR-Funkton zum KS-Test void kstwo(VecDoub_IO &data1, VecDoub_IO &data2, Doub &d, Doub &prob) { // Given an array data1[0..n1-1], and an array data2[0..n2-1], this routine returns the K–S // statistic d and the p-value prob for the null hypothesis that the data sets are drawn from the // same distribution. Small values of prob show that the cumulative distribution function of data1 // is significantly different from that of data2. The arrays data1 and data2 are modified by being // sorted into ascending order. int j1 = 0, j2 = 0, n1 = data1.size(), n2 = data2.size(); double d1, d2, dt, en1, en2, en, fn1 = 0.0, fn2 = 0.0; KSdist ks; sort(data1); sort(data2); // Ausgabe der beiden sortierten Collections //int nrRealVecLength = data1.size(); //int nrGumbelVecLength = data2.size(); //for (int i = 0; i < nrRealVecLength; i++) { // cout << "Debug nrRealVec data1 ... Lauf " << i << ": " << data1[i] << endl; //} //for (int i = 0; i < nrGumbelVecLength; i++) { // cout << "Debug nrGumbelVec data2 ... Lauf " << i << ": " << data2[i] << endl; //} en1 = n1; en2 = n2; d = 0.0; while (j1 < n1 && j2 < n2) // If we are not done... { if ((d1 = data1[j1]) <= (d2 = data2[j2])) // Next step is in data1. { do { fn1 = ++j1 / en1; } while (j1 < n1 && d1 == data1[j1]); } if (d2 <= d1) // Next step is in data2. { do { fn2 = ++j2 / en2; } while (j2 < n2 && d2 == data2[j2]); } if ((dt = abs(fn2 - fn1)) > d) { //cout << " fn2 " << fn2 << "; fn1 " << fn1 << "; d " << d << endl; d = dt; } } en = sqrt(en1 * en2 / (en1 + en2)); //cout << "* en " << en << " aus en1 " << en1 << " und en2 " << en2 << endl; prob = ks.qks( (en + 0.12 + 0.11 / en) * d); // Compute p-value. } // XXXXXXXXXXXXXXXXX KS-Test Ende XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX double doubleFromString( const std::string& s ) { std::istringstream stream(s); double t; stream >> t; return t; } double roundDouble(double number, unsigned int digits) { number *= pow(10, digits); if (number >= 0) number = floor(number + 0.5); else number = ceil(number - 0.5); number /= pow(10, digits); return number; } int fitAndTest() { // References: // Curve-Fitting: C++-Minpack of Quantlib: quantcode.com/modules/mydownloads/singlefile.php?lid=436 // Gumbel-Distribution: en.wikipedia.org/wiki/Gumbel_distribution // Histograms: gnu.org/software/gsl/manual/html_node/index.html#Top // Kolmogorov-Smirnov-Test: Numerical Recipes - kstwo() cout << "Fit And Test \n" << endl; vector<double> uvalsRealVector; // data collection double elements = 1000; // experimental data double bins = 100; // histogram-bins fstream fstrm; // read data from file string path = "/home/joba/data01.txt"; fstrm.open(path.c_str(), ::std::ios_base::in | ::std::ios_base::binary); if (!fstrm) { cout << "XXX File not found! \n"; exit(0); } string currentValString; while (!fstrm.eof()) { getline(fstrm, currentValString); //cout << "Zeile : " << curuval << endl; if ( currentValString == "" ){ } else // add value to collection { double currentVal = doubleFromString( currentValString ); uvalsRealVector.push_back( currentVal ); } } fstrm.close(); // prepare data for GSL int numberOfElements = uvalsRealVector.size(); double uvalsRealDataArray[ numberOfElements ]; for( int i = 0; i < numberOfElements; i++ ) { uvalsRealDataArray[i] = uvalsRealVector.at(i); } // GSL, compute mean and sdev, gsl_stats_mean(dataarray, stepwidth, numberOfElements); double uvalsRealMean = gsl_stats_mean(uvalsRealDataArray, 1, numberOfElements); double uvalsRealSdev = gsl_stats_sd(uvalsRealDataArray, 1, numberOfElements); // calculate mue and beta from mean and sd - see gumbel distribution double beta = ( ( uvalsRealSdev * sqrt(6) ) / M_PI ); double mue = ( uvalsRealMean - ( M_EULER * beta ) ); cout << " ... mue: " << mue << "; beta: " << beta << " (calculated via mean and sd);" << endl; // start double gumbelmue = mue; // 0.0939307 bei den genauen 30, aus R double gumbelbeta = beta; // 0.0231414 bei den genauen 30, aus R // ---- // histogram - real data int numberofbins = bins; gsl_histogram * h = gsl_histogram_alloc(numberofbins); // hist-pointer, n bins, n is rangeElements -1 double range[(numberofbins + 1)]; double currentBinElement = 0.0; for (int i = 0; i < (numberofbins + 1); i++) { range[i] = currentBinElement; currentBinElement = currentBinElement + 0.01; // Case: 100 bins TODO fix to relative } // bin-width, lower border inkl., upper border exkl. gsl_histogram_set_ranges(h, range, (numberofbins + 1)); for (int i = 0; i < numberOfElements; i++) // push vals to bins { double uvalRandom = uvalsRealDataArray[i]; gsl_histogram_increment(h, uvalRandom); } FILE * pFile; // write hist to file string fileName = "/home/joba/curhistoreal.txt"; pFile = fopen(fileName.c_str(), "w"); gsl_histogram_fprintf(pFile, h, "%g", "%g"); // paras: FILE, const gsl_histogram * h, const char * range_format, const char * bin_format fclose(pFile); //cout << " ... Histogram realData is written! \n"; // Betrachten via awk '{print $1, $3 ; print $2, $3}' /home/joba/curhisto.txt | graph -T X // or via cat /home/joba/curhisto.txt // ---- // cumulated histogram from noncumulated data, real data int numberofbinscum = bins; gsl_histogram * hcum = gsl_histogram_alloc(numberofbinscum); double rangecum[(numberofbinscum + 1)]; double currentBinElementCum = 0.0; for (int i = 0; i < (numberofbinscum + 1); i++) { rangecum[i] = currentBinElementCum; currentBinElementCum = currentBinElementCum + 0.01; // Case: 100 bins TODO fix to relative } gsl_histogram_set_ranges(hcum, rangecum, (numberofbinscum + 1)); for (int i = 0; i < numberOfElements; i++) { double uvalRandom = uvalsRealDataArray[i]; gsl_histogram_increment(hcum, uvalRandom); } double curCountsRealCum = 0.0; // summing up bins for (int i = 0; i < numberofbinscum; i++) { double lower = 0.0; // get mid of bin double upper = 0.0; gsl_histogram_get_range( hcum, i, &lower, &upper ); double classmid = (upper + lower) / 2; double curBinHits = gsl_histogram_get( hcum, i ); gsl_histogram_accumulate( hcum, classmid, curCountsRealCum ); curCountsRealCum = curCountsRealCum + curBinHits; //double curHits = gsl_histogram_get( hcum, i ); // TODO Debug-Only //cout << "Curhistocum - Klassenmitte " << classmid << ", Hits: " << curHits << endl; } FILE * pFileCum; string fileNameCum = "/home/joba/curhistocum.txt"; pFileCum = fopen(fileNameCum.c_str(), "w"); gsl_histogram_fprintf(pFileCum, hcum, "%g", "%g"); fclose(pFileCum); //cout << " ... Histogram realcum is written! \n"; // --- // grab bin mids and bin counts of one or both histograms // maybe compute theoretical gumbel-height to bin mids // -> base for Minpack-Curve-Fit and KS-Test is available // afterwards. int elementsToCompare = 100; // number of bins - elements to compare vector<double> binMidList; VecDoub nrRealVec( elementsToCompare ); // data points of experiment VecDoub nrGumbelVec( elementsToCompare ); // data points of theoretical distribution VecDoub nrRealVecCumulated( elementsToCompare ); // data points of experiment //VecDoub nrGumbelVecCumulated( elementsToCompare ); // data points of theoretical distribution for ( int i = 0; i < elementsToCompare; i++ ) { // TODO there are useless computation for example redundant mid-calcs // grab bin-mids of real data for KS-Test double currentRealClassLower = 0.0; double currentRealClassUpper = 0.0; gsl_histogram_get_range( h, i, ¤tRealClassLower, ¤tRealClassUpper ); double currentRealClassMid = (currentRealClassUpper + currentRealClassLower) / 2; binMidList.push_back( currentRealClassMid ); // for Minpack-Fit double absBinElements = gsl_histogram_get( h, i); // bin-count nrRealVec[i] = absBinElements; // / relBinsElements; // compute-y-val of gumbel-distribution for KS-test double x = currentRealClassMid; //double ydist = exp( - exp( - ( x - gumbelmue ) / gumbelbeta ) ); // cumulated gumbel dist double ydist = exp( ( -1 * ( x - gumbelmue ) ) / gumbelbeta ) * exp( -1 * ( exp( ( -1 * ( x - gumbelmue ) ) / gumbelbeta )) ) / gumbelbeta ; // gumbel-dist density double gumbelDouble = ydist * ( elements / bins ); nrGumbelVec[i] = roundDouble(gumbelDouble, 0); //cout << " -> Run - " << i << "; x - " << currentRealClassMid << "; real - " << nrRealVec[i] << "; gumbel - " << nrGumbelVec[i] << "; y-func: " << ydist << "; y-binelerel-func: " << endl; // grab bin-mids of real cumulated data for Minpack-Fit double currentRealClassLowerCumulated = 0.0; double currentRealClassUpperCumulated = 0.0; gsl_histogram_get_range( hcum, i, ¤tRealClassLowerCumulated, ¤tRealClassUpperCumulated ); double currentRealClassMidCumulated = (currentRealClassUpperCumulated + currentRealClassLowerCumulated) / 2; double absBinElementsCumulated = gsl_histogram_get( hcum, i); // bin-count nrRealVecCumulated[i] = absBinElementsCumulated; // / relBinsElements; // compute-y-val of gumbel-cum-distribution for Minpack-Fit //double xCumulated = currentRealClassMidCumulated; //double ydistCumulated = exp( - exp( - ( xCumulated - gumbelmue ) / gumbelbeta ) ); // cumulated gumbel dist //double ydist = exp( ( -1 * ( x - gumbelmue ) ) / gumbelbeta ) * exp( -1 * ( exp( ( -1 * ( x - gumbelmue ) ) / gumbelbeta )) ) / gumbelbeta ; // gumbel-dist density //double gumbelDoubleCumulated = ydistCumulated * ( elements / bins ); //nrGumbelVecCumulated[i] = roundDouble(gumbelDoubleCumulated, 5); //cout << " -> Run - " << i << "; xCumulated - " << currentRealClassMidCumulated << "; realCumulated - " << nrRealVecCumulated[i] << ";" << endl; // gumbelCumulated - " << nrGumbelVecCumulated[i] << "; y-func: " << ydistCumulated << "; y-binelerel-func: " << gumbelDoubleCumulated << endl; } // free histograms gsl_histogram_free( h ); gsl_histogram_free( hcum ); // KS-test on gumbel with mue and beta from calculation double nrDis = 0.0; double nrProb = 0.0; //cout << "V1 realData ... " << endl; //for ( int i = 0; i < nrRealVec.size(); i++ ) //{ // cout << nrRealVec[i] << ", "; //} //cout << endl; //cout << "V1 gumbelData ... " << endl; //for ( int i = 0; i < nrGumbelVec.size(); i++ ) //{ // cout << nrGumbelVec[i] << ", "; //} //cout << endl; kstwo( nrRealVec, nrGumbelVec, nrDis, nrProb ); cout << "KS-Test: Dist: " << nrDis << "; P: " << nrProb << endl; // --- // fit with Quantlib-Minpack to Gumbel-Distribution, take cumulated relative bin heights and cumulated model // model goes to fcn() in header-section of this file // Data to fit int m = 100; // number of observations or functions int n = 2; // number of parameters to fit, here mue and beta independent_variables = new double[m]; // this is the vector of independent variables, the x-values, bin-mids oberved_values = new double[m]; // this is the vector of observed variables to be fitted, the y-values of observations for( int i = 0; i < m; i++ ) { // set x and y value, x bin mid, y = realCumBinHeight independent_variables[i] = binMidList.at( i ); oberved_values[i] = nrRealVecCumulated[ i ] / 1000.0; // TODO fix to variable } double* x=new double[n]; // initial estimate of parameters vector x[0]=0.01; // mue-Guess x[1]=0.005; // beta-Guess double* fvec=new double[m]; //no need to populate double ftol=1e-08; //tolerance double xtol=1e-08; //tolerance double gtol=1e-08; //tolerance int maxfev=400; //maximum function evaluations double epsfcn=1e-08; //tolerance double* diag=new double[n]; //some internal thing int mode=1; //some internal thing double factor=1; // a default recommended value int nprint=0; //don't know what it does int info=0; //output variable int nfev=0; //output variable will store no. of function evals double* fjac=new double[m*n]; //output array of jacobian int ldfjac=m; //recommended setting int* ipvt=new int[n]; //for internal use double* qtf=new double[n]; //for internal use double* wa1=new double[n]; //for internal use double* wa2=new double[n]; //for internal use double* wa3=new double[n]; //for internal use double* wa4=new double[m]; //for internal use QuantLib::MINPACK::lmdif( m, n, x, fvec, ftol, xtol, gtol, maxfev, epsfcn, diag, mode, factor, nprint, &info, &nfev, fjac,ldfjac, ipvt, qtf, wa1, wa2, wa3, wa4); //the below is out result. compare it with the values used in fcn function double a = x[0]; double b = x[1]; cout << " mue-Minpack-Fit: " << a << endl; cout << " beta-Minpack-Fit: " << b << endl; return 0; } int main(int argc, char** argv) { fitAndTest(); return 0; }
-
LOL.
warum eigentlich der Umweg über Histogramme und nicht direkt maximum likelyhood? Und warum musste es Levenberg-Marquardt sein? Bei dem problem reicht dir auch primitiver Gradeientenabstieg.