Recursive structure of MOS scales: Difference between revisions

Inthar (talk | contribs)
No edit summary
Ganaram inukshuk (talk | contribs)
Finding a generator: Fixed an error in one of the algorithms; replace some pseudocode with python code; will revisit for further rewriting (to newer, personal standards)
Line 201: Line 201:
### Let z = m2 mod m1 and w = m1 - z.
### Let z = m2 mod m1 and w = m1 - z.
### Let prescale be the mos string for zL ws. Recursively call this algorithm to find the scale for zL ws; the final scale will be based on this.
### Let prescale be the mos string for zL ws. Recursively call this algorithm to find the scale for zL ws; the final scale will be based on this.
### If x > y, reverse the order of characters in the prescale. This is only needed if there are more L's than s's in the final scale.
### If x < y, reverse the order of characters in the prescale. This is only needed if there are more L's than s's in the final scale.
### To produce the final scale, the L's and s's of the prescale 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 final scale, the L's and s's of the prescale 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 the prescale 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 in its brightest mode.
#### If x > y, every instance of an L in the prescale 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 in its brightest mode.
#### If y > x, every instance of an L in the prescale 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 in its brightest mode.
#### If y > x, every instance of an L in the prescale 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 in its brightest mode.


=== Pseudocode ===
=== Python code ===
This is the pseudocode for the algorithm described above. (work-in-progress)
The code shown here is a Python function that implements the algorithm described above.<!-- TODO: Add comments to code --><syntaxhighlight lang="python3" line="1">
Pseudocode
def CalculateMos(x, y):
 
    mos_string = ""
 
    if (x == 1 or y == 1):
        if (x == 1 and y == 1):
            mos_string = "Ls"
        elif (x == 1 and y > 1):
            mos_string = "L" + "s" * y
        elif (x > 1 and y == 1):
            mos_string = "L" * x + "s"
        else:
            mos_string = "unable to find mos"
   
    else:
       
        k = min(x, y)
        while (k > 0):
            if (x % k == 0 and y % k == 0):
                break
            k -= 1
 
        if (k != 1):
            mos_string = CalculateMos(x // k, y // k) * k
        elif (k == 1):
            m1 = min(x, y)
            m2 = max(x, y)
            z = m2 % m1
            w = m1 - z
 
            prescale = CalculateMos(z, w)
 
            if (x < y):
                prescale = prescale[::-1]
 
            l_rule = ""
            s_rule = ""
            if (x > y):
                l_rule = "L" * math.ceil(m2 / m1) + "s"
                s_rule = "L" * math.floor(m2 / m1) + "s"
            else:
                l_rule = "L" + "s" * math.ceil(m2 / m1)
                s_rule = "L" + "s" * math.floor(m2 / m1)
 
            for step in prescale:
                if step == "L":
                    mos_string += l_rule
                elif step == "s":
                    mos_string += s_rule
 
        else:
            mos_string = "unable to find mos"
 
    return mos_string
</syntaxhighlight>
   
   
ProduceMaximallyEvenScale(integer x : number of large steps, integer y : number of small steps) returns string : string encodes a MOS/MV2 scale represented in its brightest mode {
string final_scale = ""
// This is a separate function that returns the greatest common factor between x and y
// This should return 1 if x and y are coprime
integer common_factor = FindLargestCommonFactor(x, y)
// x and y have a common factor greater than 1, so it's a multi-period scale
if (common_factor > 1) {
string intermediate_scale = ProduceMaximallyEvenScale(x/common_factor, y/common_factor)
for (integer i = 0 to common_factor) {
final_scale += intermediate_scale
}
}
// Both x and y are equal to 1
else if (x == 1 AND y == 1) {
final_scale = "Ls"
}
// ONLY x is equal to 1
else if (x == 1 AND y != 1) {
final_scale += "L"
for (integer i = 0 to y) {
final_scale += "s"
}
}
// ONLY y is equal to 1
else if (x != 1 AND y == 1) {
for (integer i = 0 to x) {
final_scale += "L"
}
final_scale += "s"
}
// Neither x nor y are equal to 1
else if (x != 1 AND y != 1) {
// There are more large steps than small steps
if (x > y) {
integer large_group_count = x % y
integer small_group_count = y - large_group_count
string large_group_production_rule = ""
string small_group_production_rule = ""
integer l_count = floor(x/y)
for (integer i = 0 to l_count) {
large_group_production_rule += "L"
small_group_production_rule += "L"
}
large_group_production_rule += "Ls"
small_group_production_rule += "s"
string intermediate_scale = ProduceMaximallyEvenScale(large_group_count, small_group_count)
for (integer i = 0 to intermediate_scale.length) {
if (intermediate_scale[i] = "L") {
final_scale += large_group_production_rule
} else if (intermediate_scale[i] = "s") {
final_scale += small_group_production_rule
}
}
}
// There are more small steps than large steps
if (y > x) {
integer large_group_count = y % x
integer small_group_count = y - large_group_count
string large_group_production_rule = ""
string small_group_production rule = ""
integer l_count = floor(y/x)
for (integer i = 0 to l_count) {
large_group_production_rule += "s"
small_group_production_rule += "s"
}
large_group_production_rule += "sL"
small_group_production_rule += "L"
string intermediate_scale = ProduceMaximallyEvenScale(large_group_count, small_group_count)
for (integer i = 0 to intermediate_scale.length) {
if (intermediate_scale[i] = "L") {
final_scale += large_group_production_rule
} else if (intermediate_scale[i] = "s") {
final_scale += small_group_production_rule
}
}
}
}
return final_scale
}


=== Example ===
=== Example ===
We take 5L7s, which will have 7 chunks
To find the generators for 5L 7s, we can start at step 2.3.1, as the mos is neither multi-period nor 1L 1s. Using these steps, we can recursively find the mos 2L 3s, which means we recursively find the mos 1L 1s.


The chunks must partition the 5 "L"s, and 5 = 1 + 1 + 1 + 1 + 1 + 0 + 0.
Since our mos is Ls, we need to expand L and s to find the mos for 2L 3s. Since we have more small steps than large steps, we need to reverse the scale into sL. Since we recursively called this algorithm for 2L 3s, we can deduce that L is replaced with Lss, and s with Ls, using steps 2.3.5.1 and 2.3.5.2, producing the mos 2L 3s represented as LsLss.


So we get 5L2s, which we know is LLLsLLs and turns back to sL sL sL s sL sL s.
Since our mos is LsLss, we need to expand L and s again to find the mos for 5L 7s. Since we have more small steps than large steps, we need to reverse the scale into ssLsL. Since we recursively called this algorithm for 5L 7s, we can deduce that L is replaced with Lss and s with Ls.
 
With that, our final mos in its brightest mode is LsLsLssLsLss.
 
To make sure that we have the correct mos, we can relate it back to 5L 2s, whose mos LLLsLLs. By replacing "Ls" with L, we can reduce our mos into that for 5L 2s.


== Finding a generator ==
== Finding a generator ==
The recursive structure also allows a recursive algorithm to find the 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.
 
=== 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.
 
# 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 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 y is equal to 1, then the bright generator is "L" and the dark generator is x-1 L's followed by "s".
# If neither x nor y is equal to 1 (recursive cases):
## Let k be the greatest common factor of x and y.
## If x and y share a common factor k, where k is greater than 1, then recursively call this algorithm to find the generators for (x/k)L (y/k)s; the generators returned this way will apply to the period rather than the octave.
## If x and y don't share a common factor that is greater than 1 (if x and y are coprime), then:
### Let m1 = min(x, y) and m2 = max(x, y).
### 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.
### 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.
### 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 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.
 
=== 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">
def CalculateMosGenerators(x, y):
 
    mos_string_left = ""
    mos_string_right = ""
 
    if (x == 1 or y == 1):
        if (x == 1 and y == 1):
            mos_string_left = "L"
            mos_string_right = "s"
        elif (x == 1 and y > 1):
            mos_string_left = "L" + "s" * (y - 1)
            mos_string_right = "s"
        elif (x > 1 and y == 1):
            mos_string_left = "L"
            mos_string_right = "L" * (x - 1) + "s"
        else:
            mos_string_left = "unable to find generator"
            mos_string_right = "unable to find generator"
   
    else:
       
        k = min(x, y)
        while (k > 0):
            if (x % k == 0 and y % k == 0):
                break
            k -= 1
 
        if (k != 1):
            mos_string_left, mos_string_right = CalculateMosGenerators(x // k, y // k)
        elif (k == 1):
            m1 = min(x, y)
            m2 = max(x, y)
            z = m2 % m1
            w = m1 - z
 
            prescale_left, prescale_right = CalculateMosGenerators(z, w)
 
            if (x < y):
                prescale_left = prescale_left[::-1]
                prescale_right = prescale_right[::-1]
               
                temp = prescale_left
                prescale_left = prescale_right
                prescale_right = temp
 
            l_rule = ""
            s_rule = ""
            if (x > y):
                l_rule = "L" * math.ceil(m2 / m1) + "s"
                s_rule = "L" * math.floor(m2 / m1) + "s"
            else:
                l_rule = "L" + "s" * math.ceil(m2 / m1)
                s_rule = "L" + "s" * math.floor(m2 / m1)
 
            for step in prescale_left:
                if step == "L":
                    mos_string_left += l_rule
                elif step == "s":
                    mos_string_left += s_rule
                   
            for step in prescale_right:
                if step == "L":
                    mos_string_right += l_rule
                elif step == "s":
                    mos_string_right += s_rule
 
        else:
            mos_string_left = "unable to find generator"
            mos_string_right = "unable to find generator"
 
    return mos_string_left, mos_string_right
</syntaxhighlight>
 
=== Example ===
To find the generators for 5L 7s, we can start at step 2.3.1, as the mos is neither multi-period nor 1L 1s. Using these steps, we can recursively find the generators for the mos 2L 3s, which means we recursively find the generators for 1L 1s.
 
Since our generators are L and s, we need to expand L and s to find the generators for 2L 3s. Since we have more small steps than large steps, we need to reverse and swap the generators, so our bright generator is s and dark generator is L. Since we recursively called this algorithm for 2L 3s, we can deduce that L is replaced with Lss, and s with Ls, using steps 2.3.5.1 and 2.3.5.2, producing the generator pair of Ls and Lss.


To find the generator you reduce to a simple pattern you know the generator of, then plug everything back in.
Since our generators are now Ls and Lss, we need to expand them again to find the generators for 5L 7s. Since we have more small steps than large steps, we need to reverse and swap the generators, so our bright generator is ssL and dark generator is sL. Since we recursively called this algorithm for 5L 7s, we can deduce that L is replaced with Lss and s with Ls.


If you do that to 5L 7s you get 5L 2s: LLLsLLs. You know that the generator of 5L 2s is LLLs, so using the above procedure for finding the pattern, the generator of 5L 7s is LsLsLss. If you don't know anything you just reduce all the way to 1L 1s and plug everything back in.
With that, our final generator pair is LsLsLss and LsLss.


The reason why this works is that the reduction both ''preserves'' and ''reflects'' the generator; see the ''proofs'' section.
To make sure that we have the correct generator pair, we can relate it back to 5L 2s, whose generator pair is LLLs and LLs. By replacing "Ls" with L, we can reduce our generator pair into that for 5L 2s.


==Tree of MOS patterns==
==Tree of MOS patterns==