Example #1
0
 def __init__(self):
     self.problem = constraint.Problem()
     self._halt = 0
     self.chords = TimeList()
     self.harmonies = TimeList()
     self.num_solutions = 200 # max nb solutions consider (if too big, then solver takes too long)
     # list solutions:
     #   solutions[i] -> ["<singer><time>", int pitchnum]
     #   will be sorted in the following way:
     #     [['s0',int],['s1',int],...,['sN',int],
     #      ['a0',int],['a1',int],...,['aN',int],
     #      ['t0',int],['t1',int],...,['tN',int],
     #      ['b0',int],['b1',int],...,['bN',int]]
     self.solutions = []
Example #2
0
 def __init__(self):
     self.problem = constraint.Problem()
     self._halt = 0
     self.chords = TimeList()
     self.harmonies = TimeList()
     self.num_solutions = 200  # max nb solutions consider (if too big, then solver takes too long)
     # list solutions:
     #   solutions[i] -> ["<singer><time>", int pitchnum]
     #   will be sorted in the following way:
     #     [['s0',int],['s1',int],...,['sN',int],
     #      ['a0',int],['a1',int],...,['aN',int],
     #      ['t0',int],['t1',int],...,['tN',int],
     #      ['b0',int],['b1',int],...,['bN',int]]
     self.solutions = []
