libprima / prima

PRIMA is a package for solving general nonlinear optimization problems without using derivatives. It provides the reference implementation for Powell's derivative-free optimization methods, i.e., COBYLA, UOBYQA, NEWUOA, BOBYQA, and LINCOA. PRIMA means Reference Implementation for Powell's methods with Modernization and Amelioration, P for Powell.
http://libprima.net
BSD 3-Clause "New" or "Revised" License
308 stars 43 forks source link

Alternative C API #189

Open emmt opened 7 months ago

emmt commented 7 months ago

This PR implements the alternative C API discussed here and here. The changes come in two commits:

All C examples compile and run fine with the first commit. Example are needed for the 2nd commit but it is mainly there for being discussed.

The two king of API can coexist. In fact the latter one is implemented on top of the former one and is intended to facilitate the writing of bindings in other languages without requiring to directly access C structures in these languages.

emmt commented 7 months ago

Silly me, I forgot to change the tests too (done in 7b35c05a61bde0540e36afce1c9817723141e824). I should have made 2 different PRs (one for passing structures by address, the other for the alternative API) sorry for that.

zaikunzhang commented 7 months ago

Thank you @emmt !

int prima_minimize(const prima_algorithm_t algorithm, const prima_problem_t *problem, const prima_options_t *options, prima_result_t *const result);

Why do we use pointers here for problem and options?

emmt commented 7 months ago

Why do we use pointers here for problem and options?

Because passing them by value is not necessary and is a waste of stack space. It is possible but rather unusual in C to pass structures by value (except maybe very small ones). The pointers of these are const so their contents is not supposed to be modified.

zaikunzhang commented 7 months ago

Then may I suggest

int prima_minimize(const prima_algorithm_t algorithm, const prima_problem_t *const problem, const prima_options_t *const options, prima_result_t *const result);

as neither the pointer itself nor its content is supposed to be changed in the function body?

Of course, any change to the pointer itself will be lost since itself is passed by value, but this will effectively prevent the developers (ourselves, future ourselves, and others) from making mistakes when composing or modifying the implementation of this function. I had a discussion with @amontoison about it and this was the consensus.

Thank you.

emmt commented 7 months ago

Honestly, the const qualifier in function prototypes is only useful for arrays and pointers to indicate that the corresponding content is not modified by the called function. Otherwise, it indicates that the value of the argument (not the content at that address if it is an array or a pointer) is not modified which is irrelevant for the caller and may only be relevant for the optimizer. Modern optimizers can infer this themselves and const is then just a nuisance for the caller when he/she attempt to decode the meaning of the function prototype. To add to the confusion, the const qualifier can be before or after the type. Hence, to simplify the task of understanding the function prototype for the user, I usually restrict the use of const to the former case. But I'll adapt to the consensus...

amontoison commented 7 months ago

Why do we use pointers here for problem and options?

Because passing them by value is not necessary and is a waste of stack space. It is possible but rather unusual in C to pass structures by value (except maybe very small ones). The pointers of these are const so their contents is not supposed to be modified.

Thanks for the explanation with stack space! I understand now why it's more relevant to pass structures by pointer.

For the const, we should think of a compromise between preventing he developers from making mistakes but also the readability of the headers. @emmt is more expert than me on C, we should probably follow his advice.

For the new C API, it looks good to me. 👍

zaikunzhang commented 7 months ago

To add to the confusion, the const qualifier can be before or after the type. Hence, to simplify the task of understanding the function prototype for the user, I usually restrict the use of const to the former case.

Then a user like me would need to check a documentation to make sure what the const really means, because, as you said, there are two different cases. If we use both, the user knows automatically that everything is supposed to be constant.

emmt commented 7 months ago

@zaikunzhang Except that when there is a single const for a pointer the user has to wonder whether it is the value of the pointer that remains constant (which is irrelevant for the user) or the values at the address given by the pointer that are left unchanged (which is of much importance for the caller). If you look at the prototypes of the functions of the C standard library, they follow what I suggest: reserve the const qualifier to indicate that a pointer argument is for read-only access to the pointed memory (without const, read-write access has to be assumed). You can check that this is also the case of many well known C library. This usage of the const qualifier is simpler to interpret and more clear.