Multiclass-interface 1.14__tar.gz → 1.17__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.14 → multiclass_interface-1.17}/.gitlab-ci.yml +4 -23
- {multiclass_interface-1.14 → multiclass_interface-1.17/Multiclass_interface.egg-info}/PKG-INFO +3 -2
- {multiclass_interface-1.14/Multiclass_interface.egg-info → multiclass_interface-1.17}/PKG-INFO +3 -2
- {multiclass_interface-1.14 → multiclass_interface-1.17}/multiclass_interface/mpi_interface.py +63 -45
- multiclass_interface-1.17/multiclass_interface/tests/test_mpi.py +213 -0
- {multiclass_interface-1.14 → multiclass_interface-1.17}/multiclass_interface/version.py +9 -4
- {multiclass_interface-1.14 → multiclass_interface-1.17}/pyproject.toml +0 -3
- multiclass_interface-1.14/multiclass_interface/tests/test_mpi.py +0 -99
- {multiclass_interface-1.14 → multiclass_interface-1.17}/.gitignore +0 -0
- {multiclass_interface-1.14 → multiclass_interface-1.17}/LICENSE +0 -0
- {multiclass_interface-1.14 → multiclass_interface-1.17}/Multiclass_interface.egg-info/SOURCES.txt +0 -0
- {multiclass_interface-1.14 → multiclass_interface-1.17}/Multiclass_interface.egg-info/dependency_links.txt +0 -0
- {multiclass_interface-1.14 → multiclass_interface-1.17}/Multiclass_interface.egg-info/requires.txt +0 -0
- {multiclass_interface-1.14 → multiclass_interface-1.17}/Multiclass_interface.egg-info/top_level.txt +0 -0
- {multiclass_interface-1.14 → multiclass_interface-1.17}/README.md +0 -0
- {multiclass_interface-1.14 → multiclass_interface-1.17}/multiclass_interface/__init__.py +0 -0
- {multiclass_interface-1.14 → multiclass_interface-1.17}/multiclass_interface/multi_object_list.py +0 -0
- {multiclass_interface-1.14 → multiclass_interface-1.17}/multiclass_interface/multiprocess_interface.py +0 -0
- {multiclass_interface-1.14 → multiclass_interface-1.17}/multiclass_interface/tests/__init__.py +0 -0
- {multiclass_interface-1.14 → multiclass_interface-1.17}/multiclass_interface/tests/my_test_cls.py +0 -0
- {multiclass_interface-1.14 → multiclass_interface-1.17}/multiclass_interface/tests/test_multiclass_list.py +0 -0
- {multiclass_interface-1.14 → multiclass_interface-1.17}/multiclass_interface/tests/test_multiprocessinterface.py +0 -0
- {multiclass_interface-1.14 → multiclass_interface-1.17}/sequence_diagrams.md +0 -0
- {multiclass_interface-1.14 → multiclass_interface-1.17}/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 twine
|
|
45
|
+
- pip install twine packaging --upgrade
|
|
65
46
|
- python3 -m build
|
|
66
47
|
- twine upload dist/* -u $TWINE_USERNAME -p $TWINE_PASSWORD
|
|
67
48
|
rules:
|
{multiclass_interface-1.14 → multiclass_interface-1.17/Multiclass_interface.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: Multiclass_interface
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.17
|
|
4
4
|
Summary: Multiclass interface
|
|
5
5
|
Author-email: "Mads M. Pedersen" <mmpe@dtu.dk>
|
|
6
6
|
Project-URL: Homepage, https://gitlab.windenergy.dtu.dk/DYNAMIKS/multiclass_interface
|
|
@@ -15,6 +15,7 @@ Requires-Dist: numpy
|
|
|
15
15
|
Provides-Extra: test
|
|
16
16
|
Requires-Dist: pytest; extra == "test"
|
|
17
17
|
Requires-Dist: pytest-cov; extra == "test"
|
|
18
|
+
Dynamic: license-file
|
|
18
19
|
|
|
19
20
|
# Multiclass interface
|
|
20
21
|
|
{multiclass_interface-1.14/Multiclass_interface.egg-info → multiclass_interface-1.17}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: Multiclass_interface
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.17
|
|
4
4
|
Summary: Multiclass interface
|
|
5
5
|
Author-email: "Mads M. Pedersen" <mmpe@dtu.dk>
|
|
6
6
|
Project-URL: Homepage, https://gitlab.windenergy.dtu.dk/DYNAMIKS/multiclass_interface
|
|
@@ -15,6 +15,7 @@ Requires-Dist: numpy
|
|
|
15
15
|
Provides-Extra: test
|
|
16
16
|
Requires-Dist: pytest; extra == "test"
|
|
17
17
|
Requires-Dist: pytest-cov; extra == "test"
|
|
18
|
+
Dynamic: license-file
|
|
18
19
|
|
|
19
20
|
# Multiclass interface
|
|
20
21
|
|
{multiclass_interface-1.14 → multiclass_interface-1.17}/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,7 +38,6 @@ 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
|
|
|
@@ -59,6 +58,7 @@ def main_run(f, *args, **kwargs):
|
|
|
59
58
|
|
|
60
59
|
class MPIClassInterface():
|
|
61
60
|
def __init__(self, cls, args_lst):
|
|
61
|
+
self.closed = False
|
|
62
62
|
if len(args_lst) > size:
|
|
63
63
|
if rank == 0:
|
|
64
64
|
raise Exception(f"Not enough mpi slots. Slots: {size}, Requested: {len(args_lst)}")
|
|
@@ -73,13 +73,12 @@ class MPIClassInterface():
|
|
|
73
73
|
|
|
74
74
|
def __getattr__(self, name):
|
|
75
75
|
raise AttributeError(
|
|
76
|
-
f"
|
|
76
|
+
f"Rank {rank} trying to access {name}, but only {len(args_lst)} instances of {cls.__name__} exists")
|
|
77
77
|
self.object = Dummy()
|
|
78
|
-
self.closed = False
|
|
79
78
|
|
|
80
79
|
self.use_rank = np.array([True] * size)
|
|
81
80
|
self.use_rank[len(args_lst):] = False
|
|
82
|
-
if rank > 0 and COLLECTIVE_MPI
|
|
81
|
+
if rank > 0 and COLLECTIVE_MPI:
|
|
83
82
|
self.work_loop()
|
|
84
83
|
|
|
85
84
|
def __enter__(self):
|
|
@@ -92,20 +91,20 @@ class MPIClassInterface():
|
|
|
92
91
|
while True:
|
|
93
92
|
method, args, kwargs = comm.scatter(None)
|
|
94
93
|
comm.gather(self.do_task(method, args, kwargs))
|
|
95
|
-
if
|
|
96
|
-
if method == 'close':
|
|
97
|
-
if TERMINATE_ON_CLOSE:
|
|
98
|
-
# comm.gather(f'Exit, rank {rank}')
|
|
99
|
-
print("sys.exit", rank, flush=True)
|
|
100
|
-
sys.exit(0)
|
|
101
|
-
else:
|
|
102
|
-
raise ChildProcessError('MPI worker done')
|
|
103
|
-
else:
|
|
94
|
+
if method == 'exit_work_loop':
|
|
104
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')
|
|
105
104
|
|
|
106
105
|
def do_task(self, method, args, kwargs):
|
|
107
106
|
try:
|
|
108
|
-
if method == 'skip':
|
|
107
|
+
if method == 'skip' or method == 'exit_work_loop':
|
|
109
108
|
res = None
|
|
110
109
|
elif method == 'setattr':
|
|
111
110
|
name, value = args
|
|
@@ -119,29 +118,28 @@ class MPIClassInterface():
|
|
|
119
118
|
return res
|
|
120
119
|
|
|
121
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"
|
|
122
122
|
comm.scatter(input_lst, root=0)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
else:
|
|
127
|
-
res = None
|
|
123
|
+
method, args, kwargs = input_lst[0]
|
|
124
|
+
res = self.do_task(method, args, kwargs)
|
|
125
|
+
|
|
128
126
|
use_rank = self.use_rank
|
|
129
127
|
|
|
130
128
|
res = [res for i, res in enumerate(comm.gather(res, root=0)) if use_rank[i]]
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
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])
|
|
135
132
|
return res
|
|
136
133
|
|
|
137
134
|
def get_input(self, name, i, args, kwargs):
|
|
138
135
|
use_rank = self.use_rank
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
|
145
143
|
|
|
146
144
|
if use_rank[i]:
|
|
147
145
|
def get_arg(arg):
|
|
@@ -149,6 +147,7 @@ class MPIClassInterface():
|
|
|
149
147
|
return arg[j]
|
|
150
148
|
else:
|
|
151
149
|
return arg
|
|
150
|
+
|
|
152
151
|
return (name, [get_arg(arg) for arg in args], {k: get_arg(v) for k, v in kwargs.items()})
|
|
153
152
|
else:
|
|
154
153
|
return ('skip', [], {})
|
|
@@ -156,10 +155,7 @@ class MPIClassInterface():
|
|
|
156
155
|
def __getattribute__(self, name):
|
|
157
156
|
if name in MPIClassInterfaceAttributes:
|
|
158
157
|
return object.__getattribute__(self, name)
|
|
159
|
-
elif COLLECTIVE_MPI and rank
|
|
160
|
-
self.work_loop()
|
|
161
|
-
return lambda *args, **kwargs: 1
|
|
162
|
-
elif rank > 0 and not self.use_rank[rank]:
|
|
158
|
+
elif not COLLECTIVE_MPI and not self.use_rank[rank]:
|
|
163
159
|
raise ChildProcessError(f'MPI worker {rank} not used')
|
|
164
160
|
|
|
165
161
|
def wrap(*args, **kwargs):
|
|
@@ -167,20 +163,28 @@ class MPIClassInterface():
|
|
|
167
163
|
inp = [self.get_input(name, i, args, kwargs) for i in range(size)]
|
|
168
164
|
return self.run_task(inp)
|
|
169
165
|
else:
|
|
170
|
-
method, args, kwargs = self.get_input(name,
|
|
171
|
-
return [self.do_task(
|
|
172
|
-
|
|
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__'):
|
|
173
174
|
res = wrap
|
|
174
175
|
else:
|
|
175
176
|
res = wrap()
|
|
176
177
|
return res
|
|
177
178
|
|
|
178
179
|
def __setattr__(self, name, value):
|
|
179
|
-
if rank > 0 or name in MPIClassInterfaceAttributes:
|
|
180
|
+
if (COLLECTIVE_MPI and rank > 0) or name in MPIClassInterfaceAttributes:
|
|
180
181
|
return object.__setattr__(self, name, value)
|
|
181
182
|
else:
|
|
182
183
|
inp = [self.get_input('setattr', i, (name, value), {}) for i in range(size)]
|
|
183
|
-
|
|
184
|
+
if COLLECTIVE_MPI:
|
|
185
|
+
return self.run_task(inp)
|
|
186
|
+
else:
|
|
187
|
+
return self.do_task(*inp[rank])
|
|
184
188
|
|
|
185
189
|
def __getitem__(self, slice):
|
|
186
190
|
use_rank = np.full_like(self.use_rank, False)
|
|
@@ -189,14 +193,28 @@ class MPIClassInterface():
|
|
|
189
193
|
return self
|
|
190
194
|
return SubsetMPIClassInterface(self.cls, self.object, use_rank)
|
|
191
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
|
+
|
|
192
211
|
def close(self):
|
|
212
|
+
|
|
193
213
|
if not self.closed:
|
|
214
|
+
|
|
194
215
|
if COLLECTIVE_MPI:
|
|
195
216
|
if rank == 0:
|
|
196
217
|
res = self.run_task([('close', [], {}) for _ in range(size)])
|
|
197
|
-
else:
|
|
198
|
-
self.work_loop()
|
|
199
|
-
res = None
|
|
200
218
|
else:
|
|
201
219
|
res = self.do_task('close', [], {})
|
|
202
220
|
self.closed = True
|
|
@@ -0,0 +1,213 @@
|
|
|
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
|
+
assert m[1].get_id() == [2]
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
npt.assert_array_equal(m[1:3].get_id(), np.arange(1, 3) + 1)
|
|
67
|
+
except ChildProcessError:
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
m.x = "hello"
|
|
71
|
+
assert m.x == ['hello'] * 4
|
|
72
|
+
|
|
73
|
+
assert m.return_input([101, 102, 103, 104])[0] == f'1 got: ({101},) and ' + '{}'
|
|
74
|
+
m.x = 'hello'
|
|
75
|
+
assert m.x == ['hello'] * 4
|
|
76
|
+
m[1::2].x = 'world'
|
|
77
|
+
assert m[:].x == ['hello', 'world', 'hello', 'world']
|
|
78
|
+
|
|
79
|
+
m.release_mpi_workers()
|
|
80
|
+
m.x = [1, 2, 3, 4]
|
|
81
|
+
assert m.x == [1, 2, 3, 4]
|
|
82
|
+
m.y = list(np.array(m.x) + 10)
|
|
83
|
+
assert m.y == [11, 12, 13, 14]
|
|
84
|
+
t = time.time()
|
|
85
|
+
m.work(1)
|
|
86
|
+
t = time.time() - t
|
|
87
|
+
assert t < 1.1
|
|
88
|
+
|
|
89
|
+
with pytest.raises(Exception, match='Cannot close SubsetMPIClassInterface. Please close all instances at once'):
|
|
90
|
+
m[:3].close()
|
|
91
|
+
with pytest.raises(Exception, match='Cannot make subset of SubsetMPIClassInterface'):
|
|
92
|
+
m[:3][1]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@mpirun
|
|
96
|
+
def test_mpi_collective_close_released_mpi_workers():
|
|
97
|
+
mpi_interface.activate_mpi()
|
|
98
|
+
N = 4
|
|
99
|
+
rank = mpi_interface.rank
|
|
100
|
+
with MPIClassInterface(MyTest, [(i + 1,) for i in range(N)]) as m:
|
|
101
|
+
with m.release_mpi_workers():
|
|
102
|
+
m.close()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@mpirun
|
|
106
|
+
def test_mpi_ProcessClass():
|
|
107
|
+
mpi_interface.activate_mpi()
|
|
108
|
+
with ProcessClass(MyTest) as cls:
|
|
109
|
+
myTest = cls(1)
|
|
110
|
+
assert myTest.get_id() == 1
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@mpirun
|
|
114
|
+
def test_non_collective_mpi2():
|
|
115
|
+
mpi_interface.activate_mpi(collective_mpi=False)
|
|
116
|
+
rank = mpi_interface.rank
|
|
117
|
+
N = 3
|
|
118
|
+
with MPIClassInterface(MyTest, [(i + 10,) for i in range(N)]) as m:
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
assert m.get_id()[0] == rank + 10, m.get_id()
|
|
122
|
+
main_id = mpi_interface.main_run(lambda: m.get_id())
|
|
123
|
+
assert main_id == [10], (rank, main_id)
|
|
124
|
+
assert rank < N # rank > N will fail with ChildProcessError
|
|
125
|
+
assert m.return_input([101, 102, 103])[0] == f'{rank + 10} got: ({rank + 101},) and ' + '{}'
|
|
126
|
+
m.x = 'hello'
|
|
127
|
+
assert m.x[0] == 'hello', m.x[0]
|
|
128
|
+
m.x = [1, 2, 3]
|
|
129
|
+
assert m.x[0] == rank + 1
|
|
130
|
+
m.y = m.x[0] + 10
|
|
131
|
+
assert m.y[0] == rank + 11
|
|
132
|
+
except ChildProcessError:
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
if rank == 3:
|
|
136
|
+
with pytest.raises(AttributeError, match='Rank 3 trying to access cls, but only 3 instances of MyTest exists'):
|
|
137
|
+
m.object.cls
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@mpirun
|
|
141
|
+
def test_mpi_non_collective_MyTest():
|
|
142
|
+
mpi_interface.activate_mpi(collective_mpi=False)
|
|
143
|
+
rank = mpi_interface.rank
|
|
144
|
+
N = 4
|
|
145
|
+
|
|
146
|
+
with MPIClassInterface(MyTest, [(i + 1,) for i in range(N)]) as m:
|
|
147
|
+
i = m.get_id()[0]
|
|
148
|
+
assert i == rank + 1
|
|
149
|
+
i = mpi_interface.main_run(lambda: m.get_id())
|
|
150
|
+
npt.assert_array_equal(i, 1) # id of rank0 broadcast to all
|
|
151
|
+
try:
|
|
152
|
+
assert m[1:3].get_id()[0] == rank + 1
|
|
153
|
+
except ChildProcessError:
|
|
154
|
+
assert rank in [0, 3]
|
|
155
|
+
|
|
156
|
+
# rank>0 fails, but only rank=0 is needed, so should not fail
|
|
157
|
+
mpi_interface.main_run(lambda rank=rank: 1 / [0, 1][rank == 0])
|
|
158
|
+
|
|
159
|
+
assert m.return_input([101, 102, 103, 104])[0] == f'{rank + 1} got: ({rank + 101},) and ' + '{}'
|
|
160
|
+
m.x = "hello"
|
|
161
|
+
assert m.x[0] == 'hello'
|
|
162
|
+
|
|
163
|
+
m[1::2].x = 'world'
|
|
164
|
+
assert m.x[0] == ['hello', 'world'][rank % 2]
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
assert m[1::2].x[0] == 'world'
|
|
168
|
+
assert m[1::2][:].x[0] == 'world'
|
|
169
|
+
except ChildProcessError:
|
|
170
|
+
pass
|
|
171
|
+
|
|
172
|
+
m.x = [1, 2, 3, 4]
|
|
173
|
+
assert m.x[0] == rank + 1
|
|
174
|
+
|
|
175
|
+
m.y = m.x[0] + 10
|
|
176
|
+
assert m.y[0] == rank + 11
|
|
177
|
+
t = time.time()
|
|
178
|
+
m.work(1)
|
|
179
|
+
t = time.time() - t
|
|
180
|
+
assert t < 1.1
|
|
181
|
+
|
|
182
|
+
with pytest.raises(Exception, match='Cannot close SubsetMPIClassInterface. Please close all instances at once'):
|
|
183
|
+
m[:3].close()
|
|
184
|
+
with pytest.raises(Exception, match='Cannot make subset of SubsetMPIClassInterface'):
|
|
185
|
+
m[:3][1]
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@mpirun
|
|
189
|
+
def test_multiprocessinterface_tests():
|
|
190
|
+
from multiclass_interface.tests.test_multiprocessinterface import test_attribute, test_missing_attribute, test_execption, test_setattr, test_setattr_method
|
|
191
|
+
mpi_interface.activate_mpi()
|
|
192
|
+
|
|
193
|
+
try:
|
|
194
|
+
with MPIClassInterface(MyTest, [(1,), (2,), (3,)]) as mpici:
|
|
195
|
+
for f in [test_attribute, test_missing_attribute, test_execption, test_setattr, test_setattr_method]:
|
|
196
|
+
f(mpici)
|
|
197
|
+
except ChildProcessError:
|
|
198
|
+
pass
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@mpirun
|
|
202
|
+
def test_multiprocessinterface_tests_diff_interface():
|
|
203
|
+
from multiclass_interface.tests.test_multiprocessinterface import test_attribute, test_missing_attribute, test_execption, test_setattr, test_setattr_method
|
|
204
|
+
mpi_interface.activate_mpi(terminate_on_close=False)
|
|
205
|
+
|
|
206
|
+
for f in [test_attribute, test_missing_attribute, test_execption, test_setattr, test_setattr_method]:
|
|
207
|
+
# print (mpi_interface.rank, f.__name__,flush=True)
|
|
208
|
+
try:
|
|
209
|
+
with MPIClassInterface(MyTest, [(1,), (2,), (3,)]) as mpici:
|
|
210
|
+
f(mpici)
|
|
211
|
+
except ChildProcessError:
|
|
212
|
+
pass
|
|
213
|
+
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.17'
|
|
21
|
+
__version_tuple__ = version_tuple = (1, 17)
|
|
@@ -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,99 +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
|
-
i = mpi_interface.main_run(lambda: m.get_id())
|
|
25
|
-
npt.assert_array_equal(i, 0)
|
|
26
|
-
npt.assert_array_equal(m[1:3].get_id(), np.arange(1, 3))
|
|
27
|
-
t = time.time()
|
|
28
|
-
m.work(1)
|
|
29
|
-
t = time.time() - t
|
|
30
|
-
assert t < 1.1
|
|
31
|
-
|
|
32
|
-
with pytest.raises(Exception, match='Cannot close SubsetMPIClassInterface. Please close all instances at once'):
|
|
33
|
-
m[:3].close()
|
|
34
|
-
with pytest.raises(Exception, match='Cannot make subset of SubsetMPIClassInterface'):
|
|
35
|
-
m[:3][1]
|
|
36
|
-
|
|
37
|
-
print("done, test_mpi_MyTest")
|
|
38
|
-
except ChildProcessError:
|
|
39
|
-
pass
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def mpitest_mpi_ProcessClass():
|
|
43
|
-
|
|
44
|
-
with ProcessClass(MyTest) as cls:
|
|
45
|
-
myTest = cls(1)
|
|
46
|
-
assert myTest.get_id() == 1
|
|
47
|
-
print("done, test_mpi_ProcessClass", flush=True)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def mpitest_non_collective_mpi():
|
|
51
|
-
mpi_interface.COLLECTIVE_MPI = False
|
|
52
|
-
rank = mpi_interface.rank
|
|
53
|
-
import time
|
|
54
|
-
N = 3
|
|
55
|
-
with MPIClassInterface(MyTest, [(i + 10,) for i in range(N)]) as m:
|
|
56
|
-
|
|
57
|
-
try:
|
|
58
|
-
assert m.get_id()[0] == rank + 10, m.get_id()
|
|
59
|
-
main_id = mpi_interface.main_run(lambda: m.get_id())
|
|
60
|
-
assert main_id == [10], (rank, main_id)
|
|
61
|
-
assert rank < N # rank > N will fail with ChildProcessError
|
|
62
|
-
except ChildProcessError:
|
|
63
|
-
pass
|
|
64
|
-
|
|
65
|
-
mpi_interface.COLLECTIVE_MPI = True
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def mpitest_all():
|
|
69
|
-
mpi_interface.activate_mpi()
|
|
70
|
-
|
|
71
|
-
mpi_interface.exit_mpi_on_close = False
|
|
72
|
-
rank = mpi_interface.rank
|
|
73
|
-
from multiclass_interface.tests.test_multiprocessinterface import test_attribute, test_missing_attribute, test_execption, test_setattr, test_setattr_method
|
|
74
|
-
mpi_interface.TERMINATE_ON_CLOSE = False
|
|
75
|
-
|
|
76
|
-
def run(f, *args):
|
|
77
|
-
try:
|
|
78
|
-
if rank == 0:
|
|
79
|
-
print(f"Rank {rank} start {f.__name__}", flush=True)
|
|
80
|
-
f(*args)
|
|
81
|
-
except ChildProcessError:
|
|
82
|
-
pass
|
|
83
|
-
finally:
|
|
84
|
-
if rank == 0:
|
|
85
|
-
print(f"Rank {rank} done {f.__name__}", flush=True)
|
|
86
|
-
for f in [mpitest_mpi_MyTest,
|
|
87
|
-
mpitest_mpi_ProcessClass]:
|
|
88
|
-
run(f)
|
|
89
|
-
|
|
90
|
-
try:
|
|
91
|
-
with MPIClassInterface(MyTest, [(1,), (2,), (3,)]) as mpici:
|
|
92
|
-
for f in [test_attribute, test_missing_attribute, test_execption, test_setattr, test_setattr_method]:
|
|
93
|
-
run(f, mpici)
|
|
94
|
-
except ChildProcessError:
|
|
95
|
-
pass
|
|
96
|
-
|
|
97
|
-
run(mpitest_non_collective_mpi)
|
|
98
|
-
|
|
99
|
-
print(f"Rank {rank} Done test_all")
|
|
File without changes
|
|
File without changes
|
{multiclass_interface-1.14 → multiclass_interface-1.17}/Multiclass_interface.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{multiclass_interface-1.14 → multiclass_interface-1.17}/Multiclass_interface.egg-info/requires.txt
RENAMED
|
File without changes
|
{multiclass_interface-1.14 → multiclass_interface-1.17}/Multiclass_interface.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{multiclass_interface-1.14 → multiclass_interface-1.17}/multiclass_interface/multi_object_list.py
RENAMED
|
File without changes
|
|
File without changes
|
{multiclass_interface-1.14 → multiclass_interface-1.17}/multiclass_interface/tests/__init__.py
RENAMED
|
File without changes
|
{multiclass_interface-1.14 → multiclass_interface-1.17}/multiclass_interface/tests/my_test_cls.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|