diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 846c7874f1..fa8c6af96b 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -2259,5 +2259,6 @@ nuj PARKCH TESTQUAD WAITALL +timeinfo xcodebuild ICX diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 70c3636ceb..d9cacf14ec 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -73,6 +73,10 @@ jobs: # 2. On windows-latest, we want to use '-Wall -W5 -Werror-all' as what we do for the intel-classic # compiler, but the (new) intel c compiler does not recognize '-W5 -Werror-all', even though the # official documentation of the compiler mentions them. Why? + # Comments on flags of `cl`: + # -wd4820 disables the warning about "... bytes padding added after data member" + # -wd4464 disables the warning about "relative include path contains '..'" + # -wd4710 disables the warning about "... function is not inlined" - os: windows-latest toolchain: {compiler: intel, version: '2023.2', cflags: '-Wall -Werror', fflags: '/warn:all /debug:extended /Z7 /fimplicit-none /standard-semantics'} - os: windows-latest @@ -80,7 +84,7 @@ jobs: - os: windows-latest toolchain: {compiler: intel, version: '2024.1', cflags: '-Wall -Werror', fflags: '/warn:all /debug:extended /Z7 /fimplicit-none /standard-semantics'} - os: windows-latest - toolchain: {compiler: intel, version: '2024.1', cflags: '-Wall -W4 -WX', fflags: '/warn:all /debug:extended /Z7 /fimplicit-none /standard-semantics', cc: cl} + toolchain: {compiler: intel, version: '2024.1', cflags: '-wd4820 -wd4464 -wd4710 -Wall -W4 -WX', fflags: '/warn:all /debug:extended /Z7 /fimplicit-none /standard-semantics /assume:recursion', cc: cl} # N.B.: As of 20240401, setup-fortran fails constantly with windows-latest and intel-classic # 2021.8. Thus this combination is not included. - os: windows-latest @@ -88,7 +92,7 @@ jobs: - os: windows-latest toolchain: {compiler: intel-classic, version: '2021.10', cflags: '-Qdiag-disable:10441 -Wall -W5 -Werror-all', fflags: '/warn:all /debug:extended /Z7 /fimplicit-none /standard-semantics'} - os: windows-latest - toolchain: {compiler: intel-classic, version: '2021.10', cflags: '-Wall -W4 -WX', fflags: '/warn:all /debug:extended /Z7 /fimplicit-none /standard-semantics', cc: cl} + toolchain: {compiler: intel-classic, version: '2021.10', cflags: '-wd4820 -wd4464 -wd4710 -Wall -W4 -WX', fflags: '/warn:all /debug:extended /Z7 /fimplicit-none /standard-semantics /assume:recursion', cc: cl} steps: diff --git a/c/include/prima/prima.h b/c/include/prima/prima.h index 9893108747..172e15e545 100644 --- a/c/include/prima/prima.h +++ b/c/include/prima/prima.h @@ -73,11 +73,6 @@ typedef enum { } prima_rc_t; -// Function to get the message string corresponding to a return code -PRIMAC_API -const char *prima_get_rc_string(const prima_rc_t rc); - - /* * Objective function required by UOBYQA, NEWUOA, BOBYQA, and LINCOA * x : on input, the vector of variables (should not be modified) diff --git a/c/include/prima/prima_internal.h b/c/include/prima/prima_internal.h new file mode 100644 index 0000000000..f4b8f26e78 --- /dev/null +++ b/c/include/prima/prima_internal.h @@ -0,0 +1,30 @@ +#ifndef PRIMA_INTERNAL_H +#define PRIMA_INTERNAL_H + +#include "prima/prima.h" + +// Functions implemented in Fortran (*_c.f90) +int cobyla_c(const int m_nlcon, const prima_objcon_t calcfc, const void *data, const int n, double x[], double *const f, double *const cstrv, double nlconstr[], + const int m_ineq, const double Aineq[], const double bineq[], + const int m_eq, const double Aeq[], const double beq[], + const double xl[], const double xu[], + const double f0, const double nlconstr0[], + int *const nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int iprint, const double ctol, + const prima_callback_t callback, int *const info); + +int bobyqa_c(prima_obj_t calfun, const void *data, const int n, double x[], double *const f, const double xl[], const double xu[], + int *const nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int npt, const int iprint, const prima_callback_t callback, int *const info); + +int newuoa_c(prima_obj_t calfun, const void *data, const int n, double x[], double *const f, + int *const nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int npt, const int iprint, const prima_callback_t callback, int *const info); + +int uobyqa_c(prima_obj_t calfun, const void *data, const int n, double x[], double *const f, + int *const nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int iprint, const prima_callback_t callback, int *const info); + +int lincoa_c(prima_obj_t calfun, const void *data, const int n, double x[], double *const f, + double *const cstrv, const int m_ineq, const double Aineq[], const double bineq[], + const int m_eq, const double Aeq[], const double beq[], const double xl[], const double xu[], + int *const nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int npt, const int iprint, const double ctol, + const prima_callback_t callback, int *const info); + +#endif diff --git a/c/prima.c b/c/prima.c index 5d380971c1..e6fce82711 100644 --- a/c/prima.c +++ b/c/prima.c @@ -1,8 +1,8 @@ // Dedicated to the late Professor M. J. D. Powell FRS (1936--2015). -#include "prima/prima.h" -#include +#include "prima/prima_internal.h" +#include // This provides DBL_EPSILON, which will be removed once ctol is introduced #include #include #include @@ -63,7 +63,7 @@ prima_rc_t prima_init_options(prima_options_t *const options) // Function to check whether the problem matches the algorithm -prima_rc_t prima_check_problem(const prima_problem_t problem, const prima_algorithm_t algorithm) +static prima_rc_t prima_check_problem(const prima_problem_t problem, const prima_algorithm_t algorithm) { if (!problem.x0) return PRIMA_NULL_X0; @@ -75,8 +75,62 @@ prima_rc_t prima_check_problem(const prima_problem_t problem, const prima_algori } +// Function to get the string corresponding to the return code +static const char *prima_get_rc_string(const prima_rc_t rc) +{ + switch (rc) { + case PRIMA_SMALL_TR_RADIUS: + return "Trust region radius reaches its lower bound"; + case PRIMA_FTARGET_ACHIEVED: + return "The target function value is reached"; + case PRIMA_TRSUBP_FAILED: + return "A trust region step failed to reduce the model"; + case PRIMA_MAXFUN_REACHED: + return "Maximum number of function evaluations reached"; + case PRIMA_MAXTR_REACHED: + return "Maximum number of trust region iterations reached"; + case PRIMA_NAN_INF_X: + return "The input X contains NaN of Inf"; + case PRIMA_NAN_INF_F: + return "The objective or constraint functions return NaN or +Inf"; + case PRIMA_NAN_INF_MODEL: + return "NaN or Inf occurs in the model"; + case PRIMA_NO_SPACE_BETWEEN_BOUNDS: + return "No space between bounds"; + case PRIMA_DAMAGING_ROUNDING: + return "Rounding errors are becoming damaging"; + case PRIMA_ZERO_LINEAR_CONSTRAINT: + return "One of the linear constraints has a zero gradient"; + case PRIMA_CALLBACK_TERMINATE: + return "Callback function requested termination of optimization"; + case PRIMA_INVALID_INPUT: + return "Invalid input"; + case PRIMA_ASSERTION_FAILS: + return "Assertion fails"; + case PRIMA_VALIDATION_FAILS: + return "Validation fails"; + case PRIMA_MEMORY_ALLOCATION_FAILS: + return "Memory allocation fails"; + case PRIMA_NULL_OPTIONS: + return "NULL options"; + case PRIMA_NULL_PROBLEM: + return "NULL problem"; + case PRIMA_NULL_X0: + return "NULL x0"; + case PRIMA_NULL_RESULT: + return "NULL result"; + case PRIMA_NULL_FUNCTION: + return "NULL function"; + case PRIMA_RESULT_INITIALIZED: + return "Result is initialized but not properly set"; + default: + return "Invalid return code"; + } +} + + // Function to initialize the result -prima_rc_t prima_init_result(prima_result_t *const result, const prima_problem_t problem) +static prima_rc_t prima_init_result(prima_result_t *const result, const prima_problem_t problem) { if (!result) return PRIMA_NULL_RESULT; @@ -96,7 +150,7 @@ prima_rc_t prima_init_result(prima_result_t *const result, const prima_problem_t result->status = PRIMA_RESULT_INITIALIZED; // message: exit message - result->message = NULL; + result->message = prima_get_rc_string(result->status); // x: returned point result->x = (double*)malloc(problem.n * sizeof(double)); @@ -136,94 +190,17 @@ prima_rc_t prima_free_result(prima_result_t *const result) } -// Function to get the string corresponding to the return code -const char *prima_get_rc_string(const prima_rc_t rc) -{ - switch (rc) { - case PRIMA_SMALL_TR_RADIUS: - return "Trust region radius reaches its lower bound"; - case PRIMA_FTARGET_ACHIEVED: - return "The target function value is reached"; - case PRIMA_TRSUBP_FAILED: - return "A trust region step failed to reduce the model"; - case PRIMA_MAXFUN_REACHED: - return "Maximum number of function evaluations reached"; - case PRIMA_MAXTR_REACHED: - return "Maximum number of trust region iterations reached"; - case PRIMA_NAN_INF_X: - return "The input X contains NaN of Inf"; - case PRIMA_NAN_INF_F: - return "The objective or constraint functions return NaN or +Inf"; - case PRIMA_NAN_INF_MODEL: - return "NaN or Inf occurs in the model"; - case PRIMA_NO_SPACE_BETWEEN_BOUNDS: - return "No space between bounds"; - case PRIMA_DAMAGING_ROUNDING: - return "Rounding errors are becoming damaging"; - case PRIMA_ZERO_LINEAR_CONSTRAINT: - return "One of the linear constraints has a zero gradient"; - case PRIMA_CALLBACK_TERMINATE: - return "Callback function requested termination of optimization"; - case PRIMA_INVALID_INPUT: - return "Invalid input"; - case PRIMA_ASSERTION_FAILS: - return "Assertion fails"; - case PRIMA_VALIDATION_FAILS: - return "Validation fails"; - case PRIMA_MEMORY_ALLOCATION_FAILS: - return "Memory allocation fails"; - case PRIMA_NULL_OPTIONS: - return "NULL options"; - case PRIMA_NULL_PROBLEM: - return "NULL problem"; - case PRIMA_NULL_X0: - return "NULL x0"; - case PRIMA_NULL_RESULT: - return "NULL result"; - case PRIMA_NULL_FUNCTION: - return "NULL function"; - default: - return "Invalid return code"; - } -} - - -// Functions implemented in Fortran (*_c.f90) -int cobyla_c(const int m_nlcon, const prima_objcon_t calcfc, const void *data, const int n, double x[], double *const f, double *const cstrv, double nlconstr[], - const int m_ineq, const double Aineq[], const double bineq[], - const int m_eq, const double Aeq[], const double beq[], - const double xl[], const double xu[], - const double f0, const double nlconstr0[], - int *const nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int iprint, const double ctol, - const prima_callback_t callback, int *const info); - -int bobyqa_c(prima_obj_t calfun, const void *data, const int n, double x[], double *const f, const double xl[], const double xu[], - int *const nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int npt, const int iprint, const prima_callback_t callback, int *const info); - -int newuoa_c(prima_obj_t calfun, const void *data, const int n, double x[], double *const f, - int *const nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int npt, const int iprint, const prima_callback_t callback, int *const info); - -int uobyqa_c(prima_obj_t calfun, const void *data, const int n, double x[], double *const f, - int *const nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int iprint, const prima_callback_t callback, int *const info); - -int lincoa_c(prima_obj_t calfun, const void *data, const int n, double x[], double *const f, - double *const cstrv, const int m_ineq, const double Aineq[], const double bineq[], - const int m_eq, const double Aeq[], const double beq[], const double xl[], const double xu[], - int *const nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int npt, const int iprint, const double ctol, - const prima_callback_t callback, int *const info); - - // The function that does the minimization using a PRIMA solver prima_rc_t prima_minimize(const prima_algorithm_t algorithm, const prima_problem_t problem, const prima_options_t options, prima_result_t *const result) { - prima_rc_t info = prima_init_result(result, problem); + int info = (int) prima_init_result(result, problem); if (info == PRIMA_RC_DFT) - info = prima_check_problem(problem, algorithm); + info = (int) prima_check_problem(problem, algorithm); if (info == PRIMA_RC_DFT) { // We copy x0 into result->x only after prima_check_problem has succeeded, - // so that if prima_check_problem failed, result->x will not contained a + // so that if prima_check_problem failed, result->x will not contain a // seemingly valid value. for (int i = 0; i < problem.n; i++) { result->x[i] = problem.x0[i]; @@ -258,16 +235,18 @@ prima_rc_t prima_minimize(const prima_algorithm_t algorithm, const prima_problem break; default: - return PRIMA_INVALID_INPUT; + info = (int) PRIMA_INVALID_INPUT; } } - result->status = info; - result->message = prima_get_rc_string(info); + result->status = (prima_rc_t) info; + result->message = prima_get_rc_string(result->status); - return info; + return result->status; } + +// The function that checks whether the result is "successful" bool prima_is_success(const prima_result_t result) { return (result.status == PRIMA_SMALL_TR_RADIUS || diff --git a/c/tests/stress.c b/c/tests/stress.c index d66d685046..c8c0bc164a 100644 --- a/c/tests/stress.c +++ b/c/tests/stress.c @@ -14,6 +14,15 @@ #define M_INEQ_MAX 1000 #define M_NLCON 200 +// Thread-safe version of localtime +// N.B.: localtime_s is typically associated with Microsoft's C runtime library while localtime_r +// is typically associated with POSIX-compliant systems; they have different argument orders. +#ifdef _WIN32 +#define localtime_safe(a, b) localtime_s(a, b) +#else +#define localtime_safe(a, b) localtime_r(b, a) +#endif + int n = 0; int m_ineq = 0; const double alpha = 4.0; @@ -61,12 +70,14 @@ static void fun_con(const double x[], double *const f, double constr[], const vo } // A function generating a seed that alters weekly -unsigned int get_random_seed(void) { +static unsigned int get_random_seed(void) +{ // Set the random seed to year/week char buf[10] = {0}; time_t t = time(NULL); - struct tm *tmp = localtime(&t); - int rc = strftime(buf, 10, "%y%W", tmp); + struct tm timeinfo; + localtime_safe(&timeinfo, &t); + size_t rc = strftime(buf, 10, "%y%W", &timeinfo); if (!rc) return 42; else @@ -87,7 +98,7 @@ int main(int argc, char * argv[]) printf("Debug = %d\n", debug); unsigned int seed = get_random_seed(); - printf("Random seed = %d\n", seed); + printf("Random seed = %u\n", seed); srand(seed); // Set up the options