"""Contains a parallel runner implementation, with support
for various backends

"""

from microscopes.common import validator
from microscopes.common.rng import rng
import multiprocessing as mp


def _mp_work(args):
    runner, niters, seed = args
    prng = rng(seed)
    runner.run(r=prng, niters=niters)
    return runner


class runner(object):

    def __init__(self, runners, backend='multiprocessing', **kwargs):
        self._runners = runners
        if backend not in ('multiprocessing',):
            raise ValueError("invalid backend: {}".format(backend))
        self._backend = backend
        if backend == 'multiprocessing':
            validator.validate_kwargs(kwargs, ('processes',))
            if 'processes' not in kwargs:
                kwargs['processes'] = mp.cpu_count()
            validator.validate_positive(kwargs['processes'], 'processes')
            self._processes = kwargs['processes']
        else:
            assert False, 'should not be reached'

    def run(self, r, niters=10000):
        """Run each runner for `niters`, using the backend for parallelism

        Parameters
        ----------
        r : rng
        niters : int, optional

        """
        validator.validate_type(r, rng, param_name='r')
        validator.validate_positive(niters, param_name='niters')
        if self._backend == 'multiprocessing':
            pool = mp.Pool(processes=self._processes)
            args = [(runner, niters, r.next()) for runner in self._runners]
            # map_async() + get() allows us to workaround a bug where
            # control-C doesn't kill multiprocessing workers
            self._runners = pool.map_async(_mp_work, args).get(10000000)
            pool.close()
            pool.join()
        else:
            assert False, 'should not be reached'

    def get_latents(self):
        return [runner.get_latent() for runner in self._runners]

@@ -189,16 +189,113 @@
  as mp%0A%0A
+try:%0A    import multyvac%0A    _has_multyvac = True%0Aexcept ImportError:%0A    _has_multyvac = False%0A%0A
 %0Adef _mp
@@ -588,16 +588,28 @@
 essing',
+ 'multyvac',
 ):%0A     
@@ -1025,16 +1025,1309 @@
 esses'%5D%0A
+        elif backend == 'multyvac':%0A            if not _has_multyvac:%0A                raise ValueError(%22multyvac module not installed on machine%22)%0A            validator.validate_kwargs(kwargs, ('layer', 'core',))%0A            if 'layer' not in kwargs:%0A                msg = ('multyvac support requires setting up a layer.'%0A                       'see scripts in bin')%0A                raise ValueError(msg)%0A            self._layer = kwargs%5B'layer'%5D%0A            if (not multyvac.config.api_key or%0A                    not multyvac.config.api_secret_key):%0A                raise ValueError(%22multyvac is not authed%22)%0A            # XXX(stephentu): currently defaults to the good stuff%0A            self._core = kwargs.get('core', 'f2')%0A            self._env = %7B%7D%0A            jid = multyvac.shell_submit('echo $PATH', _layer=self._layer)%0A            path = multyvac.get(jid).get_result()%0A            # XXX(stephentu): fragile, and assumes you used the setup%0A            # multyvac scripts we provide%0A            self._env%5B'PATH'%5D = '%7B%7D:%7B%7D'.format(%0A                '/home/multyvac/miniconda/envs/build/bin', path)%0A            self._env%5B'CONDA_DEFAULT_ENV'%5D = 'build'%0A            # this is needed for multyvacinit.pybootstrap%0A            self._env%5B'PYTHONPATH'%5D = '/usr/local/lib/python2.7/dist-packages'%0A
         
@@ -3120,16 +3120,16 @@
 close()%0A
-
         
@@ -3144,16 +3144,737 @@
 .join()%0A
+        elif self._backend == 'multyvac':%0A            # XXX(stephentu): the only parallelism strategy thus far is every%0A            # runner gets a dedicated core (multicore=1) on a machine%0A            jids = %5B%5D%0A            for i, runner in enumerate(self._runners):%0A                args = (runner, niters, r.next())%0A                jids.append(%0A                    multyvac.submit(%0A                        _mp_work,%0A                        args,%0A                        _layer=self._layer,%0A                        _env=self._env,%0A                        _core=self._core,%0A                        _name='kernels-parallel-runner-%7B%7D'.format(i)))%0A            self._runners = %5Bmultyvac.get(jid).get_result() for jid in jids%5D%0A
         

add multyvac support