package edu.stanford.nlp.optimization;
import edu.stanford.nlp.util.logging.Redwood;
import java.util.function.Function;
import edu.stanford.nlp.util.Generics;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Map;
import java.util.Arrays;
/**
* A class to do golden section line search. Should it implement Minimizer? Prob. not.
*
* @author Galen Andrew
*/
public class GoldenSectionLineSearch implements LineSearcher {
/** A logger for this class */
private static Redwood.RedwoodChannels log = Redwood.channels(GoldenSectionLineSearch.class);
private static final double GOLDEN_RATIO = (1.0 + Math.sqrt(5.0)) / 2.0;
private static final double GOLDEN_SECTION = (GOLDEN_RATIO / (1.0 + GOLDEN_RATIO));
private static boolean VERBOSE = true;
private Map<Double, Double> memory = Generics.newHashMap(); //remember where it was called and what were the values
private boolean geometric;
private double tol;
private double low;
private double high;
public GoldenSectionLineSearch(double tol, double low, double high) {
this(false, tol, low, high);
}
public GoldenSectionLineSearch(double tol, double low, double high, boolean verbose) {
this(false, tol, low, high, verbose);
}
public GoldenSectionLineSearch(boolean geometric) {
this(geometric, 1e-4, 1e-2, 10);
}
public GoldenSectionLineSearch(boolean geometric, double tol, double low, double high) {
this.geometric = geometric;
this.tol = tol;
this.low = low;
this.high = high;
}
public GoldenSectionLineSearch(boolean geometric, double tol, double low, double high, boolean verbose) {
this.geometric = geometric;
this.tol = tol;
this.low = low;
this.high = high;
GoldenSectionLineSearch.VERBOSE = verbose;
}
private static final NumberFormat nf = new DecimalFormat("0.000");
public double minimize(Function<Double, Double> function, double tol, double low, double high) {
this.tol = tol;
this.low = low;
this.high = high;
return minimize(function);
}
public double minimize(Function<Double, Double> function) {
double tol = this.tol;
double low = this.low;
double high = this.high;
// cdm Oct 2006: The code used to do nothing to find or check
// the validity of an initial
// bracketing; it just blindly placed the midpoint at the golden ratio
// I now try to grid search a little in case the function is very flat
// (RTE contradictions).
double flow = function.apply(low);
double fhigh = function.apply(high);
if (VERBOSE) {
log.info("Finding min between " + low + " (value: " +
flow + ") and " + high + " (value: " + fhigh + ")");
}
double mid;
double oldY;
boolean searchRight;
if (false) {
// initialize with golden means
mid = goldenMean(low, high);
oldY = function.apply(mid);
if (VERBOSE) log.info("Initially probed at " + mid + ", value is " + oldY);
if (oldY < flow || oldY < fhigh) {
searchRight = false; // Galen had this true; should be false
} else {
mid = goldenMean(high, low);
oldY = function.apply(mid);
if (VERBOSE) log.info("Probed at " + mid + ", value is " + oldY);
searchRight = true;
if ( ! (oldY < flow || oldY < fhigh)) {
log.info("Warning: GoldenSectionLineSearch init didn't find slope!!");
}
}
} else {
// grid search a little; this case doesn't do geometric differently...
if (VERBOSE) log.info("20 point gridsearch for good mid point....");
double bestPoint = low;
double bestVal = flow;
double incr = (high - low)/22.0;
for (mid = low+incr; mid < high; mid += incr) {
oldY = function.apply(mid);
if (VERBOSE) log.info("Probed at " + mid + ", value is " + oldY);
if (oldY < bestVal) {
bestPoint = mid;
bestVal = oldY;
if (VERBOSE) log.info(" [best so far!]");
}
if (VERBOSE) log.info();
}
mid = bestPoint;
oldY = bestVal;
searchRight = mid < low + (high - low)/2.0;
if (oldY < flow && oldY < fhigh) {
if (VERBOSE) log.info("Found a good mid point at (" + mid + ", " + oldY + ")");
} else {
log.info("Warning: GoldenSectionLineSearch grid search couldn't find slope!!");
// revert to initial positioning and pray
mid = goldenMean(low, high);
oldY = function.apply(mid);
searchRight = false;
}
}
memory.put(mid, oldY);
while (geometric ? (high / low > 1 + tol) : high - low > tol) {
if (VERBOSE) log.info("Current low, mid, high: " + nf.format(low) + " " + nf.format(mid) + " " + nf.format(high));
double newX = goldenMean(searchRight ? high : low, mid);
double newY = function.apply(newX);
memory.put(newX, newY);
if (VERBOSE) log.info("Probed " + (searchRight ? "right": "left") + " at " + newX + ", value is " + newY);
if (newY < oldY) {
// keep going in this direction
if (searchRight) low = mid; else high = mid;
mid = newX;
oldY = newY;
} else {
// go the other way
if (searchRight) high = newX; else low = newX;
searchRight = !searchRight;
}
}
return mid;
}
/**
* dump the <x,y> pairs it computed found
*/
public void dumpMemory() {
Double[] keys = memory.keySet().toArray(new Double[memory.keySet().size()]);
Arrays.sort(keys);
for (Double key : keys) {
log.info(key + "\t" + memory.get(key));
}
}
public void discretizeCompute(Function<Double, Double> function, int numPoints, double low, double high) {
double inc = (high - low) / numPoints;
memory = Generics.newHashMap();
for (int i = 0; i < numPoints; i++) {
double x = low + i * inc;
double y = function.apply(x);
memory.put(x, y);
log.info("for point " + x + "\t" + y);
}
dumpMemory();
}
/**
* The point that is the GOLDEN_SECTION along the way from a to b.
* a may be less or greater than b, you find the point 60-odd percent
* of the way from a to b.
*
* @param a Interval minimum
* @param b Interval maximum
* @return The GOLDEN_SECTION along the way from a to b.
*/
private double goldenMean(double a, double b) {
if (geometric) {
return a * Math.pow(b / a, GOLDEN_SECTION);
} else {
return a + (b - a) * GOLDEN_SECTION;
}
}
public static void main(String[] args) {
GoldenSectionLineSearch min =
new GoldenSectionLineSearch(true, 0.00001, 0.001, 121.0);
Function<Double, Double> f1 = x -> Math.log(x * x - x + 1);
System.out.println(min.minimize(f1));
System.out.println();
min = new GoldenSectionLineSearch(false, 0.00001, 0.0, 1.0);
Function<Double,Double> f2 = x -> x < 0.1 ? 0.0: (x > 0.2 ? 0.0: (x - 0.1) * (x - 0.2));
System.out.println(min.minimize(f2));
} // end main
}