Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 75 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
# To use a specific compiler:
#set(CMAKE_C_COMPILER "gcc-7")
#set(CMAKE_CXX_COMPILER "/usr/bin/g++-7")

#set(CMAKE_C_COMPILER "clang-18")
#set(CMAKE_CXX_COMPILER "clang++-18")

project(codac VERSION ${VERSION} LANGUAGES CXX)

if(NOT VERSION_ID)
Expand Down Expand Up @@ -81,8 +83,79 @@
if(MSVC)
add_compile_options(/W4)
else()
add_compile_options(-Wall -Wextra -Wpedantic)
add_compile_options(-Wall -Wextra -Wpedantic )
endif()

if(CMAKE_BUILD_TYPE STREQUAL "Debug")
add_compile_options(-Wreturn-type -Wno-unused-parameter)
add_compile_options( -fno-omit-frame-pointer -g -O0 )
add_compile_options( -fsanitize=address -fsanitize=undefined)
add_link_options(-fsanitize=address,undefined)
endif()



# FMA (fused multiply-add) is only ever activated here through an explicit
# compiler flag, and only when check_cxx_compiler_flag() confirms it is
# actually recognized. This matters because "the FMA flag" is not a
# portable concept:
# - x86/x86_64 (GCC/Clang): -mfma enables the FMA3 ISA extension.
# - x86/x64 (MSVC): there is no dedicated FMA flag; /arch:AVX2 implies it.
# - AArch64 (ARMv8-A, e.g. regular Raspberry Pi OS 64-bit): FMA
# (FMADD/FMLA) is part of the base ISA, scalar and NEON alike, so no
# flag exists or is needed; -mfma is simply unknown to the compiler
# there, the check below fails, and the flag is skipped (the compiler
# still emits FMA instructions on its own when it can).
# - ARMv7-A / 32-bit ARMv8-A (e.g. a Raspberry Pi 2 in AArch32, or a
# Raspberry Pi 3/4 running a 32-bit OS: the silicon is ARMv8-A but the
# 32-bit userland reports itself as armv7l or armv8l): FMA hardware is
# optional on ARMv7-A but mandatory on ARMv8-A even in its 32-bit
# execution state, and in both cases it is exposed the same way, via
# the NEON-VFPv4 FPU extension, activated by
# -mfpu=neon-vfpv4 -mfloat-abi=hard (plus -ffp-contract=fast, since
# C++ does not permit fusing a*b+c into FMA by default). -mfma itself
# is not a recognized flag on any 32-bit ARM target, so this is
# handled in its own branch below rather than falling through to the
# generic GCC/Clang case.
# - ARMv6 hard-float (e.g. Raspbian Bullseye armv6hf, Raspberry Pi
# Zero/1): the FPU predates fused multiply-add support (FMA was only
# introduced with VFPv4/ARMv7-A), so there is no equivalent flag to
# fall back to either; the check fails and we correctly get plain
# (unfused) multiply+add on that target.
# In all unsupported cases the check below simply leaves the flag out,
# which is the correct behavior rather than a build failure.
# CODAC_ARCH_CXX_FLAGS collects whichever of the flags below actually got
# applied, so that it can be re-exported as part of CODAC_CXX_FLAGS in the
# generated codac-config.cmake (see src/CMakeLists.txt). Without this,
# downstream consumers (examples, user projects built via find_package(CODAC))
# compile Eigen's header-only templates with different alignment/instruction-set
# assumptions than the ones baked into the prebuilt static libraries they link
# against -- an ODR/ABI mismatch that Eigen documents as unsafe, and that in
# practice corrupts the heap (mismatched aligned_malloc/aligned_free paths).
set(CODAC_ARCH_CXX_FLAGS "")

