Skip to content

Commit

Permalink
Merge pull request libprima#124 from nbelakovski/python_binding
Browse files Browse the repository at this point in the history
Python bindings and profiles. Thank @nbelakovski for the huge efforts! This is a major milestone!
  • Loading branch information
zaikunzhang authored Apr 30, 2024
2 parents 5070380 + 9e3c257 commit 8aa15f3
Show file tree
Hide file tree
Showing 47 changed files with 3,897 additions and 34 deletions.
66 changes: 66 additions & 0 deletions .github/actions/spelling/allow.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2052,6 +2052,7 @@ constrc
execstack
FUNPTR
PROCPOINTER
PYTHONPATH
cfun
cobj
NVHPC
Expand Down Expand Up @@ -2190,6 +2191,71 @@ nosplash
noopengl
ogfile
nend
capfd
cibuildwheel
dtype
dummybaseobject
GIL
maxcv
myargs
ndarray
newfun
NEWPYTHON
nfev
nlconstrlist
nlcs
pybind
pypa
pystr
rtol
scikit
ucrt
whl
xlist
ARCHS
CIBW
cibw
rtools
amd
edgeitems
printoptions
maxfev
testname
skipif
outerr
libprimac
libprimaf
libprimafc
htmlcov
delocate
broadcastable
autoselection
auditwheel
Cbuild
Ceditable
asarray
jac
lstsq
nanmax
nanmin
prepdfo
rcond
subcases
newconstraint
inear
PYCUTEST
pycutest
excludelist
slsqp
optiprofiler
manylinux
Opti
pathspec
itemsize
tcpclient
tomemoryview
scm
nuj
PARKCH
TESTQUAD
WAITALL
61 changes: 61 additions & 0 deletions .github/workflows/build_python.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Build python wheels

on: [push, pull_request]

jobs:
build_wheels:
name: Build wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-20.04, windows-2019, macos-11]

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Get tags for use with git describe

- name: Checkout pybind11 submodule
run: git submodule update --init python/pybind11

- uses: fortran-lang/setup-fortran@main
if: ${{ runner.os == 'macOS' }}
with:
compiler: gcc
version: 8

# Copied from https://github.com/scipy/scipy/blob/main/.github/workflows/wheels.yml
- name: win_amd64 - install rtools
run: |
# mingw-w64
choco install rtools -y --no-progress --force --version=4.0.0.20220206
echo "c:\rtools40\ucrt64\bin;" >> $env:GITHUB_PATH
if: ${{ runner.os == 'Windows' }}

- name: Build wheels
uses: pypa/[email protected]

