diff --git a/CMakeLists.txt b/CMakeLists.txt index 776c4e3be35b6a2044015774c760d7b5b0d3956c..499c2971cb60f979e72419cf65b9897d0613bf0a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.18) set(CXX_STANDARD 14) file(STRINGS "${CMAKE_SOURCE_DIR}/version.txt" version) @@ -84,6 +84,8 @@ if( ${ENABLE_ASAN} ) endif() # PYTHON BINDING +set(AIDGE_REQUIRES_PYTHON FALSE) # Will be set if aidge_core lib depends upon python interpreter +set(AIDGE_PYTHON_HAS_EMBED FALSE) # Will be set if python interpreter is found on the system if (PYBIND) # Python binding lib is by default installed in <prefix>/python_packages/<package>/ # When installed from python, setup.py should set it to the python package dir @@ -92,13 +94,17 @@ if (PYBIND) include(PybindModuleCreation) generate_python_binding(${pybind_module_name} ${module_name}) - # Handles Python + pybind11 headers dependencies - target_link_libraries(${module_name} - PUBLIC - pybind11::pybind11 - PRIVATE - Python::Module - ) + ## + # As of now, when PYBIND is set, the core archive itself depends upon pybind/python, + # we define -DPYBIND and the dependencies on pybind/python runtime where necessary. + + # Add -DPYBIND to compilation and interface + target_compile_definitions(${module_name} PUBLIC PYBIND) + + # Add dependencies on pybind/python. See details in add_pybind_dependency() + include(PybindDependency) + add_pybind_dependency(${module_name}) + ## endif() target_link_libraries(${module_name} PUBLIC Threads::Threads fmt::fmt) @@ -206,10 +212,10 @@ export(EXPORT ${CMAKE_PROJECT_NAME}-targets ############################################## ## Add test if(TEST) - if(PYBIND) - message(FATAL_ERROR "PYBIND and TEST are both enabled. But cannot compile with catch_2.\nChoose between pybind and Catch2 for compilation.") + if (AIDGE_REQUIRES_PYTHON AND NOT AIDGE_PYTHON_HAS_EMBED) + message(WARNING "Skipping compilation of tests: missing Python embedded interpreter") + else() + enable_testing() + add_subdirectory(unit_tests) endif() - enable_testing() - add_subdirectory(unit_tests) endif() - diff --git a/README.md b/README.md index 4b7954d410bce0de1fb1f07c5a268cc962445d29..fe8fd5a4252054c730be8e948d0d2e415c009d47 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ pip install . -v > - `AIDGE_INSTALL` : to set the installation folder. Defaults to `<python_prefix>/lib/libAidge` > - `AIDGE_PYTHON_BUILD_TYPE` : to set the compilation mode to **Debug** or **Release** or "" (for default flags). Defaults to **Release**. > - `AIDGE_BUILD_GEN` : to set the build backend (for development mode) or "" for the cmake default. Default to "". +> - `AIDGE_BUILD_TEST` : to build the C++ unit tests. Set to "ON" or "OFF". Default to "OFF". ## Pip installation for development @@ -24,9 +25,10 @@ To setup aidge_core using pip in development (or editable mode), use the `--no-b For instance run the following command in your python environnement for a typical setup : ``` bash +export AIDGE_BUILD_TEST=ON # enable C++ unit tests export AIDGE_PYTHON_BUILD_TYPE= # default flags (no debug info but fastest build time) export AIDGE_PYTHON_BUILD_TYPE=Debug # or if one really need to debug the C++ code -pip install setuptools setuptools_scm[toml] cmake # Pre-install build requirements (refer to the pyproject.toml [build-system] section) +pip install -U pip setuptools setuptools_scm[toml] cmake # Pre-install build requirements (refer to the pyproject.toml [build-system] section) pip install -v --no-build-isolation -e . ``` @@ -41,7 +43,7 @@ cmake --build build -j $(nproc) && cmake --install build One can also use an alternate cmake build backend such as ninja which can be installed easily though pip, for instance : ``` bash -pip install ninja +pip install -U ninja export AIDGE_BUILD_GEN=Ninja pip install -v --no-build-isolation -e . ``` @@ -85,9 +87,12 @@ make all install | *-DCMAKE_INSTALL_PREFIX:PATH* | ``str`` | Path to the install folder | | *-DCMAKE_BUILD_TYPE* | ``str`` | If ``Debug``, compile in debug mode, ``Release`` compile with highest optimisations or "" (empty) , default= ``Release`` | | *-DWERROR* | ``bool`` | If ``ON`` show warning as error during compilation phase, default=``OFF`` | -| *-DPYBIND* | ``bool`` | If ``ON`` activate python binding, default=``ON`` | +| *-DTEST* | ``bool`` | If ``ON`` build C++ unit tests, default=``ON`` | +| *-DPYBIND* | ``bool`` | If ``ON`` activate python binding, default=``OFF`` | +| *-DPYBIND_INSTALL_PREFIX:PATH*| ``str`` | Path to the python module install folder when ``-DPYBIND=ON``, defaults to ``$CMAKE_INSTALL_PREFIX/python_packages/<module>`` | -If you have compiled with PyBind you can find at the root of the ``build`` file the python lib ``aidge_core.cpython*.so`` +If one compiles with ``-DPYBIND=ON``, ``-DPYBIND_INSTALL_PREFIX:PATH`` can be used to install the python module directly in the +python sources tree (for instance ``$PWD/aidge_core``). ``setup.py`` takes care of this and installs the module at the right place. ## Run tests ### CPP diff --git a/aidge_core-config.cmake.in b/aidge_core-config.cmake.in index d97afe8a2a1ca98eb862d66c388081bca7b72edc..abe55b6faef64aa61d4df4076c035ac0c5f998b4 100644 --- a/aidge_core-config.cmake.in +++ b/aidge_core-config.cmake.in @@ -3,6 +3,11 @@ include(CMakeFindDependencyMacro) find_dependency(fmt) find_dependency(Threads) +set(AIDGE_REQUIRES_PYTHON @AIDGE_REQUIRES_PYTHON@) +set(AIDGE_PYTHON_HAS_EMBED @AIDGE_PYTHON_HAS_EMBED@) +if (AIDGE_REQUIRES_PYTHON AND AIDGE_PYTHON_HAS_EMBED) + find_dependency(Python COMPONENTS Interpreter Development) +endif() include(${CMAKE_CURRENT_LIST_DIR}/aidge_core-config-version.cmake) diff --git a/aidge_core/aidge_export_aidge/static/CMakeLists.txt b/aidge_core/aidge_export_aidge/static/CMakeLists.txt index 4220bb9d502474301cf748252930ff8bdd5c97e3..d7fe26d9c286f72d898a21d07baae2c91d08b71a 100644 --- a/aidge_core/aidge_export_aidge/static/CMakeLists.txt +++ b/aidge_core/aidge_export_aidge/static/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.18) set(CXX_STANDARD 14) file(STRINGS "${CMAKE_SOURCE_DIR}/project_name.txt" project_name) @@ -18,6 +18,7 @@ set(module_name _${CMAKE_PROJECT_NAME}) # target name ############################################## # Define options option(PYBIND "python binding" ON) +option(STANDALONE "Build standalone executable" ON) option(WERROR "Warning as error" OFF) option(TEST "Enable tests" OFF) option(COVERAGE "Enable coverage" OFF) @@ -61,16 +62,8 @@ set_property(TARGET ${module_name} PROPERTY POSITION_INDEPENDENT_CODE ON) # PYTHON BINDING if (PYBIND) - # Handles Python + pybind11 headers dependencies include(PybindModuleCreation) generate_python_binding(${CMAKE_PROJECT_NAME} ${module_name}) - - target_link_libraries(${module_name} - PUBLIC - pybind11::pybind11 - PRIVATE - Python::Python - ) endif() if( ${ENABLE_ASAN} ) @@ -94,7 +87,6 @@ target_include_directories(${module_name} ${CMAKE_CURRENT_SOURCE_DIR}/src ) -target_link_libraries(${module_name} PUBLIC fmt::fmt) target_compile_features(${module_name} PRIVATE cxx_std_14) target_compile_options(${module_name} PRIVATE @@ -151,8 +143,13 @@ install(FILES ## Exporting from the build tree message(STATUS "Exporting created targets to use them in another build") export(EXPORT ${CMAKE_PROJECT_NAME}-targets - FILE "${CMAKE_CURRENT_BINARY_DIR}/${project}-targets.cmake") - -# Compile executable -add_executable(main main.cpp) -target_link_libraries(main PUBLIC _aidge_core ${module_name}) + FILE "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}-targets.cmake") + +if(STANDALONE) + if(AIDGE_REQUIRES_PYTHON AND NOT AIDGE_PYTHON_HAS_EMBED) + message(WARNING "Skipping compilation of standalone executable: missing Python embedded interpreter") + else() + add_executable(main main.cpp) + target_link_libraries(main PRIVATE ${module_name}) + endif() +endif() diff --git a/aidge_core/aidge_export_aidge/static/cmake/PybindModuleCreation.cmake b/aidge_core/aidge_export_aidge/static/cmake/PybindModuleCreation.cmake index 193f3332231ac384daab2e5bf75c1a5de0d2bf1d..217a48351def531cf7da39c9e78e0627fdba87f4 100644 --- a/aidge_core/aidge_export_aidge/static/cmake/PybindModuleCreation.cmake +++ b/aidge_core/aidge_export_aidge/static/cmake/PybindModuleCreation.cmake @@ -1,8 +1,7 @@ function(generate_python_binding name target_to_bind) - find_package(Python COMPONENTS Interpreter Development) + find_package(Python COMPONENTS Interpreter Development.Module) - add_definitions(-DPYBIND) Include(FetchContent) FetchContent_Declare( PyBind11 @@ -15,11 +14,9 @@ function(generate_python_binding name target_to_bind) file(GLOB_RECURSE pybind_src_files "python_binding/*.cpp") pybind11_add_module(${name} MODULE ${pybind_src_files} "NO_EXTRAS") # NO EXTRA recquired for pip install - target_include_directories(${name} PUBLIC "python_binding") + target_include_directories(${name} PRIVATE "python_binding") + + # Link target library to bind + target_link_libraries(${name} PRIVATE ${target_to_bind}) - # Handles Python + pybind11 headers dependencies - target_link_libraries(${name} - PUBLIC - ${target_to_bind} - ) endfunction() diff --git a/aidge_core/aidge_export_aidge/static/export-config.cmake.in b/aidge_core/aidge_export_aidge/static/export-config.cmake.in index f3604be11c27d86caf1ad8a48b333b9bd8f30625..f0be5e076dbdfef359fc00fd41c25c0bba815839 100644 --- a/aidge_core/aidge_export_aidge/static/export-config.cmake.in +++ b/aidge_core/aidge_export_aidge/static/export-config.cmake.in @@ -1,3 +1,8 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(aidge_core) + include(${CMAKE_CURRENT_LIST_DIR}/aidge_backend_cpu-config-version.cmake) include(${CMAKE_CURRENT_LIST_DIR}/aidge_backend_cpu-targets.cmake) diff --git a/aidge_core/aidge_export_aidge/static/main.cpp b/aidge_core/aidge_export_aidge/static/main.cpp index ab8bac1851b6d2dae4bf97bd3af10e19e0b71c1e..61bc3ebeb915be12570c6300965e3b64ac2870dd 100644 --- a/aidge_core/aidge_export_aidge/static/main.cpp +++ b/aidge_core/aidge_export_aidge/static/main.cpp @@ -1,6 +1,10 @@ #include <iostream> #include <aidge/backend/cpu.hpp> +/* Register default cpu Tensor implementation */ +#include <aidge/backend/cpu/data/TensorImpl.hpp> + +/* Include model generator */ #include "include/dnn.hpp" int main() diff --git a/aidge_core/unit_tests/static/main.cpp b/aidge_core/unit_tests/static/main.cpp index 06171e2a036a18b0dea3dca40de34c296d99222d..640fc1fe60b55070de41ca4ce35ccd08084498b9 100644 --- a/aidge_core/unit_tests/static/main.cpp +++ b/aidge_core/unit_tests/static/main.cpp @@ -4,6 +4,10 @@ This file is copied in the test export. */ #include <iostream> +/* Register default cpu Tensor implementation */ +#include <aidge/backend/cpu/data/TensorImpl.hpp> + +/* Include model generator */ #include "include/dnn.hpp" int main() diff --git a/aidge_core/unit_tests/test_export.py b/aidge_core/unit_tests/test_export.py index e53e5f927d4390dede6b3e5a687d19f35cf10b33..23a65c8a2e0204d1f8a60b97673a3ba26ef131b3 100644 --- a/aidge_core/unit_tests/test_export.py +++ b/aidge_core/unit_tests/test_export.py @@ -65,6 +65,7 @@ class test_export(unittest.TestCase): def setUp(self): self.EXPORT_PATH: pathlib.Path = pathlib.Path("dummy_export") self.BUILD_DIR: pathlib.Path = self.EXPORT_PATH / "build" + self.INSTALL_DIR: pathlib.Path = (self.EXPORT_PATH / "install").absolute() def tearDown(self): pass @@ -90,11 +91,16 @@ class test_export(unittest.TestCase): # Export model aidge_core.serialize_to_cpp(self.EXPORT_PATH, model) - self.assertTrue(self.EXPORT_PATH.is_dir(), "Export folder has not been generated") - os.makedirs(self.EXPORT_PATH / "build", exist_ok=True) + + self.assertTrue( + self.EXPORT_PATH.is_dir(), "Export folder has not been generated" + ) + os.makedirs(self.BUILD_DIR, exist_ok=True) + clean_dir(self.BUILD_DIR) # if build dir existed already ensure its emptyness + clean_dir(self.INSTALL_DIR) # Test compilation of export - install_path = ( + search_path = ( os.path.join(sys.prefix, "lib", "libAidge") if "AIDGE_INSTALL" not in os.environ else os.environ["AIDGE_INSTALL"] @@ -112,14 +118,16 @@ class test_export(unittest.TestCase): [ "cmake", str(self.EXPORT_PATH.absolute()), - "-DPYBIND=1", - f"-DCMAKE_INSTALL_PREFIX:PATH={install_path}", + "-DPYBIND=ON", + f"-DCMAKE_PREFIX_PATH={search_path}", # search dependencies + f"-DCMAKE_INSTALL_PREFIX:PATH={self.INSTALL_DIR}", # local install ], cwd=str(self.BUILD_DIR), ): print(std_line, end="") except subprocess.CalledProcessError as e: print(f"An error occurred: {e}\nFailed to configure export.") + raise SystemExit(1) ########################## # BUILD EXPORT @@ -131,6 +139,7 @@ class test_export(unittest.TestCase): print(std_line, end="") except subprocess.CalledProcessError as e: print(f"An error occurred: {e}\nFailed to build export.") + raise SystemExit(1) ########################## # INSTALL EXPORT @@ -142,6 +151,7 @@ class test_export(unittest.TestCase): print(std_line, end="") except subprocess.CalledProcessError as e: print(f"An error occurred: {e}\nFailed to install export.") + raise SystemExit(1) if __name__ == "__main__": diff --git a/cmake/PybindDependency.cmake b/cmake/PybindDependency.cmake new file mode 100644 index 0000000000000000000000000000000000000000..1f4e7d426fa8d78a98d6bcce44d9d7dfab17ec1e --- /dev/null +++ b/cmake/PybindDependency.cmake @@ -0,0 +1,56 @@ +function(add_pybind_dependency target_name) + + # This function add dependencies on pybind/python in the + # case where a target depends on it. This is orthogonal to + # the creation of a pybind python module. + + # In this case we need to add additional dependencies and distinguish the two link time usage for the archive: + + #### 1. link for producing a python binding module, which must not include the python interpreter + + # For the case 1, the archive is bound to a python module which will provide the runtime, + # hence we add dependency only on the pybind and python headers. Also we install the pybind headers + # for backward compatibility of dependent build systems which may not depend upon pybind. + + #### 2. link for producing an executable (tests for instance) which must include the python interpreter + + # For the case 2, a library or executable must also depend on the embedded python libraries, + # hence we add dependency on Python::Python when the target is not a module. Also we account for + # the case where the python libraries are not present (such as on cibuildwheel). In this case + # only python modules can be built, not standalone executables. + + # Make detection of Development.Embed optional, we need to separate the components detections + # otherwise the variables set by the Interpreter components may be undefined. + find_package(Python COMPONENTS Interpreter) + find_package(Python COMPONENTS Development) + if(NOT Python_Development.Embed_FOUND) + message(WARNING "Could not find Python embed libraries, fall back to Python Module only mode. If you are running this from `cibuildwheel, this warning is nominal.") + find_package(Python COMPONENTS Development.Module) + endif() + + # Set these variables which are used in the package config (aidge_core-config.cmake.in) + # and for conditional build on the presence on the python interpreter library + set(AIDGE_REQUIRES_PYTHON TRUE PARENT_SCOPE) + set(AIDGE_PYTHON_HAS_EMBED ${Python_Development.Embed_FOUND} PARENT_SCOPE) + + # Add pybind11 headers dependencies, the headers for the package interface are installed below + target_include_directories(${target_name} SYSTEM PUBLIC + $<INSTALL_INTERFACE:include/_packages_deps/${target_name}> + $<BUILD_INTERFACE:${pybind11_INCLUDE_DIR}>) + + # Add include dirs for Python.h + target_include_directories(${target_name} SYSTEM PUBLIC ${Python_INCLUDE_DIRS}) + + # Add Python embedded interpreter when the target is not a module (tests executables for instance) + # Also requires to have Development.Embed installed on the system + if (Python_Development.Embed_FOUND) + set(target_is_module $<STREQUAL:$<TARGET_PROPERTY:TYPE>,MODULE_LIBRARY>) + target_link_libraries(${target_name} INTERFACE $<$<NOT:${target_is_module}>:Python::Python>) + endif() + + # Install pybind headers such that dependent modules can find them + install(DIRECTORY ${pybind11_INCLUDE_DIR}/pybind11 + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/_packages_deps/${target_name} + ) + +endfunction() diff --git a/cmake/PybindModuleCreation.cmake b/cmake/PybindModuleCreation.cmake index e2bbb2c3fb57867e8add781805033fa5979393a9..853810e24b40eadb0830645a4373c238177ad649 100644 --- a/cmake/PybindModuleCreation.cmake +++ b/cmake/PybindModuleCreation.cmake @@ -2,7 +2,6 @@ function(generate_python_binding name target_to_bind) find_package(Python COMPONENTS Interpreter Development.Module) - add_definitions(-DPYBIND) Include(FetchContent) FetchContent_Declare( PyBind11 @@ -15,11 +14,8 @@ function(generate_python_binding name target_to_bind) file(GLOB_RECURSE pybind_src_files "python_binding/*.cpp") pybind11_add_module(${name} MODULE ${pybind_src_files} "NO_EXTRAS") # NO EXTRA recquired for pip install - target_include_directories(${name} PUBLIC "python_binding") + target_include_directories(${name} PRIVATE "python_binding") - # Handles Python + pybind11 headers dependencies - target_link_libraries(${name} - PUBLIC - ${target_to_bind} - ) + # Link specified target to bind + target_link_libraries(${name} PRIVATE ${target_to_bind}) endfunction() diff --git a/include/aidge/operator/Operator.hpp b/include/aidge/operator/Operator.hpp index 05cd6e8ae1a9d98405ac3d6d2ec74731f4e4104a..93e9664e266db6a14947170d960d52f198dcdce0 100644 --- a/include/aidge/operator/Operator.hpp +++ b/include/aidge/operator/Operator.hpp @@ -129,6 +129,7 @@ public: } virtual void setBackend(const std::string& name, DeviceIdx_t device = 0) = 0; + void setBackend(const std::vector<std::pair<std::string, DeviceIdx_t>>& backends); virtual void setDataType(const DataType& dataType) const = 0; virtual void setDataFormat(const DataFormat& dataFormat) const = 0; diff --git a/include/aidge/utils/DynamicAttributes.hpp b/include/aidge/utils/DynamicAttributes.hpp index 2e87f41f6ac972b9e2d59ecabbcf80df4340cc6e..04ed58f7e636d6a0d528f1946ead110857312576 100644 --- a/include/aidge/utils/DynamicAttributes.hpp +++ b/include/aidge/utils/DynamicAttributes.hpp @@ -54,7 +54,18 @@ public: { mAnyCompare.emplace(std::make_pair<std::type_index, bool(*)(const future_std::any&, const future_std::any&)>(typeid(T), [](const future_std::any& lhs, const future_std::any& rhs) { - return (future_std::any_cast<T>(lhs) < future_std::any_cast<T>(rhs)); +#ifdef PYBIND + if (lhs.type() == typeid(py::object)) { + return (future_std::any_cast<py::object>(lhs).cast<T>() < future_std::any_cast<T>(rhs)); + } + else if (rhs.type() == typeid(py::object)) { + return (future_std::any_cast<T>(lhs) < future_std::any_cast<py::object>(rhs).cast<T>()); + } + else +#endif + { + return (future_std::any_cast<T>(lhs) < future_std::any_cast<T>(rhs)); + } })); const auto dot = name.find('.'); @@ -94,7 +105,18 @@ public: { mAnyCompare.emplace(std::make_pair<std::type_index, bool(*)(const future_std::any&, const future_std::any&)>(typeid(T), [](const future_std::any& lhs, const future_std::any& rhs) { - return (future_std::any_cast<T>(lhs) < future_std::any_cast<T>(rhs)); +#ifdef PYBIND + if (lhs.type() == typeid(py::object)) { + return (future_std::any_cast<py::object>(lhs).cast<T>() < future_std::any_cast<T>(rhs)); + } + else if (rhs.type() == typeid(py::object)) { + return (future_std::any_cast<T>(lhs) < future_std::any_cast<py::object>(rhs).cast<T>()); + } + else +#endif + { + return (future_std::any_cast<T>(lhs) < future_std::any_cast<T>(rhs)); + } })); const auto dot = name.find('.'); @@ -127,7 +149,18 @@ public: { mAnyCompare.emplace(std::make_pair<std::type_index, bool(*)(const future_std::any&, const future_std::any&)>(typeid(T), [](const future_std::any& lhs, const future_std::any& rhs) { - return (future_std::any_cast<T>(lhs) < future_std::any_cast<T>(rhs)); +#ifdef PYBIND + if (lhs.type() == typeid(py::object)) { + return (future_std::any_cast<py::object>(lhs).cast<T>() < future_std::any_cast<T>(rhs)); + } + else if (rhs.type() == typeid(py::object)) { + return (future_std::any_cast<T>(lhs) < future_std::any_cast<py::object>(rhs).cast<T>()); + } + else +#endif + { + return (future_std::any_cast<T>(lhs) < future_std::any_cast<T>(rhs)); + } })); const auto dot = name.find('.'); diff --git a/pyproject.toml b/pyproject.toml index cc0a43c83394a2dd61ae4f99572bd902eb724c9b..b838aca5ee100d182ba88b79f23f3a2ebff9acf3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ test = [ requires = [ "setuptools>=64", "setuptools_scm[toml]==7.1.0", - "cmake>=3.15.3.post1" + "cmake>=3.18.4.post1" ] build-backend = "setuptools.build_meta" @@ -51,15 +51,15 @@ write_to = "aidge_core/_version.py" [tool.cibuildwheel] build-frontend = "build" test-requires = "pytest" -# FIXME: The ignored export test requires a to build the generated export via cmake. -# However due to a strange bug I haven't been able to properly link Python::Module to the export target -# Resulting in the need to link Python::Python which is the python interpreter. -# This suppresses the issue but sadly this target is not available on the cibuilwheel image. -# Hence the test is ignored. If you want to try and solve this bug go on. -# Just take care to increment the counter just below. -# -# Work time spent on this bug : 24h -test-command = "pytest --ignore={package}/aidge_core/unit_tests/test_export.py {package}/aidge_core/unit_tests" +# WARNING: in the test suite the `test_export.py` used to be skipped +# because it did not build when the python embedded interpreter is not available +# as it is the case for cibuildwheel containers. +# Now the build system takes care of this and skips the generation of a standalone +# executable when it is not possible. +# The root causes for this conditional build is that 1. the python embedded interpreter +# is not alweays available, and 2. the aidge_core library depends on it as of now. +# Hopefully this latter dependency may be removed in the future, simplifying the build. +test-command = "pytest -v --capture=no {package}/aidge_core/unit_tests" # uncomment to run cibuildwheel locally on selected distros # build=[ # "cp38-manylinux_x86_64", diff --git a/python_binding/graph/pybind_Node.cpp b/python_binding/graph/pybind_Node.cpp index 1fa552ce153b2b0f655ca9f38d1d80f62390184b..d8e77bb259cbcbae7940a09dc405bb8f50b5b79b 100644 --- a/python_binding/graph/pybind_Node.cpp +++ b/python_binding/graph/pybind_Node.cpp @@ -48,6 +48,16 @@ void init_Node(py::module& m) { :rtype: str )mydelimiter") + .def("create_unique_name", &Node::createUniqueName, py::arg("base_name"), + R"mydelimiter( + Given a base name, generate a new name which is unique in all the GraphViews containing this node. + + :param base_name: proposed name for the node. + :type base_name: str + :rtype: str + )mydelimiter") + + .def("__repr__", &Node::repr) .def("add_child", diff --git a/python_binding/operator/pybind_Operator.cpp b/python_binding/operator/pybind_Operator.cpp index dbf71a3cad870d848fbc2f5f67c13d5347b38b89..81a62f4ed0eb12844453581f68165a282fff9817 100644 --- a/python_binding/operator/pybind_Operator.cpp +++ b/python_binding/operator/pybind_Operator.cpp @@ -53,7 +53,8 @@ void init_Operator(py::module& m){ )mydelimiter") .def("associate_input", &Operator::associateInput, py::arg("inputIdx"), py::arg("data")) .def("set_datatype", &Operator::setDataType, py::arg("dataType")) - .def("set_backend", &Operator::setBackend, py::arg("name"), py::arg("device") = 0) + .def("set_backend", py::overload_cast<const std::string&, DeviceIdx_t>(&Operator::setBackend), py::arg("name"), py::arg("device") = 0) + .def("set_backend", py::overload_cast<const std::vector<std::pair<std::string, DeviceIdx_t>>&>(&Operator::setBackend), py::arg("backends")) .def("forward", &Operator::forward) // py::keep_alive forbide Python to garbage collect the implementation lambda as long as the Operator is not deleted ! .def("set_impl", &Operator::setImpl, py::arg("implementation"), py::keep_alive<1, 2>()) diff --git a/python_binding/scheduler/pybind_Scheduler.cpp b/python_binding/scheduler/pybind_Scheduler.cpp index ac35ce0a62408a69637a4160c9a008aba9dceb66..472af2a9465b121593613492f5120ddc9d7fe254 100644 --- a/python_binding/scheduler/pybind_Scheduler.cpp +++ b/python_binding/scheduler/pybind_Scheduler.cpp @@ -25,6 +25,7 @@ void init_Scheduler(py::module& m){ .def(py::init<std::shared_ptr<GraphView>&>(), py::arg("graph_view")) .def("graph_view", &Scheduler::graphView) .def("save_scheduling_diagram", &Scheduler::saveSchedulingDiagram, py::arg("file_name")) + .def("save_static_scheduling_diagram", &Scheduler::saveStaticSchedulingDiagram, py::arg("file_name")) .def("resetScheduling", &Scheduler::resetScheduling) .def("generate_scheduling", &Scheduler::generateScheduling) .def("get_static_scheduling", &Scheduler::getStaticScheduling, py::arg("step") = 0) diff --git a/setup.py b/setup.py index f0c41626f2fa348ac5d52778d0d865a31b4c344c..4f2e21711f193eb7d5c37ace7b5ad83ac63d3635 100644 --- a/setup.py +++ b/setup.py @@ -61,13 +61,14 @@ class CMakeBuild(build_ext): if build_gen else [] ) + test_onoff = os.environ.get("AIDGE_BUILD_TEST", "OFF") self.spawn( [ "cmake", *build_gen_opts, str(cwd), - "-DTEST=OFF", + f"-DTEST={test_onoff}", f"-DCMAKE_INSTALL_PREFIX:PATH={install_path}", f"-DCMAKE_BUILD_TYPE={compile_type}", "-DPYBIND=ON", diff --git a/src/graph/Node.cpp b/src/graph/Node.cpp index 1e0cdd2a9a19660bb70d0d38548ec2751d6e8eea..b2ceb903d51dbb880979cd2191825a6310f9e5ff 100644 --- a/src/graph/Node.cpp +++ b/src/graph/Node.cpp @@ -73,13 +73,24 @@ void Aidge::Node::setName(const std::string& name) { mName = name; } -std::string Aidge::Node::createUniqueName(std::string name){ - for (auto graphView : views()){ - if (graphView->inView(name)){ - return createUniqueName(name.append("_")); +std::string Aidge::Node::createUniqueName(std::string baseName) +{ + int index = 0; + bool nameAlreadyUsed = true; + std::string newName; + while (nameAlreadyUsed) { + std::string suffix = "_" + std::to_string(index); + newName = (index == 0) ? baseName : baseName + suffix; + nameAlreadyUsed = false; + for (auto graphView : views()) { + if (graphView->inView(newName)) { + nameAlreadyUsed = true; + break; + } } + index++; } - return name; + return newName; } /////////////////////////////////////////////////////// diff --git a/src/operator/Conv.cpp b/src/operator/Conv.cpp index fc3a91f3cb48b67d388789acd7d3b62792ffdc2c..836c47645c20ff23539b836af8593cddfbb48498 100644 --- a/src/operator/Conv.cpp +++ b/src/operator/Conv.cpp @@ -43,16 +43,17 @@ bool Aidge::Conv_Op<DIM>::forwardDims(bool /*allowDataDependency*/) { if (inputsAssociated()) { // first check weight since it defines inChannels and outChannels AIDGE_ASSERT((getInput(1)->nbDims() == (DIM+2)), - "Wrong weight Tensor dimension: {} for Conv{}D operator.", getInput(1)->nbDims(), DIM); + "Wrong weight Tensor dimension: {} for Conv{}D operator. Expected number of dimensions is {}.", getInput(1)->nbDims(), DIM, DIM+2); // check data AIDGE_ASSERT((getInput(0)->nbDims() == (DIM+2)) && (getInput(0)->template dims<DIM+2>()[1] == inChannels()), - "Wrong input size for Conv operator."); + "Wrong input size ({}) for Conv operator. Expected dims are [x, {}, {}].", getInput(0)->dims(), inChannels(), fmt::join(std::vector<std::string>(DIM, "x"), ", ")); // check optional bias if(getInput(2)) AIDGE_ASSERT((getInput(2)->nbDims() == (1)) && (getInput(2)->template dims<1>()[0] == outChannels()), - "Wrong bias size for Conv operator."); + "Wrong bias size ({}) for Conv operator. Expected dims are [{}].", getInput(2)->dims(), outChannels()); + std::array<DimSize_t, DIM + 2> outputDims{}; const std::array<DimSize_t, DIM + 2> inputDims(getInput(0)->template dims<DIM+2>()); diff --git a/src/operator/ConvDepthWise.cpp b/src/operator/ConvDepthWise.cpp index 7de0094a36d450d1b3f060e1f4b7251b783f0c6b..d2a1c9e3d08d8e2c0400d436c6123aeb5f7ce66b 100644 --- a/src/operator/ConvDepthWise.cpp +++ b/src/operator/ConvDepthWise.cpp @@ -44,16 +44,17 @@ bool Aidge::ConvDepthWise_Op<DIM>::forwardDims(bool /*allowDataDependency*/) { if (inputsAssociated()) { // first check weight since it defines nbChannels AIDGE_ASSERT((getInput(1)->nbDims() == (DIM+2)), - "Wrong weight Tensor dimension: {} for Conv{}D operator.", getInput(1)->nbDims(), DIM); + "Wrong weight Tensor dimension: {} for ConvDepthWise{}D operator. Expected number of dimensions is {}.", getInput(1)->nbDims(), DIM, DIM+2); // check data AIDGE_ASSERT((getInput(0)->nbDims() == (DIM+2)) && (getInput(0)->template dims<DIM+2>()[1] == nbChannels()), - "Wrong input size for Conv operator."); + "Wrong input size ({}) for ConvDepthWise operator. Expected dims are [x, {}, {}].", getInput(0)->dims(), nbChannels(), fmt::join(std::vector<std::string>(DIM, "x"), ", ")); // check optional bias if(getInput(2)) AIDGE_ASSERT((getInput(2)->nbDims() == (1)) && (getInput(2)->template dims<1>()[0] == nbChannels()), - "Wrong bias size for Conv operator."); + "Wrong bias size ({}) for ConvDepthWise operator. Expected dims are [{}].", getInput(2)->dims(), nbChannels()); + std::array<DimSize_t, DIM + 2> outputDims = {}; const std::array<DimSize_t, DIM + 2> inputDims(getInput(0)->template dims<DIM+2>()); diff --git a/src/operator/MetaOperator.cpp b/src/operator/MetaOperator.cpp index a5240864ef21a9f05ea3cc949f1d38a202704b83..e3acba9b4cccdf525d80f85344ba500cc7ac885f 100644 --- a/src/operator/MetaOperator.cpp +++ b/src/operator/MetaOperator.cpp @@ -102,10 +102,13 @@ std::shared_ptr<Aidge::Attributes> Aidge::MetaOperator_Op::attributes() const { auto attrs = std::make_shared<DynamicAttributes>(); for (const auto& node : mGraph->getRankedNodesName("{3}")) { - const auto nodeAttrs = DynamicAttributes(node.first->getOperator()->attributes()->getAttrs()); - attrs->addAttr(node.first->type() + "#" + node.second, nodeAttrs); - if (node.second == "0") { - attrs->addAttr(node.first->type(), nodeAttrs); + const auto attributes = node.first->getOperator()->attributes(); + if (attributes) { + const auto nodeAttrs = DynamicAttributes(attributes->getAttrs()); + attrs->addAttr(node.first->type() + "#" + node.second, nodeAttrs); + if (node.second == "0") { + attrs->addAttr(node.first->type(), nodeAttrs); + } } } diff --git a/src/operator/Operator.cpp b/src/operator/Operator.cpp index 762d5fda8655c3094abcc7cb9118f4a00683a879..f15a7dc3899a7bc864e8e76ff0946fb70584bf05 100644 --- a/src/operator/Operator.cpp +++ b/src/operator/Operator.cpp @@ -80,3 +80,17 @@ void Aidge::Operator::backward() { AIDGE_ASSERT(mImpl != nullptr, "backward(): an implementation is required for {}!", type()); mImpl->backward(); } + +void Aidge::Operator::setBackend(const std::vector<std::pair<std::string, DeviceIdx_t>>& backends) { + const auto& availableBackends = getAvailableBackends(); + // By default, try to set the last backend anyway + auto selectedBackend = backends.back(); + for (const auto& backend : backends) { + if (availableBackends.find(backend.first) != availableBackends.end()) { + selectedBackend = backend; + break; + } + } + + setBackend(selectedBackend.first, selectedBackend.second); +} diff --git a/src/utils/DynamicAttributes.cpp b/src/utils/DynamicAttributes.cpp index 56731f234cec303c4b5329d69634a7e4589236d3..909d3bb2f5fda977ac497a19e1a1088eb52cfc88 100644 --- a/src/utils/DynamicAttributes.cpp +++ b/src/utils/DynamicAttributes.cpp @@ -14,7 +14,18 @@ std::map<std::type_index, bool(*)(const future_std::any&, const future_std::any&)> Aidge::DynamicAttributes::mAnyCompare; bool future_std::operator<(const future_std::any& lhs, const future_std::any& rhs) { - return (lhs.type() == rhs.type()) - ? Aidge::DynamicAttributes::mAnyCompare.at(lhs.type())(lhs, rhs) - : (lhs.type().before(rhs.type())); + if (lhs.type() == rhs.type()) { + return Aidge::DynamicAttributes::mAnyCompare.at(lhs.type())(lhs, rhs); + } +#ifdef PYBIND + else if (lhs.type() == typeid(py::object)) { + return Aidge::DynamicAttributes::mAnyCompare.at(rhs.type())(lhs, rhs); + } + else if (rhs.type() == typeid(py::object)) { + return Aidge::DynamicAttributes::mAnyCompare.at(lhs.type())(lhs, rhs); + } +#endif + else { + return (lhs.type().before(rhs.type())); + } } diff --git a/unit_tests/CMakeLists.txt b/unit_tests/CMakeLists.txt index 9280d5fbdfd0a6a35724e5afd5caf672fefd8bf8..fd96b060630c162e93143e8f51019a0ce3e82cc9 100644 --- a/unit_tests/CMakeLists.txt +++ b/unit_tests/CMakeLists.txt @@ -55,7 +55,7 @@ target_link_options(tests${module_name} PUBLIC $<$<OR:$<CXX_COMPILER_ID:Clang>,$ endif() -target_link_libraries(tests${module_name} PUBLIC ${module_name}) +target_link_libraries(tests${module_name} PRIVATE ${module_name}) target_link_libraries(tests${module_name} PRIVATE Catch2::Catch2WithMain)