Multiclass-interface 1.15__tar.gz → 1.18__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.15 → multiclass_interface-1.18}/.gitlab-ci.yml +1 -1
  2. {multiclass_interface-1.15 → multiclass_interface-1.18/Multiclass_interface.egg-info}/PKG-INFO +3 -2
  3. {multiclass_interface-1.15 → multiclass_interface-1.18}/Multiclass_interface.egg-info/SOURCES.txt +2 -0
  4. multiclass_interface-1.18/Multiclass_interface.egg-info/scm_file_list.json +19 -0
  5. multiclass_interface-1.18/Multiclass_interface.egg-info/scm_version.json +8 -0
  6. {multiclass_interface-1.15/Multiclass_interface.egg-info → multiclass_interface-1.18}/PKG-INFO +3 -2
  7. {multiclass_interface-1.15 → multiclass_interface-1.18}/multiclass_interface/mpi_interface.py +71 -1
  8. {multiclass_interface-1.15 → multiclass_interface-1.18}/multiclass_interface/multi_object_list.py +6 -0
  9. {multiclass_interface-1.15 → multiclass_interface-1.18}/multiclass_interface/multiprocess_interface.py +3 -2
  10. {multiclass_interface-1.15 → multiclass_interface-1.18}/multiclass_interface/tests/my_test_cls.py +1 -0
  11. {multiclass_interface-1.15 → multiclass_interface-1.18}/multiclass_interface/tests/test_mpi.py +28 -8
  12. multiclass_interface-1.18/multiclass_interface/version.py +24 -0
  13. multiclass_interface-1.15/multiclass_interface/version.py +0 -21
  14. {multiclass_interface-1.15 → multiclass_interface-1.18}/.gitignore +0 -0
  15. {multiclass_interface-1.15 → multiclass_interface-1.18}/LICENSE +0 -0
  16. {multiclass_interface-1.15 → multiclass_interface-1.18}/Multiclass_interface.egg-info/dependency_links.txt +0 -0
  17. {multiclass_interface-1.15 → multiclass_interface-1.18}/Multiclass_interface.egg-info/requires.txt +0 -0
  18. {multiclass_interface-1.15 → multiclass_interface-1.18}/Multiclass_interface.egg-info/top_level.txt +0 -0
  19. {multiclass_interface-1.15 → multiclass_interface-1.18}/README.md +0 -0
  20. {multiclass_interface-1.15 → multiclass_interface-1.18}/multiclass_interface/__init__.py +0 -0
  21. {multiclass_interface-1.15 → multiclass_interface-1.18}/multiclass_interface/tests/__init__.py +0 -0
  22. {multiclass_interface-1.15 → multiclass_interface-1.18}/multiclass_interface/tests/test_multiclass_list.py +0 -0
  23. {multiclass_interface-1.15 → multiclass_interface-1.18}/multiclass_interface/tests/test_multiprocessinterface.py +0 -0
  24. {multiclass_interface-1.15 → multiclass_interface-1.18}/pyproject.toml +0 -0
  25. {multiclass_interface-1.15 → multiclass_interface-1.18}/sequence_diagrams.md +0 -0
  26. {multiclass_interface-1.15 → multiclass_interface-1.18}/setup.cfg +0 -0
@@ -42,7 +42,7 @@ pypi_linux:
42
42
  #- pip install pypandoc
43
43
  - pip install -e . --upgrade
44
44
  - pip install --upgrade build
45
- - pip install twine==6.0.1 --upgrade
45
+ - pip install twine packaging --upgrade
46
46
  - python3 -m build