- uses: actions/upload-artifact@v4
with:
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
path: ./wheelhouse/*.whl

- uses: actions/upload-artifact@v4
with:
name: coverage-report-${{ matrix.os }}-${{ strategy.job-index }}
path: ./prima_htmlcov


build_sdist:
name: Build source distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Build sdist
run: pipx run build --sdist

- uses: actions/upload-artifact@v4
with:
name: cibw-sdist
path: dist/*.tar.gz
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ install/
build/
include/
!c/include/
!python/pybind11/include/
lib/
mod/

Expand Down Expand Up @@ -108,6 +109,7 @@ parts/
sdist/
var/
wheels/
wheelhouse
share/python-wheels/
*.egg-info/
.installed.cfg
Expand Down Expand Up @@ -227,3 +229,6 @@ cython_debug/

# Mac files
.DS_Store

# Version file
_version.txt
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
[submodule ".development"]
path = .development
url = [email protected]:libprima/prima_development.git
[submodule "python/pybind11"]
path = python/pybind11
url = https://github.com/pybind/pybind11
49 changes: 40 additions & 9 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -79,28 +79,59 @@ endif ()

# Get the version number
find_package(Git)
set(IS_REPO FALSE)
if(GIT_EXECUTABLE)
# --always means git describe will output the commit hash if no tags are found
# This is usually the case for forked repos since they do not clone tags by default.
execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --always --dirty
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_VARIABLE PRIMA_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE)
else()
# If git is not available, that may indicate we are building on macports which
# downloads the bundle from github (which uses git archive) and so the version
# number should be in .git-archival.txt
file(STRINGS .git-archival.txt PRIMA_VERSION)
if(PRIMA_VERSION MATCHES "describe")
message(WARNING "No git detected and .git-archival.txt does not contain a version number")
set(PRIMA_VERSION "unknown")
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE GIT_RESULT ERROR_QUIET)
if (GIT_RESULT EQUAL 0)
set(IS_REPO TRUE)
endif()
endif()
if(NOT GIT_EXECUTABLE OR NOT IS_REPO)
# If git is not available, or this isn't a repo, that may indicate we are building
# on macports which downloads the bundle from github (which uses git archive) and
# so the version number should be in .git-archival.txt.
# Alternatively it might mean that we're building the Python bindings, in which case
# the version is output in _version.txt. I know, it's complicated. I don't make the rules.
if(EXISTS _version.txt)
file(STRINGS _version.txt PRIMA_VERSION)
else()
file(STRINGS .git-archival.txt PRIMA_VERSION)
if(PRIMA_VERSION MATCHES "describe")
message(WARNING "No git detected and .git-archival.txt does not contain a version number")
set(PRIMA_VERSION "unknown")
endif()
endif()

endif()
# Remove the leading v from PRIMA_VERSION, if it contains one.
string(REGEX REPLACE "^v" "" PRIMA_VERSION ${PRIMA_VERSION})
message(STATUS "Setting PRIMA version to ${PRIMA_VERSION}")

option (PRIMA_ENABLE_PYTHON "Python binding" OFF)
if (PRIMA_ENABLE_PYTHON)
if(NOT PRIMA_ENABLE_C)
message(FATAL_ERROR "Building Python bindings requires C bindings. Please turn on PRIMA_ENABLE_C")
endif()
if(BUILD_SHARED_LIBS)
# This will include libprimaf, libprimafc, and libprimac into the compiled Python binding, removing the need
# to properly set the rpath or find those libraries at runtime.
# Even if we did make it successfully build with shared libraries, delocate/auditwheel will copy them into the
# bindings anyway
message(FATAL_ERROR "Building Python bindings requires static libraries. Please disable BUILD_SHARED_LIBS")
endif()
if(NOT CMAKE_Fortran_COMPILER_ID MATCHES "GNU")
message(WARNING "Compiling Python bindings with compilers other than GNU has not been tested and no support is planned at this time")
endif()
enable_language(CXX)
add_subdirectory(python)
endif ()

install(
TARGETS primaf ${primac_target}
EXPORT prima-targets
Expand Down
7 changes: 6 additions & 1 deletion c/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ set_target_properties(primac PROPERTIES POSITION_INDEPENDENT_CODE ON C_STANDARD

if (NOT BUILD_SHARED_LIBS)
target_compile_definitions(primac PUBLIC PRIMAC_STATIC)
target_link_libraries (primac INTERFACE ${CMAKE_Fortran_IMPLICIT_LINK_LIBRARIES})
# The line below caused issues when compiling prima_pybind on Windows with MinGW. We get errors
# about multiple definition of unwind_resume due to the inclusion of gcc_s.
# It's unclear why this was added as initial reason given in the issue (108) seems to work fine for me
if (NOT WIN32)
target_link_libraries (primac INTERFACE ${CMAKE_Fortran_IMPLICIT_LINK_LIBRARIES})
endif()
endif ()

# Export symbols on Windows. See more comments in fortran/CMakeLists.txt.
Expand Down
12 changes: 7 additions & 5 deletions c/include/prima/prima.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,7 @@ typedef enum {
PRIMA_NULL_X0 = 112,
PRIMA_NULL_RESULT = 113,
PRIMA_NULL_FUNCTION = 114,
PRIMA_PROBLEM_SOLVER_MISMATCH_NONLINEAR_CONSTRAINTS = 115,
PRIMA_PROBLEM_SOLVER_MISMATCH_LINEAR_CONSTRAINTS = 116,
PRIMA_PROBLEM_SOLVER_MISMATCH_BOUNDS = 117,
PRIMA_RESULT_INITIALIZED = 115,
} prima_rc_t;


Expand Down Expand Up @@ -234,7 +232,7 @@ typedef struct {
// of sqrt(machine epsilon) will be used.
double ctol;

// data: user data, will be passed through the objective function callback
// data: user data, will be passed through the objective function
// Default: NULL
void *data;

Expand Down Expand Up @@ -272,7 +270,7 @@ typedef struct {
int nf;

// status: return code
int status;
prima_rc_t status;

// message: exit message
const char *message;
Expand Down Expand Up @@ -300,6 +298,10 @@ PRIMAC_API
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);


// Function to check if PRIMA returned normally or ran into abnormal conditions
PRIMAC_API
bool prima_is_success(const prima_result_t result);

#ifdef __cplusplus
}
#endif
Expand Down
28 changes: 9 additions & 19 deletions c/prima.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


#include "prima/prima.h"
#include <limits.h>
#include <float.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
Expand Down Expand Up @@ -65,16 +65,6 @@ 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)
{
if (algorithm != PRIMA_COBYLA && (problem.calcfc || problem.nlconstr0 || problem.m_nlcon > 0))
return PRIMA_PROBLEM_SOLVER_MISMATCH_NONLINEAR_CONSTRAINTS;

if ((algorithm != PRIMA_COBYLA && algorithm != PRIMA_LINCOA) &&
(problem.m_ineq > 0 || problem.m_eq > 0 || problem.Aineq || problem.bineq || problem.Aeq || problem.beq))
return PRIMA_PROBLEM_SOLVER_MISMATCH_LINEAR_CONSTRAINTS;

if ((algorithm != PRIMA_COBYLA && algorithm != PRIMA_LINCOA && algorithm != PRIMA_BOBYQA) && (problem.xl || problem.xu))
return PRIMA_PROBLEM_SOLVER_MISMATCH_BOUNDS;

if (!problem.x0)
return PRIMA_NULL_X0;

Expand All @@ -100,10 +90,10 @@ prima_rc_t prima_init_result(prima_result_t *const result, const prima_problem_t
result->cstrv = NAN;

// nf: number of function evaluations
result->nf = INT_MIN;
result->nf = 0;

// status: return code
result->status = INT_MIN;
result->status = PRIMA_RESULT_INITIALIZED;

// message: exit message
result->message = NULL;
Expand Down Expand Up @@ -192,12 +182,6 @@ const char *prima_get_rc_string(const prima_rc_t rc)
return "NULL result";
case PRIMA_NULL_FUNCTION:
return "NULL function";
case PRIMA_PROBLEM_SOLVER_MISMATCH_NONLINEAR_CONSTRAINTS:
return "Nonlinear constraints were provided for an algorithm that cannot handle them";
case PRIMA_PROBLEM_SOLVER_MISMATCH_LINEAR_CONSTRAINTS:
return "Linear constraints were provided for an algorithm that cannot handle them";
case PRIMA_PROBLEM_SOLVER_MISMATCH_BOUNDS:
return "Bounds were provided for an algorithm that cannot handle them";
default:
return "Invalid return code";
}
Expand Down Expand Up @@ -283,3 +267,9 @@ prima_rc_t prima_minimize(const prima_algorithm_t algorithm, const prima_problem

return info;
}

bool prima_is_success(const prima_result_t result)
{
return (result.status == PRIMA_SMALL_TR_RADIUS ||
result.status == PRIMA_FTARGET_ACHIEVED) && (result.cstrv <= sqrt(DBL_EPSILON));
}
Loading

0 comments on commit 8aa15f3

Please sign in to comment.