diff --git a/.github/workflows/buildwheels.yml b/.github/workflows/buildwheels.yml index 1055f011..6abc5fdd 100644 --- a/.github/workflows/buildwheels.yml +++ b/.github/workflows/buildwheels.yml @@ -82,7 +82,7 @@ jobs: - uses: actions/upload-artifact@v4 with: - name: dist-${{matrix.os}}-${{matrix.manylinux}} + name: dist-${{matrix.os}} path: dist run_example_notebooks: @@ -106,7 +106,7 @@ jobs: - uses: actions/download-artifact@v4 with: - name: dist-${{matrix.os}}-${{matrix.manylinux}} + name: dist-${{matrix.os}} path: dist # install fortran compiler needed by dustpy @@ -155,7 +155,7 @@ jobs: - run: pip install twine auditwheel - uses: actions/download-artifact@v4 with: - name: dist-${{matrix.os}}-${{matrix.manylinux}} + name: dist-${{matrix.os}} path: dist - run: twine check --strict dist/* - run: for i in dist/*-manylinux*.whl; do auditwheel show $i; done; @@ -223,3 +223,251 @@ jobs: uses: pypa/gh-action-pypi-publish@release/v1.14 with: attestations: false + + readme_julia: + runs-on: ${{ matrix.os }} + needs: [build_wheels] + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, ubuntu-24.04-arm] + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - uses: actions/setup-python@v6 + with: + python-version: "3.13" + + - uses: actions/download-artifact@v4 + with: + name: dist-${{matrix.os}} + path: dist + + - run: | + temp=`find dist/ -name "*cp313*.whl"` + python -m pip install $temp[examples] + - run: pip install pytest-codeblocks pytest + - run: python -c "import pytest_codeblocks; code=pytest_codeblocks.extract_from_file('README.md'); f=open('readme.jl', 'w'); f.writelines(block.code for block in code if block.syntax=='Julia'); f.close()" + - run: cat -n readme.jl + + - uses: julia-actions/setup-julia@v3 + - run: mkdir readme_output + - run: julia readme.jl > readme_output/julia_${{ matrix.os }}.txt + - uses: actions/upload-artifact@v4 + with: + name: readme_output-julia_${{matrix.os}} + path: readme_output + + readme_python: + runs-on: ${{ matrix.os }} + needs: [build_wheels] + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest] + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - uses: actions/setup-python@v6 + with: + python-version: "3.13" + + - uses: actions/download-artifact@v4 + with: + name: dist-${{matrix.os}} + path: dist + + - run: | + temp=`find dist/ -name "*cp313*.whl"` + python -m pip install $temp[examples] + + - run: pip install pytest-codeblocks pytest + - run: python -c "import pytest_codeblocks; code=pytest_codeblocks.extract_from_file('README.md'); f=open('readme.py', 'w'); f.writelines(block.code for block in code if block.syntax=='Python'); f.close()" + - run: cat -n readme.py + + - run: mkdir readme_output + - run: python -We readme.py > readme_output/python_${{ matrix.os }}.txt + - uses: actions/upload-artifact@v4 + with: + name: readme_output-python_${{matrix.os}} + path: readme_output + + readme_fortran: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, ubuntu-24.04-arm] + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - run: | + sudo apt-get update + sudo apt-get install libnetcdff-dev + - run: | + cd readme_fortran + mkdir build + cd build + PARTMC_HOME=../../gitmodules/partmc cmake .. + make + + - run: | + mkdir readme_output + cd readme_fortran + ./build/main > ../readme_output/fortran_${{matrix.os}}.txt + + - uses: actions/upload-artifact@v4 + with: + name: readme_output-fortran_${{matrix.os}} + path: readme_output + + readme_assert: + runs-on: ubuntu-latest + needs: [readme_julia, readme_python, readme_matlab, readme_fortran, readme_cpp] + steps: + - uses: actions/setup-python@v6 + - run: pip install numpy + - uses: actions/download-artifact@v4 + with: + pattern: readme_output-* + merge-multiple: true + path: readme_output + - run : python -c 'import numpy as np; import os; dir="readme_output/"; data=[float(np.loadtxt(dir+file)) for file in os.listdir(dir)]; print("data:", data); similar_as_first = np.array([abs(data[0]-k)/data[0] for k in data[1:]]); print("similar_as_first", similar_as_first); assert((similar_as_first < .5).all())' + + readme_cpp: + runs-on: ${{ matrix.os }} + needs: [build_wheels] + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, ubuntu-24.04-arm, macos-15, macos-15-intel, windows-latest] + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - uses: jwlawson/actions-setup-cmake@v2.2.0 + with: + cmake-version: '3.26.x' + + - if: matrix.os == 'windows-latest' + uses: msys2/setup-msys2@v2 + with: + msystem: UCRT64 + update: false + install: >- + mingw-w64-ucrt-x86_64-gcc + mingw-w64-ucrt-x86_64-ninja + m4 + + - if: matrix.os == 'windows-latest' + shell: msys2 {0} + run: | + echo "$(cygpath -m /ucrt64/bin)" >> $GITHUB_PATH + echo "$(cygpath -m /usr/bin)" >> $GITHUB_PATH + echo "CMAKE_GENERATOR=Ninja" >> $GITHUB_ENV + echo "CMAKE_ARGS=-DNC_M4=$(cygpath -m $(which m4))" >> $GITHUB_ENV + + - if: startsWith(matrix.os, 'macos-') + run: | + brew reinstall gcc + pip install --only-binary=llvmlite numba llvmlite!=0.46.0b1 + + - uses: actions/download-artifact@v4 + with: + name: dist-${{matrix.os}} + path: dist + + - run: | + temp=`find dist/ -name "*cp312*.whl"` + python -m pip install $temp[examples] + + - run: pip install pytest-codeblocks pytest + - run: mkdir readme_cpp + - run: python -c "import pytest_codeblocks; code=pytest_codeblocks.extract_from_file('README.md'); f=open('readme_cpp/test.cpp', 'w'); f.writelines(block.code for block in code if block.syntax=='cpp'); f.close()" + - run: cat -n readme_cpp/test.cpp + + - run: python -c "import pytest_codeblocks; code=pytest_codeblocks.extract_from_file('README.md'); f=open('readme_cpp/CMakeLists.txt', 'w'); f.writelines(block.code for block in code if block.syntax=='cmake'); f.close()" + - run: cat -n readme_cpp/CMakeLists.txt + + - name: build + run: | + cd readme_cpp + mkdir build + cd build + cmake .. + cmake --build . + - if: matrix.os == 'windows-latest' + run: | + gdb -batch \ + -ex "run" \ + -ex "bt full" \ + -ex "quit $_exitcode" \ + --args readme_cpp/build/my_test.exe + - name: Execute tests + run: | + mkdir -p readme_output + if [ "$RUNNER_OS" == "Windows" ]; then + ./readme_cpp/build/my_test.exe > readme_output/cpp_${{matrix.os}}.txt + else + ./readme_cpp/build/my_test > readme_output/cpp_${{matrix.os}}.txt + fi + - uses: actions/upload-artifact@v4 + with: + name: readme_output-cpp_${{matrix.os}} + path: readme_output + + readme_matlab: + runs-on: ${{ matrix.os }} + needs: [build_wheels] + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - uses: actions/setup-python@v6 + with: + python-version: "3.10" + + - uses: actions/download-artifact@v4 + with: + name: dist-${{matrix.os}} + path: dist + + - run: pip install --verbose dist/*cp310*manylinux*.whl + + - run: pip install pytest-codeblocks pytest + - run: python -c "import pytest_codeblocks; code=pytest_codeblocks.extract_from_file('README.md'); f=open('readme.m', 'w'); f.writelines(block.code for block in code if block.syntax=='Matlab'); f.close()" + - run: cat -n readme.m + + - uses: matlab-actions/setup-matlab@v2.6.1 + with: + release: R2024b + cache: true + + - run: mkdir readme_output + + - uses: matlab-actions/run-command@v2.4.0 + with: + startup-options: -nojvm + command: diary('readme_output/matlab.txt'), readme + + - uses: actions/upload-artifact@v4 + with: + name: readme_output-matlab + path: readme_output diff --git a/.github/workflows/readme_listings.yml b/.github/workflows/readme_listings.yml deleted file mode 100644 index 7a9d5f4e..00000000 --- a/.github/workflows/readme_listings.yml +++ /dev/null @@ -1,151 +0,0 @@ -name: readme_listings - -defaults: - run: - shell: bash - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - schedule: - - cron: '0 13 * * 4' - -jobs: - julia: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - - - uses: actions/setup-python@v6 - with: - python-version: "3.10" - - - uses: jwlawson/actions-setup-cmake@v2.2 - with: - cmake-version: '3.26.x' - - - run: pip install -e . - - run: pip install pytest-codeblocks pytest - - run: python -c "import pytest_codeblocks; code=pytest_codeblocks.extract_from_file('README.md'); f=open('readme.jl', 'w'); f.writelines(block.code for block in code if block.syntax=='Julia'); f.close()" - - run: cat -n readme.jl - - - uses: julia-actions/setup-julia@v3 - - run: mkdir readme_output - - run: julia readme.jl > readme_output/julia.txt - - uses: actions/upload-artifact@v4 - with: - name: readme_output-julia - path: readme_output - - python: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - - - uses: actions/setup-python@v6 - with: - python-version: "3.10" - - - uses: jwlawson/actions-setup-cmake@v2.2 - with: - cmake-version: '3.26.x' - - - run: pip install -e . - - run: pip install pytest-codeblocks pytest - - run: python -c "import pytest_codeblocks; code=pytest_codeblocks.extract_from_file('README.md'); f=open('readme.py', 'w'); f.writelines(block.code for block in code if block.syntax=='Python'); f.close()" - - run: cat -n readme.py - - - run: mkdir readme_output - - run: python -We readme.py > readme_output/python.txt - - uses: actions/upload-artifact@v4 - with: - name: readme_output-python - path: readme_output - - fortran: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - - - run: | - sudo apt-get update - sudo apt-get install libnetcdff-dev - - run: | - cd readme_fortran - mkdir build - cd build - PARTMC_HOME=../../gitmodules/partmc cmake .. - make - - - run: | - mkdir readme_output - cd readme_fortran - ./build/main > ../readme_output/fortran.txt - - - uses: actions/upload-artifact@v4 - with: - name: readme_output-fortran - path: readme_output - - assert: - runs-on: ubuntu-latest - needs: [julia, python, matlab, fortran] - steps: - - uses: actions/setup-python@v6 - - run: pip install numpy - - uses: actions/download-artifact@v4 - with: - pattern: readme_output-* - merge-multiple: true - path: readme_output - - run : python -c 'import numpy as np; import os; dir="readme_output/"; data=[float(np.loadtxt(dir+file)) for file in os.listdir(dir)]; print("data:", data); similar_as_first = np.array([abs(data[0]-k)/data[0] for k in data[1:]]); print("similar_as_first", similar_as_first); assert((similar_as_first < .5).all())' - - matlab: - runs-on: ubuntu-22.04 - - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - - - uses: actions/setup-python@v6 - with: - python-version: "3.10" - - - uses: RalfG/python-wheels-manylinux-build@v0.7.1-manylinux2010_x86_64 - with: - python-versions: cp310-cp310 - build-requirements: 'setuptools_scm' - pre-build-command: 'git config --global --add safe.directory "*"' - - - run: pip install --verbose dist/*manylinux*.whl - - - run: pip install pytest-codeblocks pytest - - run: python -c "import pytest_codeblocks; code=pytest_codeblocks.extract_from_file('README.md'); f=open('readme.m', 'w'); f.writelines(block.code for block in code if block.syntax=='Matlab'); f.close()" - - run: cat -n readme.m - - - uses: matlab-actions/setup-matlab@v3.0.1 - with: - release: R2024b - cache: true - - - run: mkdir readme_output - - - uses: matlab-actions/run-command@v3.2.0 - with: - startup-options: -nojvm - command: diary('readme_output/matlab.txt'), readme - - - uses: actions/upload-artifact@v4 - with: - name: readme_output-matlab - path: readme_output - diff --git a/.gitignore b/.gitignore index 28191207..02e7f643 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ examples/tmp*.pdf examples/tmp*.svg examples/tmp*.gif -**/__pycache__/ \ No newline at end of file +**/__pycache__/ +build/* +readme_cpp/build/* \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index d6282368..35e06605 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -293,7 +293,7 @@ function(scoped_sundials_setup_config) file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/sundials) execute_process( - COMMAND ${PYTHON_EXECUTABLE} "-c" "import sys; print(''.join([line for line in sys.stdin if line.startswith('set(PACKAGE_VERSION_')]))" + COMMAND ${Python_EXECUTABLE} "-c" "import sys; print(''.join([line for line in sys.stdin if line.startswith('set(PACKAGE_VERSION_')]))" INPUT_FILE ${CMAKE_SOURCE_DIR}/gitmodules/sundials/CMakeLists.txt OUTPUT_FILE ${CMAKE_BINARY_DIR}/sundials/CMakeLists.txt ) @@ -339,7 +339,7 @@ target_include_directories(camplib PRIVATE ) file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/include/camp) execute_process( - COMMAND ${PYTHON_EXECUTABLE} "-c" "import sys; print(''.join([line.replace(')','').replace('set(','#define ').replace('PACKAGE_', 'CAMP_') for line in sys.stdin if line.startswith('set(PACKAGE_VERSION')]))" + COMMAND ${Python_EXECUTABLE} "-c" "import sys; print(''.join([line.replace(')','').replace('set(','#define ').replace('PACKAGE_', 'CAMP_') for line in sys.stdin if line.startswith('set(PACKAGE_VERSION')]))" INPUT_FILE ${CMAKE_SOURCE_DIR}/gitmodules/camp/CMakeLists.txt OUTPUT_FILE ${CMAKE_BINARY_DIR}/include/camp/version.h ) @@ -366,7 +366,7 @@ endif() foreach(file ${hdf5_GENERATED_HEADERS}) execute_process( - COMMAND ${PYTHON_EXECUTABLE} "-c" "import re;open(1,'wb').write(re.sub(b'\\r',b'',open(0,'rb').read()))" + COMMAND ${Python_EXECUTABLE} "-c" "import re;open(1,'wb').write(re.sub(b'\\r',b'',open(0,'rb').read()))" INPUT_FILE ${CMAKE_SOURCE_DIR}/${file} OUTPUT_FILE ${CMAKE_BINARY_DIR}/${file} ) @@ -391,7 +391,7 @@ string(CONCAT cmd ) include(CheckFunctionExists) execute_process( - COMMAND ${PYTHON_EXECUTABLE} "-c" "${cmd}" + COMMAND ${Python_EXECUTABLE} "-c" "${cmd}" INPUT_FILE ${CMAKE_SOURCE_DIR}/gitmodules/netcdf-c/CMakeLists.txt OUTPUT_FILE ${CMAKE_BINARY_DIR}/netcdf/CMakeLists.txt ) @@ -511,15 +511,21 @@ add_dependencies(partmclib ${SUNDIALS_items}) string(CONCAT cmd "import sys;" + "print('#pragma once');" "print(''.join([line if line.startswith('#') else f'#define {line.split()[3]} {line.split()[5]}\\n' for line in sys.stdin if 'parameter ::' in line or line.startswith('#')]))" ) include(CheckFunctionExists) foreach(file aero_data;gas_data;output) execute_process( - COMMAND ${PYTHON_EXECUTABLE} "-c" "${cmd}" + COMMAND ${Python_EXECUTABLE} "-c" "${cmd}" INPUT_FILE ${CMAKE_SOURCE_DIR}/gitmodules/partmc/src/${file}.F90 OUTPUT_FILE ${CMAKE_BINARY_DIR}/include/${file}_parameters.hpp ) + + _install( + FILES ${CMAKE_BINARY_DIR}/include/${file}_parameters.hpp + DESTINATION PyPartMC/include/ + ) endforeach() target_include_directories(partmclib PRIVATE @@ -546,7 +552,13 @@ endif() find_package(nanobind CONFIG REQUIRED) add_subdirectory(gitmodules/nanobind) -nanobind_add_module(_PyPartMC STABLE_ABI ${PyPartMC_sources}) +if(APPLE) + set(PYPARTMC_MODULE_TYPE SHARED) +else() + set(PYPARTMC_MODULE_TYPE MODULE) +endif() + +nanobind_add_module(_PyPartMC ${PYPARTMC_MODULE_TYPE} STABLE_ABI ${PyPartMC_sources}) add_dependencies(_PyPartMC partmclib) set(PYPARTMC_INCLUDE_DIRS "${CMAKE_BINARY_DIR}/include;" @@ -627,3 +639,85 @@ foreach(file ${PyPartMC_headers}) endforeach() _install(TARGETS _PyPartMC LIBRARY DESTINATION PyPartMC) + +# for C++ users who wish to link against the PyPartMC shared library +set_target_properties(_PyPartMC PROPERTIES CXX_VISIBILITY_PRESET default C_VISIBILITY_PRESET default) + +set_target_properties(_PyPartMC PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON) + +if(WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_link_options(_PyPartMC PRIVATE -Wl,--export-all-symbols) +endif() + +_install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/ + DESTINATION PyPartMC/include/PyPartMC + FILES_MATCHING PATTERN "*.hpp" +) + +_install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/ + DESTINATION PyPartMC/include/PyPartMC + FILES_MATCHING PATTERN "*.hpp" +) + +_install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/gitmodules/json/include/nlohmann + DESTINATION PyPartMC/include +) + +_install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/gitmodules/span/include/tcb + DESTINATION PyPartMC/include +) + +_install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/gitmodules/string_view-standalone/include/bpstd + DESTINATION PyPartMC/include +) + +_install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/gitmodules/optional/include/tl + DESTINATION PyPartMC/include +) + +_install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/gitmodules/nanobind_json/include/nanobind_json + DESTINATION PyPartMC/include +) + +_install(TARGETS partmclib + ARCHIVE DESTINATION PyPartMC/lib +) + + execute_process( + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/PyPartMC + COMMAND ${Python_EXECUTABLE} + "-c" + "from __generate_si import __generate_si;\nsi=__generate_si(); print('#pragma once\\nstruct si {' + '\\n'.join([f'static constexpr auto {unit}={getattr(si, unit)};' for unit in dir(si) if type(getattr(si, unit)) == float]) + '};')" + OUTPUT_FILE ${CMAKE_BINARY_DIR}/si.hpp + ) + +_install( + FILES ${CMAKE_BINARY_DIR}/si.hpp + DESTINATION PyPartMC/include/PyPartMC +) + +file(GLOB ALL_HPP_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.hpp") + +file(WRITE ${CMAKE_BINARY_DIR}/PyPartMC.hpp "#pragma once\n") + +foreach(HPP_FILE IN LISTS ALL_HPP_FILES) + get_filename_component(FILENAME ${HPP_FILE} NAME) + file(APPEND ${CMAKE_BINARY_DIR}/PyPartMC.hpp "#include \n") +endforeach() + +_install( + FILES ${CMAKE_BINARY_DIR}/PyPartMC.hpp + DESTINATION PyPartMC/include/ +) + +_install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/PyPartMC/cmake + DESTINATION PyPartMC +) \ No newline at end of file diff --git a/README.md b/README.md index 7bccf29c..1b157a09 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Additionally, the ability to easily package examples, simple simulations, and re instructors, and students, with nominal software and hardware requirements. Documentation of PyPartMC is hosted at https://open-atmos.github.io/PyPartMC. -PyPartMC is implemented in C++ and it also constitutes a C++ API to the PartMC Fortran internals. +PyPartMC is implemented in C++ and it also constitutes a C++ API to the PartMC Fortran internals (C++ example below). The Python API can facilitate using PartMC from other environments - see, e.g., Julia and Matlab examples below. For an outline of the project, rationale, architecture, and features, refer to: [D'Aquino et al., 2024 (SoftwareX)](https://doi.org/10.1016/j.softx.2023.101613) (please cite if PyPartMC is used in your research). @@ -176,7 +176,94 @@ aero_state.dist_sample(aero_dist) print(np.dot(aero_state.masses(), aero_state.num_concs), "# kg/m3") ``` +#### C++ + +```cpp +#include +#include +#include + +#include "PyPartMC.hpp" +#include "PyPartMC/si.hpp" + +int main() { + auto aero_data = std::make_shared(nlohmann::json({ + { {"OC", {1000.0 * si::kg / (si::m * si::m * si::m), 0.0, 1e-3 * si::kg / si::mol, 0.001, 0.0, 0.0}} }, + { {"BC", {1800.0 * si::kg / (si::m * si::m * si::m), 0.0, 1e-3 * si::kg / si::mol, 0.0, 0.0, 0.0}} } + })); + + auto aero_dist = AeroDist( + aero_data, + nlohmann::ordered_json::array({ + nlohmann::ordered_json::object({ + {"cooking", { + {"mass_frac", nlohmann::ordered_json::array({ nlohmann::ordered_json::object({{"OC", {1.0}}}) }) }, + {"diam_type", "geometric"}, + {"mode_type", "log_normal"}, + {"num_conc", 3200.0 / (si::cm * si::cm * si::cm)}, + {"geom_mean_diam", 8.64 * si::nm}, + {"log10_geom_std_dev", 0.28} + }}, + {"diesel", { + {"mass_frac", nlohmann::ordered_json::array({ nlohmann::ordered_json::object({{"OC", {0.3}}}), nlohmann::ordered_json::object({{"BC", {0.7}}}) }) }, + {"diam_type", "geometric"}, + {"mode_type", "log_normal"}, + {"num_conc", 2900.0 / (si::cm * si::cm * si::cm)}, + {"geom_mean_diam", 50.0 * si::nm}, + {"log10_geom_std_dev", 0.24} + }} + }) + }) + ); + + int n_part = 100; + auto aero_state = AeroState(aero_data, n_part, "nummass_source"); + + AeroState::dist_sample(aero_state, aero_dist, 1.0, 0.0, true, true); + + auto masses = AeroState::masses(aero_state, {}, {}); + auto num_concs = AeroState::num_concs(aero_state); + + double total_mass = std::inner_product( + std::begin(num_concs), std::end(num_concs), + std::begin(masses), + 0.0 + ); + + std::cout << std::scientific << total_mass << " # kg/m3\n"; + + return 0; +} +``` + +What can be compiled using CMake with following CMakeLists.txt: + +```cmake +cmake_minimum_required(VERSION 3.22) +project(AeroSimulation LANGUAGES CXX) + +enable_testing() +set(CMAKE_CXX_STANDARD 17) + +find_package(Python 3.8 REQUIRED COMPONENTS Interpreter) +execute_process( + COMMAND ${Python_EXECUTABLE} -c "import PyPartMC, pathlib; print(pathlib.Path(PyPartMC.cmake_dir()).as_posix())" + OUTPUT_VARIABLE PYPARTMC_CMAKE_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE +) +set(PyPartMC_DIR "${PYPARTMC_CMAKE_DIR}") + +find_package(PyPartMC REQUIRED) + +add_executable(my_test test.cpp) +target_link_libraries(my_test PRIVATE PyPartMC::PyPartMC) +pypartmc_setup_runtime(my_test) + +add_test(NAME maketest COMMAND my_test) +``` + #### Julia (using [PyCall.jl](https://github.com/JuliaPy/PyCall.jl)) + ```Julia using Pkg Pkg.add("PyCall") diff --git a/src/PyPartMC/__generate_si.py b/src/PyPartMC/__generate_si.py new file mode 100644 index 00000000..ecab7945 --- /dev/null +++ b/src/PyPartMC/__generate_si.py @@ -0,0 +1,37 @@ +from collections import namedtuple + + +def __generate_si(): + prefixes = { + "T": 1e12, + "G": 1e9, + "M": 1e6, + "k": 1e3, + "h": 1e2, + "da": 1e1, + "": 1e0, + "d": 1e-1, + "c": 1e-2, + "m": 1e-3, + "u": 1e-6, + "n": 1e-9, + "p": 1e-12, + } + units = { + "m": 1e0, + "g": 1e-3, + "s": 1e0, + "K": 1e0, + "Pa": 1e0, + "mol": 1e0, + "W": 1e0, + "J": 1e0, + "N": 1e0, + } + return namedtuple("SI", [prefix + unit for prefix in prefixes for unit in units])( + **{ + prefix_k + unit_k: prefix_v * unit_v + for prefix_k, prefix_v in prefixes.items() + for unit_k, unit_v in units.items() + } + ) diff --git a/src/PyPartMC/__init__.py b/src/PyPartMC/__init__.py index 9c27ae04..d7295e54 100644 --- a/src/PyPartMC/__init__.py +++ b/src/PyPartMC/__init__.py @@ -8,7 +8,6 @@ # pylint: disable=invalid-name,wrong-import-position import os -from collections import namedtuple from contextlib import contextmanager from pathlib import Path @@ -34,41 +33,7 @@ def __build_extension_env(): cookie.close() -def __generate_si(): - prefixes = { - "T": 1e12, - "G": 1e9, - "M": 1e6, - "k": 1e3, - "h": 1e2, - "da": 1e1, - "": 1e0, - "d": 1e-1, - "c": 1e-2, - "m": 1e-3, - "u": 1e-6, - "n": 1e-9, - "p": 1e-12, - } - units = { - "m": 1e0, - "g": 1e-3, - "s": 1e0, - "K": 1e0, - "Pa": 1e0, - "mol": 1e0, - "W": 1e0, - "J": 1e0, - "N": 1e0, - } - return namedtuple("SI", [prefix + unit for prefix in prefixes for unit in units])( - **{ - prefix_k + unit_k: prefix_v * unit_v - for prefix_k, prefix_v in prefixes.items() - for unit_k, unit_v in units.items() - } - ) - +from .__generate_si import __generate_si si = __generate_si() """ a utility namedtuple aimed at clrifying physics-related code by providing @@ -106,3 +71,8 @@ def __generate_si(): # workaround for MATLAB bindings # pylint: disable=undefined-variable setattr(nanobind, "nb_type_0", type(_PyPartMC.AeroData)) + + +# Function that returns cmakes config file location +def cmake_dir() -> str: + return os.path.join(os.path.dirname(__file__), "cmake") diff --git a/src/PyPartMC/cmake/PyPartMCConfig.cmake b/src/PyPartMC/cmake/PyPartMCConfig.cmake new file mode 100644 index 00000000..6387db46 --- /dev/null +++ b/src/PyPartMC/cmake/PyPartMCConfig.cmake @@ -0,0 +1,65 @@ +if(TARGET PyPartMC::PyPartMC) + return() +endif() + +find_package(Python 3.8 REQUIRED COMPONENTS Interpreter Development) + +set(PyPartMC_RUNTIME_LIBRARY "${PYPARTMC_LIB_FILE}") + +execute_process( + COMMAND ${Python_EXECUTABLE} -c "import nanobind, PyPartMC, os; from PyPartMC import _PyPartMC; print(f'{os.path.dirname(PyPartMC.__file__)};{nanobind.include_dir()};{_PyPartMC.__file__}')" + OUTPUT_VARIABLE PY_PATHS + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE res +) + +if(res) + message(FATAL_ERROR "filed to find PyPartMC libraries") +endif() + +list(GET PY_PATHS 0 PYPARTMC_DIR) +list(GET PY_PATHS 1 NANOBIND_INCLUDE_DIR) +list(GET PY_PATHS 2 PYPARTMC_LIB_FILE) + +get_filename_component(VENV_PYPARTMC_DIR "${PYPARTMC_LIB_FILE}" DIRECTORY) + +add_library(PyPartMC::PyPartMC INTERFACE IMPORTED) + +add_library(PyPartMC::RuntimeCore UNKNOWN IMPORTED) +set_target_properties(PyPartMC::RuntimeCore PROPERTIES IMPORTED_LOCATION "${PYPARTMC_LIB_FILE}") + +set(POTENTIAL_INCLUDE_DIRS + "${PYPARTMC_DIR}/include" + "${VENV_PYPARTMC_DIR}/include" + "${NANOBIND_INCLUDE_DIR}" +) + +set(ACTUAL_INCLUDE_DIRS "") +foreach(DIR IN LISTS POTENTIAL_INCLUDE_DIRS) + if(EXISTS "${DIR}") + list(APPEND ACTUAL_INCLUDE_DIRS "${DIR}") + endif() +endforeach() + +target_include_directories(PyPartMC::PyPartMC INTERFACE + ${ACTUAL_INCLUDE_DIRS} + ${Python_INCLUDE_DIRS} +) + +target_link_libraries(PyPartMC::PyPartMC INTERFACE + "${VENV_PYPARTMC_DIR}/lib/libpartmclib.a" + PyPartMC::RuntimeCore + ${Python_LIBRARIES} +) + +function(pypartmc_setup_runtime TARGET_NAME) + if(WIN32) + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${PYPARTMC_LIB_FILE}" + $ + ) + endif() +endfunction() + +message(STATUS "Found PyPartMC: ${PYPARTMC_DIR}") diff --git a/src/input_guard.hpp b/src/input_guard.hpp index 0b638bd0..090943cf 100644 --- a/src/input_guard.hpp +++ b/src/input_guard.hpp @@ -1,3 +1,5 @@ +#pragma once + #include #include #include