Multiclass-interface 1.17__tar.gz → 1.19__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.
Files changed (26) hide show
  1. {multiclass_interface-1.17 → multiclass_interface-1.19/Multiclass_interface.egg-info}/PKG-INFO +1 -1
  2. {multiclass_interface-1.17 → multiclass_interface-1.19}/Multiclass_interface.egg-info/SOURCES.txt +2 -0
  3. multiclass_interface-1.19/Multiclass_interface.egg-info/scm_file_list.json +19 -0
  4. multiclass_interface-1.19/Multiclass_interface.egg-info/scm_version.json +8 -0
  5. {multiclass_interface-1.17/Multiclass_interface.egg-info → multiclass_interface-1.19}/PKG-INFO +1 -1
  6. {multiclass_interface-1.17 → multiclass_interface-1.19}/multiclass_interface/mpi_interface.py +76 -0
  7. {multiclass_interface-1.17 → multiclass_interface-1.19}/multiclass_interface/multi_object_list.py +6 -0
  8. {multiclass_interface-1.17 → multiclass_interface-1.19}/multiclass_interface/multiprocess_interface.py +3 -2
  9. {multiclass_interface-1.17 → multiclass_interface-1.19}/multiclass_interface/tests/my_test_cls.py +1 -0
  10. {multiclass_interface-1.17 → multiclass_interface-1.19}/multiclass_interface/tests/test_mpi.py +52 -8
  11. multiclass_interface-1.19/multiclass_interface/version.py +24 -0
  12. multiclass_interface-1.17/multiclass_interface/version.py +0 -21
  13. {multiclass_interface-1.17 → multiclass_interface-1.19}/.gitignore +0 -0
  14. {multiclass_interface-1.17 → multiclass_interface-1.19}/.gitlab-ci.yml +0 -0
  15. {multiclass_interface-1.17 → multiclass_interface-1.19}/LICENSE +0 -0
  16. {multiclass_interface-1.17 → multiclass_interface-1.19}/Multiclass_interface.egg-info/dependency_links.txt +0 -0
  17. {multiclass_interface-1.17 → multiclass_interface-1.19}/Multiclass_interface.egg-info/requires.txt +0 -0
  18. {multiclass_interface-1.17 → multiclass_interface-1.19}/Multiclass_interface.egg-info/top_level.txt +0 -0
  19. {multiclass_interface-1.17 → multiclass_interface-1.19}/README.md +0 -0
  20. {multiclass_interface-1.17 → multiclass_interface-1.19}/multiclass_interface/__init__.py +0 -0
  21. {multiclass_interface-1.17 → multiclass_interface-1.19}/multiclass_interface/tests/__init__.py +0 -0
  22. {multiclass_interface-1.17 → multiclass_interface-1.19}/multiclass_interface/tests/test_multiclass_list.py +0 -0
  23. {multiclass_interface-1.17 → multiclass_interface-1.19}/multiclass_interface/tests/test_multiprocessinterface.py +0 -0
  24. {multiclass_interface-1.17 → multiclass_interface-1.19}/pyproject.toml +0 -0
  25. {multiclass_interface-1.17 → multiclass_interface-1.19}/sequence_diagrams.md +0 -0
  26. {multiclass_interface-1.17 → multiclass_interface-1.19}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Multiclass_interface
3
- Version: 1.17
3
+ Version: 1.19
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
@@ -8,6 +8,8 @@ Multiclass_interface.egg-info/PKG-INFO
8
8
  Multiclass_interface.egg-info/SOURCES.txt
9
9
  Multiclass_interface.egg-info/dependency_links.txt
10
10
  Multiclass_interface.egg-info/requires.txt
11
+ Multiclass_interface.egg-info/scm_file_list.json
12
+ Multiclass_interface.egg-info/scm_version.json
11
13
  Multiclass_interface.egg-info/top_level.txt
12
14
  multiclass_interface/__init__.py
13
15
  multiclass_interface/mpi_interface.py
