Skip to content
Merged
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
65 changes: 65 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: LPC3D CI

on:
push:
pull_request:
workflow_dispatch:

jobs:
lpc3d-cpu:
name: LPC3D (CPU) - ${{ matrix.case }} - Python ${{ matrix.python-version }}
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.12"]
case:
- bulk
- supercapacitor

steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip

- name: Install dependencies
run: |
python -m pip install --upgrade pip
# LPC3D uses pystencils.session, removed in pystencils 2.x
pip install "pystencils<2" numba ipython matplotlib pyevtk
pip freeze > pip-freeze.txt

- name: Prepare run directory
run: |
mkdir -p run
cp tests/ci/${{ matrix.case }}/lattice_gas.inpt run/
if [ "${{ matrix.case }}" = "supercapacitor" ]; then
cp examples/short-test/psd.txt examples/short-test/pore_dens_freq_2neg.txt run/
fi

- name: Run LPC3D
working-directory: run
env:
OMP_NUM_THREADS: "2"
NUMBA_NUM_THREADS: "2"
run: |
set -o pipefail
python3 ../LPC3D.py -i lattice_gas.inpt | tee log.lpc3d

- name: Check outputs
run: |
if [ "${{ matrix.case }}" = "supercapacitor" ]; then NB=3; else NB=1; fi
python3 tests/ci/check_outputs.py run $NB

- name: Upload outputs and environment
if: always()
uses: actions/upload-artifact@v4
with:
name: lpc3d-${{ matrix.case }}-py${{ matrix.python-version }}
path: |
run/
pip-freeze.txt
4 changes: 3 additions & 1 deletion LPC3D.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,9 @@ def fourier_transform(ReGtotsmp, ImGtotsmp, Nvalues, dwell, larmorfreq, tau, nsa
minvalue = np.min(NMR_spec)
NMR_spec -= minvalue

save = np.trapz(NMR_spec, NMR_freq)
# np.trapz was renamed np.trapezoid in NumPy 2.0 and later removed
trapezoid = getattr(np, "trapezoid", None) or np.trapz
save = trapezoid(NMR_spec, NMR_freq)

name = f"Normalised_spectrum-{nsample}-bloc-{nb}.dat"
with open(name, "w") as outFT:
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ Here, a manual and example input files are provided.

Compared to the previous version of the program, written in C and serial (https://github.com/cmerlet/LPC3D-C-serial), this code is written using pystencils (https://pypi.org/project/pystencils/), is parallel and can use CPU and GPU.

## Continuous integration

![CI](https://github.com/multixscale/LPC3D/actions/workflows/ci.yml/badge.svg)

## Multiscale coupling with LAMMPS

The `LAMMPS-LPC3D/` folder contains a coupled version of the code, in which LPC3D drives an ensemble of independent LAMMPS simulations, one for each pore size. The pore-resolved densities obtained from molecular dynamics are transferred on the fly to the lattice gas model. The coupling is implemented in Python with mpi4py and the LAMMPS Python interface, and can be run on CPU or GPU (LAMMPS with Kokkos). Examples for both modes are provided in `LAMMPS-LPC3D/Examples/`, and the installation, input format and usage are described in the manual.
Expand Down
21 changes: 21 additions & 0 deletions tests/ci/bulk/lattice_gas.inpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
cpu
10 10 10
true true true
1.0
5e-6
1
298
282
100
0.0
0
bulk
1

bulk
1 10
1 10
1 10
0.296297
0.000

48 changes: 48 additions & 0 deletions tests/ci/check_outputs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""Check that an LPC3D run produced the expected, valid output files.

Usage: python3 check_outputs.py <run_directory> <number_of_blocks>
"""
import sys
from pathlib import Path

import numpy as np


def main():
run_dir = Path(sys.argv[1])
nblocks = int(sys.argv[2])
errors = []

for b in range(1, nblocks + 1):
expected = [
f"density_{b}.txt",
f"vacf_{b}.txt",
f"Diffusion_coeff_x_{b}.txt",
f"Diffusion_coeff_y_{b}.txt",
f"Diffusion_coeff_z_{b}.txt",
f"FT_signal-1-bloc-{b}.dat",
f"Normalised_spectrum-1-bloc-{b}.dat",
]
for name in expected:
path = run_dir / name
if not path.is_file() or path.stat().st_size == 0:
errors.append(f"missing or empty: {name}")
continue
try:
data = np.loadtxt(path, ndmin=1)
except ValueError as exc:
errors.append(f"unreadable: {name} ({exc})")
continue
if not np.all(np.isfinite(data)):
errors.append(f"NaN or Inf values in: {name}")

if errors:
print("Output check FAILED:")
for e in errors:
print(" -", e)
sys.exit(1)
print(f"Output check passed for {nblocks} block(s) in {run_dir}")


if __name__ == "__main__":
main()
37 changes: 37 additions & 0 deletions tests/ci/supercapacitor/lattice_gas.inpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
cpu
10 10 30
true true false
1.0
5e-6
1
298
282
100
0.0
0
porous_carbon
3

electrode
1
1 10
1 10
1 10
psd.txt
pore_dens_freq_2neg.txt

bulk
1 10
1 10
11 20
0.296297
0.000

electrode
1
1 10
1 10
21 30
psd.txt
pore_dens_freq_2neg.txt

Loading