code: def nonuniform_mutation(random, candidate, args):
      
      bounder = args['_ec'].bounder
      num_gens = args['_ec'].num_generations
      max_gens = args['max_generations']
      strength = args.setdefault('mutation_strength', 1)
      exponent = (1.0 - num_gens / float(max_gens)) ** strength
      mutant = copy.copy(candidate)
      for i, (c, lo, hi) in enumerate(zip(candidate, bounder.lower_bound, bounder.upper_bound)):
          if random.random() <= 0.5:
              new_value = c + (hi - c) * (1.0 - random.random() ** exponent)
          else:
              new_value = c - (c - lo) * (1.0 - random.random() ** exponent)
          mutant[i] = new_value
      return mutant
docs: Return the mutants produced by nonuniform mutation on the candidates.
  
      The function performs nonuniform mutation as specified in
      (Michalewicz, "Genetic Algorithms + Data Structures = Evolution
      Programs," Springer, 1996). This function also makes use of the 
      bounder function as specified in the EC's ``evolve`` method.
      
      .. note::
      
         This function **requires** that *max_generations* be specified in 
         the *args* dictionary. Therefore, it is best to use this operator 
         in conjunction with the ``generation_termination`` terminator. 
  
      .. Arguments:
         random -- the random number generator object
         candidate -- the candidate solution
         args -- a dictionary of keyword arguments
  
      Required keyword arguments in args:
      
      - *max_generations* -- the maximum number of generations for which
        evolution should take place
      
      Optional keyword arguments in args:
      
      - *mutation_strength* -- the strength of the mutation, where higher
        values correspond to greater variation (default 1)