Recursive structure of MOS scales: Difference between revisions

Ganaram inukshuk (talk | contribs)
Ganaram inukshuk (talk | contribs)
Finding a generator: Better worded explanation of output, clarified that the first half is the generator and the second half its octave complement; code comments
Line 207: Line 207:


=== Python code ===
=== Python code ===
The code shown here is a Python function that implements the algorithm described above.<!-- TODO: Add comments to code --><syntaxhighlight lang="python3" line="1">
The code shown here is a Python function that implements the algorithm described above.<syntaxhighlight lang="python3" line="1">
def CalculateMos(x, y):
def CalculateMos(x, y):


     mos_string = ""
     mos_string = ""


    # Base cases
     if (x == 1 or y == 1):
     if (x == 1 or y == 1):
         if (x == 1 and y == 1):
         if (x == 1 and y == 1):
Line 222: Line 223:
             mos_string = "unable to find mos"
             mos_string = "unable to find mos"
      
      
    # Recursive case
     else:
     else:
          
          
        # Fing GCF
         k = min(x, y)
         k = min(x, y)
         while (k > 0):
         while (k > 0):
Line 230: Line 233:
             k -= 1
             k -= 1


        # If mos is multi-period
         if (k != 1):
         if (k != 1):
             mos_string = CalculateMos(x // k, y // k) * k
             mos_string = CalculateMos(x // k, y // k) * k
           
        # If mos is single-period
         elif (k == 1):
         elif (k == 1):
             m1 = min(x, y)
             m1 = min(x, y)
Line 238: Line 244:
             w = m1 - z
             w = m1 - z


            # Recursive call
             prescale = CalculateMos(z, w)
             prescale = CalculateMos(z, w)
 
           
            # Revesal needed in some mosses
             if (x < y):
             if (x < y):
                 prescale = prescale[::-1]
                 prescale = prescale[::-1]


            # Building up production rules
             l_rule = ""
             l_rule = ""
             s_rule = ""
             s_rule = ""
Line 252: Line 261:
                 s_rule = "L" + "s" * math.floor(m2 / m1)
                 s_rule = "L" + "s" * math.floor(m2 / m1)


            # Apply rules
             for step in prescale:
             for step in prescale:
                 if step == "L":
                 if step == "L":
Line 277: Line 287:


== Finding a generator ==
== Finding a generator ==
The recursive structure also allows a recursive algorithm to find the generator. Note that every mos actually has two generators: its bright and dark generator. Whichever generator is used depends on the situation, so the algorithm, which is a modification of the mos-finding algorithm from last section, finds both. The reason why this works is that the reduction both ''preserves'' and ''reflects'' the generator; see the ''proofs'' section.
The recursive structure also allows a recursive algorithm to find the generator, as a substring of the mos pattern. The reason why this works is that the reduction both ''preserves'' and ''reflects'' the generator; see the ''proofs'' section.


=== General algorithm ===
=== General algorithm ===
This is the general algorithm for finding a moment of symmetry scale's bright and dark generators for a mos xL ys as two (unequal) halves of the mos pattern xL ys. This algorithm will produce that scale's generators without any prior knowledge of how the scale's steps are ordered. This algorithm is recursive. Let x be the number of L's and y the number of s's.  
This is the general algorithm for finding a moment of symmetry scale's generator and its [[octave complement]] for a mos xL ys as two, not-necessarily-equal halves of the mos pattern xL ys, or if multi-period, two halves of the mos's period. This algorithm will produce that scale's generators without any prior knowledge of how the scale's steps are ordered. This algorithm is recursive. Let x be the number of L's and y the number of s's.  


# If either x or y is equal to 1 (base cases):  
# If either x or y is equal to 1 (base cases):  
## If both x and y are equal to 1, then the bright generator is "L" and the dark generator is "s".
## If both x and y are equal to 1, then the generator is "L" and its complement is "s".
## If only x is equal to 1, then the bright generator is "L" followed by y-1 s's, and the dark generator is "s".
## If only x is equal to 1, then the generator is "L" followed by y-1 s's, and the complement is "s".
## If only y is equal to 1, then the bright generator is "L" and the dark generator is x-1 L's followed by "s".
## If only y is equal to 1, then the generator is "L" and the complement is x-1 L's followed by "s".
# If neither x nor y is equal to 1 (recursive cases):
# If neither x nor y is equal to 1 (recursive cases):
## Let k be the greatest common factor of x and y.
## Let k be the greatest common factor of x and y.
Line 292: Line 302:
### Let m1 = min(x, y) and m2 = max(x, y).
### Let m1 = min(x, y) and m2 = max(x, y).
### Let z = m2 mod m1 and w = m1 - z.
### Let z = m2 mod m1 and w = m1 - z.
### Let pregen1 and pregen2 be the bright and dark generators for the mos zL ws. Recursively call this algorithm to find the generators for zL ws; the final scale's generators will be based on this.
### Let gen be the scale's generator and comp be the generator's octave complement. for the mos zL ws. Recursively call this algorithm to find the generators for zL ws; the final scale's generators will be based on this.
### If x < y, reverse the order of characters in pregen1 and pregen2, then swap pregen1 and pregen2. This is only needed if there are more L's than s's in the scale.
### If x < y, reverse the order of characters in gen and comp, then swap gen and comp. This is only needed if there are more L's than s's in the scale.
### To produce the scale's generators, the L's and s's of the two generators must be replaced with substrings consisting of L's and s's. Let u = ceil(m2/m1) and v = floor(m2/m1).
### To produce the scale's generators, the L's and s's of the two generators must be replaced with substrings consisting of L's and s's. Let u = ceil(m2/m1) and v = floor(m2/m1).
#### If x > y, every instance of an L in both generators is replaced with one L and u s's, and every s replaced with one L and v s's. This produces the final scale's generators, where pregen1 is the bright generator and pregen2 is the dark generator.
#### If x > y, every instance of an L in both generators is replaced with one L and u s's, and every s replaced with one L and v s's. This produces the final scale's generators.
#### If y > x, every instance of an L in both generators is replaced with u L's and one s, and every s replaced with v L's and one s. This produces the final scale's generators, where pregen1 is the bright generator and pregen2 is the dark generator.
#### If y > x, every instance of an L in both generators is replaced with u L's and one s, and every s replaced with v L's and one s. This produces the final scale's generators.
Note that one of the mos properties is that generic interval classes have two specific sizes, and the generators produced this way are actually in their large interval sizes: the bright generator is a perfect bright generator, and the dark generator is an augmented dark generator. To produce the generators in their small form, that is a diminished bright generator and perfect dark generator, replace one L in either generator with an s.
 
==== Notes on output ====
The algorithm described here returns what is known as the chroma-positive generator, or bright generator, and is usually what is meant by ''the'' generator of a scale. The octave-complement returned this way is the scale's chroma-negative generator, or dark generator. More specifically, the bright generator returned is in its perfect form, and the dark generator is in its augmented form. If the perfect dark generator is needed instead, a quick way to produce it is to replace one L in the dark generator with an s.


If the number of steps in the generators is needed, simply count the total number of L's and s's in each generator.
If the number of steps in the generators is needed, simply count the total number of L's and s's in each generator.


=== Python code ===
=== Python code ===
The code shown here is a Python function that implements the algorithm described above.<!-- TODO: Add comments to code --><syntaxhighlight lang="python3" line="1">
The code shown here is a Python function that implements the algorithm described above.<syntaxhighlight lang="python3" line="1">
def CalculateMosGenerators(x, y):
def CalculateMosGenerators(x, y):


    # Left is (bright) generator, right is octave-complement (dark generator)
     mos_string_left = ""
     mos_string_left = ""
     mos_string_right = ""
     mos_string_right = ""


    # Base cases
     if (x == 1 or y == 1):
     if (x == 1 or y == 1):
         if (x == 1 and y == 1):
         if (x == 1 and y == 1):
Line 322: Line 336:
             mos_string_right = "unable to find generator"
             mos_string_right = "unable to find generator"
      
      
    # Recursive case
     else:
     else:
          
          
        # Find GCF
         k = min(x, y)
         k = min(x, y)
         while (k > 0):
         while (k > 0):
Line 330: Line 346:
             k -= 1
             k -= 1


        # If mos is multi-period
         if (k != 1):
         if (k != 1):
             mos_string_left, mos_string_right = CalculateMosGenerators(x // k, y // k)
             mos_string_left, mos_string_right = CalculateMosGenerators(x // k, y // k)
           
        # If mos is single-period
         elif (k == 1):
         elif (k == 1):
             m1 = min(x, y)
             m1 = min(x, y)
Line 338: Line 357:
             w = m1 - z
             w = m1 - z


            # Recursive call
             prescale_left, prescale_right = CalculateMosGenerators(z, w)
             prescale_left, prescale_right = CalculateMosGenerators(z, w)


            # Reversal step, needed in some mosses
             if (x < y):
             if (x < y):
                 prescale_left = prescale_left[::-1]
                 prescale_left = prescale_left[::-1]
Line 348: Line 369:
                 prescale_right = temp
                 prescale_right = temp


            # Build up production rules
             l_rule = ""
             l_rule = ""
             s_rule = ""
             s_rule = ""
Line 357: Line 379:
                 s_rule = "L" + "s" * math.floor(m2 / m1)
                 s_rule = "L" + "s" * math.floor(m2 / m1)


            # Apply rules
             for step in prescale_left:
             for step in prescale_left:
                 if step == "L":
                 if step == "L":
Line 362: Line 385:
                 elif step == "s":
                 elif step == "s":
                     mos_string_left += s_rule
                     mos_string_left += s_rule
                   
             for step in prescale_right:
             for step in prescale_right:
                 if step == "L":
                 if step == "L":