@@ -0,0 +1,19 @@
1
+ {
2
+ "files": [
3
+ ".gitignore",
4
+ ".gitlab-ci.yml",
5
+ "LICENSE",
6
+ "README.md",
7
+ "multiclass_interface/__init__.py",
8
+ "multiclass_interface/mpi_interface.py",
9
+ "multiclass_interface/multi_object_list.py",
10
+ "multiclass_interface/multiprocess_interface.py",
11
+ "multiclass_interface/tests/__init__.py",
12
+ "multiclass_interface/tests/my_test_cls.py",
13
+ "multiclass_interface/tests/test_mpi.py",
14
+ "multiclass_interface/tests/test_multiclass_list.py",
15
+ "multiclass_interface/tests/test_multiprocessinterface.py",
16
+ "pyproject.toml",
17
+ "sequence_diagrams.md"
18
+ ]
19
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "1.19",
3
+ "distance": 0,
4
+ "node": "g26b6778cd0dd2eedd49e1ca5a050e6c18834fc8d",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-10-23"
8
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Multiclass_interface
3
- Version: 1.17
3
+ Version: 1.19
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
@@ -1,4 +1,9 @@
1
+ import inspect
2
+ from pathlib import Path
3
+ import pytest
4
+ import os
1
5
  import numpy as np
6
+ import shutil
2
7
  import sys
3
8
  import traceback
4
9
  from contextlib import contextmanager
@@ -234,3 +239,74 @@ class SubsetMPIClassInterface(MPIClassInterface):
234
239
 
235
240
  def close(self):
236
241
  raise Exception('Cannot close SubsetMPIClassInterface. Please close all instances at once')
242
+
243
+
244
+ def barrier_print(s):
245
+ from mpi4py import MPI
246
+
247
+ newData = comm.gather(rank, root=0)
248
+ newData = comm.gather(rank, root=0)
249
+ import sys
250
+ sys.stdout.flush()
251
+ if rank == 0:
252
+ print("#########", s, newData, flush=True)
253
+
254
+
255
+ def bcast_function_result(f):
256
+ # Decorator to broadcast the return value of a function as a wt dependent list to all mpi workers
257
+ # collect the return value from each mpiworker and broadcast the full list with wt as first dimension to all workers
258
+ def wrapper(*args, **kwargs):
259
+ if mpi:
260
+ try:
261
+ v = f(*args, **kwargs)
262
+ if not isinstance(v, list):
263
+ v = [v]
264
+ v = v[0]
265
+ except ChildProcessError:
266
+ v = None
267
+
268
+ # collect the return value from each mpiworker (wt as first dimension)
269
+ v = comm.gather(v, root=0)
270
+ # broadcast the full list to all workers
271
+ return comm.bcast(v, root=0)
272
+ else:
273
+ v = f(*args, **kwargs)
274
+ if not isinstance(v, list):
275
+ v = [v]
276
+ return v
277
+ return wrapper
278
+
279
+
280
+ def mpi_run_test(n, cwd='.', mpirun_cmd='mpirun'):
281
+ if callable(n):
282
+ return mpi_run_test(n=4)(n)
283
+
284
+ def decorator(test_function):
285
+ try:
286
+ import mpi4py
287
+ assert shutil.which('mpirun') is not None
288
+ except Exception: # pragma: no cover
289
+ return lambda: pytest.xfail('mpirun or mpi4py missing')
290
+ name = test_function.__name__
291
+
292
+ def wrap(mpirun=False):
293
+ cwd = os.getcwd()
294
+ try:
295
+ if mpirun:
296
+ os.chdir(cwd)
297
+ return test_function()
298
+ else:
299
+
300
+ import_path = Path(inspect.getsourcefile(test_function)).relative_to(os.getcwd()).as_posix()
301
+ import_path = import_path[:-3].replace('/', '.')
302
+ cmd = f'''{mpirun_cmd} -n {n} {sys.executable} -c "from {import_path} import {name}; {name}(mpirun=True)"'''
303
+ print(f"cd {os.getcwd()} && {cmd}", flush=True)
304
+ assert os.system(cmd) == 0, name
305
+
306
+ finally:
307
+ os.chdir(cwd)
308
+
309
+ wrap.__name__ = name
310
+ return wrap
311
+
312
+ return decorator
@@ -9,6 +9,12 @@ class MultiObjectList():
9
9
  def __str__(self):
10
10
  return f'{self.__class__.__name__}({self.obj_lst})'
11
11
 
12
+ def __enter__(self):
13
+ return self
14
+
15
+ def __exit__(self, exc_type, exc_val, exc_tb):
16
+ pass
17
+
12
18
  def get_obj_args_lst(self, args, kwargs):
13
19
  N = len(self.obj_lst)
14
20
 
@@ -21,7 +21,8 @@ from multiclass_interface.multi_object_list import MultiObjectList
21
21
 
22
22
  def run(cls, inputQueue, outputQueue, cls_args, **kwargs):
23
23
  o = cls(*cls_args, **kwargs)
24
- while True:
24
+ running = True
25
+ while running:
25
26
  method, args, kwargs = inputQueue.get()
26
27
  try:
27
28
  if method == 'getattr':
@@ -48,7 +49,7 @@ def run(cls, inputQueue, outputQueue, cls_args, **kwargs):
48
49
  finally:
49
50
  if method == 'close':
50
51
  outputQueue.put('Exit process')
51
- return
52
+ running = False
52
53
 
53
54
 
54
55
  class ProcessClass():
@@ -1,5 +1,6 @@
1
1
  import time
2
2
  import os
3
+ from multiclass_interface.mpi_interface import bcast_function_result
3
4
 
4
5
 
5
6
  class MyTest():
@@ -10,6 +10,8 @@ from multiclass_interface.multiprocess_interface import ProcessClass
10
10
  import numpy as np
11
11
  from multiclass_interface.tests.my_test_cls import MyTest
12
12
  from multiclass_interface import mpi_interface
13
+ from multiclass_interface.mpi_interface import mpi_run_test
14
+ from multiclass_interface.multi_object_list import MultiClassInterface
13
15
 
14
16
 
15
17
  def mpirun(f):
@@ -27,7 +29,7 @@ def mpirun(f):
27
29
  return f
28
30
 
29
31
 
30
- @mpirun
32
+ @mpi_run_test
31
33
  def test_mpi_collective_too_few_slots():
32
34
  mpi_interface.activate_mpi()
33
35
  N = 4
@@ -42,7 +44,7 @@ def test_mpi_collective_too_few_slots():
42
44
  assert str(e) == 'Not enough mpi slots. Slots: 4, Requested: 5'
43
45
 
44
46
 
45
- @mpirun
47
+ @mpi_run_test
46
48
  def test_mpi_collective_MyTest():
47
49
  mpi_interface.activate_mpi()
48
50
  N = 4
@@ -92,7 +94,7 @@ def test_mpi_collective_MyTest():
92
94
  m[:3][1]
93
95
 
94
96
 
95
- @mpirun
97
+ @mpi_run_test
96
98
  def test_mpi_collective_close_released_mpi_workers():
97
99
  mpi_interface.activate_mpi()
98
100
  N = 4
@@ -102,7 +104,7 @@ def test_mpi_collective_close_released_mpi_workers():
102
104
  m.close()
103
105
 
104
106
 
105
- @mpirun
107
+ @mpi_run_test
106
108
  def test_mpi_ProcessClass():
107
109
  mpi_interface.activate_mpi()
108
110
  with ProcessClass(MyTest) as cls:
@@ -110,13 +112,19 @@ def test_mpi_ProcessClass():
110
112
  assert myTest.get_id() == 1
111
113
 
112
114
 
113
- @mpirun
115
+ @mpi_run_test
114
116
  def test_non_collective_mpi2():
115
117
  mpi_interface.activate_mpi(collective_mpi=False)
116
118
  rank = mpi_interface.rank
117
119
  N = 3
118
120
  with MPIClassInterface(MyTest, [(i + 10,) for i in range(N)]) as m:
119
121
 
122
+ id_lst = mpi_interface.bcast_function_result(lambda: m.get_id())()
123
+ assert id_lst == [10, 11, 12, None], id_lst
124
+ id_lst = mpi_interface.bcast_function_result(lambda: m.return_input(['a', 'b', 'c']))()
125
+ assert id_lst == ["10 got: ('a',) and {}", "11 got: ('b',) and {}", "12 got: ('c',) and {}", None], id_lst
126
+
127
+ mpi_interface.barrier_print('hello world')
120
128
  try:
121
129
  assert m.get_id()[0] == rank + 10, m.get_id()
122
130
  main_id = mpi_interface.main_run(lambda: m.get_id())
@@ -137,7 +145,43 @@ def test_non_collective_mpi2():
137
145
  m.object.cls
138
146
 
139
147
 
140
- @mpirun
148
+ @pytest.mark.parametrize('N', [1, 3])
149
+ def test_bcast_function_result_mci(N):
150
+ with MultiClassInterface(MyTest, [(i + 10,) for i in range(N)]) as m:
151
+
152
+ id_lst = mpi_interface.bcast_function_result(lambda: m.get_id())()
153
+ assert id_lst == [10, 11, 12][:N], id_lst
154
+ id_lst = mpi_interface.bcast_function_result(lambda: m.return_input(['a', 'b', 'c'][:N]))()
155
+ assert id_lst == ["10 got: ('a',) and {}", "11 got: ('b',) and {}", "12 got: ('c',) and {}"][:N], id_lst
156
+
157
+ assert mpi_interface.bcast_function_result(lambda: 0)() == [0]
158
+
159
+
160
+ @mpi_run_test
161
+ def test_bcast_function_result_mpi1():
162
+ mpi_interface.activate_mpi(collective_mpi=False)
163
+ with MPIClassInterface(MyTest, [(i + 10,) for i in range(1)]) as m:
164
+ id_lst = mpi_interface.bcast_function_result(lambda: m.get_id())()
165
+ assert id_lst == [10, None, None, None], id_lst
166
+ id_lst = mpi_interface.bcast_function_result(lambda: m.return_input(['a']))()
167
+ assert id_lst == ["10 got: ('a',) and {}", None, None, None], id_lst
168
+
169
+ assert mpi_interface.bcast_function_result(lambda: mpi_interface.rank)() == [0, 1, 2, 3]
170
+
171
+
172
+ @mpi_run_test
173
+ def test_bcast_function_result_mpi3():
174
+ mpi_interface.activate_mpi(collective_mpi=False)
175
+ with MPIClassInterface(MyTest, [(i + 10,) for i in range(3)]) as m:
176
+ id_lst = mpi_interface.bcast_function_result(lambda: m.get_id())()
177
+ assert id_lst == [10, 11, 12, None], id_lst
178
+ id_lst = mpi_interface.bcast_function_result(lambda: m.return_input(['a', 'b', 'c']))()
179
+ assert id_lst == ["10 got: ('a',) and {}", "11 got: ('b',) and {}", "12 got: ('c',) and {}", None], id_lst
180
+
181
+ assert mpi_interface.bcast_function_result(lambda: mpi_interface.rank)() == [0, 1, 2, 3]
182
+
183
+
184
+ @mpi_run_test(4)
141
185
  def test_mpi_non_collective_MyTest():
142
186
  mpi_interface.activate_mpi(collective_mpi=False)
143
187
  rank = mpi_interface.rank
@@ -185,7 +229,7 @@ def test_mpi_non_collective_MyTest():
185
229
  m[:3][1]
186
230
 
187
231
 
188
- @mpirun
232
+ @mpi_run_test(4)
189
233
  def test_multiprocessinterface_tests():
190
234
  from multiclass_interface.tests.test_multiprocessinterface import test_attribute, test_missing_attribute, test_execption, test_setattr, test_setattr_method
191
235
  mpi_interface.activate_mpi()
@@ -198,7 +242,7 @@ def test_multiprocessinterface_tests():
198
242
  pass
199
243
 
200
244
 
201
- @mpirun
245
+ @mpi_run_test(4)
202
246
  def test_multiprocessinterface_tests_diff_interface():
203
247
  from multiclass_interface.tests.test_multiprocessinterface import test_attribute, test_missing_attribute, test_execption, test_setattr, test_setattr_method
204
248
  mpi_interface.activate_mpi(terminate_on_close=False)
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '1.19'
22
+ __version_tuple__ = version_tuple = (1, 19)
23
+
24
+ __commit_id__ = commit_id = 'g26b6778cd'
@@ -1,21 +0,0 @@
1
- # file generated by setuptools-scm
2
- # don't change, don't track in version control
3
-
4
- __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
5
-
6
- TYPE_CHECKING = False
7
- if TYPE_CHECKING:
8
- from typing import Tuple
9
- from typing import Union
10
-
11
- VERSION_TUPLE = Tuple[Union[int, str], ...]
12
- else:
13
- VERSION_TUPLE = object
14
-
15
- version: str
16
- __version__: str
17
- __version_tuple__: VERSION_TUPLE
18
- version_tuple: VERSION_TUPLE
19
-
20
- __version__ = version = '1.17'
21
- __version_tuple__ = version_tuple = (1, 17)