/// <summary> /// Originally described at https://github.com/stevengj/nlopt/blob/master/src/api/options.c#L235. /// </summary> internal static nlopt_result nlopt_set_precond_min_objective(nlopt_opt opt, nlopt_func f, nlopt_precond pre, object[] f_data, int f_data_offset) { if (opt != null) { nlopt_unset_errmsg(opt); if (opt.munge_on_destroy != null) { opt.munge_on_destroy(opt.f_data, opt.f_data_offset); } opt.f = f; opt.f_data = f_data; opt.f_data_offset = f_data_offset; opt.pre = pre; opt.maximize = false; if (NLoptStopping.nlopt_isinf(opt.stopval) && opt.stopval > 0) { opt.stopval = double.MinValue; // switch default from max to min } return(nlopt_result.NLOPT_SUCCESS); } return(nlopt_result.NLOPT_INVALID_ARGS); }
/// <summary> /// Note that we implement a hidden feature not in the standard nlopt_minimize_constrained interface: whenever the /// constraint function returns NaN, that constraint becomes inactive. /// Originally described at /// </summary> /// <param name="n"></param> /// <param name="f"></param> /// <param name="f_data"></param> /// <param name="m"></param> /// <param name="fc"></param> /// <param name="lb">Lower bounds. Will not be modified.</param> /// <param name="ub">Upper bounds. Will not be modified.</param> /// <param name="x">On input: initial guess. On output: minimizer</param> /// <param name="minf"> /// The minimum value of the objective function: <paramref name="minf"/> = <paramref name="f"/>(<paramref name="x"/>). /// </param> /// <param name="stop"></param> /// <param name="dual_opt"></param> internal static nlopt_result mma_minimize(int n, nlopt_func f, object[] f_data, int m, nlopt_constraint[] fc, double[] lb, double[] ub, double[] x, ref double minf, nlopt_stopping stop, nlopt_opt dual_opt) { // Translated from https://github.com/stevengj/nlopt/blob/master/src/algs/mma/mma.c#L145 nlopt_result ret = nlopt_result.NLOPT_SUCCESS; double rho, fcur; double[] xcur, sigma, dfdx, dfdx_cur, xprev, xprevprev; double[] dfcdx, dfcdx_cur; double[] fcval, fcval_cur, rhoc, gcval, y, dual_lb, dual_ub; int i, ifc, j, k = 0; dual_data dd; bool feasible; double infeasibility; int mfc; m = NLoptStopping.nlopt_count_constraints(mfc = m, fc); if (NLoptApi.nlopt_get_dimension(dual_opt) != m) { NLoptStopping.nlopt_stop_msg(stop, string.Format("dual optimizer has wrong dimension %d != %d", NLoptApi.nlopt_get_dimension(dual_opt), m)); return(nlopt_result.NLOPT_INVALID_ARGS); } try { // The original source malloc'd a large array sigma and set the rest as offsets. sigma = new double[n]; dfdx = new double[n]; dfdx_cur = new double[n]; xcur = new double[n]; xprev = new double[n]; xprevprev = new double[n]; fcval = new double[m]; fcval_cur = new double[m]; rhoc = new double[m]; gcval = new double[m]; dual_lb = new double[m]; dual_ub = new double[m]; y = new double[m]; dfcdx = new double[m * n]; dfcdx_cur = new double[m * n]; } catch (OutOfMemoryException) { return(nlopt_result.NLOPT_OUT_OF_MEMORY); } dd = new dual_data(); dd.n = n; dd.x = x; dd.lb = lb; dd.ub = ub; dd.sigma = sigma; dd.dfdx = dfdx; dd.dfcdx = dfcdx; dd.fcval = fcval; dd.rhoc = rhoc; dd.xcur = xcur; dd.gcval = gcval; for (j = 0; j < n; ++j) { if (NLoptStopping.nlopt_isinf(ub[j]) || NLoptStopping.nlopt_isinf(lb[j])) { sigma[j] = 1.0; // arbitrary default } else { sigma[j] = 0.5 * (ub[j] - lb[j]); } } rho = 1.0; for (i = 0; i < m; ++i) { rhoc[i] = 1.0; dual_lb[i] = y[i] = 0.0; dual_ub[i] = double.MaxValue; // Originally dual_ub[i] = HUGE_VAL; } dd.fval = fcur = minf = f(n, x, 0, dfdx, 0, f_data, 0); ++stop.nevals_p; Array.Copy(x, xcur, n); if (NLoptStopping.nlopt_stop_forced(stop)) { ret = nlopt_result.NLOPT_FORCED_STOP; return(ret); } feasible = true; infeasibility = 0; for (i = ifc = 0; ifc < mfc; ++ifc) { NLoptStopping.nlopt_eval_constraint(fcval, i, dfcdx, i * n, fc, ifc, n, x, 0); i += fc[ifc].m; if (NLoptStopping.nlopt_stop_forced(stop)) { ret = nlopt_result.NLOPT_FORCED_STOP; return(ret); } } for (i = 0; i < m; ++i) { feasible = feasible && (fcval[i] <= 0 || NLoptStopping.nlopt_isnan(fcval[i])); if (fcval[i] > infeasibility) { infeasibility = fcval[i]; } } // For non-feasible initial points, set a finite (large) upper-bound on the dual variables. What this means is that, // if no feasible solution is found from the dual problem, it will minimize the dual objective with the unfeasible // constraint weighted by 1e40 -- basically, minimizing the unfeasible constraint until it becomes feasible or until // we at least obtain a step towards a feasible point. Svanberg suggested a different approach in his 1987 paper, // basically introducing additional penalty variables for unfeasible constraints, but this is easier to implement // and at least as efficient. if (!feasible) { for (i = 0; i < m; ++i) { dual_ub[i] = 1e40; } } NLoptOptions.nlopt_set_min_objective(dual_opt, dual_func, new object[] { dd }, 0); NLoptOptions.nlopt_set_lower_bounds(dual_opt, dual_lb); NLoptOptions.nlopt_set_upper_bounds(dual_opt, dual_ub); NLoptApi.nlopt_set_stopval(dual_opt, double.MinValue); NLoptOptions.nlopt_remove_inequality_constraints(dual_opt); NLoptOptions.nlopt_remove_equality_constraints(dual_opt); while (true) //outer iterations { double fprev = fcur; if (NLoptStopping.nlopt_stop_forced(stop)) { ret = nlopt_result.NLOPT_FORCED_STOP; } else if (NLoptStopping.nlopt_stop_evals(stop)) { ret = nlopt_result.NLOPT_MAXEVAL_REACHED; } else if (NLoptStopping.nlopt_stop_time(stop)) { ret = nlopt_result.NLOPT_MAXTIME_REACHED; } else if (feasible && (minf < stop.minf_max)) { ret = nlopt_result.NLOPT_MINF_MAX_REACHED; } if (ret != nlopt_result.NLOPT_SUCCESS) { return(ret); } if (++k > 1) { Array.Copy(xprev, xprevprev, n); } Array.Copy(xcur, xprev, n); while (true) //inner iterations { double min_dual = double.NaN; double infeasibility_cur; bool feasible_cur, inner_done; int save_verbose; bool new_infeasible_constraint; nlopt_result reti; // Solve dual problem dd.rho = rho; dd.count = 0; save_verbose = mma_verbose; mma_verbose = 0; // no recursive verbosity reti = NLoptOptimize.nlopt_optimize_limited(dual_opt, y, 0, ref min_dual, 0, stop.maxtime - (stop.watch.Elapsed.TotalSeconds - stop.start)); mma_verbose = save_verbose; if (reti < 0 || reti == nlopt_result.NLOPT_MAXTIME_REACHED) { ret = reti; return(ret); } dual_func(m, y, 0, null, 0, new object[] { dd }, 0); // evaluate final xcur etc. if (mma_verbose == 1) { Console.WriteLine("MMA dual converged in %d iterations to g=%g:", dd.count, dd.gval); for (i = 0; i < Math.Min(mma_verbose, m); ++i) { Console.WriteLine(" MMA y[%u]=%g, gc[%u]=%g\n", i, y[i], i, dd.gcval[i]); } } fcur = f(n, xcur, 0, dfdx_cur, 0, f_data, 0); ++stop.nevals_p; if (NLoptStopping.nlopt_stop_forced(stop)) { ret = nlopt_result.NLOPT_FORCED_STOP; return(ret); } feasible_cur = true; infeasibility_cur = 0; new_infeasible_constraint = false; inner_done = dd.gval >= fcur; for (i = ifc = 0; ifc < mfc; ++ifc) { NLoptStopping.nlopt_eval_constraint(fcval_cur, i, dfcdx_cur, i * n, fc, ifc, n, xcur, 0); i += fc[ifc].m; if (NLoptStopping.nlopt_stop_forced(stop)) { ret = nlopt_result.NLOPT_FORCED_STOP; return(ret); } } for (i = ifc = 0; ifc < mfc; ++ifc) { int i0 = i, inext = i + fc[ifc].m; for (; i < inext; ++i) { if (!NLoptStopping.nlopt_isnan(fcval_cur[i])) { feasible_cur = feasible_cur && (fcval_cur[i] <= fc[ifc].tol[i - i0]); if (!NLoptStopping.nlopt_isnan(fcval[i])) { inner_done = inner_done && (dd.gcval[i] >= fcval_cur[i]); } else if (fcval_cur[i] > 0) { new_infeasible_constraint = true; } if (fcval_cur[i] > infeasibility_cur) { infeasibility_cur = fcval_cur[i]; } } } } if ((fcur < minf && (inner_done || feasible_cur || !feasible)) || (!feasible && infeasibility_cur < infeasibility)) { if (mma_verbose == 1 && !feasible_cur) { Console.WriteLine("MMA - using infeasible point?"); } dd.fval = minf = fcur; infeasibility = infeasibility_cur; Array.Copy(fcval_cur, fcval, m); Array.Copy(xcur, x, n); Array.Copy(dfdx_cur, dfdx, n); Array.Copy(dfcdx_cur, dfcdx, n * m); // once we have reached a feasible solution, the algorithm should never make the solution infeasible // again (if inner_done), although the constraints may be violated slightly by rounding errors etc. so we // must be a little careful about checking feasibility if (infeasibility_cur == 0) { if (!feasible) // reset upper bounds to infin. { for (i = 0; i < m; ++i) { dual_ub[i] = double.MaxValue; } NLoptOptions.nlopt_set_upper_bounds(dual_opt, dual_ub); } feasible = true; } else if (new_infeasible_constraint) { feasible = false; } } if (NLoptStopping.nlopt_stop_forced(stop)) { ret = nlopt_result.NLOPT_FORCED_STOP; } else if (NLoptStopping.nlopt_stop_evals(stop)) { ret = nlopt_result.NLOPT_MAXEVAL_REACHED; } else if (NLoptStopping.nlopt_stop_time(stop)) { ret = nlopt_result.NLOPT_MAXTIME_REACHED; } else if (feasible && minf < stop.minf_max) { ret = nlopt_result.NLOPT_MINF_MAX_REACHED; } if (ret != nlopt_result.NLOPT_SUCCESS) { return(ret); } if (inner_done) { break; } if (fcur > dd.gval) { rho = Math.Min(10 * rho, 1.1 * (rho + (fcur - dd.gval) / dd.wval)); } for (i = 0; i < m; ++i) { if (!NLoptStopping.nlopt_isnan(fcval_cur[i]) && fcval_cur[i] > dd.gcval[i]) { rhoc[i] = Math.Min(10 * rhoc[i], 1.1 * (rhoc[i] + (fcval_cur[i] - dd.gcval[i]) / dd.wval)); } } if (mma_verbose == 1) { Console.WriteLine("MMA inner iteration: rho -> %g", rho); } for (i = 0; i < Math.Min(mma_verbose, m); ++i) { Console.WriteLine(" MMA rhoc[%u] -> %g", i, rhoc[i]); } } if (NLoptStopping.nlopt_stop_ftol(stop, fcur, fprev)) { ret = nlopt_result.NLOPT_FTOL_REACHED; } if (NLoptStopping.nlopt_stop_x(stop, xcur, xprev)) { ret = nlopt_result.NLOPT_XTOL_REACHED; } if (ret != nlopt_result.NLOPT_SUCCESS) { return(ret); } /* update rho and sigma for iteration k+1 */ rho = Math.Max(0.1 * rho, MMA_RHOMIN); if (mma_verbose == 1) { Console.WriteLine("MMA outer iteration: rho -> %g", rho); } for (i = 0; i < m; ++i) { rhoc[i] = Math.Max(0.1 * rhoc[i], MMA_RHOMIN); } for (i = 0; i < Math.Min(mma_verbose, m); ++i) { Console.WriteLine(" MMA rhoc[%u] -> %g", i, rhoc[i]); } if (k > 1) { for (j = 0; j < n; ++j) { double dx2 = (xcur[j] - xprev[j]) * (xprev[j] - xprevprev[j]); double gam = dx2 < 0 ? 0.7 : (dx2 > 0 ? 1.2 : 1); sigma[j] *= gam; if (!NLoptStopping.nlopt_isinf(ub[j]) && !NLoptStopping.nlopt_isinf(lb[j])) { sigma[j] = Math.Min(sigma[j], 10 * (ub[j] - lb[j])); sigma[j] = Math.Max(sigma[j], 0.01 * (ub[j] - lb[j])); } } for (j = 0; j < Math.Min(mma_verbose, n); ++j) { Console.WriteLine(" MMA sigma[%u] -> %g\n", j, sigma[j]); } } } }