Replication Experiments from Dr. Jon Clindaniel’s Harvard Thesis


By Ashok Khosla

Appendix 4 - Replication of Dr. Jon Clindaniel’s Thesis Research

In this appendix, a few elements of Dr. Jon Clindaniel’s khipu research, documented in his PhD thesis at Harvard, will be replicated and examined.

1. Cord Type Distribution

Table 4.1 of Jon’s Thesis: Frequency of Cord Color Sign Types in Inkawasi Khipus and Globally in the Khipu Database

Code
(khipu_dict, all_khipus) = kamayuq.fetch_khipus()
khipu_colors = []
for aKhipu in all_khipus:
    khipu_colors += [(cord.longest_ascher_color(), cord.knotted_value()) for cord in aKhipu.cc_cords()]
    
def is_solid(aColor):
    return len({':','%','-','*'} & set(aColor)) == 0
def is_mottled(aColor):
    return aColor.count(":") > 0
def is_barberpole(aColor):
    return aColor.count("-") > 0
def is_double_mottled(aColor):
    return aColor.count(":") > 1
def is_double_barberpoled(aColor):
    return aColor.count("-") > 1
def is_mottled_barberpoled(aColor):
    return is_barberpole(aColor) and is_mottled(aColor)

solid_colors = [aColor for aColor in khipu_colors if is_solid(aColor[0])]
mottled_colors = [aColor for aColor in khipu_colors if is_mottled(aColor[0])]
barberpole_colors = [aColor for aColor in khipu_colors if is_barberpole(aColor[0])]
double_mottled_colors = [aColor for aColor in khipu_colors if is_double_mottled(aColor[0])]
mottled_barberpoled_colors = [aColor for aColor in khipu_colors if is_mottled_barberpoled(aColor[0])]
double_barberpoled_colors = [aColor for aColor in khipu_colors if is_double_barberpoled(aColor[0])]

solid_cord_values = [aColor[1] for aColor in solid_colors]
mottled_cord_values = [aColor[1] for aColor in mottled_colors]
barberpole_cord_values = [aColor[1] for aColor in barberpole_colors]
double_mottled_cord_values = [aColor[1] for aColor in double_mottled_colors]
mottled_barberpoled_cord_values = [aColor[1] for aColor in mottled_barberpoled_colors]
double_barberpoled_cord_values = [aColor[1] for aColor in double_barberpoled_colors]

num_solids = len(solid_colors)
num_mottled =  len(mottled_colors)
num_barberpoles =  len(barberpole_colors)
num_double_mottled =  len(double_mottled_colors)
num_mottled_barberpoled =  len(mottled_barberpoled_colors)
num_double_barberpoled =  len(double_barberpoled_colors)

from statistics import mean, stdev

print(f"             {num_solids=: 6.0f} - Average cord value={mean(solid_cord_values): 4.0f} with Std_dev={stdev(solid_cord_values): 5.0f}")
print(f"            {num_mottled=: 6.0f} - Average cord value={mean(mottled_cord_values): 4.0f} with Std_dev={stdev(mottled_cord_values): 5.0f}")
print(f"        {num_barberpoles=: 6.0f} - Average cord value={mean(barberpole_cord_values): 4.0f} with Std_dev={stdev(barberpole_cord_values): 5.0f}")
print(f"     {num_double_mottled=: 6.0f} - Average cord value={mean(double_mottled_cord_values): 4.0f} with Std_dev={stdev(double_mottled_cord_values): 5.0f}")
print(f"{num_mottled_barberpoled=: 6.0f} - Average cord value={mean(mottled_barberpoled_cord_values): 4.0f} with Std_dev={stdev(mottled_barberpoled_cord_values): 5.0f}")
print(f" {num_double_barberpoled=: 6.0f} - Average cord value={mean(double_barberpoled_cord_values): 4.0f} with Std_dev={stdev(double_barberpoled_cord_values): 5.0f}")
             num_solids= 43926 - Average cord value= 165 with Std_dev= 2494
            num_mottled= 11306 - Average cord value= 102 with Std_dev=  881
        num_barberpoles=  2329 - Average cord value= 118 with Std_dev=  748
     num_double_mottled=   403 - Average cord value= 100 with Std_dev=  389
