| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 | <?php// Levenberg-Marquardt in PHP// http://www.idiom.com/~zilla/Computer/Javanumeric/LM.javaclass LevenbergMarquardt {	/**	 * Calculate the current sum-squared-error	 *	 * Chi-squared is the distribution of squared Gaussian errors,	 * thus the name.	 *	 * @param double[][] $x	 * @param double[] $a	 * @param double[] $y,	 * @param double[] $s,	 * @param object $f	 */	function chiSquared($x, $a, $y, $s, $f) {		$npts = count($y);		$sum = 0.0;		for ($i = 0; $i < $npts; ++$i) {			$d = $y[$i] - $f->val($x[$i], $a);			$d = $d / $s[$i];			$sum = $sum + ($d*$d);		}		return $sum;	}	//	function chiSquared()	/**	 * Minimize E = sum {(y[k] - f(x[k],a)) / s[k]}^2	 * The individual errors are optionally scaled by s[k].	 * Note that LMfunc implements the value and gradient of f(x,a),	 * NOT the value and gradient of E with respect to a!	 *	 * @param x array of domain points, each may be multidimensional	 * @param y corresponding array of values	 * @param a the parameters/state of the model	 * @param vary false to indicate the corresponding a[k] is to be held fixed	 * @param s2 sigma^2 for point i	 * @param lambda blend between steepest descent (lambda high) and	 *	jump to bottom of quadratic (lambda zero).	 * 	Start with 0.001.	 * @param termepsilon termination accuracy (0.01)	 * @param maxiter	stop and return after this many iterations if not done	 * @param verbose	set to zero (no prints), 1, 2	 *	 * @return the new lambda for future iterations.	 *  Can use this and maxiter to interleave the LM descent with some other	 *  task, setting maxiter to something small.	 */	function solve($x, $a, $y, $s, $vary, $f, $lambda, $termepsilon, $maxiter, $verbose) {		$npts = count($y);		$nparm = count($a);		if ($verbose > 0) {			print("solve x[".count($x)."][".count($x[0])."]");			print(" a[".count($a)."]");			println(" y[".count(length)."]");		}		$e0 = $this->chiSquared($x, $a, $y, $s, $f);		//double lambda = 0.001;		$done = false;		// g = gradient, H = hessian, d = step to minimum		// H d = -g, solve for d		$H = array();		$g = array();		//double[] d = new double[nparm];		$oos2 = array();		for($i = 0; $i < $npts; ++$i) {			$oos2[$i] = 1./($s[$i]*$s[$i]);		}		$iter = 0;		$term = 0;	// termination count test		do {			++$iter;			// hessian approximation			for( $r = 0; $r < $nparm; ++$r) {				for( $c = 0; $c < $nparm; ++$c) {					for( $i = 0; $i < $npts; ++$i) {						if ($i == 0) $H[$r][$c] = 0.;						$xi = $x[$i];						$H[$r][$c] += ($oos2[$i] * $f->grad($xi, $a, $r) * $f->grad($xi, $a, $c));					}  //npts				} //c			} //r			// boost diagonal towards gradient descent			for( $r = 0; $r < $nparm; ++$r)				$H[$r][$r] *= (1. + $lambda);			// gradient			for( $r = 0; $r < $nparm; ++$r) {				for( $i = 0; $i < $npts; ++$i) {					if ($i == 0) $g[$r] = 0.;					$xi = $x[$i];					$g[$r] += ($oos2[$i] * ($y[$i]-$f->val($xi,$a)) * $f->grad($xi, $a, $r));				}			} //npts			// scale (for consistency with NR, not necessary)			if ($false) {				for( $r = 0; $r < $nparm; ++$r) {					$g[$r] = -0.5 * $g[$r];					for( $c = 0; $c < $nparm; ++$c) {						$H[$r][$c] *= 0.5;					}				}			}			// solve H d = -g, evaluate error at new location			//double[] d = DoubleMatrix.solve(H, g);//			double[] d = (new Matrix(H)).lu().solve(new Matrix(g, nparm)).getRowPackedCopy();			//double[] na = DoubleVector.add(a, d);//			double[] na = (new Matrix(a, nparm)).plus(new Matrix(d, nparm)).getRowPackedCopy();//			double e1 = chiSquared(x, na, y, s, f);//			if (verbose > 0) {//				System.out.println("\n\niteration "+iter+" lambda = "+lambda);//				System.out.print("a = ");//				(new Matrix(a, nparm)).print(10, 2);//				if (verbose > 1) {//					System.out.print("H = ");//					(new Matrix(H)).print(10, 2);//					System.out.print("g = ");//					(new Matrix(g, nparm)).print(10, 2);//					System.out.print("d = ");//					(new Matrix(d, nparm)).print(10, 2);//				}//				System.out.print("e0 = " + e0 + ": ");//				System.out.print("moved from ");//				(new Matrix(a, nparm)).print(10, 2);//				System.out.print("e1 = " + e1 + ": ");//				if (e1 < e0) {//					System.out.print("to ");//					(new Matrix(na, nparm)).print(10, 2);//				} else {//					System.out.println("move rejected");//				}//			}			// termination test (slightly different than NR)//			if (Math.abs(e1-e0) > termepsilon) {//				term = 0;//			} else {//				term++;//				if (term == 4) {//					System.out.println("terminating after " + iter + " iterations");//					done = true;//				}//			}//			if (iter >= maxiter) done = true;			// in the C++ version, found that changing this to e1 >= e0			// was not a good idea.  See comment there.			////			if (e1 > e0 || Double.isNaN(e1)) { // new location worse than before//				lambda *= 10.;//			} else {		// new location better, accept new parameters//				lambda *= 0.1;//				e0 = e1;//				// simply assigning a = na will not get results copied back to caller//				for( int i = 0; i < nparm; i++ ) {//					if (vary[i]) a[i] = na[i];//				}//			}		} while(!$done);		return $lambda;	}	//	function solve()}	//	class LevenbergMarquardt
 |