Multiclass-interface 1.12__tar.gz → 1.15__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {multiclass_interface-1.12 → multiclass_interface-1.15}/.gitlab-ci.yml +4 -23
- {multiclass_interface-1.12 → multiclass_interface-1.15/Multiclass_interface.egg-info}/PKG-INFO +1 -1
- {multiclass_interface-1.12/Multiclass_interface.egg-info → multiclass_interface-1.15}/PKG-INFO +1 -1
- {multiclass_interface-1.12 → multiclass_interface-1.15}/multiclass_interface/mpi_interface.py +77 -45
- multiclass_interface-1.15/multiclass_interface/tests/test_mpi.py +211 -0
- {multiclass_interface-1.12 → multiclass_interface-1.15}/multiclass_interface/version.py +9 -4
- {multiclass_interface-1.12 → multiclass_interface-1.15}/pyproject.toml +0 -3
- multiclass_interface-1.12/multiclass_interface/tests/test_mpi.py +0 -97
- {multiclass_interface-1.12 → multiclass_interface-1.15}/.gitignore +0 -0
- {multiclass_interface-1.12 → multiclass_interface-1.15}/LICENSE +0 -0
- {multiclass_interface-1.12 → multiclass_interface-1.15}/Multiclass_interface.egg-info/SOURCES.txt +0 -0
- {multiclass_interface-1.12 → multiclass_interface-1.15}/Multiclass_interface.egg-info/dependency_links.txt +0 -0
- {multiclass_interface-1.12 → multiclass_interface-1.15}/Multiclass_interface.egg-info/requires.txt +0 -0
- {multiclass_interface-1.12 → multiclass_interface-1.15}/Multiclass_interface.egg-info/top_level.txt +0 -0
- {multiclass_interface-1.12 → multiclass_interface-1.15}/README.md +0 -0
- {multiclass_interface-1.12 → multiclass_interface-1.15}/multiclass_interface/__init__.py +0 -0
- {multiclass_interface-1.12 → multiclass_interface-1.15}/multiclass_interface/multi_object_list.py +0 -0
- {multiclass_interface-1.12 → multiclass_interface-1.15}/multiclass_interface/multiprocess_interface.py +0 -0
- {multiclass_interface-1.12 → multiclass_interface-1.15}/multiclass_interface/tests/__init__.py +0 -0
- {multiclass_interface-1.12 → multiclass_interface-1.15}/multiclass_interface/tests/my_test_cls.py +0 -0
- {multiclass_interface-1.12 → multiclass_interface-1.15}/multiclass_interface/tests/test_multiclass_list.py +0 -0
- {multiclass_interface-1.12 → multiclass_interface-1.15}/multiclass_interface/tests/test_multiprocessinterface.py +0 -0
- {multiclass_interface-1.12 → multiclass_interface-1.15}/sequence_diagrams.md +0 -0
- {multiclass_interface-1.12 → multiclass_interface-1.15}/setup.cfg +0 -0
|
@@ -2,39 +2,22 @@ image: continuumio/anaconda3
|
|
|
2
2
|
|
|
3
3
|
stages:
|
|
4
4
|
- test
|
|
5
|
-
- build
|
|
6
5
|
- deploy
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
test_ubuntu: # name the job what we like
|
|
10
|
-
image: continuumio/anaconda3
|
|
11
|
-
stage: # build, test, deploy defined by default [2]
|
|
12
|
-
test
|
|
13
|
-
script:
|
|
14
|
-
- pip install -e .[test]
|
|
15
|
-
- pytest --cov-report term-missing:skip-covered --cov=multiclass_interface multiclass_interface/tests
|
|
16
|
-
coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
|
|
17
|
-
tags: # only runners with this tag can do the job [3]
|
|
18
|
-
- ci-ubuntu
|
|
19
|
-
rules:
|
|
20
|
-
- if: $CI_COMMIT_BRANCH == "main"
|
|
21
|
-
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
mpitest-linux_openmpi:
|
|
7
|
+
test_mpitest_openmpi:
|
|
25
8
|
image: registry.windenergy.dtu.dk/hawc2/hawc2lib:mpi
|
|
26
9
|
variables:
|
|
27
10
|
GIT_SUBMODULE_STRATEGY: recursive # need to checkout hawc2 and utils
|
|
28
11
|
script:
|
|
29
12
|
- pip install .[test]
|
|
30
|
-
-
|
|
31
|
-
- python -c "from multiclass_interface import version; print (f'{version.__version__}')"
|
|
13
|
+
- pytest --cov-report term-missing:skip-covered --cov=multiclass_interface multiclass_interface/tests
|
|
32
14
|
rules:
|
|
33
15
|
- if: $CI_COMMIT_BRANCH == "main"
|
|
34
16
|
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
35
17
|
tags:
|
|
36
18
|
- ci-ubuntu
|
|
37
19
|
|
|
20
|
+
|
|
38
21
|
# ===== Check code style =====
|
|
39
22
|
check_code_style: # name the job what we like
|
|
40
23
|
stage: # build, test, deploy defined by default [2]
|
|
@@ -49,8 +32,6 @@ check_code_style: # name the job what we like
|
|
|
49
32
|
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
50
33
|
|
|
51
34
|
|
|
52
|
-
|
|
53
|
-
|
|
54
35
|
pypi_linux:
|
|
55
36
|
stage:
|
|
56
37
|
deploy
|
|
@@ -61,7 +42,7 @@ pypi_linux:
|
|
|
61
42
|
#- pip install pypandoc
|
|
62
43
|
- pip install -e . --upgrade
|
|
63
44
|
- pip install --upgrade build
|
|
64
|
-
- pip install --upgrade
|
|
45
|
+
- pip install twine==6.0.1 --upgrade
|
|
65
46
|
- python3 -m build
|
|
66
47
|
- twine upload dist/* -u $TWINE_USERNAME -p $TWINE_PASSWORD
|
|
67
48
|
rules:
|
{multiclass_interface-1.12 → multiclass_interface-1.15}/multiclass_interface/mpi_interface.py
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
import sys
|
|
3
3
|
import traceback
|
|
4
|
-
import
|
|
4
|
+
from contextlib import contextmanager
|
|
5
5
|
|
|
6
6
|
comm = None
|
|
7
7
|
size = 1
|
|
@@ -11,8 +11,8 @@ mpi = False
|
|
|
11
11
|
main = True
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
def activate_mpi(collective_mpi=True,
|
|
15
|
-
global comm, size, rank, name, main, mpi,
|
|
14
|
+
def activate_mpi(collective_mpi=True, terminate_on_close=True):
|
|
15
|
+
global comm, size, rank, name, main, mpi, TERMINATE_ON_CLOSE, COLLECTIVE_MPI
|
|
16
16
|
from mpi4py import MPI
|
|
17
17
|
comm = MPI.COMM_WORLD
|
|
18
18
|
size = MPI.COMM_WORLD.Get_size()
|
|
@@ -21,12 +21,12 @@ def activate_mpi(collective_mpi=True, loop_until_close=True, terminate_on_close=
|
|
|
21
21
|
mpi = True
|
|
22
22
|
main = rank == 0
|
|
23
23
|
COLLECTIVE_MPI = collective_mpi
|
|
24
|
-
LOOP_UNTIL_CLOSE = loop_until_close
|
|
25
24
|
TERMINATE_ON_CLOSE = terminate_on_close
|
|
26
25
|
|
|
27
26
|
|
|
28
27
|
MPIClassInterfaceAttributes = {
|
|
29
28
|
'close',
|
|
29
|
+
'release_mpi_workers',
|
|
30
30
|
'use_rank',
|
|
31
31
|
'cls',
|
|
32
32
|
'work_loop',
|
|
@@ -38,13 +38,27 @@ MPIClassInterfaceAttributes = {
|
|
|
38
38
|
'closed'}
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
LOOP_UNTIL_CLOSE = True # MPI workers (rank>0) will enter the work loop and wait for commands from rank 0
|
|
42
41
|
TERMINATE_ON_CLOSE = True # MPI workers (rank>0) will terminate (sys.exit) when the close command is called via rank0
|
|
43
42
|
COLLECTIVE_MPI = True # rank0 acts as a single point of contact (scatters command+arg and gathers results)
|
|
44
43
|
|
|
45
44
|
|
|
45
|
+
def main_run(f, *args, **kwargs):
|
|
46
|
+
if main:
|
|
47
|
+
r = f(*args, **kwargs)
|
|
48
|
+
if COLLECTIVE_MPI:
|
|
49
|
+
return r[0]
|
|
50
|
+
else:
|
|
51
|
+
try:
|
|
52
|
+
f(*args, **kwargs)
|
|
53
|
+
except BaseException:
|
|
54
|
+
pass
|
|
55
|
+
r = None
|
|
56
|
+
return comm.bcast(r)
|
|
57
|
+
|
|
58
|
+
|
|
46
59
|
class MPIClassInterface():
|
|
47
60
|
def __init__(self, cls, args_lst):
|
|
61
|
+
self.closed = False
|
|
48
62
|
if len(args_lst) > size:
|
|
49
63
|
if rank == 0:
|
|
50
64
|
raise Exception(f"Not enough mpi slots. Slots: {size}, Requested: {len(args_lst)}")
|
|
@@ -59,13 +73,12 @@ class MPIClassInterface():
|
|
|
59
73
|
|
|
60
74
|
def __getattr__(self, name):
|
|
61
75
|
raise AttributeError(
|
|
62
|
-
f"
|
|
76
|
+
f"Rank {rank} trying to access {name}, but only {len(args_lst)} instances of {cls.__name__} exists")
|
|
63
77
|
self.object = Dummy()
|
|
64
|
-
self.closed = False
|
|
65
78
|
|
|
66
79
|
self.use_rank = np.array([True] * size)
|
|
67
80
|
self.use_rank[len(args_lst):] = False
|
|
68
|
-
if rank > 0 and COLLECTIVE_MPI
|
|
81
|
+
if rank > 0 and COLLECTIVE_MPI:
|
|
69
82
|
self.work_loop()
|
|
70
83
|
|
|
71
84
|
def __enter__(self):
|
|
@@ -78,20 +91,20 @@ class MPIClassInterface():
|
|
|
78
91
|
while True:
|
|
79
92
|
method, args, kwargs = comm.scatter(None)
|
|
80
93
|
comm.gather(self.do_task(method, args, kwargs))
|
|
81
|
-
if
|
|
82
|
-
if method == 'close':
|
|
83
|
-
if TERMINATE_ON_CLOSE:
|
|
84
|
-
# comm.gather(f'Exit, rank {rank}')
|
|
85
|
-
print("sys.exit", rank, flush=True)
|
|
86
|
-
sys.exit(0)
|
|
87
|
-
else:
|
|
88
|
-
raise ChildProcessError('MPI worker done')
|
|
89
|
-
else:
|
|
94
|
+
if method == 'exit_work_loop':
|
|
90
95
|
break
|
|
96
|
+
if method == 'close':
|
|
97
|
+
if TERMINATE_ON_CLOSE:
|
|
98
|
+
# comm.gather(f'Exit, rank {rank}')
|
|
99
|
+
print("sys.exit", rank, flush=True)
|
|
100
|
+
self.closed = True
|
|
101
|
+
sys.exit(0)
|
|
102
|
+
else:
|
|
103
|
+
raise ChildProcessError('MPI worker done')
|
|
91
104
|
|
|
92
105
|
def do_task(self, method, args, kwargs):
|
|
93
106
|
try:
|
|
94
|
-
if method == 'skip':
|
|
107
|
+
if method == 'skip' or method == 'exit_work_loop':
|
|
95
108
|
res = None
|
|
96
109
|
elif method == 'setattr':
|
|
97
110
|
name, value = args
|
|
@@ -105,29 +118,28 @@ class MPIClassInterface():
|
|
|
105
118
|
return res
|
|
106
119
|
|
|
107
120
|
def run_task(self, input_lst):
|
|
121
|
+
assert rank == 0, "other ranks should be in work_loop when invoking run_task. For non-collective mpi, do_task should be used"
|
|
108
122
|
comm.scatter(input_lst, root=0)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
else:
|
|
113
|
-
res = None
|
|
123
|
+
method, args, kwargs = input_lst[0]
|
|
124
|
+
res = self.do_task(method, args, kwargs)
|
|
125
|
+
|
|
114
126
|
use_rank = self.use_rank
|
|
115
127
|
|
|
116
128
|
res = [res for i, res in enumerate(comm.gather(res, root=0)) if use_rank[i]]
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
raise r[0].__class__(r[1])
|
|
129
|
+
for r in res:
|
|
130
|
+
if isinstance(r, tuple) and len(r) > 1 and isinstance(r[0], BaseException):
|
|
131
|
+
raise r[0].__class__(r[1])
|
|
121
132
|
return res
|
|
122
133
|
|
|
123
134
|
def get_input(self, name, i, args, kwargs):
|
|
124
135
|
use_rank = self.use_rank
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
136
|
+
N = np.sum(use_rank)
|
|
137
|
+
j = sum(use_rank[:i]) # needed for subsets
|
|
138
|
+
|
|
139
|
+
# if COLLECTIVE_MPI:
|
|
140
|
+
# j = sum(use_rank[:i])
|
|
141
|
+
# else:
|
|
142
|
+
# j = 0
|
|
131
143
|
|
|
132
144
|
if use_rank[i]:
|
|
133
145
|
def get_arg(arg):
|
|
@@ -135,6 +147,7 @@ class MPIClassInterface():
|
|
|
135
147
|
return arg[j]
|
|
136
148
|
else:
|
|
137
149
|
return arg
|
|
150
|
+
|
|
138
151
|
return (name, [get_arg(arg) for arg in args], {k: get_arg(v) for k, v in kwargs.items()})
|
|
139
152
|
else:
|
|
140
153
|
return ('skip', [], {})
|
|
@@ -142,10 +155,7 @@ class MPIClassInterface():
|
|
|
142
155
|
def __getattribute__(self, name):
|
|
143
156
|
if name in MPIClassInterfaceAttributes:
|
|
144
157
|
return object.__getattribute__(self, name)
|
|
145
|
-
elif
|
|
146
|
-
self.work_loop()
|
|
147
|
-
return lambda *args, **kwargs: 1
|
|
148
|
-
elif rank > 0 and not self.use_rank[rank]:
|
|
158
|
+
elif not self.use_rank[rank]:
|
|
149
159
|
raise ChildProcessError(f'MPI worker {rank} not used')
|
|
150
160
|
|
|
151
161
|
def wrap(*args, **kwargs):
|
|
@@ -153,20 +163,28 @@ class MPIClassInterface():
|
|
|
153
163
|
inp = [self.get_input(name, i, args, kwargs) for i in range(size)]
|
|
154
164
|
return self.run_task(inp)
|
|
155
165
|
else:
|
|
156
|
-
method, args, kwargs = self.get_input(name,
|
|
157
|
-
return [self.do_task(
|
|
158
|
-
|
|
166
|
+
method, args, kwargs = self.get_input(name, rank, args, kwargs)
|
|
167
|
+
return [self.do_task(method, args, kwargs)]
|
|
168
|
+
try:
|
|
169
|
+
att = getattr(self.object, name)
|
|
170
|
+
except AttributeError as e:
|
|
171
|
+
raise type(e)(f'Rank {rank}: {e}').with_traceback(sys.exc_info()[2])
|
|
172
|
+
|
|
173
|
+
if hasattr(att, '__call__'):
|
|
159
174
|
res = wrap
|
|
160
175
|
else:
|
|
161
176
|
res = wrap()
|
|
162
177
|
return res
|
|
163
178
|
|
|
164
179
|
def __setattr__(self, name, value):
|
|
165
|
-
if rank > 0 or name in MPIClassInterfaceAttributes:
|
|
180
|
+
if (COLLECTIVE_MPI and rank > 0) or name in MPIClassInterfaceAttributes:
|
|
166
181
|
return object.__setattr__(self, name, value)
|
|
167
182
|
else:
|
|
168
183
|
inp = [self.get_input('setattr', i, (name, value), {}) for i in range(size)]
|
|
169
|
-
|
|
184
|
+
if COLLECTIVE_MPI:
|
|
185
|
+
return self.run_task(inp)
|
|
186
|
+
else:
|
|
187
|
+
return self.do_task(*inp[rank])
|
|
170
188
|
|
|
171
189
|
def __getitem__(self, slice):
|
|
172
190
|
use_rank = np.full_like(self.use_rank, False)
|
|
@@ -175,14 +193,28 @@ class MPIClassInterface():
|
|
|
175
193
|
return self
|
|
176
194
|
return SubsetMPIClassInterface(self.cls, self.object, use_rank)
|
|
177
195
|
|
|
196
|
+
@contextmanager
|
|
197
|
+
def release_mpi_workers(self):
|
|
198
|
+
global COLLECTIVE_MPI
|
|
199
|
+
"""Use to release mpi_workers. All object statements including 'release_mpi_workers' must be protected by 'if mpi_interface.main:' """
|
|
200
|
+
if COLLECTIVE_MPI:
|
|
201
|
+
if rank == 0:
|
|
202
|
+
self.run_task([('exit_work_loop', [], {}) for _ in range(size)])
|
|
203
|
+
COLLECTIVE_MPI = False
|
|
204
|
+
try:
|
|
205
|
+
yield self
|
|
206
|
+
finally:
|
|
207
|
+
COLLECTIVE_MPI = True
|
|
208
|
+
if rank > 0 and not self.closed:
|
|
209
|
+
self.work_loop()
|
|
210
|
+
|
|
178
211
|
def close(self):
|
|
212
|
+
|
|
179
213
|
if not self.closed:
|
|
214
|
+
|
|
180
215
|
if COLLECTIVE_MPI:
|
|
181
216
|
if rank == 0:
|
|
182
217
|
res = self.run_task([('close', [], {}) for _ in range(size)])
|
|
183
|
-
else:
|
|
184
|
-
self.work_loop()
|
|
185
|
-
res = None
|
|
186
218
|
else:
|
|
187
219
|
res = self.do_task('close', [], {})
|
|
188
220
|
self.closed = True
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
import traceback
|
|
5
|
+
from numpy import testing as npt
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from multiclass_interface.mpi_interface import MPIClassInterface
|
|
9
|
+
from multiclass_interface.multiprocess_interface import ProcessClass
|
|
10
|
+
import numpy as np
|
|
11
|
+
from multiclass_interface.tests.my_test_cls import MyTest
|
|
12
|
+
from multiclass_interface import mpi_interface
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def mpirun(f):
|
|
16
|
+
if 'main()' in traceback.format_stack()[0]:
|
|
17
|
+
n = f.__name__
|
|
18
|
+
|
|
19
|
+
def wrap():
|
|
20
|
+
exe = sys.executable.replace("\\", "/")
|
|
21
|
+
cmd = f'''mpirun -n 4 {exe} -c "from multiclass_interface.tests.test_mpi import {n}; {n}()"'''
|
|
22
|
+
# print(cmd)
|
|
23
|
+
assert os.system(cmd) == 0, n
|
|
24
|
+
wrap.__name__ = n
|
|
25
|
+
return wrap
|
|
26
|
+
else:
|
|
27
|
+
return f
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@mpirun
|
|
31
|
+
def test_mpi_collective_too_few_slots():
|
|
32
|
+
mpi_interface.activate_mpi()
|
|
33
|
+
N = 4
|
|
34
|
+
rank = mpi_interface.rank
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
with MPIClassInterface(MyTest, [(i + 1,) for i in range(N + 1)]) as m:
|
|
38
|
+
pass
|
|
39
|
+
assert rank in [1, 2, 3]
|
|
40
|
+
except Exception as e:
|
|
41
|
+
assert rank == 0
|
|
42
|
+
assert str(e) == 'Not enough mpi slots. Slots: 4, Requested: 5'
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@mpirun
|
|
46
|
+
def test_mpi_collective_MyTest():
|
|
47
|
+
mpi_interface.activate_mpi()
|
|
48
|
+
N = 4
|
|
49
|
+
rank = mpi_interface.rank
|
|
50
|
+
|
|
51
|
+
with MPIClassInterface(MyTest, [(i + 1,) for i in range(N)]) as m:
|
|
52
|
+
if mpi_interface.main:
|
|
53
|
+
i = m.get_id()
|
|
54
|
+
npt.assert_array_equal(i, np.arange(N) + 1)
|
|
55
|
+
|
|
56
|
+
with m.release_mpi_workers():
|
|
57
|
+
assert m.get_id() == [rank + 1]
|
|
58
|
+
|
|
59
|
+
assert m.get_id() == [1, 2, 3, 4]
|
|
60
|
+
i = mpi_interface.main_run(lambda: m.get_id())
|
|
61
|
+
npt.assert_array_equal(i, 1)
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
npt.assert_array_equal(m[1:3].get_id(), np.arange(1, 3) + 1)
|
|
65
|
+
except ChildProcessError:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
m.x = "hello"
|
|
69
|
+
assert m.x == ['hello'] * 4
|
|
70
|
+
|
|
71
|
+
assert m.return_input([101, 102, 103, 104])[0] == f'1 got: ({101},) and ' + '{}'
|
|
72
|
+
m.x = 'hello'
|
|
73
|
+
assert m.x == ['hello'] * 4
|
|
74
|
+
m[1::2].x = 'world'
|
|
75
|
+
assert m[:].x == ['hello', 'world', 'hello', 'world']
|
|
76
|
+
|
|
77
|
+
m.release_mpi_workers()
|
|
78
|
+
m.x = [1, 2, 3, 4]
|
|
79
|
+
assert m.x == [1, 2, 3, 4]
|
|
80
|
+
m.y = list(np.array(m.x) + 10)
|
|
81
|
+
assert m.y == [11, 12, 13, 14]
|
|
82
|
+
t = time.time()
|
|
83
|
+
m.work(1)
|
|
84
|
+
t = time.time() - t
|
|
85
|
+
assert t < 1.1
|
|
86
|
+
|
|
87
|
+
with pytest.raises(Exception, match='Cannot close SubsetMPIClassInterface. Please close all instances at once'):
|
|
88
|
+
m[:3].close()
|
|
89
|
+
with pytest.raises(Exception, match='Cannot make subset of SubsetMPIClassInterface'):
|
|
90
|
+
m[:3][1]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@mpirun
|
|
94
|
+
def test_mpi_collective_close_released_mpi_workers():
|
|
95
|
+
mpi_interface.activate_mpi()
|
|
96
|
+
N = 4
|
|
97
|
+
rank = mpi_interface.rank
|
|
98
|
+
with MPIClassInterface(MyTest, [(i + 1,) for i in range(N)]) as m:
|
|
99
|
+
with m.release_mpi_workers():
|
|
100
|
+
m.close()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@mpirun
|
|
104
|
+
def test_mpi_ProcessClass():
|
|
105
|
+
mpi_interface.activate_mpi()
|
|
106
|
+
with ProcessClass(MyTest) as cls:
|
|
107
|
+
myTest = cls(1)
|
|
108
|
+
assert myTest.get_id() == 1
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@mpirun
|
|
112
|
+
def test_non_collective_mpi2():
|
|
113
|
+
mpi_interface.activate_mpi(collective_mpi=False)
|
|
114
|
+
rank = mpi_interface.rank
|
|
115
|
+
N = 3
|
|
116
|
+
with MPIClassInterface(MyTest, [(i + 10,) for i in range(N)]) as m:
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
assert m.get_id()[0] == rank + 10, m.get_id()
|
|
120
|
+
main_id = mpi_interface.main_run(lambda: m.get_id())
|
|
121
|
+
assert main_id == [10], (rank, main_id)
|
|
122
|
+
assert rank < N # rank > N will fail with ChildProcessError
|
|
123
|
+
assert m.return_input([101, 102, 103])[0] == f'{rank + 10} got: ({rank + 101},) and ' + '{}'
|
|
124
|
+
m.x = 'hello'
|
|
125
|
+
assert m.x[0] == 'hello', m.x[0]
|
|
126
|
+
m.x = [1, 2, 3]
|
|
127
|
+
assert m.x[0] == rank + 1
|
|
128
|
+
m.y = m.x[0] + 10
|
|
129
|
+
assert m.y[0] == rank + 11
|
|
130
|
+
except ChildProcessError:
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
if rank == 3:
|
|
134
|
+
with pytest.raises(AttributeError, match='Rank 3 trying to access cls, but only 3 instances of MyTest exists'):
|
|
135
|
+
m.object.cls
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@mpirun
|
|
139
|
+
def test_mpi_non_collective_MyTest():
|
|
140
|
+
mpi_interface.activate_mpi(collective_mpi=False)
|
|
141
|
+
rank = mpi_interface.rank
|
|
142
|
+
N = 4
|
|
143
|
+
|
|
144
|
+
with MPIClassInterface(MyTest, [(i + 1,) for i in range(N)]) as m:
|
|
145
|
+
i = m.get_id()[0]
|
|
146
|
+
assert i == rank + 1
|
|
147
|
+
i = mpi_interface.main_run(lambda: m.get_id())
|
|
148
|
+
npt.assert_array_equal(i, 1) # id of rank0 broadcast to all
|
|
149
|
+
try:
|
|
150
|
+
assert m[1:3].get_id()[0] == rank + 1
|
|
151
|
+
except ChildProcessError:
|
|
152
|
+
assert rank in [0, 3]
|
|
153
|
+
|
|
154
|
+
# rank>0 fails, but only rank=0 is needed, so should not fail
|
|
155
|
+
mpi_interface.main_run(lambda rank=rank: 1 / [0, 1][rank == 0])
|
|
156
|
+
|
|
157
|
+
assert m.return_input([101, 102, 103, 104])[0] == f'{rank + 1} got: ({rank + 101},) and ' + '{}'
|
|
158
|
+
m.x = "hello"
|
|
159
|
+
assert m.x[0] == 'hello'
|
|
160
|
+
|
|
161
|
+
m[1::2].x = 'world'
|
|
162
|
+
assert m.x[0] == ['hello', 'world'][rank % 2]
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
assert m[1::2].x[0] == 'world'
|
|
166
|
+
assert m[1::2][:].x[0] == 'world'
|
|
167
|
+
except ChildProcessError:
|
|
168
|
+
pass
|
|
169
|
+
|
|
170
|
+
m.x = [1, 2, 3, 4]
|
|
171
|
+
assert m.x[0] == rank + 1
|
|
172
|
+
|
|
173
|
+
m.y = m.x[0] + 10
|
|
174
|
+
assert m.y[0] == rank + 11
|
|
175
|
+
t = time.time()
|
|
176
|
+
m.work(1)
|
|
177
|
+
t = time.time() - t
|
|
178
|
+
assert t < 1.1
|
|
179
|
+
|
|
180
|
+
with pytest.raises(Exception, match='Cannot close SubsetMPIClassInterface. Please close all instances at once'):
|
|
181
|
+
m[:3].close()
|
|
182
|
+
with pytest.raises(Exception, match='Cannot make subset of SubsetMPIClassInterface'):
|
|
183
|
+
m[:3][1]
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@mpirun
|
|
187
|
+
def test_multiprocessinterface_tests():
|
|
188
|
+
from multiclass_interface.tests.test_multiprocessinterface import test_attribute, test_missing_attribute, test_execption, test_setattr, test_setattr_method
|
|
189
|
+
mpi_interface.activate_mpi()
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
with MPIClassInterface(MyTest, [(1,), (2,), (3,)]) as mpici:
|
|
193
|
+
for f in [test_attribute, test_missing_attribute, test_execption, test_setattr, test_setattr_method]:
|
|
194
|
+
f(mpici)
|
|
195
|
+
except ChildProcessError:
|
|
196
|
+
pass
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@mpirun
|
|
200
|
+
def test_multiprocessinterface_tests_diff_interface():
|
|
201
|
+
from multiclass_interface.tests.test_multiprocessinterface import test_attribute, test_missing_attribute, test_execption, test_setattr, test_setattr_method
|
|
202
|
+
mpi_interface.activate_mpi(terminate_on_close=False)
|
|
203
|
+
|
|
204
|
+
for f in [test_attribute, test_missing_attribute, test_execption, test_setattr, test_setattr_method]:
|
|
205
|
+
# print (mpi_interface.rank, f.__name__,flush=True)
|
|
206
|
+
try:
|
|
207
|
+
with MPIClassInterface(MyTest, [(1,), (2,), (3,)]) as mpici:
|
|
208
|
+
f(mpici)
|
|
209
|
+
except ChildProcessError:
|
|
210
|
+
pass
|
|
211
|
+
sys.exit(0)
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
# file generated by
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
2
|
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
3
6
|
TYPE_CHECKING = False
|
|
4
7
|
if TYPE_CHECKING:
|
|
5
|
-
from typing import Tuple
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
6
11
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
12
|
else:
|
|
8
13
|
VERSION_TUPLE = object
|
|
@@ -12,5 +17,5 @@ __version__: str
|
|
|
12
17
|
__version_tuple__: VERSION_TUPLE
|
|
13
18
|
version_tuple: VERSION_TUPLE
|
|
14
19
|
|
|
15
|
-
__version__ = version = '1.
|
|
16
|
-
__version_tuple__ = version_tuple = (1,
|
|
20
|
+
__version__ = version = '1.15'
|
|
21
|
+
__version_tuple__ = version_tuple = (1, 15)
|
|
@@ -41,9 +41,6 @@ version_file = "multiclass_interface/version.py"
|
|
|
41
41
|
[tool.coverage.run]
|
|
42
42
|
omit = [
|
|
43
43
|
"multiclass_interface/version.py",
|
|
44
|
-
"multiclass_interface/mpi_utils.py",
|
|
45
|
-
"multiclass_interface/mpi_interface.py",
|
|
46
|
-
"multiclass_interface/tests/my_test_cls.py",
|
|
47
44
|
"multiclass_interface/tests/*",
|
|
48
45
|
]
|
|
49
46
|
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import time
|
|
3
|
-
|
|
4
|
-
from numpy import testing as npt
|
|
5
|
-
import pytest
|
|
6
|
-
|
|
7
|
-
from multiclass_interface.mpi_interface import MPIClassInterface
|
|
8
|
-
from multiclass_interface.multiprocess_interface import ProcessClass, MultiProcessClassInterface
|
|
9
|
-
import numpy as np
|
|
10
|
-
from multiclass_interface.tests.my_test_cls import MyTest
|
|
11
|
-
from multiclass_interface import mpi_interface
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def mpitest_mpi_MyTest():
|
|
15
|
-
|
|
16
|
-
N = 4
|
|
17
|
-
try:
|
|
18
|
-
with MPIClassInterface(MyTest, [(i,) for i in range(N)]) as m:
|
|
19
|
-
|
|
20
|
-
time.sleep(.1)
|
|
21
|
-
i = m.get_id()
|
|
22
|
-
npt.assert_array_equal(i, np.arange(N))
|
|
23
|
-
|
|
24
|
-
npt.assert_array_equal(m[1:3].get_id(), np.arange(1, 3))
|
|
25
|
-
t = time.time()
|
|
26
|
-
m.work(1)
|
|
27
|
-
t = time.time() - t
|
|
28
|
-
assert t < 1.1
|
|
29
|
-
|
|
30
|
-
with pytest.raises(Exception, match='Cannot close SubsetMPIClassInterface. Please close all instances at once'):
|
|
31
|
-
m[:3].close()
|
|
32
|
-
with pytest.raises(Exception, match='Cannot make subset of SubsetMPIClassInterface'):
|
|
33
|
-
m[:3][1]
|
|
34
|
-
|
|
35
|
-
print("done, test_mpi_MyTest")
|
|
36
|
-
except ChildProcessError:
|
|
37
|
-
pass
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def mpitest_mpi_ProcessClass():
|
|
41
|
-
|
|
42
|
-
with ProcessClass(MyTest) as cls:
|
|
43
|
-
myTest = cls(1)
|
|
44
|
-
assert myTest.get_id() == 1
|
|
45
|
-
print("done, test_mpi_ProcessClass", flush=True)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def mpitest_non_collective_mpi():
|
|
49
|
-
mpi_interface.COLLECTIVE_MPI = False
|
|
50
|
-
rank = mpi_interface.rank
|
|
51
|
-
import time
|
|
52
|
-
N = 3
|
|
53
|
-
with MPIClassInterface(MyTest, [(i + 10,) for i in range(N)]) as m:
|
|
54
|
-
|
|
55
|
-
try:
|
|
56
|
-
assert m.get_id()[0] == rank + 10, m.get_id()
|
|
57
|
-
assert rank < N # rank > N will fail with ChildProcessError
|
|
58
|
-
except ChildProcessError:
|
|
59
|
-
pass
|
|
60
|
-
|
|
61
|
-
mpi_interface.COLLECTIVE_MPI = True
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def mpitest_all():
|
|
65
|
-
mpi_interface.activate_mpi()
|
|
66
|
-
|
|
67
|
-
mpi_interface.exit_mpi_on_close = False
|
|
68
|
-
rank = mpi_interface.rank
|
|
69
|
-
from multiclass_interface.tests.test_multiprocessinterface import test_attribute, test_missing_attribute, test_execption, test_setattr, test_setattr_method
|
|
70
|
-
mpi_interface.TERMINATE_ON_CLOSE = False
|
|
71
|
-
|
|
72
|
-
def run(f, *args):
|
|
73
|
-
try:
|
|
74
|
-
if rank == 0:
|
|
75
|
-
print(f"Rank {rank} start {f.__name__}", flush=True)
|
|
76
|
-
f(*args)
|
|
77
|
-
except ChildProcessError:
|
|
78
|
-
pass
|
|
79
|
-
finally:
|
|
80
|
-
if rank == 0:
|
|
81
|
-
print(f"Rank {rank} done {f.__name__}", flush=True)
|
|
82
|
-
for f in [mpitest_mpi_MyTest,
|
|
83
|
-
mpitest_mpi_ProcessClass]:
|
|
84
|
-
run(f)
|
|
85
|
-
|
|
86
|
-
try:
|
|
87
|
-
with MPIClassInterface(MyTest, [(1,), (2,), (3,)]) as mpici:
|
|
88
|
-
for f in [test_attribute, test_missing_attribute, test_execption, test_setattr, test_setattr_method]:
|
|
89
|
-
|
|
90
|
-
run(f, mpici)
|
|
91
|
-
|
|
92
|
-
except ChildProcessError:
|
|
93
|
-
pass
|
|
94
|
-
|
|
95
|
-
run(mpitest_non_collective_mpi)
|
|
96
|
-
|
|
97
|
-
print(f"Rank {rank} Done test_all")
|
|
File without changes
|
|
File without changes
|
{multiclass_interface-1.12 → multiclass_interface-1.15}/Multiclass_interface.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{multiclass_interface-1.12 → multiclass_interface-1.15}/Multiclass_interface.egg-info/requires.txt
RENAMED
|
File without changes
|
{multiclass_interface-1.12 → multiclass_interface-1.15}/Multiclass_interface.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{multiclass_interface-1.12 → multiclass_interface-1.15}/multiclass_interface/multi_object_list.py
RENAMED
|
File without changes
|
|
File without changes
|
{multiclass_interface-1.12 → multiclass_interface-1.15}/multiclass_interface/tests/__init__.py
RENAMED
|
File without changes
|
{multiclass_interface-1.12 → multiclass_interface-1.15}/multiclass_interface/tests/my_test_cls.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|