47
47
  - twine upload dist/* -u $TWINE_USERNAME -p $TWINE_PASSWORD
48
48
  rules:
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: Multiclass_interface
3
- Version: 1.15
3
+ Version: 1.18
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
 
@@ -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.18",
3
+ "distance": 0,
4
+ "node": "gb934019843ac9640a5beae7880add16d3c7e3838",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-10-23"
8
+ }
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: Multiclass_interface
3
- Version: 1.15
3
+ Version: 1.18
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
 
@@ -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
@@ -155,7 +160,7 @@ class MPIClassInterface():
155
160
  def __getattribute__(self, name):
156
161
  if name in MPIClassInterfaceAttributes:
157
162
  return object.__getattribute__(self, name)
158
- elif not self.use_rank[rank]:
163
+ elif not COLLECTIVE_MPI and not self.use_rank[rank]:
159
164
  raise ChildProcessError(f'MPI worker {rank} not used')
160
165
 
161
166
  def wrap(*args, **kwargs):
@@ -234,3 +239,68 @@ 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)[0]
262
+ except ChildProcessError:
263
+ v = None
264
+
265
+ # collect the return value from each mpiworker (wt as first dimension)
266
+ v = comm.gather(v, root=0)
267
+ # broadcast the full list to all workers
268
+ return comm.bcast(v, root=0)
269
+ else:
270
+ return f(*args, **kwargs)
271
+ return wrapper
272
+
273
+
274
+ def mpi_run_test(n, cwd='.', mpirun_cmd='mpirun'):
275
+ if callable(n):
276
+ return mpi_run_test(n=4)(n)
277
+
278
+ def decorator(test_function):
279
+ try:
280
+ import mpi4py
281
+ assert shutil.which('mpirun') is not None
282
+ except Exception: # pragma: no cover
283
+ return lambda: pytest.xfail('mpirun or mpi4py missing')
284
+ name = test_function.__name__
285
+
286
+ def wrap(mpirun=False):
287
+ cwd = os.getcwd()
288
+ try:
289
+ if mpirun:
290
+ os.chdir(cwd)
291
+ return test_function()
292
+ else:
293
+
294
+ import_path = Path(inspect.getsourcefile(test_function)).relative_to(os.getcwd()).as_posix()
295
+ import_path = import_path[:-3].replace('/', '.')
296
+ cmd = f'''{mpirun_cmd} -n {n} {sys.executable} -c "from {import_path} import {name}; {name}(mpirun=True)"'''
297
+ print(f"cd {os.getcwd()} && {cmd}", flush=True)
298
+ assert os.system(cmd) == 0, name
299
+
300
+ finally:
301
+ os.chdir(cwd)
302
+
303
+ wrap.__name__ = name
304
+ return wrap
305
+
306
+ 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
@@ -60,6 +62,8 @@ def test_mpi_collective_MyTest():
60
62
  i = mpi_interface.main_run(lambda: m.get_id())
61
63
  npt.assert_array_equal(i, 1)
62
64
 
65
+ assert m[1].get_id() == [2]
66
+
63
67
  try:
64
68
  npt.assert_array_equal(m[1:3].get_id(), np.arange(1, 3) + 1)
65
69
  except ChildProcessError:
@@ -90,7 +94,7 @@ def test_mpi_collective_MyTest():
90
94
  m[:3][1]
91
95
 
92
96
 
93
- @mpirun
97
+ @mpi_run_test
94
98
  def test_mpi_collective_close_released_mpi_workers():
95
99
  mpi_interface.activate_mpi()
96
100
  N = 4
@@ -100,7 +104,7 @@ def test_mpi_collective_close_released_mpi_workers():
100
104
  m.close()
101
105
 
102
106
 
103
- @mpirun
107
+ @mpi_run_test
104
108
  def test_mpi_ProcessClass():
105
109
  mpi_interface.activate_mpi()
106
110
  with ProcessClass(MyTest) as cls:
@@ -108,13 +112,19 @@ def test_mpi_ProcessClass():
108
112
  assert myTest.get_id() == 1
109
113
 
110
114
 
111
- @mpirun
115
+ @mpi_run_test
112
116
  def test_non_collective_mpi2():
113
117
  mpi_interface.activate_mpi(collective_mpi=False)
114
118
  rank = mpi_interface.rank
115
119
  N = 3
116
120
  with MPIClassInterface(MyTest, [(i + 10,) for i in range(N)]) as m:
117
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')
118
128
  try:
119
129
  assert m.get_id()[0] == rank + 10, m.get_id()
120
130
  main_id = mpi_interface.main_run(lambda: m.get_id())
@@ -135,7 +145,17 @@ def test_non_collective_mpi2():
135
145
  m.object.cls
136
146
 
137
147
 
138
- @mpirun
148
+ def test_non_collective_mpi2_no_mpi():
149
+ N = 3
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], id_lst
154
+ id_lst = mpi_interface.bcast_function_result(lambda: m.return_input(['a', 'b', 'c']))()
155
+ assert id_lst == ["10 got: ('a',) and {}", "11 got: ('b',) and {}", "12 got: ('c',) and {}"], id_lst
156
+
157
+
158
+ @mpi_run_test(4)
139
159
  def test_mpi_non_collective_MyTest():
140
160
  mpi_interface.activate_mpi(collective_mpi=False)
141
161
  rank = mpi_interface.rank
@@ -183,7 +203,7 @@ def test_mpi_non_collective_MyTest():
183
203
  m[:3][1]
184
204
 
185
205
 
186
- @mpirun
206
+ @mpi_run_test(4)
187
207
  def test_multiprocessinterface_tests():
188
208
  from multiclass_interface.tests.test_multiprocessinterface import test_attribute, test_missing_attribute, test_execption, test_setattr, test_setattr_method
189
209
  mpi_interface.activate_mpi()
@@ -196,7 +216,7 @@ def test_multiprocessinterface_tests():
196
216
  pass
197
217
 
198
218
 
199
- @mpirun
219
+ @mpi_run_test(4)
200
220
  def test_multiprocessinterface_tests_diff_interface():
201
221
  from multiclass_interface.tests.test_multiprocessinterface import test_attribute, test_missing_attribute, test_execption, test_setattr, test_setattr_method
202
222
  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.18'
22
+ __version_tuple__ = version_tuple = (1, 18)
23
+
24
+ __commit_id__ = commit_id = 'gb93401984'
@@ -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.15'
21
- __version_tuple__ = version_tuple = (1, 15)