num_mottled_barberpoled=    68 - Average cord value=  32 with Std_dev=  122
 num_double_barberpoled=    29 - Average cord value=   6 with Std_dev=   21

2. Addition, Credit, and Debit - Marked Sign Analysis

We can take advantage of the Pendant Pendant Sum Fieldmark, which has done most of the heavy lifting for us. Let’s as a first pass, look for two summand cord matches using the White=Dark + White:Dark model proposed in Jon’s thesis.

Code
#=======================================================
# INITIALIZE
# Read in the Fieldmark and its associated dataframes
#=======================================================
from fieldmark_ascher_pendant_pendant_sum import Fieldmark_Pendant_Pendant_Sum
aFieldmark = Fieldmark_Pendant_Pendant_Sum()
khipu_df = aFieldmark.dataframes[0].dataframe
sum_cord_df = aFieldmark.dataframes[1].dataframe

# Initialize plotly
plotly.offline.init_notebook_mode(connected = False);
Code
sum_cord_df = aFieldmark.dataframes[1].dataframe
sum_cord_df
Unnamed: 0 khipu_id name cord_id cord_name cord_position cord_position_index cord_value cord_color num_summands sum_string handedness
0 0 1000166 AS010 3016349 p13 [3, 2] 2 20 LB 4 [2, 0]:4 +[2, 1]:1 +[2, 2]:12 +[3, 0]:3 -4
1 1 1000166 AS010 3016360 p24 [9, 2] 2 38 W 6 [3, 0]:3 +[3, 1]:3 +[3, 2]:20 +[4, 0]:3 +[5, 0]:0 +[6, 0]:9 -11
2 2 1000166 AS010 3016340 p4 [1, 2] 2 23 W:RG 3 [1, 5]:18 +[2, 0]:4 +[2, 1]:1 4
3 3 1000166 AS010 3016343 p7 [1, 5] 5 18 W:LB 3 [2, 2]:12 +[3, 0]:3 +[3, 1]:3 4
4 4 1000166 AS010 3016346 p10 [2, 2] 2 12 LB 3 [4, 0]:3 +[5, 0]:0 +[6, 0]:9 5
... ... ... ... ... ... ... ... ... ... ... ... ...
8080 8080 6000090 UR253 6011731 p36 [6, 11] 11 106 MB 21 [7, 1]:17 +[8, 0]:0 +[8, 1]:0 +[8, 2]:2 +[8, 3]:0 +[8, 4]:0 +[8, 5]:0 +[8, 6]:0 +[8, 7]:4 +[8, 8]:0 +[9, 0]:1 +[9, 1]:0 +[9, 2]:3 +[9, 3]:0 +[10, 0]:2 +[10, 1]:4 +[10, 2]:5 +[10, 3]:30 +[10, 4]:11 +[10, 5]:21 +[11, 0]:6 12
8081 8081 6000090 UR253 6011752 p57 [10, 5] 5 21 0D 2 [11, 0]:6 +[11, 1]:15 2
8082 8082 6000091 UR280 6011790 p3 [0, 2] 2 87 NB 4 [5, 2]:52 +[5, 3]:3 +[5, 4]:28 +[5, 5]:4 28
8083 8083 6000091 UR280 6011810 p23 [4, 5] 5 150 NB 4 [5, 1]:67 +[5, 2]:52 +[5, 3]:3 +[5, 4]:28 8
8084 8084 6000092 UR292A 6012061 p24 [3, 0] 0 20 W 6 [4, 2]:7 +[4, 3]:6 +[5, 0]:1 +[5, 1]:0 +[5, 2]:0 +[5, 3]:6 5
Code
khipu_names = sum_cord_df.name.to_list()
sum_strings = sum_cord_df.sum_string.to_list()
khipus = [khipu_dict[name] for name in khipu_names]
def fetch_summand_colors(index):
    cords = aFieldmark.cords_from_sum_string(khipus[index], sum_strings[index], one_based=False)
    return sorted([aCord.longest_ascher_color() for aCord in cords], key=lambda x: len(x))

