
public class OneVarMinimizer
{
  private OneVarFn fn;
  public double xBest;
  public double vBest;
  public double xWorst;
  public double vWorst;
  public String oper;
  public int nIter = 0;
  public int nFnEval = 0;
  private Object user_data;

  public OneVarMinimizer( OneVarFn f )
  {
    fn = f;
  }

  public double eval( double x )
  {
    nFnEval++;
    return fn.eval( x, user_data );
  }

  private void nmSort()
  {
    if( vBest > vWorst ) {
      double tmp;
      tmp = xBest;
      xBest = xWorst;
      xWorst = tmp;
      tmp = vBest;
      vBest = vWorst;
      vWorst = tmp;
    }
  }

  public void nmInit( double a, double b, Object user_data  )
  {
    nFnEval = 0;
    nIter = 0;
    this.user_data = user_data;
    xBest = a;
    xWorst = b;
    vBest = eval( xBest );
    vWorst = eval( xWorst );
    nmSort();
    oper = "INIT";
  }

  public void nmIter()
  {
    double r, vr, t, vt;
    double displ;
    displ = xBest - xWorst;
    r = xBest + displ;
    vr = eval( r );
    if( vr < vBest ) {
      /* Try Expansion */
      t = r + displ;
      vt = eval( t );
      if( vt < vr ) {
        /* Accept Expansion */
        xWorst = t;
        vWorst = vt;
        oper = "EXPANSION";
      } else {
        /* Keep Reflection */
        xWorst = r;
        vWorst = vr;
        oper = "REFLECTION";
      }
    } else if( vr > vWorst ) {
      /* Try Inside Contraction */
      t = xBest - 0.5 * displ;
      vt = eval( t );
      if( vt < vWorst ) {
        /* Accept Inside Contraction */
        xWorst = t;
        vWorst = vt;
        oper = "INSIDE_CONTRACTION";
      } else {
        /* Shrink. Shrink Coefficient = 0.25  */
        t = xBest - 0.25 * displ;
        vt = eval( t );
        xWorst = t;
        vWorst = vt;
        oper = "SHRINK";
      }
    } else {
      /* Try Outside Contraction */
      t = xBest + 0.5 * displ;
      vt = eval( t );
      if( vt < vWorst ) {
        /* Accept Outside Contraction */
        xWorst = t;
        vWorst = vt;
        oper = "OUTSIDE_CONTRACTION";
      } else {
        /* Shrink. Shrink Coefficient = 0.25  */
        t = xBest - 0.25 * displ;
        vt = eval( t );
        xWorst = t;
        vWorst = vt;
        oper = "SHRINK";
      }
    }
    nmSort();
    nIter++;
  }

  public boolean minimize( double a, double h,
                     Object user_data, double tol, int max_iter  )
  {
    nmInit( a, a+h, user_data );
    for( ; nIter < max_iter; ) {
      nmIter();
      if( Math.abs( xWorst - xBest ) < tol )
        return true;
    }
    return false;
  }
  public boolean minimize( double a, double h, Object user_data )
  {
    return minimize( a, h, user_data, 1e-5, 100 );
  }
}
