def halt(population, generation_count): """ Given a population of candidate solutions and generation count (the number of epochs the algorithm has run) will return a boolean to indicate if an acceptable solution has been found within the referenced population. """ fittest = population[0].chromosome suspensions = 0 for i in range(1, len(fittest) - 2): # Check for a suspension. if is_suspension(fittest, i, cantus_firmus): suspensions += 1 return (population[0].fitness >= MAX_REWARD + suspensions or generation_count > DEFAULT_MAX_GENERATION)
def fitness_function(genome): """ Given a candidate solution will return its fitness score assuming the cantus_firmus in this closure. Caches the fitness score in the genome. """ # Save some time! if genome.fitness is not None: return genome.fitness # The fitness score to be returned. fitness_score = 0.0 # Counts the number of repeated notes in the contrapunctus. repeats = 0 # Counts consecutive parallel thirds. thirds = 0 # Counts consecutive parallel sixths. sixths = 0 # Counts the amount of parallel motion. parallel_motion = 0 # Counts the number of jumps in the melodic contour. jump_contour = 0 contrapunctus = genome.chromosome # Make sure the solution starts correctly (at a 5th or octave). first_interval = contrapunctus[0] - cantus_firmus[0] if first_interval == 7 or first_interval == 4: fitness_score += REWARD_FIRST else: fitness_score -= PUNISH_FIRST # Make sure the solution finishes correctly (at an octave). if contrapunctus[-1] - cantus_firmus[-1] == 7: fitness_score += REWARD_LAST else: fitness_score -= PUNISH_LAST # Ensure the penultimate note is step wise onto the final note. if abs(contrapunctus[-1] - contrapunctus[-2]) == 1: fitness_score += REWARD_LAST_STEP else: fitness_score -= PUNISH_LAST_STEP # Reward contrary motion onto the final note. cantus_firmus_motion = cantus_firmus[-1] - cantus_firmus[-2] contrapunctus_motion = contrapunctus[-1] - contrapunctus[-2] if ((cantus_firmus_motion < 0 and contrapunctus_motion > 0) or (cantus_firmus_motion > 0 and contrapunctus_motion < 0)): fitness_score += REWARD_LAST_MOTION else: fitness_score -= PUNISH_LAST_MOTION # Make sure the penultimate note isn't a repeated note. penultimate_preparation = abs(contrapunctus[-2] - contrapunctus[-3]) if penultimate_preparation == 0: fitness_score -= PUNISH_REPEATED_PENULTIMATE else: # Make sure the movement to the penultimate note isn't from too # far away (not greater than a third). if penultimate_preparation < 2: fitness_score += REWARD_PENULTIMATE_PREPARATION else: fitness_score -= PUNISH_PENULTIMATE_PREPARATION # Check the fitness of the body of the solution. solution = zip(contrapunctus, cantus_firmus) last_notes = solution.pop() last_interval = last_notes[0] - last_notes[1] for i in range(1, len(solution) - 2): current_notes = solution[i] contrapunctus_note, cantus_firmus_note = current_notes current_interval = contrapunctus_note - cantus_firmus_note # Punish parallel fifths or octaves. if ((current_interval == 4 or current_interval == 7) and (last_interval == 4 or last_interval == 7)): fitness_score -= PUNISH_PARALLEL_FIFTHS_OCTAVES # Check if the melody is a repeating note. if contrapunctus_note == last_notes[0]: repeats += 1 # Check for a suspension. if is_suspension(contrapunctus, i, cantus_firmus): fitness_score += REWARD_SUSPENSION last_notes = current_notes last_interval = current_interval # Punish too many (> 1/3) repeated notes. if repeats > repeat_threshold: fitness_score -= PUNISH_REPEATS genome.fitness = fitness_score return fitness_score