Example #3
0
class HarmonySolver():
    """
  Class that UI uses. Sort of a wraper class for constraint.Problem().
  UI does this:
    As user adds chords/voices, the HarmonySolver's internal datastructs
    are updated. Then, when user clicks "solve", the HarmonySolver its
    chords and harmonies lists, adds the relevant vars/constraints to
    its constraint.Problem(), then runs the solver.

  This is basically a complete rewrite of the above code that creates a
  problem instance from the CLI.

  TODO: Refactor this class to only have this functionality:
      add_chord()
      add_note()
      solve()
          This calls init_problem() and solve().
    """
    def __init__(self):
        self.problem = constraint.Problem()
        self._halt = 0
        self.chords = TimeList()
        self.harmonies = TimeList()
        self.num_solutions = 200 # max nb solutions consider (if too big, then solver takes too long)
        # list solutions:
        #   solutions[i] -> ["<singer><time>", int pitchnum]
        #   will be sorted in the following way:
        #     [['s0',int],['s1',int],...,['sN',int],
        #      ['a0',int],['a1',int],...,['aN',int],
        #      ['t0',int],['t1',int],...,['tN',int],
        #      ['b0',int],['b1',int],...,['bN',int]]
        self.solutions = []

    # x = "S, A, T, B"
    # y = "S, A, T, B"
    # Order by "s3, s2, s1, a3, ..., t3, ..., b3, ..."
    def myComparator(self, x, y):
        x1 = self.__voiceToNum__(x)
        y1= self.__voiceToNum__(y)
        if x1 > y1: return 1
        if x1 == y1:
            x_num = int(x[1:])
            y_num = int(y[1:])
            return x_num.__cmp__(y_num)
        # x1 < y1
        return -1

    def halt(self):
        self._halt = 1

    # translates "S, A, T, B" to "0, 1, 2, 3"
    def __voiceToNum__(self, voice):
        lowerCase = voice.lower() # Just in case we get something like "S3" versus "s3"
        return {"s": 0,
                "a": 1,
                "t": 2,
                "b": 3}[lowerCase[0]] # Grab first letter

    # Returns True if the harmony at the specified time-step is Dominant (i.e a "V") or not.
    def isDominant(self, time):
        harmony = self.harmonies.get(time)
        return (harmony[0] == "V") or (harmony == DOMINANT)

    # In an attempt to prune the domain-space of each variable, I will do preprocessing to
    # decrease the domain, rather than enforcing it with constraints.
    def getSingerDomain(self, voice, chord):
        domain = []
        voice_range = {"s": soprano_range,
                       "a": alto_range,
                       "t": tenor_range,
                       "b": bass_range}[voice]
        chordTones__= chord.getChordTones_nums()
        for note in chordTones__:
            for note2 in voice_range:
                if (note2 % 12) == note:
                    domain.append(note2)
        return domain

    def createNewProblem(self):
        """ Reset chords/harmonies database """
        self.problem = constraint.Problem()
        self._halt = 0

    # If the user wishes to specify any voice, then he/she can do so here. Should overwrite any previous
    # specified notes (for the specified voice).
    # voice := Either "soprano", "alto", "tenor", "bass"
    # notes := a Timelist() of numbers that represents the EXACT note that the singer sings
    #          at each time step.
    def specify_voice(self, voice, notes):
        voice = self._convertToConstraintForm(voice)
        if notes.is_empty():  # We don't need to specify the voice, so just use the original chord domain.
            for t in self.chords.get_times():
                chord = self.chords[t]
                var = voice+str(t)
                if var in self.problem._variables:
                    self.problem.replaceVariable(var, self.getSingerDomain(voice, chord))
                else:
                    raise RuntimeError, "Error in HarmonySolver.specify_voice() - var wasn't in self.problem._variables, \
                                            , where var is: %s" % var
        else:                 # Restrict the variable's domain to the exact note specified
            for t in notes.get_times():
                if notes[t] != None:
                    var = voice+str(t)
                    if var in self.problem._variables:
                        self.problem.replaceVariable(var, (notes[t],))
                    else:
                        raise RuntimeError, "Error in HarmonySolver.specify_voice() - var wasn't in self.problem._variables, \
                                              , where var is: %s" % var
    # This method, used by the user to initialize the CSP, will
    # add variables to the CSP (4 variables for each voice: SATB) for the
    # various time steps.
    #  problem := CSP Problem
    #  chord   := Chord object, created by user
    #  time    := time step, i.e 0, 1, 2, 3, ...
    def addChord(self, chord, time):
        problem = self.problem
        self.chords.add(time, chord)  # Update our chord database
        # Now let's update our Problem() instance's variable list
        singers = ("s"+str(time), "a"+str(time), "t"+str(time), "b"+str(time))
        problem.addVariable(singers[0], self.getSingerDomain("s", chord))
        problem.addVariable(singers[1], self.getSingerDomain("a", chord))
        problem.addVariable(singers[2], self.getSingerDomain("t", chord))
        problem.addVariable(singers[3], self.getSingerDomain("b", chord))

        problem.addConstraint(SpecifyChordConstraint(chord) , tuple(singers))
        if chord.bassNote != None:
            problem.addConstraint(SetBassConstraint(chord), ["b"+str(time)])
        ### BIG QUESTION: Why can't I say:
        #problem.addConstraint(setBass(chord), list(singers[3]))
        # ??????????????????????? :/

    def removeChord(self, time):
        self.chords.remove(time)
        singers = ("s"+str(time), "a"+str(time), "t"+str(time), "b"+str(time))
        for var in singers:
            self.problem.removeVariable(var)

    def addHarmony(self, harmony, time):
        self.harmonies.add(time, harmony)
        self.chords[time].role = harmony

    def removeHarmony(self, time):
        self.harmonies.remove(time)

    def removeAll(self):
        times = self.chords.get_times()
        for t in times:
            self.removeChord(t)
            self.removeHarmony(t)
        if (not self.chords.is_empty()) or (not self.harmonies.is_empty()):
            raise RuntimeError, "Either self.chords or self.harmonies was not empty, despite calling HarmonySolver.removeAll()"
            exit(1)
        self.solutions = []

    def addHarmonyRules(self):
        problem = self.problem
        numTimeSteps = len(self.chords.get_times())
        for t in range(numTimeSteps):
            # Make sure that voices are at most an octave away from each other
            problem.addConstraint(SpacingConstraint(), ["s"+str(t), "a"+str(t)])
            problem.addConstraint(SpacingConstraint(), ["a"+str(t), "t"+str(t)])
            problem.addConstraint(SpacingConstraint(), ["t"+str(t), "b"+str(t)])
            # Make sure that voices don't cross each other
            problem.addConstraint(CrossoverConstraint(), ["s"+str(t), "a"+str(t), "t"+str(t), "b"+str(t)])
            if t < (numTimeSteps - 1):    # Mainly, if t != numTimeSteps
                # Check Leaps
                for singer in ("s", "a", "t", "b"):
                    singer2 = singer+str(t+1)
                    problem.addConstraint(LeapConstraint(), [singer+str(t), singer2])
                # Make sure that there are no temporal overlaps
                problem.addConstraint(TemporalOverlapConstraint(), \
                                   ["s"+str(t), "s"+str(t+1), "a"+str(t), "a"+str(t+1)])
                problem.addConstraint(TemporalOverlapConstraint(), \
                                   ["a"+str(t), "a"+str(t+1), "t"+str(t), "t"+str(t+1)])
                problem.addConstraint(TemporalOverlapConstraint(), \
                                   ["t"+str(t), "t"+str(t+1), "b"+str(t), "b"+str(t+1)])
                chord = self.chords.get(t)
                # Add parallel fifth/octave handling
                singer_array = []
                history = []
                for singer in ("s", "a", "t", "b"):
                    for singer2 in ("s", "a", "t", "b"):
                        if (singer != singer2) and ((singer, singer2) not in history):
                            singer_array.append((singer+str(t), singer+str(t+1), singer2+str(t), singer2+str(t+1)))
                            history.append((singer2, singer))
                for i in range(len(singer_array)):
                    problem.addConstraint(ParallelFifthConstraint(), singer_array[i])
                    problem.addConstraint(ParallelOctaveConstraint(), singer_array[i])
                # Add behavior for soprano/bass relationship (i.e no hidden 5th, hidden octave)
                ### Note: singer_array[2] contains the tuple ( <s0>, <s1>, <b0>, <b1> ), which is what we want
                problem.addConstraint(HiddenMotionOuterConstraint(), singer_array[2])
                # Add behavior for sevenths
                if chord.getSeventh__() != None:
                    for singer in ("s", "a", "t", "b"):
                        problem.addConstraint(SeventhConstraint(chord), [singer+str(t), singer+str(t+1)])
                # Add behavior for leading tones of dominant chords)
                if self.isDominant(t):
                    for singer in ("s", "a", "t", "b"):
                        problem.addConstraint(LeadingToneConstraint(chord), [singer+str(t), singer+str(t+1)])
                # Add behavior for diminished fifths of diminished chords
                if ("dim" in chord.modifiers) or ("dim7" in chord.modifiers):
                    for singer in ("s", "a", "t", "b"):
                        problem.addConstraint(DiminishedFifthConstraint(chord), [singer+str(t), singer+str(t+1)])

    """
    Returns n solutions, where n = core.config.num_solutions
    """
    def unhalt(self):
        self._halt = 0

    def isHalt(self):
        return self._halt == 1

    def solveProblem(self):
        self.unhalt()
        solutionIter = self.problem.getSolutionIter()
        numberSolutions = 0
        solutions_graded = []

        bestGradeSoFar = -1000000000

        for solution in solutionIter:
            if self.isHalt() or (numberSolutions == self.num_solutions):
                break

            if solution != None:
                orderedSol = list()
                numberSolutions += 1
                for key in solution.keys():
                    orderedSol.append([key, solution[key]])
                orderedSol.sort(lambda x, y: self.myComparator(x[0], y[0]))
                sol_grade = grade(solution, self.chords, self.harmonies)
    #      if (bestGradeSoFar > sol_grade) and (core.config.debugging_options["old_constraint"] == 0):
    #        print "Uh oh, there seems to be a problem in my understanding of what grade_debug() does."
    #        print "=== bestGradeSoFar: ", bestGradeSoFar, "sol_grade_tuple[0]: ", sol_grade
    #        print "Apparently, constraint.py didn't return a 'bestsofar' solution. Hm."
                bestGradeSoFar = sol_grade
                solutions_graded.append( (sol_grade, orderedSol) )
            else:
                print "No solution reported."
                return None
        print "Number of solutions: ", numberSolutions
        if numberSolutions == 0:
            print "No solution reported."
            return None
        # Now to choose the solution from solutions_graded with the highest grade
        solutions_graded.sort(lambda x, y : self._solution_cmp(x, y))
        best_sol = solutions_graded[0]
        worst_sol = solutions_graded[len(solutions_graded) - 1]
        #print "Best solution: ", best_sol
        #print "Worst solution: ", worst_sol
        self.solutions = solutions_graded
        return solutions_graded


    """
    Returns n solutions, where n = core.config.num_solutions. NOTE: Not used at the moment....
    """
    def solveProblem_iter(self):
        self.unhalt()
        solutionIter = self.problem.getSolutionIter()
        numberSolutions = 0
        solutions_graded = []

        bestGradeSoFar = -1000000000

        for solution in solutionIter:
            if numberSolutions == self.num_solutions:
                break
            if solution != None:
                orderedSol = list()
                numberSolutions += 1
                for key in solution.keys():
                    orderedSol.append([key, solution[key]])
                orderedSol.sort(lambda x, y: self.myComparator(x[0], y[0]))
                sol_grade = grade(solution, self.chords, self.harmonies)
                if (bestGradeSoFar > sol_grade) and (core.config.debugging_options["old_constraint"] == 0):
                    print "Uh oh, there seems to be a problem in my understanding of what grade_debug() does."
                    print "=== bestGradeSoFar: ", bestGradeSoFar, "sol_grade_tuple[0]: ", sol_grade
                    print "Apparently, constraint.py didn't return a 'bestsofar' solution. Hm."
                bestGradeSoFar = sol_grade
                solutions_graded.append( (sol_grade, orderedSol) )
            else:
                print "No solution reported."
                #return None
        print "Number of solutions: ", numberSolutions
        if numberSolutions == 0:
            print "No solution reported."
            #return None
        # Now to choose the solution from solutions_graded with the highest grade
        solutions_graded.sort(lambda x, y : self._solution_cmp(x, y))
        best_sol = solutions_graded[0]
        worst_sol = solutions_graded[len(solutions_graded) - 1]
        print "Best solution: ", best_sol
        print "Worst solution: ", worst_sol
        #converter.convertToAbjad(best_sol[1:][0])
        #converter.convertToAbjad(worst_sol[1:][0])
        self.solutions = solutions_graded
        for sol in solutions_graded:
            yield sol

    # x, y := tuple of the form: ( <grade>, <orderedSol list of lists> )
    def _solution_cmp(self, x, y):
        grade_0 = x[0]
        grade_1 = y[0]
        if grade_0 < grade_1: return 1
        if grade_0 > grade_1: return -1
        return 0

    # Converts (soprano,alto,tenor,bass) to (s,a,t,b), since constraint.py's variables are of the form:
    # s0, b4, etc...
    def _convertToConstraintForm(self, voice):
        if (type(voice) == int):
            voice = {0:"s",1:"a",2:"t",3:"b"}[voice]
        if voice not in ("s","a","t","b"):
            try:
                voice = {"soprano":"s" , "alto":"a" , "tenor":"t" , "bass":"b"}[voice]
            except ValueError:
                raise ValueError , "Error in HarmonySolver._convertToConstraintForm() , %s" % voice
        return voice