the_summand_cord_colors = [fetch_summand_colors(index) for index in range(len(sum_cord_df))]
the_summand_cord_reps = [", ".join(val) for val in the_summand_cord_colors]
sum_cord_df = sum_cord_df.assign(summand_colors=the_summand_cord_reps)
sum_cord_df
Unnamed: 0 khipu_id name cord_id cord_name cord_position cord_position_index cord_value cord_color num_summands sum_string handedness summand_colors
0 0 1000166 AS010 3016349 p13 [3, 2] 2 20 LB 4 [2, 0]:4 +[2, 1]:1 +[2, 2]:12 +[3, 0]:3 -4 LB, LB, LB, W:LB
1 1 1000166 AS010 3016360 p24 [9, 2] 2 38 W 6 [3, 0]:3 +[3, 1]:3 +[3, 2]:20 +[4, 0]:3 +[5, 0]:0 +[6, 0]:9 -11 W, LB, LB, LB, LB, LB
2 2 1000166 AS010 3016340 p4 [1, 2] 2 23 W:RG 3 [1, 5]:18 +[2, 0]:4 +[2, 1]:1 4 LB, W:LB, W:LB
3 3 1000166 AS010 3016343 p7 [1, 5] 5 18 W:LB 3 [2, 2]:12 +[3, 0]:3 +[3, 1]:3 4 LB, LB, LB
4 4 1000166 AS010 3016346 p10 [2, 2] 2 12 LB 3 [4, 0]:3 +[5, 0]:0 +[6, 0]:9 5 W, LB, LB
... ... ... ... ... ... ... ... ... ... ... ... ... ...
8080 8080 6000090 UR253 6011731 p36 [6, 11] 11 106 MB 21 [7, 1]:17 +[8, 0]:0 +[8, 1]:0 +[8, 2]:2 +[8, 3]:0 +[8, 4]:0 +[8, 5]:0 +[8, 6]:0 +[8, 7]:4 +[8, 8]:0 +[9, 0]:1 +[9, 1]:0 +[9, 2]:3 +[9, 3]:0 +[10, 0]:2 +[10, 1]:4 +[10, 2]:5 +[10, 3]:30 +[10, 4]:11 +[10, 5]:21 +[11, 0]:6 12 W, W, W, W, W, W, W, W, AB, PK, PR, PR, PR, PR, AB, RB, AB, AB, 0D, AB:KB, W:AB:LK
8081 8081 6000090 UR253 6011752 p57 [10, 5] 5 21 0D 2 [11, 0]:6 +[11, 1]:15 2 AB, W:AB:LK
8082 8082 6000091 UR280 6011790 p3 [0, 2] 2 87 NB 4 [5, 2]:52 +[5, 3]:3 +[5, 4]:28 +[5, 5]:4 28 NB, NB, NB, NB
8083 8083 6000091 UR280 6011810 p23 [4, 5] 5 150 NB 4 [5, 1]:67 +[5, 2]:52 +[5, 3]:3 +[5, 4]:28 8 NB, NB, NB, NB
8084 8084 6000092 UR292A 6012061 p24 [3, 0] 0 20 W 6 [4, 2]:7 +[4, 3]:6 +[5, 0]:1 +[5, 1]:0 +[5, 2]:0 +[5, 3]:6 5 AB, BG, GG, KB, BB, BG

Allllll-Righty, Then…. Let’s go hunting:

Code
# First let's look at only two summand cord sets
two_summand_df = sum_cord_df[sum_cord_df.num_summands==2]

# Then let's look at white sum cord sets
white_addition_df = two_summand_df[two_summand_df.cord_color=="W"]

# Then let's look at where mottled cords occur in the summand colors
mask = [":" in color for color in white_addition_df.summand_colors.to_list()]
mottled_result_df = white_addition_df[mask]

