IPOPT with C++ functions in ASL

Hi, I’m planning to write a corresponding ASL structural adapter function for a C++ function I’ve written as an external function. Then I compile it into a .dll file, call it via ExternalFunction in Pyomo and solve it with IPOPT.

I’m now experiencing the following error message from IPOPT. It may seem a bit confusing.

Ipopt 3.14.13: ERROR: Solver (ipopt) returned non-zero return code (3221226505)
ERROR: See the solver log above for diagnostic information.
Error during solving: Solver (ipopt) did not exit normally

Here is the C++ function.

double Srk::CalcMassEntropyLiquidMixture(
        std::vector<double> &molefraction,
        double &temperature,
    	double &pressure)

Here’s the adapter function I write that corresponds to the ASL structure.

real calc_mass_entropy_liquid_mixture(arglist *al) {
    /*
     *I have no idea how to get the variable of vector type from
     *arglist, so I ask GPT for help. It seems that it doesn't work.
     */
    if (al->n != 3) {
        al->Errmsg = (char *) "calc_mass_entropy_liquid_mixture expects 3 arguments";
        return 0.0;
    }
    
    std::vector<double> molefraction(al->ra[al->at[0]], al->ra[al->at[0]] + al->nr[al->at[0]]);	//This may be incorrect, I think. 
    double temperature = al->ra[al->at[1]];
    double pressure = al->ra[al->at[2]];

    double entropy = Srk::CalcMassEntropyLiquidMixture(molefraction, temperature, pressure);
    return entropy;  // return answer
}


void funcadd(AmplExports *ae){
    int t = FUNCADD_REAL_VALUED;
    addfunc("calc_mass_entropy_liquid_mixture", (rfunc)calc_mass_entropy_liquid_mixture, t, 3, NULL);
}

Here’s the pyomo part.

def create_model():
    m = pyo.ConcreteModel()
    m.name = 'Example 1: Eason'
    m.z = pyo.Var(range(3), domain=pyo.Reals, initialize=2.)
    m.x = pyo.Var(range(3), initialize=2.)
    m.temperature = pyo.Var(initialize=260.0)
    m.pressure = pyo.Var(initialize=1823850)
    m.x[0] = 0.3
    m.x[1] = 0.3
    m.x[2] = 0.4
    m.ext_fcn = pyo.ExternalFunction(library='myfunctions.dll', function='calc_mass_entropy_liquid_mixture')	#Here calls the imported function
    m.obj = pyo.Objective(
        expr=(m.z[0]-1.0)**2 + (m.z[0]-m.z[1])**2 + (m.z[2]-1.0)**2 + (m.x[0]-1.0)**4 + (m.x[1]-1.0)**6) #
    m.c1 = pyo.Constraint(
        expr=100 + m.ext_fcn(m.x, m.temperature, m.pressure) == 2*pyo.sqrt(2.0))	#it becomes a part of the constraints here
    m.c2 = pyo.Constraint(expr=m.z[2]**4 * m.z[1]**2 + m.z[1] == 8+pyo.sqrt(2.0))
    return m
model = create_model()
solver = pyo.SolverFactory('ipopt')

try:
    result = solver.solve(model, tee=True)
    print("Solver status:", result.solver.status)
    print("Solver termination condition:", result.solver.termination_condition)
except Exception as e:
    print("Error during solving:", e)

My questions are:

  • Now that one of the arguments of my function is in the form of a one-dimensional vector, how can I get the variable of vector type from *arglist?

  • Can I leave out the derivative and Hessian for this function and let IPOPT get them by approximating it itself?

For your first question: Assuming that the arglist has the molefraction values followed by temperature and pressure, the following works (it has been compiled and tested); it is what you are looking for?

int n = al->n;
std::vector<double> molefraction = {al->ra, al->ra + n - 2};
double temperature = al->ra[n-2];
double pressure = al->ra[n-1];

Also, the example at https://github.com/ampl/plugins/tree/master/src/amplp/examples/heuristic might help.

For your second question, some options for approximating derivatives are described at https://coin-or.github.io/Ipopt/OPTIONS.html. However, you may need to use an ipopt.opt file to get these to work; for additional help, you can ask Ipopt questions at https://github.com/coin-or/Ipopt/discussions.

Thank you very much for your reply, it helps me a lot and now I have no more questions on the ASL part, thanks again.