ezRay 1.0.2__py3-none-any.whl

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.
ezRay/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ # Forwarding
2
+ from ezRay.ezRay import MultiCoreExecutionTool
3
+
4
+ __all__ = ['MultiCoreExecutionTool']
ezRay/ezRay.py ADDED
@@ -0,0 +1,650 @@
1
+ ## Dependencies:
2
+ import json
3
+ import time
4
+ import webbrowser
5
+ from typing import Any, Callable, Dict, List, NoReturn, Optional, Tuple, Union
6
+
7
+ import psutil
8
+ import ray
9
+
10
+ # typing
11
+ import ray.remote_function
12
+ import ray.runtime_context
13
+
14
+ # context aware progress bar
15
+ # detect jupyter notebook
16
+ from IPython import get_ipython
17
+
18
+ try:
19
+ ipy_str = str(type(get_ipython()))
20
+ if 'zmqshell' in ipy_str:
21
+ from tqdm.notebook import tqdm
22
+ else:
23
+ from tqdm import tqdm
24
+ except Exception as _:
25
+ from tqdm import tqdm
26
+
27
+ #%% Multi Core Execution Main
28
+ class MultiCoreExecutionTool:
29
+ RuntimeData:Dict[Any,Dict[Any,Any]]
30
+ RuntimeResults:Dict[Any,Dict[str,Any]]
31
+
32
+ runimte_context:ray.runtime_context.RuntimeContext
33
+ RuntimeMetadata:Dict[str,Union[str, bool, int, float]]
34
+ DashboardURL:str
35
+
36
+ silent:bool
37
+ DEBUG:bool
38
+
39
+ def __init__(self, RuntimeData:Dict[Any,Dict[Any,Any]] = None, /, **kwargs)->'MultiCoreExecutionTool':
40
+ """Constructor for the MultiCoreExecutionTool class.
41
+
42
+ Args:
43
+ RuntimeData (Dict[Any,Dict[Any,Any]], optional): Dictionary containing keyword arguments for the methods to run. Defaults to None.
44
+
45
+ Returns:
46
+ MultiCoreExecutionTool: MultiCoreExecutionTool object.
47
+ """
48
+ ## Default Verbosity
49
+ self.ListenerSleeptime = 0.1
50
+ self.LaunchDashboard = False
51
+ self.silent = False
52
+ self.DEBUG = False
53
+
54
+ ## Initialize attributes
55
+ self.DashboardURL = None
56
+ self.RuntimeContext = None
57
+ self.RuntimeMetadata = None
58
+ self.RuntimeResults = None
59
+
60
+ ## Setattributes
61
+ for key, value in kwargs.items():
62
+ if hasattr(self, key):
63
+ setattr(self, key, value)
64
+
65
+ ## set the debug flag
66
+ if 'DEBUG' in kwargs.keys():
67
+ self.DEBUG = kwargs['DEBUG']
68
+ self.silent = False
69
+
70
+ self.__post_init__(RuntimeData, **kwargs)
71
+
72
+ def __post_init__(self, RuntimeData:Dict[Any,Dict[Any,Any]], /, **kwargs)->NoReturn:
73
+ """Post initialization method for the MultiCoreExecutionTool class. Handles routine initialization tasks.
74
+
75
+ Args:
76
+ RuntimeData (Dict[Any,Dict[Any,Any]]): Structured data to be processed by the methods.
77
+
78
+ Returns:
79
+ NoReturn: No Return
80
+ """
81
+ self.__initialize_metadata__(**kwargs)
82
+ self.__initialize_ray_cluster__()
83
+ self.__offload_on_init__(RuntimeData)
84
+
85
+ #%% Class methods
86
+ @classmethod
87
+ def from_dict(cls, data:Dict[str,Any])->'MultiCoreExecutionTool':
88
+ """Convenience method to create a MultiCoreExecutionTool object from a dictionary.
89
+
90
+ Returns:
91
+ MultiCoreExecutionTool: MultiCoreExecutionTool object.
92
+ """
93
+ return cls(**data)
94
+
95
+ @classmethod
96
+ def from_json(cls, path:str)->'MultiCoreExecutionTool':
97
+ """Convenience method to create a MultiCoreExecutionTool object from a JSON file.
98
+
99
+ Args:
100
+ path (str): Path to the JSON file.
101
+
102
+ Returns:
103
+ MultiCoreExecutionTool: MultiCoreExecutionTool object.
104
+ """
105
+ with open(path, 'r') as file:
106
+ data = json.load(file)
107
+ return cls(**data)
108
+
109
+ #%% DEBUG & DEMO
110
+ @ray.remote(num_cpus=1, num_returns=1)
111
+ def test_function(kwargs)->Dict[Any,Any]:
112
+ """Test function for the framework that merely forwards the input."""
113
+ return {k:v for k,v in kwargs.items()}
114
+
115
+ #%% Ray Wrapper
116
+ def __setup_wrapper__(self)->Callable:
117
+ @ray.remote(**self.RuntimeMetadata['task_metadata'])
118
+ def __method_wrapper__(method:Callable, input:Dict[Any,Any])->ray.remote_function.RemoteFunction:
119
+ """Ray wrapper for arbitrary function logic.
120
+
121
+ Args:
122
+ method (Callable): Arbitrary method that takes at least one input.
123
+ input (Dict[Any,Any]): Method input that will be forwarded to the main logic.
124
+
125
+ Returns:
126
+ Callable: Returns a ray.remote callable object.
127
+ """
128
+ return method(**input)
129
+ return __method_wrapper__
130
+
131
+ #%% Main Backend
132
+ def __run__(self, worker:Union[Callable, ray.remote_function.RemoteFunction])->bool:
133
+ """Main execution method for the MultiCoreExecutionTool class. Runs the provided worker on the provided data.
134
+
135
+ Args:
136
+ worker (Union[Callable, ray.remote_function.RemoteFunction]): Main worker or logic. Can be either ray.remote or a callable.
137
+
138
+ Raises:
139
+ Exception: Exception is raised if the core logic is not ray compatible.
140
+
141
+ Returns:
142
+ bool: Boolean flag that is True if the execution is successful.
143
+ """
144
+ ## check if ray is initialized
145
+ if not ray.is_initialized():
146
+ raise Exception('Ray is not initialized. Use object.initialize() to initialize Ray.')
147
+
148
+ if not self.is_ray_compatible(worker):
149
+ try:
150
+ coreLogic = worker
151
+ worker = self.__setup_wrapper__()
152
+ except Exception as e:
153
+ print(f'Error: {e}')
154
+ return False
155
+
156
+ ## prepare schedule
157
+ schedule = self.__setup_schedule__()
158
+ if len(schedule) == 0:
159
+ print('No pending tasks to run.')
160
+ return True
161
+
162
+ ## workflow factory
163
+ if self.silent:
164
+ permision, states = self.__multicore_workflow__(worker = worker,
165
+ schedule = schedule,
166
+ listener = self.__silent_listener__,
167
+ scheduler = self.__silent_scheduler__,
168
+ coreLogic = coreLogic if 'coreLogic' in locals() else None)
169
+ else:
170
+ permision, states = self.__multicore_workflow__(worker = worker,
171
+ schedule = schedule,
172
+ listener = self.__verbose_listener__,
173
+ scheduler = self.__verbose_scheduler__,
174
+ coreLogic = coreLogic if 'coreLogic' in locals() else None)
175
+
176
+ ## update the results
177
+ if permision:
178
+ for k in schedule:
179
+ self.RuntimeResults[k].update({'result':states[k], 'status':'completed'})
180
+
181
+ return permision
182
+
183
+ def __verbose_scheduler__(self,
184
+ worker:ray.remote_function.RemoteFunction,
185
+ schedule:List[Any],
186
+ coreLogic:Optional[Callable])->Dict[ray.ObjectRef,int]:
187
+ """Verbose scheduler that handles remote task execution.
188
+
189
+ Args:
190
+ worker (ray.remote_function.RemoteFunction): Remote callable object. See ray.remote for more information.
191
+ schedule (List[Any]): List of keys referring to RuntimeData values to be processed using the provided method.
192
+ coreLogic (Optional[Callable]): Core logic of local function that will be forwarded to ray.
193
+
194
+ Returns:
195
+ Dict[ray.ObjectRef,int]: Dictionary containing the object references and their corresponding keys for keeping track of the progress and upholding the order of input data provided.
196
+ """
197
+ ## VERBOSE MODE
198
+
199
+ # if coreLogic is provided, pass it to the wrapper
200
+ if coreLogic is not None:
201
+ return {worker.remote(coreLogic, self.RuntimeData_ref[schedule_index]):schedule_index
202
+ for schedule_index in tqdm(schedule, total=len(schedule), desc="Scheduling Workers", position = 0)}
203
+
204
+ # if a ray compatible worker is provided, forward the worker directly
205
+ return {worker.remote(self.RuntimeData_ref[schedule_index]):schedule_index
206
+ for schedule_index in tqdm(schedule, total=len(schedule), desc="Scheduling Workers", position = 0)}
207
+
208
+ def __silent_scheduler__(self,
209
+ worker:ray.remote_function.RemoteFunction,
210
+ schedule:List,
211
+ coreLogic:Optional[Callable])->Dict[ray.ObjectRef,int]:
212
+ """Silent scheduler that handles remote task execution.
213
+
214
+ Args:
215
+ worker (ray.remote_function.RemoteFunction): Remote callable object. See ray.remote for more information.
216
+ schedule (List[Any]): List of keys referring to RuntimeData values to be processed using the provided method.
217
+ coreLogic (Optional[Callable]): Core logic of local function that will be forwarded to ray.
218
+
219
+ Returns:
220
+ Dict[ray.ObjectRef,int]: Dictionary containing the object references and their corresponding keys for keeping track of the progress and upholding the order of input data provided.
221
+ """
222
+ ## SILENT MODE
223
+
224
+ # if coreLogic is provided, pass it to the wrapper
225
+ if coreLogic is not None:
226
+ return {worker.remote(coreLogic, self.RuntimeData_ref[schedule_index]):schedule_index
227
+ for schedule_index in schedule}
228
+
229
+ # if a ray compatible worker is provided, forward the worker directly
230
+ return {worker.remote(self.RuntimeData_ref[schedule_index]):schedule_index
231
+ for schedule_index in schedule}
232
+
233
+ def __multicore_workflow__(self,
234
+ worker:Union[Callable, ray.remote_function.RemoteFunction],
235
+ schedule:List[Any],
236
+ listener:Callable,
237
+ scheduler:Callable,
238
+ coreLogic:Optional[Callable]
239
+ )->Tuple[bool, Dict[int,Any]]:
240
+ """Workflow for the MultiCoreExecutionTool class. Handles the main execution logic.
241
+
242
+ Args:
243
+ worker (Union[Callable, ray.remote_function.RemoteFunction]): Remote callable object. See ray.remote for more information.
244
+ schedule (List[Any]): List of keys referring to RuntimeData values to be processed using the provided method.
245
+ listener (Callable): Chosen listener.
246
+ scheduler (Callable): Chosen scheduler.
247
+ coreLogic (Optional[Callable]): Core logic of local function that will be forwarded to ray.
248
+
249
+ Returns:
250
+ Tuple[bool, Dict[int,Any]]: Boolean flag signaling the success or the execution, Dictionary containing the results of the execution.
251
+ """
252
+ ## workflow and listening
253
+ permission, finished_states = listener(scheduler(worker, schedule, coreLogic))
254
+
255
+ ## check completion
256
+ if permission:
257
+ self.RuntimeResults | {k:{'result':v, 'status':'completed'} for k,v in finished_states.items()}
258
+
259
+ ## Shutdown Ray
260
+ if self.DEBUG:
261
+ print("Multi Core Execution Complete...")
262
+ print("Use 'OverlayGenerator.shutdown_multi_core()' to shutdown the cluster.")
263
+
264
+ return True, finished_states
265
+
266
+ return False, None
267
+ #%% Process Listener
268
+ def __silent_listener__(self, object_references:Dict[ray.ObjectRef,int])->Tuple[bool, Dict[int,Any]]:
269
+ """Silently listenes to the ray progress and retrieves the results.
270
+
271
+ Args:
272
+ object_references (Dict[ray.ObjectRef,int]): Dictionary containing the object references and their corresponding keys for keeping track of the progress and upholding the order of input data provided.
273
+
274
+ Returns:
275
+ Tuple[bool, Dict[int,Any]]: Boolean flag signaling the success or the execution, Dictionary containing the results of the execution.
276
+ """
277
+
278
+ try:
279
+ # setup collection list
280
+ pending_states:list = list(object_references.keys())
281
+ finished_states:list = []
282
+
283
+ if self.DEBUG:
284
+ print('Listening to Ray Progress...')
285
+
286
+ while len(pending_states) > 0:
287
+ try:
288
+ # get the ready refs
289
+ finished, pending_states = ray.wait(
290
+ pending_states, timeout=8.0
291
+ )
292
+
293
+ finished_states.extend(finished)
294
+
295
+ except KeyboardInterrupt:
296
+ print('Interrupted')
297
+ break
298
+
299
+ if self.ListenerSleeptime > 0:
300
+ time.sleep(self.ListenerSleeptime)
301
+
302
+ # sort and return the results
303
+ finished_states = {object_references[ref]:ray.get(ref) for ref in finished_states}
304
+
305
+ return True, finished_states
306
+
307
+ except Exception as e:
308
+ print(f'Error: {e}')
309
+ return False, None
310
+
311
+ def __verbose_listener__(self, object_references:Dict[ray.ObjectRef,int])->Tuple[bool, Dict[int,Any]]:
312
+ """Listenes to and reports on the ray progress and system CPU and Memory. Retrieves results of successful tasks.
313
+
314
+ Args:
315
+ object_references (Dict[ray.ObjectRef,int]): Dictionary containing the object references and their corresponding keys for keeping track of the progress and upholding the order of input data provided.
316
+
317
+ Returns:
318
+ Tuple[bool, Dict[int,Any]]: Boolean flag signaling the success or the execution, Dictionary containing the results of the execution.
319
+ """
320
+ try:
321
+ if self.DEBUG:
322
+ print('Setting up progress monitors...')
323
+
324
+ ## create progress monitors
325
+ core_progress = tqdm(total = len(object_references), desc = 'Workers', position = 1)
326
+ cpu_progress = tqdm(total = 100, desc="CPU usage", bar_format='{desc}: {percentage:3.0f}%|{bar}|', position = 2)
327
+ mem_progress = tqdm(total=psutil.virtual_memory().total, desc="RAM usage", bar_format='{desc}: {percentage:3.0f}%|{bar}|', position = 3)
328
+
329
+ # setup collection list
330
+ pending_states:list = list(object_references.keys())
331
+ finished_states:list = []
332
+
333
+ if self.DEBUG:
334
+ print('Listening to Ray Progress...')
335
+ ## listen for progress
336
+ while len(pending_states) > 0:
337
+ try:
338
+ # get the ready refs
339
+ finished, pending_states = ray.wait(
340
+ pending_states, timeout=8.0
341
+ )
342
+
343
+ finished_states.extend(finished)
344
+
345
+ # update the progress bars
346
+ mem_progress.n = psutil.virtual_memory().used
347
+ mem_progress.refresh()
348
+
349
+ cpu_progress.n = psutil.cpu_percent()
350
+ cpu_progress.refresh()
351
+
352
+ # update the progress bar
353
+ core_progress.n = len(finished_states)
354
+ core_progress.refresh()
355
+
356
+ # sleep for a bit
357
+ if self.ListenerSleeptime > 0:
358
+ time.sleep(self.ListenerSleeptime)
359
+
360
+ except KeyboardInterrupt:
361
+ print('Interrupted')
362
+ break
363
+
364
+ # set the progress bars to success
365
+ core_progress.colour = 'green'
366
+ cpu_progress.colour = 'green'
367
+ mem_progress.colour = 'green'
368
+
369
+ # set the progress bars to their final values
370
+ core_progress.n = len(object_references)
371
+ cpu_progress.n = 0
372
+ mem_progress.n = 0
373
+
374
+ # close the progress bars
375
+ core_progress.close()
376
+ cpu_progress.close()
377
+ mem_progress.close()
378
+
379
+ # sort and return the results
380
+ finished_states = {object_references[ref]:ray.get(ref) for ref in finished_states}
381
+
382
+ if self.DEBUG:
383
+ print('Ray Progress Complete...')
384
+
385
+ return True, finished_states
386
+
387
+ except Exception as e:
388
+ print(f'Error: {e}')
389
+ return False, None
390
+
391
+ ##### API #####
392
+ #%% Main Execution
393
+ def run(self, worker:Union[Callable, ray.remote_function.RemoteFunction])->bool:
394
+ """Run API for the MultiCoreExecutionTool class. Main API for running the provided worker on the provided data.
395
+
396
+ Args:
397
+ worker (Union[Callable, ray.remote_function.RemoteFunction]): Main worker or logic. Can be either ray.remote or a callable.
398
+
399
+ Returns:
400
+ bool: Boolean flag that is True if the execution was successful.
401
+ """
402
+ try:
403
+ permission:bool = self.__run__(worker)
404
+ assert permission
405
+
406
+ if self.DEBUG:
407
+ print('Multi Core Execution Complete...')
408
+ print('Use "OverlayGenerator.get_results()" to get the results.')
409
+
410
+ return True
411
+ except Exception as e:
412
+ print(f'Error: {e}')
413
+ return False
414
+
415
+ #%% Runtime Control
416
+ def initialize(self)->NoReturn:
417
+ """Initialize the Ray cluster using the parameters found in sel.RuntimeMetadata['instance_metadata']".
418
+ See https://docs.ray.io/en/latest/ray-core/api/doc/ray.init.html for more information.
419
+
420
+ Returns:
421
+ NoReturn: No Return.
422
+ """
423
+ try:
424
+ InitInstructions = self.RuntimeMetadata['instance_metadata']
425
+ except Exception as e:
426
+ print(f'Error: {e}')
427
+ return None
428
+
429
+ self.__initialize_ray_cluster__(**InitInstructions)
430
+
431
+ def shutdown(self)->NoReturn:
432
+ """Shutdown the Ray cluster.
433
+
434
+ Returns:
435
+ NoReturn: No Return.
436
+ """
437
+ self.__shutdown__()
438
+ def reset(self, **kwargs)->NoReturn:
439
+ """Resets RuntimeData and RuntimeData reference. Restores RuntimeMetadata defaults.
440
+
441
+ Returns:
442
+ NoReturn: No Return.
443
+ """
444
+ self.__reset__(**kwargs)
445
+ def reboot(self, **kwargs)->NoReturn:
446
+ """Reboot the MultiCoreExecutionTool object. Can be provided with new instance parameters. See instance attributes for more information.
447
+
448
+ Returns:
449
+ NoReturn: No Return.
450
+ """
451
+ self.__reboot__(**kwargs)
452
+
453
+ #%% Runtime Data Control
454
+ def update_data(self, RuntimeData:Dict[Any,Dict[Any,Any]])->NoReturn:
455
+ """Update the RuntimeData with the provided data.
456
+
457
+ Args:
458
+ RuntimeData (Dict[Any,Dict[Any,Any]]): Structured data to be processed by the methods.
459
+
460
+ Returns:
461
+ NoReturn: No Return.
462
+ """
463
+ self.__update_data__(RuntimeData)
464
+
465
+ def update_metadata(self, **kwargs)->NoReturn:
466
+ self.RuntimeMetadata.update(kwargs)
467
+
468
+ #%% Runtime Handling Backend
469
+ def __initialize_metadata__(self,**kwargs)->NoReturn:
470
+ """Initializes the metadata for the MultiCoreExecutionTool class. Contains default values and will overwrite with given values.
471
+
472
+ Returns:
473
+ NoReturn: No Return
474
+ """
475
+ ## Default Metadata
476
+ self.RuntimeMetadata = {'instance_metadata':{'num_cpus': 1,
477
+ 'num_gpus': 0,
478
+ 'address': None,
479
+ 'ignore_reinit_error': True},
480
+ 'task_metadata': {'num_cpus': 1,
481
+ 'num_gpus': 0,
482
+ 'num_returns': None},
483
+ }
484
+ # update metadata with given values
485
+ self.RuntimeMetadata.update(kwargs)
486
+
487
+
488
+ def __offload_on_init__(self, RuntimeData:Dict[Any,Dict[Any,Any]])->NoReturn:
489
+ """Offload RuntimeData items to ray cluster on initialization if RuntimeData is provided.
490
+
491
+ Args:
492
+ RuntimeData (Dict[Any,Dict[Any,Any]]): Structured data to be processed by the methods.
493
+
494
+ Returns:
495
+ NoReturn: No Return
496
+ """
497
+ ## This has to be called AFTER the ray is initialized
498
+ # otherwise, a new ray object will be created and the object references will be unreachable from within the main ray object.
499
+
500
+ if RuntimeData is None:
501
+ print('No Runtime Data provided. Use the "update()" method to update the Runtime Data prior to running methods.')
502
+ return None
503
+
504
+ ## Set RuntimeData
505
+ self.RuntimeData = RuntimeData if RuntimeData is not None else None
506
+ self.RuntimeData_ref = self.__offload_data__() if RuntimeData is not None else None
507
+
508
+ def __initialize_ray_cluster__(self)->NoReturn:
509
+ """Initialize the Ray cluster using the parameters found in sel.RuntimeMetadata['instance_metadata']".
510
+ See https://docs.ray.io/en/latest/ray-core/api/doc/ray.init.html for more information.
511
+
512
+ Returns:
513
+ NoReturn: No Return
514
+ """
515
+
516
+ if self.DEBUG:
517
+ print('Setting up Ray...')
518
+
519
+ # shutdown any stray ray instances
520
+ ray.shutdown()
521
+
522
+ # ray init
523
+ cluster_context = ray.init(**self.RuntimeMetadata['instance_metadata'])
524
+ self.DashboardURL = f"http://{cluster_context.dashboard_url}/"
525
+
526
+ # dashboard
527
+ if self.LaunchDashboard:
528
+ try:
529
+ webbrowser.get('windows-default').open(self.DashboardURL,
530
+ autoraise = True,
531
+ new = 2)
532
+ except Exception as e:
533
+ print(f'Error: {e}')
534
+
535
+ if self.DEBUG:
536
+ print('Ray setup complete...')
537
+ print(f'Ray Dashboard: {self.DashboardURL}')
538
+
539
+ # set the runtime context
540
+ self.runimte_context = cluster_context
541
+
542
+ def __shutdown__(self)->bool:
543
+ """Shutdown the Ray cluster.
544
+
545
+ Returns:
546
+ bool: True if the shutdown was successful.
547
+ """
548
+ if self.DEBUG:
549
+ print('Shutting down Ray...')
550
+ try:
551
+ ray.shutdown()
552
+ return True
553
+ except Exception as e:
554
+ print(f'Error: {e}')
555
+ return False
556
+
557
+ def __reset__(self)->NoReturn:
558
+ """Resets RuntimeData and RuntimeData reference. Restores RuntimeMetadata defaults.
559
+
560
+ Returns:
561
+ NoReturn: No Return
562
+ """
563
+ self.RuntimeData_ref = None
564
+ self.RuntimeData = None
565
+ self.__initialize_metadata__()
566
+
567
+ def __reboot__(self, **kwargs)->NoReturn:
568
+ """Reboots the MultiCoreExecutionTool object. Can be provided with new instance parameters. See https://docs.ray.io/en/latest/ray-core/api/doc/ray.init.html for more information.
569
+
570
+ Returns:
571
+ NoReturn: _description_
572
+ """
573
+ InitInstructions = self.RuntimeMetadata['instance_metadata'] | kwargs
574
+ try:
575
+ self.__shutdown__()
576
+ self.__initialize_ray_cluster__(**InitInstructions)
577
+ except Exception as e:
578
+ print(f'Error: {e}')
579
+
580
+ #%% Runtime Data Handling
581
+ def __setup_schedule__(self)->List[Any]:
582
+ """Bundle the RuntimeData keys into a list for scheduling.
583
+
584
+ Returns:
585
+ List[Any]: List of keys referring to RuntimeData values to be processed using the provided method.
586
+ """
587
+
588
+ self.RuntimeResults = {k:{'result':None, 'status':'pending'} for k in self.RuntimeData.keys()}
589
+ return [k for k,v in self.RuntimeResults.items() if v['status'] == 'pending']
590
+
591
+ def __update_data__(self, RuntimeData:Dict[Any,Dict[Any,Any]])->NoReturn:
592
+ """Update the RuntimeData with the provided data and offload the data to the ray cluster.
593
+
594
+ Args:
595
+ RuntimeData (Dict[int,Dict[str,Any]]): Structured data to be processed by the methods.
596
+
597
+ Returns:
598
+ NoReturn: No Return
599
+ """
600
+ self.RuntimeData = RuntimeData
601
+ self.RuntimeData_ref = self.__offload_data__()
602
+ self.__setup_schedule__()
603
+
604
+ def __offload_data__(self)->Dict[int,ray.ObjectRef]:
605
+ """Offload the RuntimeData to the ray cluster.
606
+
607
+ Returns:
608
+ Dict[int,ray.ObjectRef]: Dictionary of keys and ray object references.
609
+ """
610
+ if self.DEBUG:
611
+ print('Offloading data to Ray...')
612
+ return {k:ray.put(v) for k,v in self.RuntimeData.items()}
613
+
614
+ #%% Helper
615
+ def is_ray_compatible(self, func:Callable)->bool:
616
+ """Check if the provided function is ray compatible.
617
+
618
+ Args:
619
+ func (Callable): Provided function.
620
+
621
+ Returns:
622
+ bool: True if the function is ray compatible. False otherwise.
623
+ """
624
+ if isinstance(func, ray.remote_function.RemoteFunction):
625
+ return True
626
+ return False
627
+
628
+ def is_initalized(self)->bool:
629
+ """Check of the Ray cluster is initialized.
630
+
631
+ Returns:
632
+ bool: True if the Ray cluster is initialized. False otherwise.
633
+ """
634
+ if self.DEBUG:
635
+ print('Checking Ray Status...')
636
+ return ray.is_initialized()
637
+
638
+ def get_results(self)->Dict[Any,Dict[str,Any]]:
639
+ """Returns RuntimeResults.
640
+
641
+ Returns:
642
+ Dict[Any,Dict[Any,Any]]: Structured data containing the results of the execution.
643
+ """
644
+ if self.DEBUG:
645
+ print('Fetching Results...')
646
+
647
+ if self.RuntimeResults is None:
648
+ print('No results found. Use the "run()" method to get results.')
649
+ return None
650
+ return self.RuntimeResults