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
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
*.so
*.o
*.mod
*.whl
__pycache__/
*.py[cod]
.pytest_cache/
.venv/
uv.lock
_psiesta.c
build/
builddir
Siesta.pc
97 changes: 97 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
cmake_minimum_required(VERSION 3.20)

project(
PSiesta
VERSION 0.3.0
LANGUAGES C Fortran
)

include(FetchContent)
include(GNUInstallDirs)

option(PSIESTA_FETCH_SIESTA "Fetch/build Siesta as a CMake subproject" ON)
option(PSIESTA_USE_SYSTEM_SIESTA "Use an installed Siesta CMake package" OFF)
set(PSIESTA_SIESTA_SOURCE_DIR "" CACHE PATH "Existing Siesta source checkout")
set(PSIESTA_SIESTA_GIT_REPOSITORY "https://gitlab.com/siesta-project/siesta.git" CACHE STRING "Siesta git repository")
set(PSIESTA_SIESTA_GIT_TAG "master" CACHE STRING "Siesta git tag, branch, or commit")
Comment on lines +15 to +16

set(CMAKE_POSITION_INDEPENDENT_CODE ON)

find_package(Python REQUIRED COMPONENTS Interpreter Development.Module)
find_package(MPI REQUIRED COMPONENTS C Fortran)

execute_process(
COMMAND "${Python_EXECUTABLE}" -c "import numpy; print(numpy.get_include())"
OUTPUT_VARIABLE NumPy_INCLUDE_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE
COMMAND_ERROR_IS_FATAL ANY
)

find_program(CYTHON_EXECUTABLE cython REQUIRED)

if(PSIESTA_USE_SYSTEM_SIESTA)
find_package(SIESTA CONFIG REQUIRED)
elseif(PSIESTA_SIESTA_SOURCE_DIR)
set(SIESTA_TESTS OFF CACHE BOOL "" FORCE)
set(SIESTA_INSTALL OFF CACHE BOOL "" FORCE)
set(SIESTA_WITH_MPI ON CACHE BOOL "" FORCE)
set(SIESTA_WITH_PEXSI OFF CACHE BOOL "" FORCE)
set(SIESTA_SHARED_LIBS OFF CACHE BOOL "" FORCE)
add_subdirectory("${PSIESTA_SIESTA_SOURCE_DIR}" "${CMAKE_BINARY_DIR}/_deps/siesta-build")
elseif(PSIESTA_FETCH_SIESTA)
set(SIESTA_TESTS OFF CACHE BOOL "" FORCE)
set(SIESTA_INSTALL OFF CACHE BOOL "" FORCE)
set(SIESTA_WITH_MPI ON CACHE BOOL "" FORCE)
set(SIESTA_WITH_PEXSI OFF CACHE BOOL "" FORCE)
set(SIESTA_SHARED_LIBS OFF CACHE BOOL "" FORCE)
FetchContent_Declare(
siesta
GIT_REPOSITORY "${PSIESTA_SIESTA_GIT_REPOSITORY}"
GIT_TAG "${PSIESTA_SIESTA_GIT_TAG}"
)
FetchContent_MakeAvailable(siesta)
else()
message(FATAL_ERROR "Set one of PSIESTA_USE_SYSTEM_SIESTA, PSIESTA_SIESTA_SOURCE_DIR, or PSIESTA_FETCH_SIESTA")
endif()

set(_siesta_target "")
foreach(candidate IN ITEMS SIESTA::libsiesta SIESTA.libsiesta SIESTA-libsiesta siesta.libsiesta siesta-libsiesta)
if(TARGET "${candidate}")
set(_siesta_target "${candidate}")
break()
endif()
endforeach()
if(NOT _siesta_target)
message(FATAL_ERROR "Could not find the upstream Siesta libsiesta CMake target")
endif()
message(STATUS "Using Siesta target: ${_siesta_target}")

set(_psiesta_c "${CMAKE_CURRENT_BINARY_DIR}/psiesta/_psiesta.c")
add_custom_command(
OUTPUT "${_psiesta_c}"
COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/psiesta"
COMMAND "${CYTHON_EXECUTABLE}" -3 "${CMAKE_CURRENT_SOURCE_DIR}/psiesta/_psiesta.pyx" -o "${_psiesta_c}"
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/psiesta/_psiesta.pyx"
VERBATIM
)

Python_add_library(_psiesta MODULE
"${_psiesta_c}"
"${CMAKE_CURRENT_SOURCE_DIR}/psiesta/c_bindings/fpsiesta.f90"
WITH_SOABI
)

target_include_directories(_psiesta PRIVATE "${NumPy_INCLUDE_DIR}")
target_link_libraries(_psiesta PRIVATE
"${_siesta_target}"
MPI::MPI_C
MPI::MPI_Fortran
)

install(TARGETS _psiesta LIBRARY DESTINATION psiesta)
install(FILES
"psiesta/__init__.py"
"psiesta/ase.py"
"psiesta/psiesta.py"
DESTINATION psiesta
)
70 changes: 62 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,70 @@ You should use a Siesta version later than the git master as of 2020-06-10, as a

