diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml index 6248fc431..22b0c3fe2 100644 --- a/.github/workflows/Build.yml +++ b/.github/workflows/Build.yml @@ -90,6 +90,58 @@ jobs: # uses: mxschmitt/action-tmate@v3 # timeout-minutes: 15 + # Build and test QUIP with MPI + ScaLAPACK (Linux only) + build-mpi: + name: Build QUIP with MPI (ubuntu-latest) + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: 'recursive' + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install system dependencies + run: | + sudo apt-get update -y + sudo apt-get install -y gfortran meson ninja-build \ + libopenblas-dev liblapack-dev \ + libnetcdf-dev libhdf5-serial-dev \ + libopenmpi-dev libscalapack-openmpi-dev + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install numpy ase meson-python build + pip install 'f90wrap>=0.3.0' + env: + FC: gfortran + CC: gcc + + - name: Build QUIP libraries (MPI) + run: | + meson setup builddir -Dmpi=true + meson compile -C builddir + + - name: Build and install quippy + run: | + cd quippy + pip install . + + - name: Run tests (including ScaLAPACK) + run: | + cd tests + BUILDDIR=${{ github.workspace }}/builddir/src/Programs \ + HAVE_SCALAPACK=1 \ + OMPI_ALLOW_RUN_AS_ROOT=1 \ + OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 \ + python run_all.py + # Builds the QUIP docs webpage image. This only happens ON the public # branch, after tests pass and pull requests are completed # TEMPORARILY DISABLED - needs investigation diff --git a/meson.build b/meson.build index f6aa346a9..7901cd4e5 100644 --- a/meson.build +++ b/meson.build @@ -31,6 +31,7 @@ if get_option('gap') '-DGAP_VERSION='+gap_version_res.stdout().strip(), '-DHAVE_GAP', '-DHAVE_TB', + '-DHAVE_QR', ], language:'fortran') endif @@ -65,8 +66,13 @@ add_global_arguments([ if get_option('mpi') message('MPI ON') + scalapack_dep = dependency('scalapack', required: false) + if not scalapack_dep.found() + message('scalapack not found via pkg-config/cmake, trying system library search') + scalapack_dep = fortran.find_library('scalapack', required: true) + endif mpi_dep = [ - dependency('scalapack'), + scalapack_dep, dependency('mpi', language: 'fortran'), dependency('mpi', language: 'c'), ] diff --git a/src/Programs/meson.build b/src/Programs/meson.build index 5a8fb65d6..015cb8140 100644 --- a/src/Programs/meson.build +++ b/src/Programs/meson.build @@ -12,7 +12,7 @@ link_quip = [ # gap_fit library (special case - needed before gap_fit_exe) gap_fit_lib = library('gap_fit', '../GAP/gap_fit_module.F90', - dependencies: [blas_dep], + dependencies: [blas_dep, mpi_dep], link_with: link_quip, ) diff --git a/tests/test_gapfit.py b/tests/test_gapfit.py index 2cd40416c..cdcb732bc 100644 --- a/tests/test_gapfit.py +++ b/tests/test_gapfit.py @@ -125,6 +125,13 @@ def check_gap_committee(self, err_tol=0.2): assert np.all(mean_errs < err_tol) np.random.seed(None) + def check_qr_path(self): + """check that QR decomposition was used (requires -DHAVE_QR)""" + with open(self.log_name) as f: + log = f.read() + self.assertIn("Using LAPACK to solve QR", log, + "QR decomposition not used - check -DHAVE_QR is set") + def check_gap_fit(self, ref_file=None): """check GP coefficients match expected values""" assert self.proc.returncode == 0, self.proc @@ -226,6 +233,7 @@ def test_si_soap_cur_points(self): gap = self.get_gap(self.gap_soap_template, 'sparse_method=cur_points') config = self.get_config('Si.np1.xyz', gap, extra='condition_number_norm=I') self.run_gap_fit(config) + self.check_qr_path() self.check_gap_fit(ref_file) def test_si_distance_2b_uniform(self): @@ -234,6 +242,7 @@ def test_si_distance_2b_uniform(self): gap = self.get_gap(self.gap_distance_2b_template, 'sparse_method=uniform') config = self.get_config('Si.np1.xyz', gap) self.run_gap_fit(config) + self.check_qr_path() self.check_gap_fit(ref_file) def test_si_two_descriptors(self): @@ -244,6 +253,7 @@ def test_si_two_descriptors(self): gap = ":".join([gap1, gap2]) config = self.get_config('Si.np1.xyz', gap) self.run_gap_fit(config) + self.check_qr_path() self.check_gap_fit(ref_file) def test_sic_distance_2b_uniform(self): @@ -252,6 +262,7 @@ def test_sic_distance_2b_uniform(self): gap = self.get_gap(self.gap_distance_2b_template, 'sparse_method=uniform') config = self.get_config('SiC.np1.xyz', gap) self.run_gap_fit(config) + self.check_qr_path() self.check_gap_fit(ref_file) def test_si_distance_2b_index(self): @@ -261,6 +272,7 @@ def test_si_distance_2b_index(self): config = self.get_config('Si.np1.xyz', gap) self.make_first_index_file_from_ref(ref_file, self.index_name_inp) self.run_gap_fit(config) + self.check_qr_path() self.check_gap_fit(ref_file) def test_si_config_file_sparsify_only(self):