include(CheckCXXCompilerFlag)
if(MSVC)
check_cxx_compiler_flag("/arch:AVX2" COMPILER_SUPPORTS_FMA)
if(COMPILER_SUPPORTS_FMA)
add_compile_options(/arch:AVX2)
set(CODAC_ARCH_CXX_FLAGS "/arch:AVX2")
endif()
elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang" AND CMAKE_SYSTEM_PROCESSOR MATCHES "^armv[7-9]")
check_cxx_compiler_flag("-mfpu=neon-vfpv4 -mfloat-abi=hard" COMPILER_SUPPORTS_FMA)
if(COMPILER_SUPPORTS_FMA)
add_compile_options(-mfpu=neon-vfpv4 -mfloat-abi=hard -ffp-contract=fast)
set(CODAC_ARCH_CXX_FLAGS "-mfpu=neon-vfpv4 -mfloat-abi=hard -ffp-contract=fast")
endif()
elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
check_cxx_compiler_flag("-mfma" COMPILER_SUPPORTS_FMA)
if(COMPILER_SUPPORTS_FMA)
add_compile_options(-mfma )
set(CODAC_ARCH_CXX_FLAGS "-mfma")
endif()
endif()



# Temporary attempts to fix errors similar to:
# _ number of sections exceeded object file format limit.
Expand Down
20 changes: 11 additions & 9 deletions examples/ellipsoid_example/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,27 @@

# Adding Eigen3

# In case you installed Eigen3 in a local directory, you need
# to specify its path with the CMAKE_PREFIX_PATH option, e.g.
# set(CMAKE_PREFIX_PATH "~/eigen/build_install")

find_package(Eigen3 3.4 REQUIRED NO_MODULE)
message(STATUS "Found Eigen3 version ${Eigen3_VERSION}")
# Codac builds and installs its own Eigen3 (see CODAC_INCLUDE_DIRS below),
# so no separate find_package(Eigen3) is needed -- and none would find it,
# since that build isn't registered as a system-discoverable package.
# Searching for an unrelated system Eigen3 here would risk pulling in a
# different version than the one codac-core/codac-graphics were compiled
# against, which is exactly the kind of ABI mismatch that broke examples
# like 01_batman (see the -mfma / CODAC_CXX_FLAGS fix above in this history).

# Adding Codac

# In case you installed Codac in a local directory, you need
# In case you installed Codac in a local directory, you need
# to specify its path with the CMAKE_PREFIX_PATH option.
set(CMAKE_PREFIX_PATH "~/Documents/Code_these/codac/build_install")
# set(CMAKE_PREFIX_PATH "~/codac/build_install")

find_package(CODAC REQUIRED)
message(STATUS "Found Codac version ${CODAC_VERSION}")


# Compilation

add_executable(${PROJECT_NAME} main.cpp)
target_compile_options(${PROJECT_NAME} PUBLIC ${CODAC_CXX_FLAGS})
target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC ${CODAC_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} PUBLIC ${CODAC_LIBRARIES} Ibex::ibex Eigen3::Eigen)
target_link_libraries(${PROJECT_NAME} PUBLIC ${CODAC_LIBRARIES} Ibex::ibex)
4 changes: 3 additions & 1 deletion python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
# Adds pybind11::headers, pybind11::module, pybind11::embed

set(PYTHON_PACKAGE_NAME ${PROJECT_NAME})
set(PYTHON_PACKAGE_DIR "${CMAKE_CURRENT_BINARY_DIR}/python_package")
set(PYTHON_PACKAGE_DIR "${CMAKE_CURRENT_BINARY_DIR}/python_package"
CACHE INTERNAL "Codac Python package build directory"
)

file(MAKE_DIRECTORY ${PYTHON_PACKAGE_DIR})
execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/${PYTHON_PACKAGE_NAME}/" "${PYTHON_PACKAGE_DIR}/${PYTHON_PACKAGE_NAME}")
Expand Down
18 changes: 18 additions & 0 deletions python/src/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,24 @@
PRIVATE ${PROJECT_NAME}-core ${PROJECT_NAME}-sympy ${LIBS} Ibex::ibex
)