# Then let's look at where the first color is not the same as the second color
from khipu_cord_color import color_code_to_grey_value
def is_white_clindaniel_relationship(sum_color, summand_color_string):
    summand_colors = [aColor.strip() for aColor in summand_color_string.split(",")]
    summand_grey_scales = [color_code_to_grey_value(aColor) for aColor in summand_colors]
    return ((len(summand_colors)==2) and 
            (summand_colors[0] != sum_color) and 
            (summand_colors[1] != summand_colors[0]) and 
            (summand_grey_scales[0] < .6) and
            (summand_colors[0] in summand_colors[1]) and
            (sum_color in summand_colors[1]) and
            not((":" in summand_colors[0]) and (":" in summand_colors[1])))
            
sum_colors = mottled_result_df.cord_color.to_list()
summand_colors = mottled_result_df.summand_colors.to_list()
mask = [is_white_clindaniel_relationship(sum_colors[index], summand_colors[index]) for index in range(len(summand_colors))]
white_clindaniel_relationship_df = mottled_result_df[mask]

khipus_that_match = sorted(list(set(white_clindaniel_relationship_df.name.to_list())))
print(f"Matches {len(khipus_that_match)} khipus with {len(white_clindaniel_relationship_df)} sum matches")

khipu_matches = [(khipu_name, [",".join(white_clindaniel_relationship_df[white_clindaniel_relationship_df.name==khipu_name].summand_colors.to_list())]) for khipu_name in khipus_that_match]

print(f"\n\nThese are the khipus sum matches where White is the sum cord, a Dark cord is present and a Mottled cord is present containing that dark cord")
for match in khipu_matches: print(match)
print(f"\n\n\n")                  
white_clindaniel_relationship_df
Matches 7 khipus with 11 sum matches


These are the khipus sum matches where White is the sum cord, a Dark cord is present and a Mottled cord is present containing that dark cord
('AS020', ['B, W:B'])
('AS092', ['MB, W:MB'])
('AS210', ['B, W:B'])
('AS212', ['B, W:B'])
('UR085', ['GG, W:GG'])
('UR086', ['AB, W:AB,AB, W:AB'])
('UR277', ['NB, W:NB,NB, W:NB,NB, W:NB,NB, W:NB'])



Unnamed: 0 khipu_id name cord_id cord_name cord_position cord_position_index cord_value cord_color num_summands sum_string handedness summand_colors
22 22 1000191 AS020 3017261 p2 [0, 1] 1 37 W 2 [1, 3]:25 +[1, 4]:12 7 B, W:B
566 566 1000234 AS092 3018905 p49 [8, 0] 0 15 W 2 [3, 1]:9 +[3, 2]:6 -28 MB, W:MB
827 827 1000017 AS210 3001526 p26 [4, 1] 1 24 W 2 [4, 2]:1 +[4, 3]:23 1 B, W:B
838 838 1000034 AS212 3005565 p21 [4, 0] 0 12 W 2 [2, 1]:8 +[2, 2]:4 -8 B, W:B
2149 2149 1000280 UR085 3022937 p146 [14, 4] 4 100 W 2 [14, 2]:73 +[14, 3]:27 -1 GG, W:GG
... ... ... ... ... ... ... ... ... ... ... ... ... ...
2212 2212 1000336 UR086 3030393 p75 [16, 4] 4 53 W 2 [17, 4]:23 +[17, 5]:30 8 AB, W:AB
5864 5864 1000622 UR277 3056074 p70 [9, 2] 2 33 W 2 [4, 5]:14 +[4, 6]:19 -41 NB, W:NB
5866 5866 1000622 UR277 3056076 p72 [9, 4] 4 34 W 2 [4, 4]:20 +[4, 5]:14 -43 NB, W:NB
5882 5882 1000622 UR277 3056022 p18 [3, 2] 2 30 W 2 [4, 9]:12 +[4, 10]:18 15 NB, W:NB
5883 5883 1000622 UR277 3056023 p19 [3, 3] 3 33 W 2 [4, 5]:14 +[4, 6]:19 10 NB, W:NB

Now let’s expand it a bit to include where the sum cord is a light cord, not just a white cord.

Code
# First let's look at only two summand cord sets
two_summand_df = sum_cord_df[sum_cord_df.num_summands==2]

