def EasyHPC(backend:In('MP', 'MPI')|Function='MP',
            n_tasks:In('implicitly many', 'many', 'one', 'count')='one',#Count is special case of implicitly many where it is already known how to split jobs 
            n_results:In('many', 'one')='one',
            aux_output:Bool=True,  # Parellelize only first entry of n_results is tuple
            reduce:Function=None,
            split_job=NotPassed,
            parallel = True,#If false, use the wrapper functionality of EasyHPC but don't actually use multiprocessing
            method = None,
            pool = None
            ):
        '''
        :param n_tasks: How many tasks does the decorated function handle? 
        :param n_results: If the decorated function handles many tasks at once, are the results reduced (n_results = 'one') or not (as many results as tasks)?
        :param reduce: Function that reduces multiple outputs to a single output
        :param splitjob: Function that converts an input (to the decorated function) that represents one large job to two smaller jobs

        NOTE: don't turn this into a class, you'll run into strange pickling errors
        '''
        self = argparse.Namespace()
        direct_call =  (~String&Function).valid(backend)
        if direct_call:
            f = backend
            backend = 'MP'
        if backend == 'MPI': 
            self.processor = _MPI_processor
            self.finalizer = _MPI_finalizer
        if backend == 'MP':
            self.processor = _MP_processor
            self.finalizer = None
        self.info = argparse.Namespace()
        self.info.n_tasks = n_tasks
        self.info.n_results = n_results
        self.info.parallel = parallel
        self.info.reduce = reduce
        self.info.wrap_MPI = False
        self.info.aux_output = aux_output 
        self.info.method = method
        self.info.pool = pool or Pool()
        self.info.split_job = split_job
        if self.info.n_tasks == 'implicitly many':
            if self.info.n_results == 'many':
                raise ValueError('Do not know how to handle functions that handle implicitly many tasks and return multiple results')
            if NotPassed(self.info.split_job):
                raise ValueError('Functions handling implicitly many tasks must specify how to split a job using `split_job`')
        if direct_call:
            def _lam(*args,**kwargs):
                return _MultiProcessorWrapper_call(args,kwargs,f,self.processor,self.finalizer,self.info)
            return _lam
        return lambda f: _easy_hpc_call(f,self)
def EasyHPC(backend:In('MP', 'MPI')|Function='MP',
            n_tasks:In('implicitly many', 'many', 'one', 'count')='one',#Count is special case of implicitly many where it is already known how to split jobs 
            n_results:In('many', 'one')='one',
            aux_output:Bool=True,  # Parellelize only first entry of n_results is tuple
            reduce:Function=None,
            split_job=NotPassed,
            parallel = True,#If false, use the wrapper functionality of EasyHPC but don't actually use multiprocessing
            method = None,
            pool = None
            ):
        '''
        :param n_tasks: How many tasks does the decorated function handle? 
        :param n_results: If the decorated function handles many tasks at once, are the results reduced (n_results = 'one') or not (as many results as tasks)?
        :param reduce: Function that reduces multiple outputs to a single output
        :param splitjob: Function that converts an input (to the decorated function) that represents one large job to two smaller jobs

        NOTE: don't turn this into a class, you'll run into strange pickling errors
        '''
        self = argparse.Namespace()
        direct_call =  (~String&Function).valid(backend)
        if direct_call:
            f = backend
            backend = 'MP'
        if backend == 'MPI': 
            self.processor = _MPI_processor
            self.finalizer = _MPI_finalizer
        if backend == 'MP':
            self.processor = _MP_processor
            self.finalizer = None
        self.info = argparse.Namespace()
        self.info.n_tasks = n_tasks
        self.info.n_results = n_results
        self.info.parallel = parallel
        self.info.reduce = reduce
        self.info.wrap_MPI = False
        self.info.aux_output = aux_output 
        self.info.method = method
        self.info.pool = pool or Pool()
        self.info.split_job = split_job
        if self.info.n_tasks == 'implicitly many':
            if self.info.n_results == 'many':
                raise ValueError('Do not know how to handle functions that handle implicitly many tasks and return multiple results')
            if NotPassed(self.info.split_job):
                raise ValueError('Functions handling implicitly many tasks must specify how to split a job using `split_job`')
        if direct_call:
            def _lam(*args,**kwargs):
                return _MultiProcessorWrapper_call(args,kwargs,f,self.processor,self.finalizer,self.info)
            return _lam
        return lambda f: _easy_hpc_call(f,self)
def EasyHPC ( backend : In ( 'MP' , 'MPI' ) | Function = 'MP' , n_tasks : In ( 'implicitly many' , 'many' , 'one' , 'count' ) = 'one' , #Count is special case of implicitly many where it is already known how to split jobs  n_results : In ( 'many' , 'one' ) = 'one' , aux_output : Bool = True , # Parellelize only first entry of n_results is tuple reduce : Function = None , split_job = NotPassed , parallel = True , #If false, use the wrapper functionality of EasyHPC but don't actually use multiprocessing method = None , pool = None ) : self = argparse . Namespace ( ) direct_call = ( ~ String & Function ) . valid ( backend ) if direct_call : f = backend backend = 'MP' if backend == 'MPI' : self . processor = _MPI_processor self . finalizer = _MPI_finalizer if backend == 'MP' : self . processor = _MP_processor self . finalizer = None self . info = argparse . Namespace ( ) self . info . n_tasks = n_tasks self . info . n_results = n_results self . info . parallel = parallel self . info . reduce = reduce self . info . wrap_MPI = False self . info . aux_output = aux_output self . info . method = method self . info . pool = pool or Pool ( ) self . info . split_job = split_job if self . info . n_tasks == 'implicitly many' : if self . info . n_results == 'many' : raise ValueError ( 'Do not know how to handle functions that handle implicitly many tasks and return multiple results' ) if NotPassed ( self . info . split_job ) : raise ValueError ( 'Functions handling implicitly many tasks must specify how to split a job using `split_job`' ) if direct_call : def _lam ( * args , * * kwargs ) : return _MultiProcessorWrapper_call ( args , kwargs , f , self . processor , self . finalizer , self . info ) return _lam return lambda f : _easy_hpc_call ( f , self )
:param n_tasks: How many tasks does the decorated function handle? 
        :param n_results: If the decorated function handles many tasks at once, are the results reduced (n_results = 'one') or not (as many results as tasks)?
        :param reduce: Function that reduces multiple outputs to a single output
        :param splitjob: Function that converts an input (to the decorated function) that represents one large job to two smaller jobs

        NOTE: don't turn this into a class, you'll run into strange pickling errors
: param n_tasks : How many tasks does the decorated function handle? : param n_results : If the decorated function handles many tasks at once are the results reduced ( n_results = one ) or not ( as many results as tasks ) ? : param reduce : Function that reduces multiple outputs to a single output : param splitjob : Function that converts an input ( to the decorated function ) that represents one large job to two smaller jobs