## Obtaining source, building and installing
You can obtain the source by simply cloning this repository.
To build, you must have a properly set up `arch.make` for Siesta in your Obj-dir, and you must have at least compiled Siesta there (see the note above for a patched Siesta).
You can then run `OBJ=/my/custom/siesta/Obj/ python3 setup.py install [--user] [--prefix=<prefix>]` (or use `build` instead of `install`) to build PSiesta.
The setup.py-file makes use of Siesta's own `Makefile` (which includes your `arch.make`) in combination with `--dry-run` to extract the compilation and link arguments.
It *should* work for both intel and gnu compilers, but be aware that LTO can complicate things, and ensure that any external libraries that are used (eg. flook) are compiled with `-fPIC`.

On some platforms it is necessary to link more libraries than Siesta is otherwise compiled with. It is currently a little unclear why, but in one case I needed to use `EXTRA_COMP_ARGS="-lmkl_avx512 -lmkl_def"` (which the setup.py-file will recognize).
The current build uses scikit-build-core and CMake. By default, CMake fetches upstream Siesta and builds it as a subproject, using Siesta's native CMake build.

As noted above, you should use a Siesta version later than the git master as of 2020-06-10.
For development, the recommended environment is Nix for native dependencies and uv for Python dependencies. Nix provides the native scientific build stack, including C, C++, and Fortran compilers, OpenMPI compiler wrappers, CMake, Ninja, BLAS/LAPACK, ScaLAPACK, NetCDF C/Fortran, HDF5, FFTW, libxc, readline, zlib, curl, and `uv`. uv manages Python build isolation and dependencies from `pyproject.toml`.

Install Nix with flakes enabled, then run commands inside the development shell:

```bash
nix develop
```

Or run a single command inside it:

```bash
nix develop -c <command>
```

Check the shell with:

```bash
nix develop -c sh -c 'cc --version && mpicc --version && mpifort --version && cmake --version && uv --version'
```

Build the wheel with:

```bash
nix develop -c uv build --wheel
```

This produces a wheel in `dist/`. Build output is written under `build/`.

You can point at an existing Siesta checkout with:

```bash
nix develop -c uv build --wheel --config-setting=cmake.define.PSIESTA_SIESTA_SOURCE_DIR=/path/to/siesta
```

Local build artifacts can be removed with:

```bash
rm -rf build dist
```

## Smoke tests

The test suite contains lightweight technical smoke tests. They are not intended
to validate scientific accuracy; they only check that the Python extension
imports, MPI starts, Siesta launches as a library, and simple H/H2 force calls
return finite energy, force, and stress arrays.

The calculation tests use `tests/fixtures/H.psml`.

Run the tests from the Nix development shell with:

```bash
nix develop -c mpirun -n 1 uv run python -m mpi4py -m pytest -q
```

The Nix development shell exports OpenMPI's library directory so uv's isolated
Python environment and `mpi4py` can locate `libmpi.so`.

The smoke calculations deliberately use very loose settings, including a tiny
basis, low mesh cutoff, one SCF step, and `SCF.MustConverge false`, so warnings
about unconverged SCF are expected.

## Behaviour
See also [the SiestaSubroutine readme](https://gitlab.com/siesta-project/siesta/tree/master/Util/SiestaSubroutine/README).
Expand All @@ -88,5 +143,4 @@ It will also prepend some settings to your fdf-file: Notably `MD.TypeOfRun force
* Only a few properties can currently be fetched directly via the bindings. Other properties must be obtained via the output-files.
Feel free to create an issue if you'd like something in particular built-in, or send a PR if you've implemented it already.
* You don't get an exception when eg. the fdf-file contains an error. Instead, the whole process dies.
This is because on error, Siesta calls `abort()` to "helpfully" crash and spit out a stacktrace.
TODO: Can we catch sigabrt and raise a Python exception with the stacktrace instead?
This is because on error, Siesta intentionally kills itself which unfortunately includes the Python process.
27 changes: 27 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 53 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
description = "PSiesta development shell";

inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";

outputs = { nixpkgs, ... }:
let
systems = [ "x86_64-linux" "aarch64-linux" ];
forAllSystems = nixpkgs.lib.genAttrs systems;
in
{
devShells = forAllSystems (system:
let
pkgs = import nixpkgs { inherit system; };
in
{
default = pkgs.mkShell {
packages = with pkgs; [
stdenv.cc
gfortran
cmake
ninja
pkg-config
git

openmpi
blas
lapack
scalapack
curl
hdf5
netcdf
netcdffortran
fftw
libxc
readline
zlib

uv
];

shellHook = ''
export CC=mpicc
export CXX=mpicxx
export FC=mpifort
export F77=mpifort
export F90=mpifort
export LD_LIBRARY_PATH=${pkgs.openmpi}/lib:$LD_LIBRARY_PATH
'';
};
});
};
}
28 changes: 28 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[build-system]
requires = ["scikit-build-core", "cython", "numpy"]
build-backend = "scikit_build_core.build"

[project]
name = "psiesta"
version = "0.3.0"
description = "Run Siesta as a subroutine in Python, with ASE bindings"
readme = "README.md"
requires-python = ">=3.9"
authors = [{ name = "Jonas Lundholm Bertelsen" }]
dependencies = ["numpy", "mpi4py", "ase", "sisl"]

[project.urls]
Homepage = "https://github.com/jonaslb/psiesta"

[dependency-groups]
dev = ["pytest"]

[tool.uv]
default-groups = ["dev"]

[tool.scikit-build]
minimum-version = "0.10"
build-dir = "build/{wheel_tag}"

[tool.pytest.ini_options]
testpaths = ["tests"]
4 changes: 0 additions & 4 deletions requirements.txt

This file was deleted.

Loading