# Then let's look at LIGHT sum cord sets
from khipu_cord_color import color_code_to_grey_value
mask = [color_code_to_grey_value(aColor)> 0.6 for aColor in two_summand_df.cord_color.to_list()]
light_addition_df = two_summand_df[mask]

# Then let's look at where mottled cords occur in the summand colors
mask = [":" in color for color in light_addition_df.summand_colors.to_list()]
mottled_result_df = light_addition_df[mask]

# Then let's look at where the first color is not the same as the second color
def is_clindaniel_relationship(sum_color, summand_color_string):
    summand_colors = [aColor.strip() for aColor in summand_color_string.split(",")]
    summand_grey_scales = [color_code_to_grey_value(aColor) for aColor in summand_colors]
    return ((len(summand_colors)==2) and 
            (summand_colors[0] != sum_color) and 
            (summand_colors[1] != summand_colors[0]) and 
            (summand_grey_scales[0] < .6) and
            (summand_colors[0] in summand_colors[1]) and
            (sum_color in summand_colors[1]) and
            not((":" in summand_colors[0]) and (":" in summand_colors[1])))
            
sum_colors = mottled_result_df.cord_color.to_list()
summand_colors = mottled_result_df.summand_colors.to_list()
mask = [is_clindaniel_relationship(sum_colors[index], summand_colors[index]) for index in range(len(summand_colors))]
clindaniel_relationship_df = mottled_result_df[mask]

khipus_that_match = sorted(list(set(clindaniel_relationship_df.name.to_list())))
print(f"Matches {len(khipus_that_match)} khipus with {len(clindaniel_relationship_df)} sum matches")

khipu_matches = [(khipu_name, [",".join(clindaniel_relationship_df[clindaniel_relationship_df.name==khipu_name].summand_colors.to_list())]) for khipu_name in khipus_that_match]

print(f"\n\nThese are the khipus sum matches where the sum cord is Light, and a Dark cord is present and a Mottled cord is present containing that dark cord")
for match in khipu_matches: print(match)
print(f"\n\n\n")                  
clindaniel_relationship_df
Matches 7 khipus with 11 sum matches


These are the khipus sum matches where the sum cord is Light, and a Dark cord is present and a Mottled cord is present containing that dark cord
('AS020', ['B, W:B'])
('AS092', ['MB, W:MB'])
('AS210', ['B, W:B'])
('AS212', ['B, W:B'])
('UR085', ['GG, W:GG'])
('UR086', ['AB, W:AB,AB, W:AB'])
('UR277', ['NB, W:NB,NB, W:NB,NB, W:NB,NB, W:NB'])



Unnamed: 0 khipu_id name cord_id cord_name cord_position cord_position_index cord_value cord_color num_summands sum_string handedness summand_colors
22 22 1000191 AS020 3017261 p2 [0, 1] 1 37 W 2 [1, 3]:25 +[1, 4]:12 7 B, W:B
566 566 1000234 AS092 3018905 p49 [8, 0] 0 15 W 2 [3, 1]:9 +[3, 2]:6 -28 MB, W:MB
827 827 1000017 AS210 3001526 p26 [4, 1] 1 24 W 2 [4, 2]:1 +[4, 3]:23 1 B, W:B
838 838 1000034 AS212 3005565 p21 [4, 0] 0 12 W 2 [2, 1]:8 +[2, 2]:4 -8 B, W:B
2149 2149 1000280 UR085 3022937 p146 [14, 4] 4 100 W 2 [14, 2]:73 +[14, 3]:27 -1 GG, W:GG
... ... ... ... ... ... ... ... ... ... ... ... ... ...
2212 2212 1000336 UR086 3030393 p75 [16, 4] 4 53 W 2 [17, 4]:23 +[17, 5]:30 8 AB, W:AB
5864 5864 1000622 UR277 3056074 p70 [9, 2] 2 33 W 2 [4, 5]:14 +[4, 6]:19 -41 NB, W:NB
5866 5866 1000622 UR277 3056076 p72 [9, 4] 4 34 W 2 [4, 4]:20 +[4, 5]:14 -43 NB, W:NB
5882 5882 1000622 UR277 3056022 p18 [3, 2] 2 30 W 2 [4, 9]:12 +[4, 10]:18 15 NB, W:NB
5883 5883 1000622 UR277 3056023 p19 [3, 3] 3 33 W 2 [4, 5]:14 +[4, 6]:19 10 NB, W:NB

