Multiclass-interface 1.1__tar.gz → 1.3__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 (23) hide show
  1. {Multiclass_interface-1.1 → multiclass_interface-1.3}/.gitlab-ci.yml +0 -1
  2. {Multiclass_interface-1.1 → multiclass_interface-1.3/Multiclass_interface.egg-info}/PKG-INFO +1 -1
  3. {Multiclass_interface-1.1 → multiclass_interface-1.3}/Multiclass_interface.egg-info/SOURCES.txt +1 -0
  4. {Multiclass_interface-1.1/Multiclass_interface.egg-info → multiclass_interface-1.3}/PKG-INFO +1 -1
  5. {Multiclass_interface-1.1 → multiclass_interface-1.3}/multiclass_interface/mpi_interface.py +29 -24
  6. {Multiclass_interface-1.1 → multiclass_interface-1.3}/multiclass_interface/tests/test_mpi.py +24 -6
  7. {Multiclass_interface-1.1 → multiclass_interface-1.3}/multiclass_interface/version.py +2 -2
  8. multiclass_interface-1.3/sequence_diagrams.md +275 -0
  9. {Multiclass_interface-1.1 → multiclass_interface-1.3}/.gitignore +0 -0
  10. {Multiclass_interface-1.1 → multiclass_interface-1.3}/LICENSE +0 -0
  11. {Multiclass_interface-1.1 → multiclass_interface-1.3}/Multiclass_interface.egg-info/dependency_links.txt +0 -0
  12. {Multiclass_interface-1.1 → multiclass_interface-1.3}/Multiclass_interface.egg-info/requires.txt +0 -0
  13. {Multiclass_interface-1.1 → multiclass_interface-1.3}/Multiclass_interface.egg-info/top_level.txt +0 -0
  14. {Multiclass_interface-1.1 → multiclass_interface-1.3}/README.md +0 -0
  15. {Multiclass_interface-1.1 → multiclass_interface-1.3}/multiclass_interface/__init__.py +0 -0
  16. {Multiclass_interface-1.1 → multiclass_interface-1.3}/multiclass_interface/multi_object_list.py +0 -0
  17. {Multiclass_interface-1.1 → multiclass_interface-1.3}/multiclass_interface/multiprocess_interface.py +0 -0
  18. {Multiclass_interface-1.1 → multiclass_interface-1.3}/multiclass_interface/tests/__init__.py +0 -0
  19. {Multiclass_interface-1.1 → multiclass_interface-1.3}/multiclass_interface/tests/my_test_cls.py +0 -0
  20. {Multiclass_interface-1.1 → multiclass_interface-1.3}/multiclass_interface/tests/test_multiclass_list.py +0 -0
  21. {Multiclass_interface-1.1 → multiclass_interface-1.3}/multiclass_interface/tests/test_multiprocessinterface.py +0 -0
  22. {Multiclass_interface-1.1 → multiclass_interface-1.3}/pyproject.toml +0 -0
  23. {Multiclass_interface-1.1 → multiclass_interface-1.3}/setup.cfg +0 -0
@@ -13,7 +13,6 @@ test_ubuntu: # name the job what we like
13
13
  script:
14
14
  - pip install -e .[test]
15
15
  - pytest --cov-report term-missing:skip-covered --cov=multiclass_interface multiclass_interface/tests
16
- - pytest --cov-report term-missing:skip-covered --cov=multiclass_interface multiclass_interface/tests
17
16
  coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
18
17
  tags: # only runners with this tag can do the job [3]
19
18
  - ci-ubuntu
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: Multiclass_interface
3
- Version: 1.1
3
+ Version: 1.3
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
@@ -3,6 +3,7 @@
3
3
  LICENSE
4
4
  README.md
5
5
  pyproject.toml
6
+ sequence_diagrams.md
6
7
  Multiclass_interface.egg-info/PKG-INFO
7
8
  Multiclass_interface.egg-info/SOURCES.txt
8
9
  Multiclass_interface.egg-info/dependency_links.txt
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: Multiclass_interface
3
- Version: 1.1
3
+ Version: 1.3
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,27 +1,24 @@
1
1
  import numpy as np
2
2
  import sys
3
- import os
4
- import subprocess
5
3
  import traceback
6
4
  import inspect
