Skip to content

Commit 6d4f44e

Browse files
authored
Enable run without mpi4py (#45)
* add file ddm/mpi.py for MockMPI and MPIWrapper (previously in Struphy, now moved here) * fix assertions in cart.py * fix mpi4py imports everywhere * make mpi4py an optional dependency; run tests without mpi too * add forgotton change * change version to 2.5.0 for last release still on GitLab * install Struphy from branch 467-no-mpi; perform Struphy unit tests on 1 and 2 processes * checkout correct Struphy branch 467-no-mpi * correct TODO to devel * remove the unneccessary if TYPE_CHECKING thing from all files, except mpi.py * set mpi_enabled to True if mpi4py is installed, TODO: check if it runs under mpirun * test with updated struphy branch * test with updated struphy branch
1 parent b338a70 commit 6d4f44e

23 files changed

+173
-62
lines changed

.github/workflows/testing.yml

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ jobs:
328328
# f = h5py.File('parallel_test.hdf5', 'w', driver='mpio', comm=MPI.COMM_WORLD)
329329
# print(f)"
330330

331-
- name: Install project
331+
- name: Install project without mpi
332332
run: |
333333
python -m pip install ".[test]" --no-cache-dir
334334
python -m pip freeze
@@ -362,15 +362,20 @@ jobs:
362362
run: |
363363
python -m pytest -n auto --pyargs psydac -m "not parallel and not petsc"
364364
365-
- name: Run MPI tests with Pytest
365+
- name: Run single-process PETSc tests with Pytest
366366
working-directory: ./pytest
367367
run: |
368-
python mpi_tester.py --mpirun="mpiexec -n 4 ${MPI_OPTS}" --pyargs psydac -m "parallel and not petsc"
368+
python -m pytest -n auto --pyargs psydac -m "not parallel and petsc"
369369
370-
- name: Run single-process PETSc tests with Pytest
370+
- name: Install project with mpi
371+
run: |
372+
python -m pip install ".[mpi]"
373+
python -m pip freeze
374+
375+
- name: Run MPI tests with Pytest
371376
working-directory: ./pytest
372377
run: |
373-
python -m pytest -n auto --pyargs psydac -m "not parallel and petsc"
378+
python mpi_tester.py --mpirun="mpiexec -n 4 ${MPI_OPTS}" --pyargs psydac -m "parallel and not petsc"
374379
375380
- name: Run MPI PETSc tests with Pytest
376381
working-directory: ./pytest
@@ -381,6 +386,7 @@ jobs:
381386
if: always()
382387
run: |
383388
rm -rf pytest
389+
384390
test_struphy:
385391
runs-on: ${{ matrix.os }}
386392
strategy:
@@ -469,7 +475,7 @@ jobs:
469475
run: |
470476
python -m pip install --upgrade pip
471477
472-
- name: Install project
478+
- name: Install project without mpi
473479
run: |
474480
python -m pip install ".[test]" --no-cache-dir
475481
python -m pip freeze
@@ -479,16 +485,17 @@ jobs:
479485
echo "PSYDAC_DIR=$GITHUB_WORKSPACE" >> $GITHUB_ENV
480486
echo "STRUPHY_DIR=$GITHUB_WORKSPACE/struphy" >> $GITHUB_ENV
481487
482-
- name: Clone struphy from GitLab #TODO: Set branch to devel
488+
- name: Clone struphy from GitLab
483489
run: |
484490
git clone https://gitlab.mpcdf.mpg.de/struphy/struphy.git $STRUPHY_DIR
485491
486-
- name: Install struphy
492+
- name: Install struphy without mpi #TODO: Set branch to devel
487493
working-directory: ${{ env.STRUPHY_DIR }}
488494
run: |
489495
echo "Psydac location for this branch"
490496
pip show psydac
491497
pip uninstall psydac -y
498+
git checkout 467-no-mpi
492499
python -m pip install ".[phys]" --no-cache-dir
493500
echo "Psydac location after installing struphy"
494501
pip show psydac
@@ -520,6 +527,16 @@ jobs:
520527
run: |
521528
struphy test models --fast
522529
530+
- name: Install mpi4py
531+
working-directory: ${{ env.STRUPHY_DIR }}
532+
run: |
533+
pip install -U mpi4py
534+
535+
- name: Run struphy unit tests with mpi
536+
working-directory: ${{ env.STRUPHY_DIR }}
537+
run: |
538+
struphy test unit --mpi 2
539+
523540
- name: Remove test directory
524541
if: always()
525542
run: |

mpi_tester.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ def MPITest(commsize):
101101
def test_stuff(comm):
102102
pass
103103
"""
104-
from mpi4py import MPI
104+
from psydac.ddm.mpi import mpi as MPI
105+
105106
if not isinstance(commsize, (tuple, list)):
106107
commsize = (commsize,)
107108

@@ -182,7 +183,7 @@ def __init__(self):
182183
#---------------------------------------------------------------------------
183184
@property
184185
def comm(self):
185-
from mpi4py import MPI
186+
from psydac.ddm.mpi import mpi as MPI
186187
return MPI.COMM_WORLD
187188

188189
#---------------------------------------------------------------------------

psydac/ddm/blocking_data_exchanger.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# coding: utf-8
22

33
import numpy as np
4-
from mpi4py import MPI
4+
from psydac.ddm.mpi import mpi as MPI
55

66
from .cart import CartDecomposition, find_mpi_type
77
from .basic import CartDataExchanger

psydac/ddm/cart.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
import os
44
import numpy as np
55
from itertools import product
6-
from mpi4py import MPI
76

7+
from psydac.ddm.mpi import mpi as MPI
8+
from psydac.ddm.mpi import MockMPI
89
from psydac.ddm.partition import compute_dims, partition_procs_per_patch
910

1011

@@ -31,11 +32,14 @@ def find_mpi_type( dtype ):
3132
MPI datatype to be used for communication.
3233
3334
"""
34-
if isinstance( dtype, MPI.Datatype ):
35-
mpi_type = dtype
35+
if not isinstance(MPI, MockMPI):
36+
if isinstance( dtype, MPI.Datatype ):
37+
mpi_type = dtype
38+
else:
39+
nt = np.dtype( dtype )
40+
mpi_type = MPI._typedict[nt.char]
3641
else:
37-
nt = np.dtype( dtype )
38-
mpi_type = MPI._typedict[nt.char]
42+
mpi_type = np.dtype( dtype )
3943

4044
return mpi_type
4145

@@ -63,7 +67,8 @@ class MultiPatchDomainDecomposition:
6367
def __init__(self, ncells, periods, comm=None, num_threads=None):
6468

6569
assert len( ncells ) == len( periods )
66-
if comm is not None:assert isinstance( comm, MPI.Comm )
70+
if not isinstance(MPI, MockMPI) and comm is not None:
71+
assert isinstance( comm, MPI.Comm )
6772
num_threads = num_threads if num_threads else int(os.environ.get('OMP_NUM_THREADS', 1))
6873

6974
# Store input arguments
@@ -206,7 +211,12 @@ def __init__(self, ncells, periods, comm=None, global_comm=None, num_threads=Non
206211
assert len( ncells ) == len( periods )
207212
assert all( n >=1 for n in ncells )
208213
assert all( isinstance( period, bool ) for period in periods )
209-
if comm is not None: assert isinstance( comm, MPI.Comm )
214+
if isinstance(MPI, MockMPI):
215+
comm = None
216+
else:
217+
if comm is not None:
218+
assert isinstance( comm, MPI.Comm )
219+
210220

211221
self._ncells = tuple ( ncells )
212222
self._periods = tuple ( periods )

psydac/ddm/interface_data_exchanger.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# coding: utf-8
22

3-
from mpi4py import MPI
3+
from psydac.ddm.mpi import mpi as MPI
44

55
from .cart import InterfaceCartDecomposition, find_mpi_type
66

psydac/ddm/mpi.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
from dataclasses import dataclass
2+
from time import time
3+
from typing import TYPE_CHECKING
4+
5+
6+
# Might not be needed
7+
class MPICommWrapper:
8+
def __init__(self, use_mpi=True):
9+
self.use_mpi = use_mpi
10+
if use_mpi:
11+
from mpi4py import MPI
12+
13+
self.comm = MPI.COMM_WORLD
14+
else:
15+
self.comm = MockComm()
16+
17+
def __getattr__(self, name):
18+
return getattr(self.comm, name)
19+
20+
21+
class MockComm:
22+
def __getattr__(self, name):
23+
# Return a function that does nothing and returns None
24+
def dummy(*args, **kwargs):
25+
return None
26+
27+
return dummy
28+
29+
# Override some functions
30+
def Get_rank(self):
31+
return 0
32+
33+
def Get_size(self):
34+
return 1
35+
36+
def Barrier(self):
37+
return
38+
39+
40+
class MPIwrapper:
41+
def __init__(self, use_mpi: bool = False):
42+
self.use_mpi = use_mpi
43+
if use_mpi:
44+
from mpi4py import MPI
45+
46+
self._MPI = MPI
47+
print("MPI is enabled")
48+
else:
49+
self._MPI = MockMPI()
50+
print("MPI is NOT enabled")
51+
52+
@property
53+
def MPI(self):
54+
return self._MPI
55+
56+
57+
class MockMPI:
58+
def __getattr__(self, name):
59+
# Return a function that does nothing and returns None
60+
def dummy(*args, **kwargs):
61+
return None
62+
63+
return dummy
64+
65+
# Override some functions
66+
@property
67+
def COMM_WORLD(self):
68+
return MockComm()
69+
70+
# def comm_Get_rank(self):
71+
# return 0
72+
73+
# def comm_Get_size(self):
74+
# return 1
75+
76+
77+
try:
78+
from mpi4py import MPI
79+
80+
_comm = MPI.COMM_WORLD
81+
rank = _comm.Get_rank()
82+
size = _comm.Get_size()
83+
mpi_enabled = True
84+
except ImportError:
85+
# mpi4py not installed
86+
mpi_enabled = False
87+
except Exception:
88+
# mpi4py installed but not running under mpirun
89+
mpi_enabled = False
90+
91+
# TODO: add environment variable for mpi use
92+
mpi_wrapper = MPIwrapper(use_mpi=mpi_enabled)
93+
94+
# TYPE_CHECKING is True when type checking (e.g., mypy), but False at runtime.
95+
if TYPE_CHECKING:
96+
from mpi4py import MPI
97+
98+
mpi = MPI
99+
else:
100+
mpi = mpi_wrapper.MPI

psydac/ddm/nonblocking_data_exchanger.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
import numpy as np
44
from itertools import product
5-
from mpi4py import MPI
65

6+
from psydac.ddm.mpi import mpi as MPI
77
from .cart import CartDecomposition, find_mpi_type
88
from .basic import CartDataExchanger
99

psydac/ddm/tests/test_cart_1d.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
def run_cart_1d( data_exchanger_type, verbose=False ):
1212

1313
import numpy as np
14-
from mpi4py import MPI
14+
from psydac.ddm.mpi import mpi as MPI
1515
from psydac.ddm.cart import DomainDecomposition, CartDecomposition
1616

1717
#---------------------------------------------------------------------------

psydac/ddm/tests/test_cart_2d.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
def run_cart_2d( data_exchanger_type, verbose=False , nprocs=None, reverse_axis=None):
1010

1111
import numpy as np
12-
from mpi4py import MPI
12+
from psydac.ddm.mpi import mpi as MPI
1313
from psydac.ddm.cart import DomainDecomposition, CartDecomposition
1414

1515
#---------------------------------------------------------------------------

psydac/ddm/tests/test_cart_3d.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
def run_cart_3d( data_exchanger_type, verbose=False ):
1010

1111
import numpy as np
12-
from mpi4py import MPI
12+
from psydac.ddm.mpi import mpi as MPI
1313
from psydac.ddm.cart import DomainDecomposition, CartDecomposition
1414

1515
#---------------------------------------------------------------------------

0 commit comments

Comments
 (0)