It’s the same set!

Let’s expand the search to not require the addition marker color to be in the summand set

Code
# First let's look at only two summand cord sets
two_summand_df = sum_cord_df[sum_cord_df.num_summands==2]

# Then let's look at LIGHT sum cord sets
from khipu_cord_color import color_code_to_grey_value
selection_mask = [color_code_to_grey_value(aColor)> 0.6 for aColor in two_summand_df.cord_color.to_list()]
light_addition_df = two_summand_df[selection_mask]

# Then let's look at where mottled cords occur in the summand colors
selection_mask = [":" in color for color in light_addition_df.summand_colors.to_list()]
mottled_result_df = light_addition_df[selection_mask]

# Then let's look at where the first color is not the same as the second color
def is_relaxed_clindaniel_relationship(sum_color, summand_color_string):
    summand_colors = [aColor.strip() for aColor in summand_color_string.split(",")]
    summand_grey_scales = [color_code_to_grey_value(aColor) for aColor in summand_colors]
    return ((len(summand_colors)==2) and 
            (summand_colors[0] != sum_color) and 
            (summand_colors[1] != summand_colors[0]) and 
            (summand_grey_scales[0] < .6) and
            (summand_colors[0] in summand_colors[1]) and
            #(sum_color in summand_colors[1]) and
            not((":" in summand_colors[0]) and (":" in summand_colors[1])))
            
sum_colors = mottled_result_df.cord_color.to_list()
summand_colors = mottled_result_df.summand_colors.to_list()
selection_mask = [is_relaxed_clindaniel_relationship(sum_colors[index], summand_colors[index]) for index in range(len(summand_colors))]
relaxed_clindaniel_relationship_result_df = mottled_result_df[selection_mask]

khipus_that_match = sorted(list(set(relaxed_clindaniel_relationship_result_df.name.to_list())))
print(f"Matches {len(khipus_that_match)} khipus with {len(relaxed_clindaniel_relationship_result_df)} sum matches")

def mottled_relationships(khipu_name):
    sum_colors = relaxed_clindaniel_relationship_result_df[relaxed_clindaniel_relationship_result_df.name==khipu_name].cord_color.to_list()
    summand_colors = relaxed_clindaniel_relationship_result_df[relaxed_clindaniel_relationship_result_df.name==khipu_name].summand_colors.to_list()
    return str(list(zip(sum_colors,summand_colors)))

khipu_matches = [(khipu_name, mottled_relationships(khipu_name)) for khipu_name in khipus_that_match]

print(f"\n\nThese are the khipus sum matches where the sum cord is Light, and a Dark cord is present and a Mottled cord is present containing that dark cord")
for match in khipu_matches: print(match)
print(f"\n\n\n")                  
relaxed_clindaniel_relationship_result_df
Matches 13 khipus with 17 sum matches


These are the khipus sum matches where the sum cord is Light, and a Dark cord is present and a Mottled cord is present containing that dark cord
('AS020', "[('W', 'B, W:B')]")
('AS092', "[('W', 'MB, W:MB')]")
('AS156', "[('W', 'B, B:GG:BB')]")
('AS210', "[('W', 'B, W:B')]")
('AS212', "[('W', 'B, W:B')]")
('UR012', "[('W', 'AB, AB:MB')]")
('UR013', "[('W', 'AB, AB:MB')]")
('UR037', "[('W', 'MB, AB:MB')]")
('UR085', "[('W', 'GG, W:GG')]")
('UR086', "[('W', 'AB, W:AB'), ('W', 'AB, W:AB')]")
('UR208', "[('W', 'AB, AB:GG')]")
('UR267A', "[('W', 'AB, AB:MB')]")
('UR277', "[('W', 'NB, W:NB'), ('W', 'NB, W:NB'), ('W', 'NB, W:NB'), ('W', 'NB, W:NB')]")



