/// <summary>
        /// Originally described at https://github.com/stevengj/nlopt/blob/master/src/api/optimize.c#L804
        /// </summary>
        private static void pre_max(int n, double[] x, int x_offset, double[] v, int v_offset, double[] vpre, int vpre_offset,
                                    object[] data, int data_offset)
        {
            f_max_data d = (f_max_data)data[data_offset];

            d.pre(n, x, x_offset, v, v_offset, vpre, vpre_offset, d.f_data, data_offset);
            for (int i = 0; i < n; ++i)
            {
                vpre[i] = -vpre[i];
            }
        }
        /// <summary>
        /// Wrapper for maximizing: just flip the sign of f and grad.
        /// Originally described at https://github.com/stevengj/nlopt/blob/master/src/api/optimize.c#L792
        /// </summary>
        private static double f_max(int n, double[] x, int x_offset, double[] grad, int grad_offset, object[] data,
                                    int data_offset)
        {
            f_max_data d   = (f_max_data)data[data_offset];
            double     val = d.f(n, x, x_offset, grad, grad_offset, d.f_data, data_offset);

            if (grad != null)
            {
                for (int i = 0; i < n; ++i)
                {
                    grad[i] = -grad[i];
                }
            }
            return(-val);
        }
        /// <summary>
        /// Originally described at https://github.com/stevengj/nlopt/blob/master/src/api/optimize.c#L813
        /// </summary>
        internal static nlopt_result nlopt_optimize(nlopt_opt opt, double[] x, int x_offset, ref double opt_f)
        {
            nlopt_func f;

            object[]      f_data;
            nlopt_precond pre;
            f_max_data    fmd = new f_max_data();
            bool          maximize;
            nlopt_result  ret;

            NLoptOptions.nlopt_unset_errmsg(opt);
            if (opt == null /*|| opt_f == null*/ || opt.f == null)
            {
                NLoptApi.RETURN_ERR(nlopt_result.NLOPT_INVALID_ARGS, opt, "NULL args to nlopt_optimize");
            }
            f      = opt.f;
            f_data = opt.f_data;
            pre    = opt.pre;

            // for maximizing, just minimize the f_max wrapper, which flips the sign of everything
            if ((maximize = opt.maximize))
            {
                fmd.f             = f;
                fmd.f_data        = f_data;
                fmd.pre           = pre;
                opt.f             = f_max;
                opt.f_data        = new object[] { fmd };
                opt.f_data_offset = 0;
                if (opt.pre != null)
                {
                    opt.pre = pre_max;
                }
                opt.stopval  = -opt.stopval;
                opt.maximize = false;
            }

            // possibly eliminate lb == ub dimensions for some algorithms
            {
                nlopt_opt elim_opt = opt;
                if (elimdim_wrapcheck(opt))
                {
                    elim_opt = elimdim_create(opt);
                    if (elim_opt == null)
                    {
                        NLoptOptions.nlopt_set_errmsg(opt, "failure allocating elim_opt");
                        ret = nlopt_result.NLOPT_OUT_OF_MEMORY;
                        goto done;
                    }
                    elimdim_shrink(opt.n, x, x_offset, opt.lb, opt.ub);
                }

                ret = nlopt_optimize_(elim_opt, x, x_offset, ref opt_f);

                if (elim_opt != opt)
                {
                    elimdim_destroy(elim_opt);
                    elimdim_expand(opt.n, x, x_offset, opt.lb, opt.ub);
                }
            }

done:
            if (maximize)  // restore original signs
            {
                opt.maximize = maximize;
                opt.stopval  = -opt.stopval;
                opt.f        = f;
                opt.f_data   = f_data;
                opt.pre      = pre;
                opt_f        = -opt_f;
            }

            return(ret);
        }