diff --git a/CHANGELOG.md b/CHANGELOG.md index fea89b5..5ee2630 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## latest +- Added support to load multiple pyFANS builds in the same Python session [#142](https://github.com/DataAnalyticsEngineering/FANS/pull/142) - Added large strain support for pyFANS [#141](https://github.com/DataAnalyticsEngineering/FANS/pull/141) - Bugfix: fixes GBDiffusion post MaterialManager update and adds a test case [#147](https://github.com/DataAnalyticsEngineering/FANS/pull/147) - Bugfix: mem-leak while reading microstructure and added wider CMake support [#140](https://github.com/DataAnalyticsEngineering/FANS/pull/140) diff --git a/README.md b/README.md index a03427d..3bcb615 100644 --- a/README.md +++ b/README.md @@ -186,14 +186,15 @@ cd ../test **Build options:** -| CMake Option | Description | Default | -| -------------- | ------------- | --------- | -| `CMAKE_BUILD_TYPE` | Build type: `Debug`, `Release`, `RelWithDebInfo` | `NONE` | -| `CMAKE_INTERPROCEDURAL_OPTIMIZATION` | Enable link-time optimization (LTO) | `ON` (if supported) | -| `FANS_BUILD_STATIC` | Build static library | `OFF` | -| `CMAKE_INSTALL_PREFIX` | Installation directory | System default | -| `FANS_LIBRARY_FOR_MICRO_MANAGER` | Build Python bindings using Pybind11 (needed) | `OFF` | -| `FANS_ENABLE_SANITIZERS` | Enable runtime sanitizers (AddressSanitizer and LeakSanitizer) for memory debugging | `OFF` | +| CMake Option | Description | Default | +|--------------------------------------|-------------------------------------------------------------------------------------------------------|---------------------| +| `CMAKE_BUILD_TYPE` | Build type: `Debug`, `Release`, `RelWithDebInfo` | `NONE` | +| `CMAKE_INTERPROCEDURAL_OPTIMIZATION` | Enable link-time optimization (LTO) | `ON` (if supported) | +| `FANS_BUILD_STATIC` | Build static library | `OFF` | +| `CMAKE_INSTALL_PREFIX` | Installation directory | System default | +| `FANS_LIBRARY_FOR_MICRO_MANAGER` | Build Python bindings (pyFANS) using Pybind11 (needed) | `OFF` | +| `FANS_ENABLE_SANITIZERS` | Enable runtime sanitizers (AddressSanitizer and LeakSanitizer) for memory debugging | `OFF` | +| `MicroSimulationSuffix` | Numeric suffix for pyFANS registration within Pybind11. Required when loading multiple pyFANS builds. | `NONE` | --- diff --git a/pyfans/CMakeLists.txt b/pyfans/CMakeLists.txt index 47882af..978e79e 100644 --- a/pyfans/CMakeLists.txt +++ b/pyfans/CMakeLists.txt @@ -2,6 +2,36 @@ pybind11_add_module(PyFANS micro.hpp micro.cpp) target_include_directories(PyFANS PRIVATE ${HDF5_INCLUDE_DIRS} ${FFTW3_INCLUDE_DIRS}) target_link_libraries(PyFANS PRIVATE FANS::FANS) +# Optional integer option (empty by default) +set(MicroSimulationSuffix "" CACHE STRING "Optional numeric variant suffix") + +# Validate: must be empty or integer +if(MicroSimulationSuffix STREQUAL "") + set(CLASS_NAME MicroSimulation) + set(INPUT_NAME input.json) + set(CONFIG_NAME pyfans-config.json) + set(MODULE_NAME PyFANS) +else() + if(NOT MicroSimulationSuffix MATCHES "^[0-9]+$") + message(FATAL_ERROR "MicroSimulationSuffix must be an integer, got '${MicroSimulationSuffix}'") + endif() + + set(CLASS_NAME MicroSimulation${MicroSimulationSuffix}) + set(INPUT_NAME input${MicroSimulationSuffix}.json) + set(CONFIG_NAME pyfans-config${MicroSimulationSuffix}.json) + set(MODULE_NAME PyFANS${MicroSimulationSuffix}) + message("Using MicroSimulationSuffix: ${MicroSimulationSuffix}") +endif() + +# Add compile definitions +target_compile_definitions(PyFANS PRIVATE + PyFANS_CLASS_NAME=${CLASS_NAME} + PyFANS_CLASS_NAME_STR="${CLASS_NAME}" + PyFANS_INPUT_NAME="${INPUT_NAME}" + PyFANS_CONFIG_NAME="${CONFIG_NAME}" + PyFANS_MODULE_NAME=${MODULE_NAME} +) + add_custom_command( TARGET PyFANS POST_BUILD diff --git a/pyfans/README.md b/pyfans/README.md index 475f005..5973960 100644 --- a/pyfans/README.md +++ b/pyfans/README.md @@ -10,6 +10,60 @@ pyFANS is a Python-wrapped library to control FANS via the [Micro Manager](https To build FANS as a Micro Manager compatible Python library, set the CMake variable `FANS_LIB` to `ON`. The CMake command to compile FANS would then be `cmake .. -DFANS_LIBRARY_FOR_MICRO_MANAGER=ON`. +To build multiple pyFANS targets configured to load different input and configuration files, respective build directories must be created. Within those, the corresponding CMAKE flags must be set. +In the following example we show how to configure two different builds: + +__NOTE__: `MicroSimulationSuffix` may only be set to non-negative integer values within 0 to 10 + +In the project root directory, create two build folders: + +```bash +cd +mkdir build0 +mkdir build1 +``` + +To configure and build the first target: + +```bash +cd build0 +cmake -DFANS_LIBRARY_FOR_MICRO_MANAGER=ON -DMicroSimulationSuffix=0 .. +cmake --build -j .. +cp pyfans/PyFANS.*.so /PyFANS0.so +cd .. +``` + +This will result in the following configuration: + +| Name | Value | +|----------------------|---------------------| +| CLASS_NAME | MicroSimulation0 | +| INPUT_FILE | input0.json | +| CONFIG_FILE | pyfans-config0.json | +| pybind11 MODULE_NAME | PyFANS0 | + +To configure and build the second target: + +```bash +cd build1 +cmake -DFANS_LIBRARY_FOR_MICRO_MANAGER=ON -DMicroSimulationSuffix=1 .. +cmake --build -j .. +cp pyfans/PyFANS.*.so /PyFANS1.so +cd .. +``` + +This will result in the following configuration: + +| Name | Value | +|----------------------|---------------------| +| CLASS_NAME | MicroSimulation1 | +| INPUT_FILE | input1.json | +| CONFIG_FILE | pyfans-config1.json | +| pybind11 MODULE_NAME | PyFANS1 | + ## Usage pyFANS is intended to be used with the Micro Manager and preCICE for two-scale coupled simulations. However, standalone use of the library is not restricted per se. Look at the [test_pyfans](../test/test_pyfans/) example to see how the library is used in a Python script. + +To disable parallelization with MPI, provide a `pyfans-config.json` file containing the field: `no_mpi: true`. +It should be located in the same directed from which for example the Micro Manager is launched. diff --git a/pyfans/micro.cpp b/pyfans/micro.cpp index 029d9d6..17ebad7 100644 --- a/pyfans/micro.cpp +++ b/pyfans/micro.cpp @@ -27,7 +27,7 @@ PyFANSConfig load_config(const std::string &file_path) return config; } -MicroSimulation::MicroSimulation(int sim_id, bool late_init, const std::string &input_file, const std::string &config_file) +PyFANS_CLASS_NAME::PyFANS_CLASS_NAME(int sim_id, bool late_init, const std::string &input_file, const std::string &config_file) : _sim_id(sim_id) { // initialize fftw mpi @@ -76,7 +76,7 @@ std::vector conv_to_vector(const py::array_t &arr, const int siz return res; } -py::dict MicroSimulation::solve(const py::dict ¯o_data, double dt) +py::dict PyFANS_CLASS_NAME::solve(const py::dict ¯o_data, double dt) { const bool is_small_strain = std::holds_alternative *>(matmanager); // Time step value dt is not used currently, but is available for future use @@ -160,14 +160,14 @@ py::dict MicroSimulation::solve(const py::dict ¯o_data, double dt) return micro_write_data; } -py::dict MicroSimulation::get_state() +py::dict PyFANS_CLASS_NAME::get_state() { // TODO populate state py::dict state; return state; } -void MicroSimulation::set_state(const py::dict &state) +void PyFANS_CLASS_NAME::set_state(const py::dict &state) { // TODO read from state, not file reader.FreeMS(); @@ -191,26 +191,26 @@ void MicroSimulation::set_state(const py::dict &state) } } -int MicroSimulation::get_id() +int PyFANS_CLASS_NAME::get_id() { return _sim_id; } -void MicroSimulation::set_id(const int id) +void PyFANS_CLASS_NAME::set_id(const int id) { _sim_id = id; } -PYBIND11_MODULE(PyFANS, m) +PYBIND11_MODULE(PyFANS_MODULE_NAME, m) { // optional docstring m.doc() = "FANS for Micro Manager"; - py::class_(m, "MicroSimulation") + py::class_(m, PyFANS_CLASS_NAME_STR) .def(py::init()) - .def("solve", &MicroSimulation::solve, py::return_value_policy::automatic) - .def("set_state", &MicroSimulation::set_state) - .def("get_state", &MicroSimulation::get_state, py::return_value_policy::automatic) - .def("get_global_id", &MicroSimulation::get_id) - .def("set_global_id", &MicroSimulation::set_id); + .def("solve", &PyFANS_CLASS_NAME::solve, py::return_value_policy::automatic) + .def("set_state", &PyFANS_CLASS_NAME::set_state) + .def("get_state", &PyFANS_CLASS_NAME::get_state, py::return_value_policy::automatic) + .def("get_global_id", &PyFANS_CLASS_NAME::get_id) + .def("set_global_id", &PyFANS_CLASS_NAME::set_id); } diff --git a/pyfans/micro.hpp b/pyfans/micro.hpp index d0d8373..0695482 100644 --- a/pyfans/micro.hpp +++ b/pyfans/micro.hpp @@ -18,9 +18,25 @@ namespace py = pybind11; -class MicroSimulation { +#ifndef PyFANS_CLASS_NAME +#define PyFANS_CLASS_NAME MicroSimulation +#endif +#ifndef PyFANS_CLASS_NAME_STR +#define PyFANS_CLASS_NAME_STR "MicroSimulation" +#endif +#ifndef PyFANS_INPUT_NAME +#define PyFANS_INPUT_NAME "input.json" +#endif +#ifndef PyFANS_CONFIG_NAME +#define PyFANS_CONFIG_NAME "pyfans-config.json" +#endif +#ifndef PyFANS_MODULE_NAME +#define PyFANS_MODULE_NAME PyFANS +#endif + +class PyFANS_CLASS_NAME { public: - MicroSimulation(int sim_id, bool late_init = false, const std::string &input_file = "input.json", const std::string &config_file = "pyfans-config.json"); + PyFANS_CLASS_NAME(int sim_id, bool late_init = false, const std::string &input_file = PyFANS_INPUT_NAME, const std::string &config_file = PyFANS_CONFIG_NAME); py::dict solve(const py::dict ¯o_write_data, double dt); py::dict get_state();