diff --git a/CMakeLists.txt b/CMakeLists.txt index fc86e4465e89791c936c4cead21aed11ceaed038..4ce372ddae5f2f435f3ec2b7bde4c771505adbaa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,6 +53,13 @@ option(ARB_WITH_ASSERTIONS "enable arb_assert() assertions in code" OFF) option(ARB_WITH_PYTHON "enable Python front end" OFF) +# Path in which to install the Python module. +# For Python 3.8, the module would ibe installed in +# ${ARB_PYTHON_PREFIX}/python3.8/site-packages +# To install Arbor eqivalently to `pip install --user` on a linux system: +# -DARB_PYTHON_PREFIX="${HOME}/.local/lib"" +set(ARB_PYTHON_PREFIX ${CMAKE_INSTALL_PREFIX} CACHE PATH "path for installing Python module for Arbor.") + #---------------------------------------------------------- # Global CMake configuration #---------------------------------------------------------- diff --git a/doc/install.rst b/doc/install.rst index 3ca16340b8f8dcdaf2021d15da845c356a26c7ad..8de0b760dc525c5b1a45bef144655d19557ff467 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -117,10 +117,11 @@ More information on building with MPI is in the `HPC cluster section <cluster_>` Python ~~~~~~ -Arbor has a Python frontend, for which Python 3.6 is required. +Arbor has a Python frontend, for which a minimum of Python 3.6 is required. In order to use MPI in combination with the python frontend the `mpi4py <https://mpi4py.readthedocs.io/en/stable/install.html#>`_ -Python package is recommended. +Python package is recommended. See :ref:`pythonfrontend` for more information. + Documentation ~~~~~~~~~~~~~~ @@ -354,6 +355,8 @@ example: Arbor supports and has been tested on the Kepler (K20 & K80), Pascal (P100) and Volta (V100) GPUs +.. _pythonfrontend: + Python Frontend ---------------- @@ -362,13 +365,46 @@ CMake ``ARB_WITH_PYTHON`` option: .. code-block:: bash - cmake -ARB_WITH_PYTHON=ON + cmake -DARB_WITH_PYTHON=ON + +By default ``ARB_WITH_PYTHON=OFF``. When this option is turned on, a Python module called :py:mod:`arbor` is built. + +A specific version of Python can be set when configuring with CMake using the +``PYTHON_EXECUTABLE`` variable. For example, to use Python 3.7 installed on a Linux +system with the executable in ``/usr/bin/python3.7``: + +.. code-block:: bash + + cmake .. -DARB_WITH_PYTHON=ON -DPYTHON_EXECUTABLE=/usr/bin/python3.7 + +By default the Python module will be installed in the standard ``CMAKE_INSTALL_PREFIX`` +location. To install the module in a different location, for example as a +user module or in a virtual environment, set ``ARB_PYTHON_PREFIX``. +For example, the CMake configuration for targetting Python 3.8 and install as a +user site package might look like the following: + +.. code-block:: bash + + cmake .. -DARB_WITH_PYTHON=ON \ + -DARB_PYTHON_PREFIX=${HOME}/.local \ + -DPYTHON_EXECUTABLE=/user/bin/python3.8 -By default ``ARB_WITH_PYTHON=OFF``. When this option is turned on, a python module called :py:mod:`arbor` is built. +On the target LINUX system, the Arbor package was installed in +``/home/$USER/.local/lib/python3.8/site-packages``. -The Arbor Python wrapper has optional support for the ``mpi4py`` Python module -for MPI. CMake will attempt to automatically detect ``mpi4py`` if configured -with both ``-ARB_WITH_PYTHON=ON`` and MPI ``-DARB_WITH_MPI=ON``. +.. Note:: + By default CMake sets ``CMAKE_INSTALL_PREFIX`` to ``/usr/local`` on Linux and OS X. + The compiled libraries are installed in ``/usr/local/lib``, headers are installed in + ``/usr/local/include``, and the Python module will be installed in a path like + ``/usr/local/lib/python3.7/site-packages``. + Because ``/usr/local`` is a system path, the installation phase needs to be run as root, + i.e. ``sudo make install``, even if ``ARB_PYTHON_PREFIX`` is set to a user path + that does not require root to install. + +The Arbor Python wrapper has optional support for the mpi4py, though +it is not required to use Arbor with Python and MPI. +CMake will attempt to automatically detect ``mpi4py`` if configured +with both ``-DARB_WITH_PYTHON=ON`` and MPI ``-DARB_WITH_MPI=ON``. If CMake fails to find ``mpi4py`` when it should, the easiest workaround is to add the path to the include directory for ``mpi4py`` to the ``CPATH`` environment variable before configuring and building Arbor: @@ -384,7 +420,7 @@ variable before configuring and building Arbor: # set CPATH and run cmake export CPATH="/path/to/python3/site-packages/mpi4py/include/:$CPATH" - cmake -ARB_WITH_PYTHON=ON -DARB_WITH_MPI=ON + cmake -DARB_WITH_PYTHON=ON -DARB_WITH_MPI=ON .. _install: diff --git a/doc/python.rst b/doc/python.rst index d3238b346766ac0f09635615a182cb9cfbbf9d8e..94ef8d82e854f4c6c07b42eda115056492f27b87 100644 --- a/doc/python.rst +++ b/doc/python.rst @@ -7,30 +7,38 @@ Arbor provides access to all of the C++ library's functionality in Python, which is the only interface for many users. The getting started guides will introduce Arbor via the Python interface. -Before starting Arbor needs to be installed with the Python interface enabled, -as per the `installation guide <_installarbor>`_. -To test that Arbor is available, open `Python 3 <python2_>`_, and try the following: +To test that Arbor is available, try the following in a `Python 3 <python2_>`_ interpreter: .. container:: example-code .. code-block:: python - import arbor - arbor.config() - - {'mpi': True, 'mpi4py': True, 'gpu': False, 'version': '0.3'} + >>> import arbor + >>> print(arbor.__config__) + {'mpi': True, 'mpi4py': True, 'gpu': False, 'version': '0.2.3-dev'} + >>> print(arbor.__version__) + 0.2.3-dev -Calling ``arbor.config()`` returns a dictionary with information about the Arbor installation. +The dictionary ``arbor.__config__`` contains information about the Arbor installation. This can be used to check that Arbor supports features that you require to run your model, or even to dynamically decide how to run a model. -Single cell models like the one introduced here we do not require parallelism like +Single cell models like do not require parallelism like that provided by MPI or GPUs, so the ``'mpi'`` and ``'gpu'`` fields can be ``False``. +Installing +------------- + +Before starting Arbor needs to be installed with the Python interface enabled, +following the :ref:`Python configuration <pythonfrontend>` in +the +:ref:`installation guide <installarbor>`. + + Performance -------------- The Python interface can incur significant performance overheads relative to C++ -during the *model building* phase, however simulation performance will be the same +during the *model building* phase, however simulation performance is be the same for both interfaces. .. _python2: @@ -38,11 +46,10 @@ for both interfaces. Python 2 ---------- -Python 2.7 will reach `end of life <https://pythonclock.org/>`_ in January 2020. -Arbor should be installed using Python 3, and all examples in the documentation are in -Python 3. It might be possible to install and run Arbor using Python 2.7, however Arbor support -will only be provided for Python 3. - -NEURON users that use Python 2 can take the opportunity of trying Arbor to make -the move to Python 3. +Python 2 reached `end of life <https://pythonclock.org/>`_ in January 2020. +Arbor only officially supports Python 3.6 and later, and all examples in the +documentation are in Python 3. While it is possible to install and run Arbor +using Python 2.7 by setting the ``PYTHON_EXECUTABLE`` variable when +configuring CMake, support is only provided for using Arbor with Python 3.6 +and later. diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index bf4d5f006fcda97c60658f4cccac595b5eb0180f..12e7c388e864d1a3ad07923abc803bac1c94b7bf 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -46,13 +46,13 @@ target_link_libraries(pyarb_obj PRIVATE arbor pybind11::module) # The Python library. MODULE will make a Python-exclusive model. add_library(pyarb MODULE $<TARGET_OBJECTS:pyarb_obj>) -# The output name of the pyarb .so file is "arbor", to facilitate "import arbor" -set_target_properties(pyarb PROPERTIES OUTPUT_NAME arbor) +# The output name of the pyarb .so file is "_arbor" +set_target_properties(pyarb PROPERTIES OUTPUT_NAME _arbor) # With this, the full name of the library will be something like: -# arbor.cpython-37m-x86_64-linux-gnu.so +# arbor.cpython-36m-x86_64-linux-gnu.so set_target_properties(pyarb PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" SUFFIX "${PYTHON_MODULE_EXTENSION}") -# this dependency has to be spelt out again, despite being added to +# This dependency has to be spelt out again, despite being added to # pyarb_obj because CMake. target_link_libraries(pyarb PRIVATE arbor pybind11::module) @@ -65,10 +65,21 @@ if (ARB_WITH_MPI) endif() endif() +# For unit tests on C++ side of Python wrappers +add_subdirectory(test) + +# Create the Python module in the build directory. +# The module contains the dynamic library, __init__.py and VERSION information. +set(python_mod_path "${CMAKE_CURRENT_BINARY_DIR}/arbor") +set_target_properties(pyarb PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${python_mod_path}") +file(COPY "${CMAKE_SOURCE_DIR}/python/__init__.py" DESTINATION "${python_mod_path}") +file(COPY "${CMAKE_SOURCE_DIR}/VERSION" DESTINATION "${python_mod_path}") + # Determine the installation path, according to the Python version. +# The installation for Python 3.8 would be: +# ${ARB_PYTHON_PREFIX}/python3.8/site-packages +# By default ARB_PYTHON_PREFIX is set to CMAKE_INSTALL_PREFIX, and can be optionally +# used to install the Python module as a user module, or in a virtualenv. find_package(PythonInterp REQUIRED) -set(ARB_PYEXECDIR "${CMAKE_INSTALL_LIBDIR}/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages") -install(TARGETS pyarb LIBRARY DESTINATION ${ARB_PYEXECDIR}) - -# for unit tests on C++ side of Python wrappers -add_subdirectory(test) +set(arb_pyexecdir "${ARB_PYTHON_PREFIX}/lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages") +install(DIRECTORY ${python_mod_path} DESTINATION ${arb_pyexecdir}) diff --git a/python/__init__.py b/python/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..dd137a74df9bc58dc680d9a9235ff76369d969c8 --- /dev/null +++ b/python/__init__.py @@ -0,0 +1,15 @@ +# The Python wrapper generated using pybind11 is a compiled dynamic library, +# with a name like _arbor.cpython-38-x86_64-linux-gnu.so +# +# The library will be installed in the same path as this file, which will imports +# the compiled part of the wrapper from the _arbor namespace. + +from ._arbor import * + +import os + +here = os.path.abspath(os.path.dirname(__file__)) +with open(os.path.join(here, 'VERSION')) as version_file: + __version__ = version_file.read().strip() + +__config__ = config() diff --git a/python/pyarb.cpp b/python/pyarb.cpp index bab7005fdc227c6188e3ba6593873a13cc539b23..ae2dc9a0843350567741874f42ebe10820f55249 100644 --- a/python/pyarb.cpp +++ b/python/pyarb.cpp @@ -27,7 +27,7 @@ void register_mpi(pybind11::module& m); #endif } -PYBIND11_MODULE(arbor, m) { +PYBIND11_MODULE(_arbor, m) { m.doc() = "arbor: multicompartment neural network models."; m.attr("__version__") = ARB_VERSION; diff --git a/python/setup.py b/python/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..efd8227e09293d81c55fbffdde182696a81539fb --- /dev/null +++ b/python/setup.py @@ -0,0 +1,38 @@ +import setuptools +import os + +here = os.path.abspath(os.path.dirname(__file__)) +with open(os.path.join(here, 'arbor/VERSION')) as version_file: + version_ = version_file.read().strip() + +setuptools.setup( + name='arbor', + packages=['arbor'], + version=version_, + author='CSCS and FSJ', + url='https://github.com/arbor-sim/arbor', + description='High performance simulation of networks of multicompartment neurons.', + long_description='', + classifiers=[ + 'Development Status :: 4 - Beta', # Upgrade to "5 - Production/Stable" on release. + 'Intended Audience :: Science/Research', + 'Topic :: Scientific/Engineering :: Build Tools', + 'License :: OSI Approved :: BSD License' + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + ], + project_urls={ + 'Source': 'https://github.com/arbor-sim/arbor', + 'Documentation': 'https://arbor.readthedocs.io', + 'Bug Reports': 'https://github.com/arbor-sim/arbor/issues', + }, + package_data={ + 'arbor': ['VERSION', '_arbor.*.so'], + }, + python_requires='>=3.6', + install_requires=[], + setup_requires=[], + zip_safe=False, +) + diff --git a/python/test/unit/test_domain_decompositions.py b/python/test/unit/test_domain_decompositions.py index fc239f9c8115a4694486eca45527945afe1316c1..eae6b2d4b0b9fc4994f0387b8917a1dbd0094a37 100644 --- a/python/test/unit/test_domain_decompositions.py +++ b/python/test/unit/test_domain_decompositions.py @@ -16,8 +16,7 @@ except ModuleNotFoundError: from test import options # check Arbor's configuration of mpi and gpu -config = arb.config() -gpu_enabled = config["gpu"] +gpu_enabled = arb.__config__["gpu"] """ all tests for non-distributed arb.domain_decomposition diff --git a/python/test/unit_distributed/runner.py b/python/test/unit_distributed/runner.py index 8a913781d660c48f7703681329bae7353456c050..b887802bb8c32df75bbebbe00ce72376983372ed 100644 --- a/python/test/unit_distributed/runner.py +++ b/python/test/unit_distributed/runner.py @@ -6,9 +6,8 @@ import unittest import arbor as arb # check Arbor's configuration of mpi -config = arb.config() -mpi_enabled = config["mpi"] -mpi4py_enabled = config["mpi4py"] +mpi_enabled = arb.__config__["mpi"] +mpi4py_enabled = arb.__config__["mpi4py"] if (mpi_enabled and mpi4py_enabled): import mpi4py.MPI as mpi diff --git a/python/test/unit_distributed/test_contexts_arbmpi.py b/python/test/unit_distributed/test_contexts_arbmpi.py index 57d15bdbc5c3936596232a9314fea09bc548bb65..30328cc77c4831335cfff130cb630642cf4b40e5 100644 --- a/python/test/unit_distributed/test_contexts_arbmpi.py +++ b/python/test/unit_distributed/test_contexts_arbmpi.py @@ -16,8 +16,7 @@ except ModuleNotFoundError: from test import options # check Arbor's configuration of mpi -config = arb.config() -mpi_enabled = config["mpi"] +mpi_enabled = arb.__config__["mpi"] """ all tests for distributed arb.context using arbor mpi wrappers diff --git a/python/test/unit_distributed/test_contexts_mpi4py.py b/python/test/unit_distributed/test_contexts_mpi4py.py index 4dd225b6e710336ab73129d2cb704febfd4f4674..82b3c3453ca24f10ef451c7c110bf515f17d6773 100644 --- a/python/test/unit_distributed/test_contexts_mpi4py.py +++ b/python/test/unit_distributed/test_contexts_mpi4py.py @@ -16,9 +16,8 @@ except ModuleNotFoundError: from test import options # check Arbor's configuration of mpi -config = arb.config() -mpi_enabled = config["mpi"] -mpi4py_enabled = config["mpi4py"] +mpi_enabled = arb.__config__["mpi"] +mpi4py_enabled = arb.__config__["mpi4py"] if (mpi_enabled and mpi4py_enabled): import mpi4py.MPI as mpi diff --git a/python/test/unit_distributed/test_domain_decompositions.py b/python/test/unit_distributed/test_domain_decompositions.py index 17bd5252216c7e87bc8b0875690e016bafbcb048..f5a3a0dfdac2fbf627dcaaf94e4945d17c2fdb69 100644 --- a/python/test/unit_distributed/test_domain_decompositions.py +++ b/python/test/unit_distributed/test_domain_decompositions.py @@ -16,9 +16,8 @@ except ModuleNotFoundError: from test import options # check Arbor's configuration of mpi and gpu -config = arb.config() -gpu_enabled = config["gpu"] -mpi_enabled = config["mpi"] +mpi_enabled = arb.__config__["mpi"] +gpu_enabled = arb.__config__["gpu"] """ all tests for distributed arb.domain_decomposition diff --git a/scripts/travis/build.sh b/scripts/travis/build.sh index 000e5e11662ddb9914b35408d43091f38f82e8b4..93b2a389eabf646430c58f8edf438ba245136804 100755 --- a/scripts/travis/build.sh +++ b/scripts/travis/build.sh @@ -48,7 +48,8 @@ fi if [[ "${WITH_PYTHON}" == "true" ]]; then echo "python : on" ARB_WITH_PYTHON="ON" - export PYTHONPATH=$PYTHONPATH:${base_path}/${build_path}/lib + # The build process creates the arbor module in build_path/python/arbor + export PYTHONPATH=$PYTHONPATH:${base_path}/${build_path}/python python_path=$base_path/python echo "python src : ${python_path}" echo "PYTHONPATH : ${PYTHONPATH}"