# --------------------------------------------------------------
# Sanitizer runtime as a shared library (Debug builds only)
# --------------------------------------------------------------
#
# _core.so is loaded via dlopen() by an unsanitized python
# interpreter. By default Clang/GCC embed the ASan/UBSan runtime
# statically into shared libraries, which leaves symbols like
# __ubsan_vptr_type_cache or __asan_* unresolved at dlopen() time
# (python has no sanitizer runtime of its own to provide them).
# -shared-libsan makes _core.so depend on the sanitizer runtime as
# a proper shared library instead; that runtime is then LD_PRELOAD-ed
# for the python tests (see tests/CMakeLists.txt).
if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
target_compile_options(_core PRIVATE -shared-libsan)
target_link_options(_core PRIVATE -shared-libsan)
endif()


# Copy the generated library in the package folder
add_custom_command(TARGET _core POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_FILE:_core>" "${PYTHON_PACKAGE_DIR}/${PYTHON_PACKAGE_NAME}"
Expand Down
2 changes: 1 addition & 1 deletion python/src/core/contractors/codac2_py_CtcFixpoint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ void export_CtcFixpoint(py::module& m, py::class_<CtcBase<IntervalVector>,pyCtcI
exported

.def(py::init(
[](const pyCtcIntervalVector& c, double ratio)
[](const CtcBase<IntervalVector>& c, double ratio)
{
return std::make_unique<CtcFixpoint>(c.copy(),ratio);
}),
Expand Down
2 changes: 1 addition & 1 deletion python/src/core/contractors/codac2_py_CtcLazy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ void export_CtcLazy(py::module& m, py::class_<CtcBase<IntervalVector>,pyCtcInter
exported

.def(py::init(
[](const pyCtcIntervalVector& c)
[](const CtcBase<IntervalVector>& c)
{
return std::make_unique<CtcLazy>(c.copy());
}),
Expand Down
2 changes: 1 addition & 1 deletion python/src/core/contractors/codac2_py_CtcNot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ void export_CtcNot(py::module& m, py::class_<CtcBase<IntervalVector>,pyCtcInterv
exported

.def(py::init(
[](const pyCtcIntervalVector& c)
[](const CtcBase<IntervalVector>& c)
{
return std::make_unique<CtcNot>(c.copy());
}),
Expand Down
2 changes: 2 additions & 0 deletions python/src/core/domains/tube/codac2_py_Slice.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@ py::class_<Slice<T>> export_Slice(py::module& m, const std::string& name)
// Methods from class SliceBase

.def("t0_tf", &Slice<T>::t0_tf,
py::return_value_policy::reference_internal,
CONST_INTERVAL_REF_SLICEBASE_T0_TF_CONST)

.def("tslice", &Slice<T>::tslice,
py::return_value_policy::reference_internal,
CONST_TSLICE_REF_SLICEBASE_TSLICE_CONST)

// Methods from class Slice<T>
Expand Down
6 changes: 4 additions & 2 deletions python/src/core/domains/tube/codac2_py_SlicedTube.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,11 @@ py::class_<SlicedTube<T>,TubeBase> export_SlicedTube(py::module& m, const std::s
py::return_value_policy::reference,
SHARED_PTR_SLICE_T_SLICEDTUBE_T_LAST_SLICE)

.def("slice", [](SlicedTube<T>& x, std::shared_ptr<TSlice> it) -> Slice<T>&
.def("slice", [](SlicedTube<T>& x, TSlice& it) -> Slice<T>&
{
return *x.slice(it);
return *x.slice(
std::shared_ptr<TSlice>(&it, [](TSlice*) {})
);
},
py::return_value_policy::reference,
SHARED_PTR_SLICE_T_SLICEDTUBE_T_SLICE_SHARED_PTR_TSLICE,
Expand Down
12 changes: 7 additions & 5 deletions python/src/core/domains/tube/codac2_py_TDomain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,21 @@ void export_TDomain(py::module& m)
.def("tslices_vector", &TDomain::tslices_vector,
VECTOR_TSLICE_TDOMAIN_TSLICES_VECTOR_CONST)

.def("tslice", [](TDomain& tdomain, double t) -> std::shared_ptr<TSlice>
.def("tslice", [](TDomain& tdomain, double t) -> TSlice&
{
auto it = tdomain.tslice(t);
return std::shared_ptr<TSlice>(&(*it), [](TSlice*){});
return *it;
},
LIST_TSLICE_ITERATOR_TDOMAIN_TSLICE_DOUBLE
py::return_value_policy::reference_internal,
LIST_TSLICE_ITERATOR_TDOMAIN_TSLICE_DOUBLE,
"t"_a)

.def("sample", [](TDomain& tdomain, double t, bool with_gate) -> std::shared_ptr<TSlice>
.def("sample", [](TDomain& tdomain, double t, bool with_gate) -> TSlice&
{
auto it = tdomain.sample(t, with_gate);
return std::shared_ptr<TSlice>(&(*it), [](TSlice*){});
return *it;
},
py::return_value_policy::reference_internal,
LIST_TSLICE_ITERATOR_TDOMAIN_SAMPLE_DOUBLE_BOOL,
"t"_a, "with_gate"_a=false)

Expand Down
6 changes: 3 additions & 3 deletions python/src/core/domains/tube/codac2_py_tube_cart_prod.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ void export_tube_cart_prod(py::module& m)
Index n = 0;
for(const auto& li : l)
{
assert_release(is_instance<SlicedTube<Interval>>(li) | is_instance<SlicedTube<IntervalVector>>(li));
assert_release(is_instance<SlicedTube<Interval>>(li) || is_instance<SlicedTube<IntervalVector>>(li));
n += is_instance<SlicedTube<Interval>>(li) ? 1 : cast<SlicedTube<IntervalVector>>(li).size();
}

assert_release(is_instance<SlicedTube<Interval>>(*l.begin()) | is_instance<SlicedTube<IntervalVector>>(*l.begin()));
assert_release(is_instance<SlicedTube<Interval>>(*l.begin()) || is_instance<SlicedTube<IntervalVector>>(*l.begin()));

std::shared_ptr<TDomain> tdomain =
is_instance<SlicedTube<Interval>>(*l.begin()) ? cast<SlicedTube<Interval>>(*l.begin()).tdomain() : cast<SlicedTube<IntervalVector>>(*l.begin()).tdomain();
Expand All @@ -42,7 +42,7 @@ void export_tube_cart_prod(py::module& m)
Index i = 0;
for(const auto& li : l)
{
assert_release(is_instance<SlicedTube<Interval>>(li) | is_instance<SlicedTube<IntervalVector>>(li));
assert_release(is_instance<SlicedTube<Interval>>(li) || is_instance<SlicedTube<IntervalVector>>(li));
IntervalVector si = cart_prod(
is_instance<SlicedTube<Interval>>(li) ? cart_prod(cast<SlicedTube<Interval>>(li).slice(it)->codomain()) :
cast<SlicedTube<IntervalVector>>(li).slice(it)->codomain());
Expand Down
6 changes: 3 additions & 3 deletions python/src/core/matrices/codac2_py_MatrixBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -261,12 +261,12 @@ void export_MatrixBase(py::module& m, py::class_<S>& pyclass)
DOC_TO_BE_DEFINED,
"nb_rows"_a, "nb_cols"_a)

.def("resize_save_values", [](S& x, Index_type nb_rows, Index_type nb_cols)
.def("conservativeResize", [](S& x, Index_type nb_rows, Index_type nb_cols)
{
matlab::test_integer(nb_rows, nb_cols);
x.resize_save_values(nb_rows, nb_cols);
x.conservativeResize(nb_rows, nb_cols);
},
MATRIX_ADDONS_MATRIXBASE_VOID_RESIZE_SAVE_VALUES_INDEX_INDEX,
"Resize the matrix while preserving the existing coefficients.",
"nb_rows"_a, "nb_cols"_a)

.def_static("zero", [](Index_type r, Index_type c)
Expand Down
6 changes: 3 additions & 3 deletions python/src/core/matrices/codac2_py_VectorBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,12 @@ void export_VectorBase([[maybe_unused]] py::module& m, py::class_<S>& pyclass)
DOC_TO_BE_DEFINED,
"n"_a)

.def("resize_save_values", [](S& x, Index_type n)
.def("conservativeResize", [](S& x, Index_type n)
{
matlab::test_integer(n);
x.resize_save_values(n);
x.conservativeResize(n);
},
MATRIX_ADDONS_VECTORBASE_VOID_RESIZE_SAVE_VALUES_INDEX,
"Resize the vector while preserving the existing coefficients.",
"n"_a)

.def("put", [](S& x, Index_type start_id, const S& x1)
Expand Down
29 changes: 26 additions & 3 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,21 @@

find_package(IBEX REQUIRED)

# codac-core links PUBLIC against Threads::Threads (see src/core/CMakeLists.txt),
# e.g. for peibos/threading code. That requirement doesn't come back through
# find_library() above (a plain path has no transitive usage requirements),
# so it has to be re-declared here -- otherwise any downstream target that
# actually pulls in that code (such as the 11_peibos example) fails to link
# with an undefined reference to pthread_create.
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)

set(CODAC_VERSION ${PROJECT_VERSION})
set(CODAC_LIBRARIES \${CODAC_CORE_LIBRARY} \${CODAC_GRAPHICS_LIBRARY} \${CODAC_UNSUPPORTED_LIBRARY} Ibex::ibex)
set(CODAC_LIBRARIES \${CODAC_CORE_LIBRARY} \${CODAC_GRAPHICS_LIBRARY} \${CODAC_UNSUPPORTED_LIBRARY} Ibex::ibex Threads::Threads)
set(CODAC_INCLUDE_DIRS \${CODAC_CORE_INCLUDE_DIR}/../ \${CODAC_CORE_INCLUDE_DIR}/../eigen3/ \${CODAC_CORE_INCLUDE_DIR} \${CODAC_GRAPHICS_INCLUDE_DIR} \${CODAC_UNSUPPORTED_INCLUDE_DIR})

set(CODAC_C_FLAGS \"\")
set(CODAC_CXX_FLAGS \"\")
set(CODAC_CXX_FLAGS \"${CODAC_ARCH_CXX_FLAGS}\")
")

if(WITH_PYTHON)
Expand Down Expand Up @@ -104,7 +113,21 @@
PATH_SUFFIXES include/${PROJECT_NAME}-capd)
find_library(CODAC_CAPD_LIBRARY NAMES ${PROJECT_NAME}-capd
PATH_SUFFIXES lib)


# Fold CAPD into the general CODAC_INCLUDE_DIRS / CODAC_LIBRARIES too, so
# that any consumer using the common find_package(CODAC) + \${CODAC_INCLUDE_DIRS}
# / \${CODAC_LIBRARIES} pattern gets CAPD for free (headers, the codac-capd
# library, and the capd::capd target), without needing to separately
# reference CODAC_CAPD_INCLUDE_DIR / CODAC_CAPD_LIBRARY / capd::capd
# themselves -- unlike the -mfma and Threads::Threads gaps, this one
# doesn't corrupt anything if forgotten (see 10_lie_groups), it just
# fails to compile, but it's the same class of oversight.
set(CODAC_INCLUDE_DIRS \${CODAC_INCLUDE_DIRS} \${CODAC_CAPD_INCLUDE_DIR})
set(CODAC_LIBRARIES \${CODAC_LIBRARIES} \${CODAC_CAPD_LIBRARY} capd::capd)

# CODAC_CAPD_LIBRARY is kept as its own variable too, for any consumer
# that prefers to opt into CAPD explicitly rather than pull it in via
# the general CODAC_LIBRARIES.
set(CODAC_CAPD_LIBRARY \${CODAC_CAPD_LIBRARY} \${CODAC_LIBRARIES})
")

Expand Down
Loading
Loading