7
- from enum import Enum
8
5
 
9
- if 'SLURM_NTASKS' in os.environ:
10
- mpi = int(os.environ['SLURM_NTASKS']) > 1
11
- elif any([k in os.environ for k in ['MPI_LOCALNRANKS', 'OMPI_COMM_WORLD_SIZE']]):
12
- mpi = True
13
- else:
14
- mpi = subprocess.run("python -c 'from mpi4py import MPI'", shell=True,
15
- check=False, stderr=subprocess.PIPE).returncode == 0
6
+ comm = None
7
+ size = 1
8
+ rank = 0
9
+ name = ""
10
+ main = True
16
11
 
17
- if mpi:
12
+
13
+ def activate_mpi():
14
+ global comm, size, rank, name, main
18
15
  from mpi4py import MPI
19
16
  comm = MPI.COMM_WORLD
20
17
  size = MPI.COMM_WORLD.Get_size()
21
18
  rank = MPI.COMM_WORLD.Get_rank()
22
19
  name = MPI.Get_processor_name()
23
- else:
24
- size = 1
20
+ main = rank == 0
21
+
25
22
 
26
23
  MPIClassInterfaceAttributes = {
27
24
  'close',
@@ -38,6 +35,7 @@ MPIClassInterfaceAttributes = {
38
35
 
39
36
  LOOP_UNTIL_CLOSE = True
40
37
  TERMINATE_ON_CLOSE = True
38
+ COLLECTIVE_MPI = True
41
39
 
42
40
 
43
41
  class MPIClassInterface():
@@ -56,10 +54,9 @@ class MPIClassInterface():
56
54
  self.object = Dummy()
57
55
  self.closed = False
58
56
 
59
- if rank == 0:
60
- self.use_rank = np.array([True] * size)
61
- self.use_rank[len(args_lst):] = False
62
- elif LOOP_UNTIL_CLOSE:
57
+ self.use_rank = np.array([True] * size)
58
+ self.use_rank[len(args_lst):] = False
59
+ if rank > 0 and COLLECTIVE_MPI and LOOP_UNTIL_CLOSE:
63
60
  self.work_loop()
64
61
 
65
62
  def __enter__(self):
@@ -136,13 +133,18 @@ class MPIClassInterface():
136
133
  def __getattribute__(self, name):
137
134
  if name in MPIClassInterfaceAttributes:
138
135
  return object.__getattribute__(self, name)
139
- elif rank > 0:
136
+ elif COLLECTIVE_MPI and rank > 0:
140
137
  self.work_loop()
141
138
  return lambda *args, **kwargs: 1
139
+ elif not self.use_rank[rank]:
140
+ raise ChildProcessError('MPI worker not used')
142
141
 
143
142
  def wrap(*args, **kwargs):
144
- inp = [self.get_input(name, i, args, kwargs) for i in range(size)]
145
- return self.run_task(inp)
143
+ if COLLECTIVE_MPI:
144
+ inp = [self.get_input(name, i, args, kwargs) for i in range(size)]
145
+ return self.run_task(inp)
146
+ else:
147
+ return self.do_task(name, args, kwargs)
146
148
 
147
149
  if hasattr(getattr(self.object, name), '__call__'):
148
150
  return wrap
@@ -165,11 +167,14 @@ class MPIClassInterface():
165
167
 
166
168
  def close(self):
167
169
  if not self.closed:
168
- if rank == 0:
169
- res = self.run_task([('close', [], {}) for _ in range(size)])
170
+ if COLLECTIVE_MPI:
171
+ if rank == 0:
172
+ res = self.run_task([('close', [], {}) for _ in range(size)])
173
+ else:
174
+ self.work_loop()
175
+ res = None
170
176
  else:
171
- self.work_loop()
172
- res = None
177
+ res = self.do_task('close', [], {})
173
178
  self.closed = True
174
179
 
175
180
 
@@ -42,13 +42,30 @@ def mpitest_mpi_ProcessClass():
42
42
  with ProcessClass(MyTest) as cls:
43
43
  myTest = cls(1)
44
44
  assert myTest.get_id() == 1
45
- print("done, test_mpi_ProcessClass")
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() == rank + 10
57
+ assert rank < N # rank > N will fail with ChildProcessError
58
+ except ChildProcessError:
59
+ pass
60
+
61
+ mpi_interface.COLLECTIVE_MPI = True
46
62
 
47
63
 
48
64
  def mpitest_all():
65
+ mpi_interface.activate_mpi()
66
+
49
67
  mpi_interface.exit_mpi_on_close = False
50
- from mpi4py import MPI
51
- rank = MPI.COMM_WORLD.Get_rank()
68
+ rank = mpi_interface.rank
52
69
  from multiclass_interface.tests.test_multiprocessinterface import test_attribute, test_missing_attribute, test_execption, test_setattr, test_setattr_method
53
70
  mpi_interface.TERMINATE_ON_CLOSE = False
54
71
 
@@ -67,13 +84,14 @@ def mpitest_all():
67
84
  run(f)
68
85
 
69
86
  try:
70
- with MPIClassInterface(MyTest, [(1,), (2,)]) as mpici:
87
+ with MPIClassInterface(MyTest, [(1,), (2,), (3,)]) as mpici:
71
88
  for f in [test_attribute, test_missing_attribute, test_execption, test_setattr, test_setattr_method]:
72
- break
89
+
73
90
  run(f, mpici)
74
91
 
75
92
  except ChildProcessError:
76
93
  pass
77
- mpi_interface.TERMINATE_ON_CLOSE = True
94
+
95
+ run(mpitest_non_collective_mpi)
78
96
 
79
97
  print(f"Rank {rank} Done test_all")
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '1.1'
16
- __version_tuple__ = version_tuple = (1, 1)
15
+ __version__ = version = '1.3'
16
+ __version_tuple__ = version_tuple = (1, 3)
@@ -0,0 +1,275 @@
1
+ # Sequence diagrams
2
+ - [ThreadClass](#threadclass)
3
+ - [ProcessClass](#processclass)
4
+ - [MultiThreadClassInterface](#multithreadclassinterface)
5
+ - [MultiProcessClassInterface](#multiprocessclassinterface)
6
+ - [MPIClassInterface](#mpiclassinterface)
7
+ - [MPIClassInterface(LOOP_UNTIL_CLOSE=False)](#mpiclassinterfaceloop_until_closefalse)
8
+ - [MPIClassInterface(COLLECTIVE_MPI=False)](#mpiclassinterfacecollective_mpifalse)
9
+
10
+ ## ThreadClass
11
+ ```mermaid
12
+ sequenceDiagram
13
+ participant U as Main
14
+ participant TC as ThreadClass
15
+
16
+ U->>+TC: cls=A
17
+
18
+ create participant Q as IOQueue
19
+ TC->>Q:
20
+ TC->>-U: ~A
21
+ U->>+TC: ~A(*args,**kwargs)
22
+ create participant T as Thread
23
+ TC->>T: A, IOQueue, args, kwargs
24
+ create participant A
25
+ T->>A: __init__(*args, **kwargs)
26
+ T->>+T: Run()
27
+ T->>+Q: get()
28
+ TC->>-U: ~a
29
+ U->>+TC: ~a.f(*args,**kwargs)
30
+ TC->>Q: put(f,args,kwargs)
31
+
32
+ Q->>-T: f,args,kwargs
33
+ T->>+A: f(*args, **kwargs)
34
+ A->>-T: res
35
+ TC->>+Q: get()
36
+ T->>Q: put(res)
37
+ Q->>-TC: res
38
+ TC->>-U: res
39
+ TC->>T: close()
40
+ T->>-T:
41
+
42
+
43
+ ```
44
+
45
+ ## ProcessClass
46
+
47
+ The `ProcessClass` is similar to the `ThreadClas` except that it uses a seprate Process instead of a Thread.
48
+ This means:
49
+ - More time used to spawn process
50
+ - Changes to `os.environ['LD_LIBRARY_PATH']` are effective in the process
51
+ - Libraries (dll/so) will have their own memory space (important if multiple instances are launched)
52
+ - Multiple processes allows parallel execution
53
+
54
+ ## MultiThreadClassInterface
55
+
56
+ ```mermaid
57
+ sequenceDiagram
58
+ participant U as Main
59
+ participant TC as MultiThreadClassInterface
60
+
61
+ U->>+TC: cls=A, [args1,args2])
62
+
63
+ create participant Q1 as IOQueue1
64
+ TC->>Q1:
65
+ create participant T1 as Thread1
66
+ TC->>T1: A, IOQueue1, args1
67
+ create participant A1
68
+ T1->>A1: __init__(*args1)
69
+ T1->>+T1: Run()
70
+ T1->>+Q1: get()
71
+
72
+ create participant Q2 as IOQueue2
73
+ TC->>Q2:
74
+ create participant T2 as Thread2
75
+ TC->>T2: A, IOQueue2, args2
76
+ create participant A2
77
+ T2->>A2: __init__(*args2)
78
+ T2->>+T2: Run()
79
+ T2->>+Q2: get()
80
+
81
+ TC->>-U: ~a
82
+ U->>+TC: ~a.f(*args,**kwargs)
83
+ TC->>Q1: put(f,args,kwargs)
84
+ TC->>Q2: put(f,args,kwargs)
85
+
86
+ Q1->>-T1: f,args,kwargs
87
+ Q2->>-T2: f,args,kwargs
88
+ T1->>+A1: f(*args, **kwargs)
89
+ T2->>+A2: f(*args, **kwargs)
90
+ A1->>-T1: res1
91
+ A2->>-T2: res2
92
+ TC->>+Q1: get()
93
+ T1->>Q1: put(res)
94
+ T2->>Q2: put(res)
95
+ Q1->>-TC: res1
96
+ TC->>+Q2: get()
97
+ Q2->>-TC: res2
98
+ TC->>-U: [res1,res2]
99
+
100
+ TC->>T1: close()
101
+ T1->>-T1:
102
+ TC->>T2: close()
103
+ T2->>-T2:
104
+ ```
105
+
106
+
107
+ ## MultiProcessClassInterface
108
+
109
+ The `MultiProcessClassInterface` is similar to the `MultiThreadClassInterfaces` except that it uses a seprate processes instead of a threads.
110
+ This means:
111
+ - More time used to spawn processes
112
+ - Changes to `os.environ['LD_LIBRARY_PATH']` are effective in the processes
113
+ - Libraries (dll/so) executed in the processes will their own individual memory space (changes to a variable in one dll does not affect the others)
114
+ - Parallel execution of class functions
115
+
116
+ ## MPIClassInterface
117
+ ```mermaid
118
+ sequenceDiagram
119
+ actor U as User
120
+
121
+ participant MPI0 as MPI,rank0<br>MPIClassInterface
122
+
123
+
124
+ MPI0->>+MPI0: cls=A, [(args0),(args1)]
125
+
126
+
127
+ create participant A0
128
+ MPI0->>+A0: args0
129
+ MPI0->>-U: ~a
130
+ participant MPI1 as MPI,rank1<br>MPIClassInterface
131
+ MPI1->>+MPI1: cls=A, [(args0),(args1)]
132
+ create participant A1
133
+ MPI1->>A1: args1
134
+ participant MPI2 as MPI,rank2<br>MPIClassInterface
135
+ MPI2->>MPI2: cls=A, [(args0),(args1)]
136
+ MPI1->>-U:
137
+ MPI2->>U:
138
+
139
+ U->>+MPI0:~a.f([args0,args1])
140
+ MPI0->>+MPI0: run_task()
141
+ MPI1->>+MPI1: work_loop
142
+ MPI2->>+MPI2: work_loop
143
+
144
+ rect rgb(191, 223, 255)
145
+ note right of MPI0: Scatter
146
+ MPI0->>MPI0: f,args0
147
+ MPI0->>MPI1: f,args1
148
+ MPI0->>MPI2: "skip",[]
149
+ end
150
+ MPI0->>+A0: f(args0)
151
+ MPI1->>+A1: f(args1)
152
+ A0->>-MPI0: res0
153
+ A1->>-MPI1: res1
154
+
155
+ rect rgb(191, 223, 255)
156
+ note right of MPI0: Gather
157
+ MPI0->>MPI0: res0
158
+ MPI1->>MPI0: res1
159
+ MPI2->>MPI0: None
160
+ end
161
+
162
+
163
+ MPI0->>-U: [res1,res2]
164
+
165
+ U->>+MPI0:~a.close()
166
+ MPI0->>+MPI0: run_task()
167
+ rect rgb(191, 223, 255)
168
+ note right of MPI0: Scatter
169
+ MPI0->>MPI0: close
170
+ MPI0->>MPI1: close
171
+ MPI0->>MPI2: close
172
+ end
173
+ MPI0->>-MPI0:
174
+ MPI1->>-MPI1:
175
+ MPI2->>-MPI2:
176
+ ```
177
+
178
+ ## MPIClassInterface(LOOP_UNTIL_CLOSE=False)
179
+
180
+ Setting `mpi_interface.LOOP_UNTIL_CLOSE=False` results in the following workflow
181
+
182
+ ```mermaid
183
+ sequenceDiagram
184
+ actor U as User
185
+
186
+ participant MPI0 as MPI,rank0<br>MPIClassInterface
187
+
188
+
189
+ MPI0->>+MPI0: cls=A, [(args0),(args1)]
190
+
191
+
192
+ create participant A0
193
+ MPI0->>+A0: args0
194
+ MPI0->>-U: ~a
195
+ participant MPI1 as MPI,rank1<br>MPIClassInterface
196
+ MPI1->>+MPI1: cls=A, [(args0),(args1)]
197
+ create participant A1
198
+ MPI1->>A1: args1
199
+ participant MPI2 as MPI,rank2<br>MPIClassInterface
200
+ MPI2->>MPI2: cls=A, [(args0),(args1)]
201
+ MPI1->>-U:
202
+ MPI2->>U:
203
+
204
+ U->>+MPI0:~a.f([args0,args1])
205
+ MPI0->>+MPI0: run_task()
206
+ MPI1->>+MPI1: work_loop
207
+ MPI2->>+MPI2: work_loop
208
+ MPI2->>-MPI2:
209
+ MPI2->>U:
210
+
211
+
212
+ rect rgb(191, 223, 255)
213
+ note right of MPI0: Scatter
214
+ MPI0->>MPI0: f,args0
215
+ MPI0->>MPI1: f,args1
216
+ MPI0->>MPI2: "skip",[]
217
+ end
218
+ MPI0->>+A0: f(args0)
219
+ MPI1->>+A1: f(args1)
220
+ A0->>-MPI0: res0
221
+ A1->>-MPI1: res1
222
+ MPI1->>-MPI1:
223
+ MPI1->>U:
224
+
225
+ rect rgb(191, 223, 255)
226
+ note right of MPI0: Gather
227
+ MPI0->>MPI0: res0
228
+ MPI1->>MPI0: res1
229
+ MPI2->>MPI0: None
230
+ end
231
+
232
+
233
+ MPI0->>-U: [res1,res2]
234
+
235
+ ```
236
+
237
+
238
+ ## MPIClassInterface(COLLECTIVE_MPI=False)
239
+ Setting `mpi_interface.COLLECTIVE_MPI=False` results in the following workflow
240
+
241
+ ```mermaid
242
+ sequenceDiagram
243
+
244
+ participant MPI0 as MPI,rank0<br>MPIClassInterface
245
+
246
+
247
+ MPI0->>+MPI0: cls=A, [(args0),(args1)]
248
+
249
+
250
+ create participant A0
251
+ MPI0->>+A0: args0
252
+
253
+ participant MPI1 as MPI,rank1<br>MPIClassInterface
254
+ MPI1->>+MPI1: cls=A, [(args0),(args1)]
255
+ create participant A1
256
+ MPI1->>A1: args1
257
+ participant MPI2 as MPI,rank2<br>MPIClassInterface
258
+ MPI2->>MPI2: cls=A, [(args0),(args1)]
259
+
260
+ MPI0->>+MPI0:~a.f([args0,args1])
261
+ MPI1->>+MPI1:~a.f([args0,args1])
262
+ MPI2->>+MPI2:~a.f([args0,args1])
263
+
264
+
265
+ MPI0->>+A0: f(args0)
266
+ MPI1->>+A1: f(args1)
267
+ MPI2->>-MPI2: ChildProcessError
268
+ A0->>-MPI0: res0
269
+ A1->>-MPI1: res1
270
+
271
+ MPI0->>-MPI0: res0
272
+ MPI1->>-MPI1: res1
273
+
274
+ ```
275
+