'A parallel set of function calls (using the specified devices).
Args:
fn: a function or a list of n functions.
*args: additional args.  Each arg should either be not a list, or a list
of length n.
**kwargs: additional keyword args.  Each arg should either be not a
list, or a list of length n.
Returns:
either a single list of length n (if fn does not return a tuple), or a
tuple of lists of length n (if fn returns a tuple).'

def __call__(self, fn, *args, **kwargs):

    if args:
        my_args = TransposeListOfLists([self._MaybeRepeat(arg) for arg in args])
    else:
        my_args = [[] for _ in xrange(self.n)]
    my_kwargs = [{} for _ in xrange(self.n)]
    for (k, v) in six.iteritems(kwargs):
        vals = self._MaybeRepeat(v)
        for i in xrange(self.n):
            my_kwargs[i][k] = vals[i]
    fns = self._MaybeRepeat(fn)
    outputs = []
    cache = {}
    for i in xrange(self.n):
        def DaisyChainGetter(getter, name, *args, **kwargs):
            'Get    a    variable    and    cache    in    a    daisy    chain.'
            device_var_key = (self._devices[i], name)
            if (device_var_key in cache):
                return cache[device_var_key]
            if (name in cache):
                v = tf.identity(cache[name])
            else:
                var = getter(name, *args, **kwargs)
                v = tf.identity(var._ref())
            cache[name] = v
            cache[device_var_key] = v
            return v
        def CachingGetter(getter, name, *args, **kwargs):
            v = getter(name, *args, **kwargs)
            key = (self._caching_devices[i], name)
            if (key in cache):
                return cache[key]
            with tf.device(self._caching_devices[i]):
                ret = tf.identity(v._ref())
            cache[key] = ret
            return ret
        if self._daisy_chain_variables:
            custom_getter = DaisyChainGetter
        elif self._caching_devices:
            custom_getter = CachingGetter
        else:
            custom_getter = None
        with tf.name_scope(('parallel_%d' % i)):
            with tf.variable_scope(tf.get_variable_scope(), reuse=(True if ((i > 0) and self._reuse) else None), caching_device=self._caching_devices[i], custom_getter=custom_getter):
                with tf.device(self._devices[i]):
                    outputs.append(fns[i](*my_args[i], **my_kwargs[i]))
    if isinstance(outputs[0], tuple):
        outputs = list(zip(*outputs))
        outputs = tuple([list(o) for o in outputs])
    return outputs
