docs: def parallel_evaluation_pp(candidates, args):
      """Evaluate the candidates in parallel using Parallel Python.
  
      This function allows parallel evaluation of candidate solutions.
      It uses the `Parallel Python <http://www.parallelpython.com>`_  (pp)
      library to accomplish the parallelization. This library must already 
      be installed in order to use this function. The function assigns the 
      evaluation of each candidate to its own job, all of which are then 
      distributed to the available processing units.
      
      .. note::
      
         All arguments to the evaluation function must be pickleable.
         Those that are not will not be sent through the ``args`` variable
         and will be unavailable to your function.
         
      .. Arguments:
         candidates -- the candidate solutions
         args -- a dictionary of keyword arguments
  
      Required keyword arguments in args:
      
      - *pp_evaluator* -- actual evaluation function to be used (This function
        should have the same signature as any other inspyred evaluation function.)
  
      Optional keyword arguments in args:
      
      - *pp_dependencies* -- tuple of functional dependencies of the serial 
        evaluator (default ())
      - *pp_modules* -- tuple of modules that must be imported for the 
        functional dependencies (default ())
      - *pp_servers* -- tuple of servers (on a cluster) that will be used 
        for parallel processing (default ("*",))
      - *pp_secret* -- string representing the secret key needed to authenticate
        on a worker node (default "inspyred")
      - *pp_nprocs* -- integer representing the number of worker processes to
        start on the local machine (default "autodetect", which sets it to the
        number of processors in the system)
        
      For more information about these arguments, please consult the
      documentation for `Parallel Python <http://www.parallelpython.com>`_.
      
      """
      import pp
      logger = args['_ec'].logger
      
      try:
          evaluator = args['pp_evaluator']
      except KeyError:
          logger.error('parallel_evaluation_pp requires \'pp_evaluator\' be defined in the keyword arguments list')
          raise 
      secret_key = args.setdefault('pp_secret', 'inspyred')
      try:
          job_server = args['_pp_job_server']
      except KeyError:
          pp_servers = args.get('pp_servers', ("*",))
          pp_nprocs = args.get('pp_nprocs', 'autodetect')
          job_server = pp.Server(ncpus=pp_nprocs, ppservers=pp_servers, secret=secret_key)
          args['_pp_job_server'] = job_server
      pp_depends = args.setdefault('pp_dependencies', ())
      pp_modules = args.setdefault('pp_modules', ())
          
      pickled_args = {}
      for key in args:
          try:
              pickle.dumps(args[key])
              pickled_args[key] = args[key]
          except (TypeError, pickle.PickleError, pickle.PicklingError):
              logger.debug('unable to pickle args parameter {0} in parallel_evaluation_pp'.format(key))
              pass
              
      func_template = pp.Template(job_server, evaluator, pp_depends, pp_modules)
      jobs = [func_template.submit([c], pickled_args) for c in candidates]
      
      fitness = []
      for i, job in enumerate(jobs):
          r = job()
          try:
              fitness.append(r[0])
          except TypeError:
              logger.warning('parallel_evaluation_pp generated an invalid fitness for candidate {0}'.format(candidates[i]))
              fitness.append(None)
      return fitness