Example #4
0
class HarmonySolver():
    """
  Class that UI uses. Sort of a wraper class for constraint.Problem().
  UI does this:
    As user adds chords/voices, the HarmonySolver's internal datastructs
    are updated. Then, when user clicks "solve", the HarmonySolver its
    chords and harmonies lists, adds the relevant vars/constraints to
    its constraint.Problem(), then runs the solver.

  This is basically a complete rewrite of the above code that creates a
  problem instance from the CLI.

  TODO: Refactor this class to only have this functionality:
      add_chord()
      add_note()
      solve()
          This calls init_problem() and solve().
    """
    def __init__(self):
        self.problem = constraint.Problem()
        self._halt = 0
        self.chords = TimeList()
        self.harmonies = TimeList()
        self.num_solutions = 200  # max nb solutions consider (if too big, then solver takes too long)
        # list solutions:
        #   solutions[i] -> ["<singer><time>", int pitchnum]
        #   will be sorted in the following way:
        #     [['s0',int],['s1',int],...,['sN',int],
        #      ['a0',int],['a1',int],...,['aN',int],
        #      ['t0',int],['t1',int],...,['tN',int],
        #      ['b0',int],['b1',int],...,['bN',int]]
        self.solutions = []

    # x = "S, A, T, B"
    # y = "S, A, T, B"
    # Order by "s3, s2, s1, a3, ..., t3, ..., b3, ..."
    def myComparator(self, x, y):
        x1 = self.__voiceToNum__(x)
        y1 = self.__voiceToNum__(y)
        if x1 > y1: return 1
        if x1 == y1:
            x_num = int(x[1:])
            y_num = int(y[1:])
            return x_num.__cmp__(y_num)
        # x1 < y1
        return -1

    def halt(self):
        self._halt = 1

    # translates "S, A, T, B" to "0, 1, 2, 3"
    def __voiceToNum__(self, voice):
        lowerCase = voice.lower(
        )  # Just in case we get something like "S3" versus "s3"
        return {
            "s": 0,
            "a": 1,
            "t": 2,
            "b": 3
        }[lowerCase[0]]  # Grab first letter

    # Returns True if the harmony at the specified time-step is Dominant (i.e a "V") or not.
    def isDominant(self, time):
        harmony = self.harmonies.get(time)
        return (harmony[0] == "V") or (harmony == DOMINANT)

    # In an attempt to prune the domain-space of each variable, I will do preprocessing to
    # decrease the domain, rather than enforcing it with constraints.
    def getSingerDomain(self, voice, chord):
        domain = []
        voice_range = {
            "s": soprano_range,
            "a": alto_range,
            "t": tenor_range,
            "b": bass_range
        }[voice]
        chordTones__ = chord.getChordTones_nums()
        for note in chordTones__:
            for note2 in voice_range:
                if (note2 % 12) == note:
                    domain.append(note2)
        return domain

    def createNewProblem(self):
        """ Reset chords/harmonies database """
        self.problem = constraint.Problem()
        self._halt = 0

    # If the user wishes to specify any voice, then he/she can do so here. Should overwrite any previous
    # specified notes (for the specified voice).
    # voice := Either "soprano", "alto", "tenor", "bass"
    # notes := a Timelist() of numbers that represents the EXACT note that the singer sings
    #          at each time step.
    def specify_voice(self, voice, notes):
        voice = self._convertToConstraintForm(voice)
        if notes.is_empty(
        ):  # We don't need to specify the voice, so just use the original chord domain.
            for t in self.chords.get_times():
                chord = self.chords[t]
                var = voice + str(t)
                if var in self.problem._variables:
                    self.problem.replaceVariable(
                        var, self.getSingerDomain(voice, chord))
                else:
                    raise RuntimeError, "Error in HarmonySolver.specify_voice() - var wasn't in self.problem._variables, \
                                            , where var is: %s" % var
        else:  # Restrict the variable's domain to the exact note specified
            for t in notes.get_times():
                if notes[t] != None:
                    var = voice + str(t)
                    if var in self.problem._variables:
                        self.problem.replaceVariable(var, (notes[t], ))
                    else:
                        raise RuntimeError, "Error in HarmonySolver.specify_voice() - var wasn't in self.problem._variables, \
                                              , where var is: %s" % var

    # This method, used by the user to initialize the CSP, will
    # add variables to the CSP (4 variables for each voice: SATB) for the
    # various time steps.
    #  problem := CSP Problem
    #  chord   := Chord object, created by user
    #  time    := time step, i.e 0, 1, 2, 3, ...
    def addChord(self, chord, time):
        problem = self.problem
        self.chords.add(time, chord)  # Update our chord database
        # Now let's update our Problem() instance's variable list
        singers = ("s" + str(time), "a" + str(time), "t" + str(time),
                   "b" + str(time))
        problem.addVariable(singers[0], self.getSingerDomain("s", chord))
        problem.addVariable(singers[1], self.getSingerDomain("a", chord))
        problem.addVariable(singers[2], self.getSingerDomain("t", chord))
        problem.addVariable(singers[3], self.getSingerDomain("b", chord))

        problem.addConstraint(SpecifyChordConstraint(chord), tuple(singers))
        if chord.bassNote != None:
            problem.addConstraint(SetBassConstraint(chord), ["b" + str(time)])
        ### BIG QUESTION: Why can't I say:
        #problem.addConstraint(setBass(chord), list(singers[3]))
        # ??????????????????????? :/

    def removeChord(self, time):
        self.chords.remove(time)
        singers = ("s" + str(time), "a" + str(time), "t" + str(time),
                   "b" + str(time))
        for var in singers:
            self.problem.removeVariable(var)

    def addHarmony(self, harmony, time):
        self.harmonies.add(time, harmony)
        self.chords[time].role = harmony

    def removeHarmony(self, time):
        self.harmonies.remove(time)

    def removeAll(self):
        times = self.chords.get_times()
        for t in times:
            self.removeChord(t)
            self.removeHarmony(t)
        if (not self.chords.is_empty()) or (not self.harmonies.is_empty()):
            raise RuntimeError, "Either self.chords or self.harmonies was not empty, despite calling HarmonySolver.removeAll()"
            exit(1)
        self.solutions = []

    def addHarmonyRules(self):
        problem = self.problem
        numTimeSteps = len(self.chords.get_times())
        for t in range(numTimeSteps):
            # Make sure that voices are at most an octave away from each other
            problem.addConstraint(SpacingConstraint(),
                                  ["s" + str(t), "a" + str(t)])
            problem.addConstraint(SpacingConstraint(),
                                  ["a" + str(t), "t" + str(t)])
            problem.addConstraint(SpacingConstraint(),
                                  ["t" + str(t), "b" + str(t)])
            # Make sure that voices don't cross each other
            problem.addConstraint(
                CrossoverConstraint(),
                ["s" + str(t), "a" + str(t), "t" + str(t), "b" + str(t)])
            if t < (numTimeSteps - 1):  # Mainly, if t != numTimeSteps
                # Check Leaps
                for singer in ("s", "a", "t", "b"):
                    singer2 = singer + str(t + 1)
                    problem.addConstraint(LeapConstraint(),
                                          [singer + str(t), singer2])
                # Make sure that there are no temporal overlaps
                problem.addConstraint(TemporalOverlapConstraint(), \
                                   ["s"+str(t), "s"+str(t+1), "a"+str(t), "a"+str(t+1)])
                problem.addConstraint(TemporalOverlapConstraint(), \
                                   ["a"+str(t), "a"+str(t+1), "t"+str(t), "t"+str(t+1)])
                problem.addConstraint(TemporalOverlapConstraint(), \
                                   ["t"+str(t), "t"+str(t+1), "b"+str(t), "b"+str(t+1)])
                chord = self.chords.get(t)
                # Add parallel fifth/octave handling
                singer_array = []
                history = []
                for singer in ("s", "a", "t", "b"):
                    for singer2 in ("s", "a", "t", "b"):
                        if (singer != singer2) and ((singer, singer2)
                                                    not in history):
                            singer_array.append(
                                (singer + str(t), singer + str(t + 1),
                                 singer2 + str(t), singer2 + str(t + 1)))
                            history.append((singer2, singer))
                for i in range(len(singer_array)):
                    problem.addConstraint(ParallelFifthConstraint(),
                                          singer_array[i])
                    problem.addConstraint(ParallelOctaveConstraint(),
                                          singer_array[i])
                # Add behavior for soprano/bass relationship (i.e no hidden 5th, hidden octave)
                ### Note: singer_array[2] contains the tuple ( <s0>, <s1>, <b0>, <b1> ), which is what we want
                problem.addConstraint(HiddenMotionOuterConstraint(),
                                      singer_array[2])
                # Add behavior for sevenths
                if chord.getSeventh__() != None:
                    for singer in ("s", "a", "t", "b"):
                        problem.addConstraint(
                            SeventhConstraint(chord),
                            [singer + str(t), singer + str(t + 1)])
                # Add behavior for leading tones of dominant chords)
                if self.isDominant(t):
                    for singer in ("s", "a", "t", "b"):
                        problem.addConstraint(
                            LeadingToneConstraint(chord),
                            [singer + str(t), singer + str(t + 1)])
                # Add behavior for diminished fifths of diminished chords
                if ("dim" in chord.modifiers) or ("dim7" in chord.modifiers):
                    for singer in ("s", "a", "t", "b"):
                        problem.addConstraint(
                            DiminishedFifthConstraint(chord),
                            [singer + str(t), singer + str(t + 1)])

    """
    Returns n solutions, where n = core.config.num_solutions
    """

    def unhalt(self):
        self._halt = 0

    def isHalt(self):
        return self._halt == 1

    def solveProblem(self):
        self.unhalt()
        solutionIter = self.problem.getSolutionIter()
        numberSolutions = 0
        solutions_graded = []

        bestGradeSoFar = -1000000000

        for solution in solutionIter:
            if self.isHalt() or (numberSolutions == self.num_solutions):
                break

            if solution != None:
                orderedSol = list()
                numberSolutions += 1
                for key in solution.keys():
                    orderedSol.append([key, solution[key]])
                orderedSol.sort(lambda x, y: self.myComparator(x[0], y[0]))
                sol_grade = grade(solution, self.chords, self.harmonies)
                #      if (bestGradeSoFar > sol_grade) and (core.config.debugging_options["old_constraint"] == 0):
                #        print "Uh oh, there seems to be a problem in my understanding of what grade_debug() does."
                #        print "=== bestGradeSoFar: ", bestGradeSoFar, "sol_grade_tuple[0]: ", sol_grade
                #        print "Apparently, constraint.py didn't return a 'bestsofar' solution. Hm."
                bestGradeSoFar = sol_grade
                solutions_graded.append((sol_grade, orderedSol))
            else:
                print "No solution reported."
                return None
        print "Number of solutions: ", numberSolutions
        if numberSolutions == 0:
            print "No solution reported."
            return None
        # Now to choose the solution from solutions_graded with the highest grade
        solutions_graded.sort(lambda x, y: self._solution_cmp(x, y))
        best_sol = solutions_graded[0]
        worst_sol = solutions_graded[len(solutions_graded) - 1]
        #print "Best solution: ", best_sol
        #print "Worst solution: ", worst_sol
        self.solutions = solutions_graded
        return solutions_graded

    """
    Returns n solutions, where n = core.config.num_solutions. NOTE: Not used at the moment....
    """

    def solveProblem_iter(self):
        self.unhalt()
        solutionIter = self.problem.getSolutionIter()
        numberSolutions = 0
        solutions_graded = []

        bestGradeSoFar = -1000000000

        for solution in solutionIter:
            if numberSolutions == self.num_solutions:
                break
            if solution != None:
                orderedSol = list()
                numberSolutions += 1
                for key in solution.keys():
                    orderedSol.append([key, solution[key]])
                orderedSol.sort(lambda x, y: self.myComparator(x[0], y[0]))
                sol_grade = grade(solution, self.chords, self.harmonies)
                if (bestGradeSoFar > sol_grade) and (
                        core.config.debugging_options["old_constraint"] == 0):
                    print "Uh oh, there seems to be a problem in my understanding of what grade_debug() does."
                    print "=== bestGradeSoFar: ", bestGradeSoFar, "sol_grade_tuple[0]: ", sol_grade
                    print "Apparently, constraint.py didn't return a 'bestsofar' solution. Hm."
                bestGradeSoFar = sol_grade
                solutions_graded.append((sol_grade, orderedSol))
            else:
                print "No solution reported."
                #return None
        print "Number of solutions: ", numberSolutions
        if numberSolutions == 0:
            print "No solution reported."
            #return None
        # Now to choose the solution from solutions_graded with the highest grade
        solutions_graded.sort(lambda x, y: self._solution_cmp(x, y))
        best_sol = solutions_graded[0]
        worst_sol = solutions_graded[len(solutions_graded) - 1]
        print "Best solution: ", best_sol
        print "Worst solution: ", worst_sol
        #converter.convertToAbjad(best_sol[1:][0])
        #converter.convertToAbjad(worst_sol[1:][0])
        self.solutions = solutions_graded
        for sol in solutions_graded:
            yield sol

    # x, y := tuple of the form: ( <grade>, <orderedSol list of lists> )
    def _solution_cmp(self, x, y):
        grade_0 = x[0]
        grade_1 = y[0]
        if grade_0 < grade_1: return 1
        if grade_0 > grade_1: return -1
        return 0

    # Converts (soprano,alto,tenor,bass) to (s,a,t,b), since constraint.py's variables are of the form:
    # s0, b4, etc...
    def _convertToConstraintForm(self, voice):
        if (type(voice) == int):
            voice = {0: "s", 1: "a", 2: "t", 3: "b"}[voice]
        if voice not in ("s", "a", "t", "b"):
            try:
                voice = {
                    "soprano": "s",
                    "alto": "a",
                    "tenor": "t",
                    "bass": "b"
                }[voice]
            except ValueError:
                raise ValueError, "Error in HarmonySolver._convertToConstraintForm() , %s" % voice
        return voice