Unnamed: 0 khipu_id name cord_id cord_name cord_position cord_position_index cord_value cord_color num_summands sum_string handedness summand_colors
22 22 1000191 AS020 3017261 p2 [0, 1] 1 37 W 2 [1, 3]:25 +[1, 4]:12 7 B, W:B
566 566 1000234 AS092 3018905 p49 [8, 0] 0 15 W 2 [3, 1]:9 +[3, 2]:6 -28 MB, W:MB
659 659 1000131 AS156 3014270 p22 [9, 1] 1 60 W 2 [6, 1]:40 +[7, 0]:20 -5 B, B:GG:BB
827 827 1000017 AS210 3001526 p26 [4, 1] 1 24 W 2 [4, 2]:1 +[4, 3]:23 1 B, W:B
838 838 1000034 AS212 3005565 p21 [4, 0] 0 12 W 2 [2, 1]:8 +[2, 2]:4 -8 B, W:B
... ... ... ... ... ... ... ... ... ... ... ... ... ...
5680 5680 1000608 UR267A 3053846 p1 [0, 0] 0 106 W 2 [0, 1]:15 +[0, 2]:91 2 AB, AB:MB
5864 5864 1000622 UR277 3056074 p70 [9, 2] 2 33 W 2 [4, 5]:14 +[4, 6]:19 -41 NB, W:NB
5866 5866 1000622 UR277 3056076 p72 [9, 4] 4 34 W 2 [4, 4]:20 +[4, 5]:14 -43 NB, W:NB
5882 5882 1000622 UR277 3056022 p18 [3, 2] 2 30 W 2 [4, 9]:12 +[4, 10]:18 15 NB, W:NB
5883 5883 1000622 UR277 3056023 p19 [3, 3] 3 33 W 2 [4, 5]:14 +[4, 6]:19 10 NB, W:NB

As a final search, we posit this question: Are there contiguous three-cord pendant sequences where sum cord = summand 1 + summand 2 (both within cluster and ignoring clusters), that return any matches? Are any of the 48 matches, that we’ve already found, made up of three directly contiguous cords, ignoring clusters?

Code
# First let's look at only two summand cord sets
two_summand_df = sum_cord_df[sum_cord_df.num_summands==2]
def is_contiguous_sum(sum_cord, summand_cords):
    if (sum_cord is None) or not (summand_cords): return False
    try:
        sum_cord_position = sum_cord.pendant_index()
        min_summand_position = min(summand_cords[0].pendant_index(), summand_cords[1].pendant_index())
        max_summand_position = max(summand_cords[0].pendant_index(), summand_cords[1].pendant_index())
        return (min_summand_position == sum_cord_position + 1) and (max_summand_position == min_summand_position + 1)
    except:
        #print(f"Error is_contiguous_sum({sum_cord=},:{summand_cords=})")
        return False

def fetch_sum_cord(index):
    try:
        khipu_name = kq.kfg_name_from_id(two_summand_df.iloc[index].khipu_id)
        khipu = khipu_dict[khipu_name]
        cord_name = two_summand_df.iloc[index].cord_name
        cord = khipu[cord_name]
    except:
        print(f"Error fetching sum cords for index {index}: khipu_name:{khipu_name}")
        cord = None        
    return cord
def fetch_summand_cords(index):
    summand_string = two_summand_df.iloc[index].sum_string.strip()
    try:
        khipu_name = kq.kfg_name_from_id(two_summand_df.iloc[index].khipu_id)
        khipu = khipu_dict[khipu_name]
        cords = aFieldmark.cords_from_sum_string(khipu, summand_string, one_based=False)
    except :
        #print(f"Error fetching summand cords for index {index}: {summand_string}")
        cords = []

    return cords

the_sum_cords= [fetch_sum_cord(index) for index in range(len(two_summand_df))]
mask = [is_contiguous_sum(fetch_sum_cord(index), fetch_summand_cords(index)) for index in range(len(the_sum_cords))]
valid_contigous_sums_df = two_summand_df[mask]
print(f"There are {len(valid_contigous_sums_df)} three-cord contiguous sums where A=B+C and A,B,C are contiguous regardless of cluster boundary")
valid_contigous_sums_df
There are 187 three-cord contiguous sums where A=B+C and A,B,C are contiguous regardless of cluster boundary
Unnamed: 0 khipu_id name cord_id cord_name cord_position cord_position_index cord_value cord_color num_summands sum_string handedness summand_colors
50 50 1000198 AS026B 3017445 p8 [0, 7] 7 24 W 2 [0, 8]:12 +[0, 9]:12 1 W, W
109 109 1000235 AS036 3018951 p39 [3, 10] 10 20 BB 2 [4, 0]:10 +[4, 1]:10 2 W, RD
110 110 1000235 AS036 3018956 p44 [4, 4] 4 30 RD 2 [4, 5]:20 +[4, 6]:10 1 W, RD
111 111 1000235 AS036 3018957 p45 [4, 5] 5 20 W 2 [4, 6]:10 +[4, 7]:10 2 RD, BB
199 199 1000212 AS045 3018189 p28 [8, 1] 1 44 W 2 [8, 2]:26 +[8, 3]:18 1 B:LC, B:LC
... ... ... ... ... ... ... ... ... ... ... ... ... ...
8006 8006 6000087 UR190 6011399 p357 [20, 20] 20 20 W 2 [20, 21]:10 +[20, 22]:10 2 W, W
8009 8009 6000087 UR190 6011423 p381 [21, 16] 16 40 MB 2 [21, 17]:30 +[21, 18]:10 2 W, MB
8010 8010 6000087 UR190 6011424 p382 [21, 17] 17 30 W 2 [21, 18]:10 +[21, 19]:20 1 MB, KB
8043 8043 6000088 UR193 6011500 p41 [2, 37] 37 120 W 2 [2, 38]:61 +[2, 39]:59 2 W, W
8081 8081 6000090 UR253 6011752 p57 [10, 5] 5 21 0D 2 [11, 0]:6 +[11, 1]:15 2 AB, W:AB:LK

And of these relationships, how many are of the light=dark+mottled relationship (ie. Clindaniel Inkawasi relationship)?

Code
# Then let's look at LIGHT sum cord sets
selection_mask = [color_code_to_grey_value(aColor)> 0.6 for aColor in valid_contigous_sums_df.cord_color.to_list()]
light_addition_df = valid_contigous_sums_df[selection_mask]

# Then let's look at where mottled cords occur in the summand colors
selection_mask = [":" in color for color in light_addition_df.summand_colors.to_list()]
mottled_result_df = light_addition_df[selection_mask]
            
# Then let's look at where a Clindaniel relationship occurs
sum_colors = mottled_result_df.cord_color.to_list()
summand_colors = mottled_result_df.summand_colors.to_list()
selection_mask = [is_relaxed_clindaniel_relationship(sum_colors[index], summand_colors[index]) for index in range(len(summand_colors))]
relaxed_contiguous_clindaniel_relationship_df = mottled_result_df[selection_mask]

print(f"There are {len(relaxed_contiguous_clindaniel_relationship_df)} three-cord contiguous sums where A=B+C and A,B,C are contiguous regardless of cluster boundary\nWHERE these relationships are of the light=dark+mottled Clindaniel type.")
relaxed_contiguous_clindaniel_relationship_df
There are 3 three-cord contiguous sums where A=B+C and A,B,C are contiguous regardless of cluster boundary
WHERE these relationships are of the light=dark+mottled Clindaniel type.
Unnamed: 0 khipu_id name cord_id cord_name cord_position cord_position_index cord_value cord_color num_summands sum_string handedness summand_colors
827 827 1000017 AS210 3001526 p26 [4, 1] 1 24 W 2 [4, 2]:1 +[4, 3]:23 1 B, W:B
2207 2207 1000336 UR086 3030366 p48 [9, 2] 2 29 W 2 [9, 3]:26 +[10, 0]:3 1 AB, W:AB
5680 5680 1000608 UR267A 3053846 p1 [0, 0] 0 106 W 2 [0, 1]:15 +[0, 2]:91 2 AB, AB:MB