Siblings of KH0468-UR231


0. Siblings of KH0468-UR231

A personal note from Manuel Medrano: >UR231… is actually in two pieces in Berlin – the two fragments are inventoried under the same accession number, as parts A and B. People have simply presumed until now that they are two parts of the same khipu, which is how they were recorded for the KDB…

Interestingly, the two fragments of UR231 appear to be separated with a pretty clean cut on the primary cord, just after cord cluster 39. If you look at the X-ray diagram, that is almost exactly the middle of the khipu, and it occurs within a group of sum clusters. The other end of part B is also cut, which means there could be a third part of UR231 that is in Berlin (or elsewhere) but just hasn’t been matched.

A very useful meta set of tools for khipu analysis and decipherment is the ability to splice two khipus together, to reverse them, etc. and then inventory and view all the Ascher summation relationships that occcur in the resulting new khipu.

Three case studies of splicing and then viewing the summation diagrams are shown:

  1. In the first example, UR231A and UR231B, are believed to be two fragments of the same khipu KH0468/UR231, currently stored separately in the Berlin Museum. We can splice UR231A and UR231B together, and then inventory all the Ascher summation relationships that occur in the spliced khipu. We call the spliced result. XX231L_XX231R. Alternatively we could splice together similar khipus to UR231A and see what results. We’ll get to what “similar” means in a moment, but a reasonable first restriction for similarity is that the khipus must have a similar primary cord Ascher color pattern.

  2. In the second example, the restriction of a similar primary cord Ascher color is ignored. This allows us to add other potentially “similar” khipus (again we’ll get to the definition of similar in a moment).

  3. Inspired by one of the khipus in the second example, the third example investigates how the Purucucho hierarchy could be evaluated using the same splicing and summation technique.

1. Splicing Khipus with Primary Cords Similar to UR231A

What is UR231A’s primary cord?

Code
(khipu_dict, all_khipus) = kamayuq.fetch_khipus()

ur231 = khipu_dict['UR231']
main_cord = ur231.primary_cord
primary_cord_colors = [color.full_color for color in main_cord.ascher_cord_colors]
primary_cord_colors
['W:AB:KB']

Previous investigations have shown that cord-color searching by exact color is very unreliable. So let’s search for primary cords, that are mottled, and have three cords. Let’s list possible candidates:

Code
primary_cord_3mottleds = []
rep_string = ""
for khipu in all_khipus:
    if khipu.name().startswith('XX'): continue #Ignore spliced khipus
    main_cord = khipu.primary_cord
    primary_cord_colors = [color.full_color for color in main_cord.ascher_cord_colors]
    has_3mottled_cord = any([color.count(':')==2 for color in primary_cord_colors])
    if has_3mottled_cord: #'W:AB:KB' in primary_cord_colors:
        primary_cord_3mottleds.append(khipu.name())
        rep_string += f"{khipu.name()}: {primary_cord_colors}, "
print(ku.multiline(rep_string, split_char="], ", continuation_char="],\n"))
print(f"{len(primary_cord_3mottleds)} khipus have 3-color mottled primary cords")
AS011: ['YB:B:GG'], AS026B: ['W:GG:FB'], AS044: ['W:BL:B'], AS079: ['W:MB:PB'],
AS139: ['W:MB:CB', 'W'], AS214: ['W:LB:TG'], UR027: ['-:-MB:AB'],
UR046: ['W:AB:MB'], UR053A: ['RL:AB:GG'], UR060: ['AB:MB:HB'],
UR069: ['AB:GG:MB'], UR093: ['AB:MB:KB'], UR1108: ['W:MB:LB'], UR1119: ['W:B:DB'],
UR1120: ['W:GG:LB'], UR117D: ['AB:W-AB:W-MB'], UR139: ['W:AB:KB'],
UR150: ['AB:GG:KB'], UR180: ['PK:AB:KB'], UR227: ['W:AB:BG'], UR231: ['W:AB:KB'],
UR239: ['CB:W-CB:W-AB'], UR244: ['W:AB:KB'], UR246: ['W:AB:MB'],
UR249: ['AB:BG:KB'], KH0033: ['MB-W:GG:AB'], KH0083: ['W:MB:DB'],
QU006: ['YB:BB:PB'], QU009: ['YB:BB:PB'], 
29 khipus have 3-color mottled primary cords

To narrow our search, we’ll add an additional constraint. I note that UR231 has:

  • The most color bands of any khipu
  • It has all verso cords attachments.
  • It has all Z knots
  • It’s mean cords per cluster size is 12
  • It has a mean cord value of 27

Accordingly, let’s start the search with khipus that have some of these characteristics in common. So let’s search for khipus that are banded, with only verso knots, in a Z direction.

Code
# Read in the Mean Cord Value Fieldmark and its associated dataframe and match dictionary
from fieldmark_color_bands import Fieldmark_Color_Bands
color_Fieldmark = Fieldmark_Color_Bands()
color_band_df = color_Fieldmark.dataframes[0].dataframe
banded_khipus_df = color_band_df[color_band_df.num_color_bands > 0]

import fieldmark_khipu_summary as fks
verso_cord_ratio_df = fks.Fieldmark_Verso_Ratio().dataframes[0].dataframe
verso_only_df = verso_cord_ratio_df[verso_cord_ratio_df.v_ratio > .99]

z_knot_ratio_df = fks.Fieldmark_Percent_Z_Knots().dataframes[0].dataframe
z_knot_only_df = z_knot_ratio_df[z_knot_ratio_df.percent_z_knots > .99]

z_knot_verso_only_color_banded_khipus = set(banded_khipus_df.name.to_list()).intersection(set(verso_only_df.kfg_name.to_list())).intersection(set(z_knot_only_df.kfg_name.to_list()))
print(f"{len(z_knot_verso_only_color_banded_khipus)} khipus have z_knots, only verso_attachments, with color_bands")
print(ku.multiline(z_knot_verso_only_color_banded_khipus, split_char=", "))
42 khipus have z_knots, only verso_attachments, with color_bands
{'HP033', 'HP001', 'UR234', 'UR262', 'UR056A', 'UR231', 'XX231L_XX231R', 'UR272', 
 'UR091', 'XX231L_UR246', 'UR201', 'XX231L_UR088', 'UR088', 'UR200', 'HP009', 
 'HP029', 'HP021', 'UR213', 'UR059', 'UR022', 'UR116A', 'UR228', 'HP051 A', 
 'UR217', 'UR134', 'UR222', 'UR056B', 'UR235', 'UR151', 'UR008', 'UR271', 'UR246', 
 'KH0001', 'UR243', 'UR221', 'UR238', 'HP015', 'UR240', 'UR252', 'UR089', 
 'XX231L_UR089', 'XX231L_UR022'}
Code
ur231_matches = sorted(list(set(z_knot_verso_only_color_banded_khipus).intersection(set(primary_cord_3mottleds))))
print(f"{len(ur231_matches)} khipus have z_knots, only verso_attachments, with color_bands, and 3-mottled primary cords")
print(ur231_matches)
2 khipus have z_knots, only verso_attachments, with color_bands, and 3-mottled primary cords
['UR231', 'UR246']

Only 1 Khipu matches, UR246. After evaluating the combination of UR231A and UR246 versus UR231A and UR231B, let’s look at the images of the combined khipus, and their associated Ascher summation Diagram.

# Ascher Sums
Khipu # Pendant Pendant Sums # Pendant Pendant Sums by Color # Pendant Pendant Sums by Index
Click on name to see symbolic image Click on number to see Ascher Summation Diagram Click on number to see Ascher Summation Diagram Click on number to see Ascher Summation Diagram
XX231L_XX231R 186 13 24
XX231L_UR246 96 8 12

We see that UR231 (UR231A,UR231B) has a great deal more pendant pendant sums, etc, than the spliced khipu UR231A,UR246. Just how many of these summations cross the broken boundary between the two khipus?

Code
from fieldmark_ascher_pendant_pendant_sum import Fieldmark_Pendant_Pendant_Sum
from fieldmark_ascher_pendant_pendants_color_sum import Fieldmark_Pendant_Pendants_Color_Sum
from fieldmark_ascher_indexed_pendant_sum import Fieldmark_Indexed_Pendant_Sum

pendant_pendant_sums_df = Fieldmark_Pendant_Pendant_Sum().dataframes[1].dataframe
pendant_pendant_color_sums_df = Fieldmark_Pendant_Pendants_Color_Sum().dataframes[1].dataframe
indexed_pendant_sums_df = Fieldmark_Indexed_Pendant_Sum().dataframes[1].dataframe
Code
def num_crossed_relations(aKhipuName, relations_df, left_khipu_num_clusters=39):
    num_crossings = 0
    khipu_df = relations_df[relations_df.name == aKhipuName]
    relation_reps = []
    
    for index, row in khipu_df.iterrows():
        sum_cluster_index = ku.cluster_index_from_cord_rep(row['cord_position'])
        summand_cluster_indices = [ku.cluster_index_from_cord_rep(cord_rep) for cord_rep in row['sum_string'].split('+')]
        (left_summand_cluster_index, right_summand_cluster_index) = (min(summand_cluster_indices), max(summand_cluster_indices))
        is_right_handed = ((sum_cluster_index < left_khipu_num_clusters) and (right_summand_cluster_index >= left_khipu_num_clusters)) #0 based index not 1 based
        is_left_handed = ((sum_cluster_index >= left_khipu_num_clusters) and (left_summand_cluster_index < left_khipu_num_clusters)) #0 based index not 1 based
        
        if (is_left_handed or is_right_handed): 
            sum_string = f"\tSum Cord [{row['cord_name']}]: {row['cord_position']} SummandString: {row['sum_string']}"
            if sum_string not in relation_reps:
                relation_reps.append(sum_string)
                num_crossings += 1
    return (num_crossings, sorted(set(relation_reps)))

for khipu_name in ['XX231L_UR246', 'XX231L_XX231R']:
    print(f"| {khipu_name} | {num_crossed_relations(khipu_name, pendant_pendant_sums_df)[0]} | {num_crossed_relations(khipu_name, pendant_pendant_color_sums_df)[0]} | {num_crossed_relations(khipu_name, indexed_pendant_sums_df)[0]} |")
| XX231L_UR246 | 25 | 4 | 4 |
| XX231L_XX231R | 49 | 9 | 7 |

2. Splicing Khipus based on “Hierarchical Similarity”

As was learned in a search for duplicate khipus the “best performing indicator” of khipu similarity, is the “similarity index”. So let’s first look at the image quilt, sorted by similarity, and see what lies near UR231. However, a visual review of the image_quilt, is not encouraging. In particular, there are no khipus close to UR231, that exhibit the pendant-pendant-sum pattern we see so often in banded khipus, where clusters of knotted cords are bordered by clusters of unknotted cords.

Let’s try an analytical approach, despite knowing after our work in duplicate khipu identification, that this approach often fails.

A quick glance at the khipu shows it to be almost all color banded, so search candidates should have a large number of color bands.

Browsing identifies a likely candidate UR089, which lies adjacent to UR231. We will splice UR231A and UR089 together, and also try UR022, and UR088. The result is three khipus:

  • XX231L_UR022
  • XX231L_UR088
  • XX231L_UR089

Again. let’s examine the subsequent sum relationships and crossings

Number of Summation Relationships:

# Ascher Sums
Khipu # Pendant Pendant Sums
 
# Pendant Pendant Sums
by Color
# Pendant Pendant Sums
by Index
To see symbolic image,
click on Name


 
To see Ascher Summation Diagram,
click on Number


#Total (Left)/(Right)
To see Ascher Summation Diagram,
click on Number


#Total (Left)/(Right)
To see Ascher Summation Diagram,
click on Number


#Total (Left)/(Right)
XX231L_XX231R 186 13 24
XX231L_UR246 96 8 12
XX231L_UR022 245 113 98
XX231L_UR088 106 7 2
XX231L_UR089 170 18 24
Code
for khipu_name in ['XX231L_XX231R', 'XX231L_UR022', 'XX231L_UR246', 'XX231L_UR088', 'XX231L_UR089']:
    print(f"| {khipu_name} | {num_crossed_relations(khipu_name, pendant_pendant_sums_df)[0]} | {num_crossed_relations(khipu_name, pendant_pendant_color_sums_df)[0]} | {num_crossed_relations(khipu_name, indexed_pendant_sums_df)[0]} |")
| XX231L_XX231R | 49 | 9 | 7 |
| XX231L_UR022 | 48 | 0 | 28 |
| XX231L_UR246 | 25 | 4 | 4 |
| XX231L_UR088 | 35 | 3 | 0 |
| XX231L_UR089 | 45 | 4 | 10 |

Number of Summation Relationships That Cross the Splice Boundary:

# Crossing Ascher Sums
Khipu # Crossing Pendant Pendant Sums # Crossing Pendant Pendant Sums by Color # Crossing Pendant Pendant Sums by Index
XX231L_XX231R 46 7 7
XX231L_UR022 47 0 28
XX231L_UR246 25 4 4
XX231L_UR088 35 3 0
XX231L_UR089 45 4 10

We can see that indeed, UR089’s splice is not as consequential as UR231B. Intriguingly, we can also see that splicing UR022 leads to a small INCREASE in the number of pendant pendant sums by index, as well overall number of sums. Could it be that perhaps UR022 is a hierarchical matching khipu like the Purucucho Hierarchy?

3. Finding Hierarchical Khipus - The Purucucho Hierarchy

In the article Information Control in the Palace of Puruchuco - An Accounting Hierarchy in a Khipu Archive from Coastal Peru by Gary Urton and Carrie J. Brezine, the authors note how seven khipus form a hierarchy of summations:

As a case study we look at how UR063 uses pendant-pendant-sums by color to sum UR068.

It takes some finessing to make the Purucucho scheme work for aligning the sums of UR068 to match a section of UR063. The number of sums in UR068 is 20, but the number of pendant cords in UR063 is 111. The Purucucho hierarchy sums six subunits (i.e 20 * 6), which is more than 111! If the subunits are “aligned” by color, with the insertion of a “null cord” holder, when needed, it aligns well. A dynamic sequence_alignment algorithm borrowed from genetic sequence matching guides the answer:

Code
pendants_UR068 = khipu_dict['UR068'].pendant_cords()
sumnames_UR068 = [aCord.level_name for aCord in pendants_UR068[33:53]]
sumcolors_UR068 = [aCord.longest_ascher_color() for aCord in pendants_UR068[33:53]]

pendants_UR063 = khipu_dict['UR063'].pendant_cords()
summandcolors_UR063 = [aCord.longest_ascher_color() for aCord in pendants_UR063]
Code
def encode_to_digitcode(aList, source_to_target):
    return [source_to_target[item] for item in aList]

def decode_from_digitcode(aList, target_to_source):
    def decode_with_null(item): return target_to_source.get(item, '_')
    return [decode_with_null(item) for item in aList]

(_, encode_dict, decode_dict) = ku.as_digitcode_vector(sumcolors_UR068 + summandcolors_UR063, asChars=True)
sum_string = encode_to_digitcode(sumcolors_UR068*6, encode_dict) # Multiply by 6 to match length of summand colors
summand_string = encode_to_digitcode(summandcolors_UR063, encode_dict)

# initialising penalties of different types
mismatch_penalty = 1
gap_penalty = 3

# calling the function to calculate the result
from sequence_alignment import get_minimum_penalty
(aligned_sequence1, aligned_sequence2, minium_penalty) = get_minimum_penalty(sum_string, summand_string, mismatch_penalty, gap_penalty)

aligned_sum_string = decode_from_digitcode(aligned_sequence1, decode_dict)
aligned_summand_string = decode_from_digitcode(aligned_sequence2, decode_dict)
Code
# Print the final answer
print("Aligned Sequences: (sum/UR068, summand/UR063)") 
subunit_names = ["a", "b", "c", "d", "e", "f"]
#subunit_names = ["f", "e", "d", "c", "b", "a"]
for i in range(6):
    beginning = i*20
    end = min(beginning + 20, 120)
    aligned_string = ", ".join([f"({aligned_sum_string[j]} - {aligned_summand_string[j]})" for j in range(beginning, end)]) 
    description = f"subunit {subunit_names[i]}: {aligned_string}"
    print(ku.multiline(description, split_char="), ", line_length=120, continuation_char="), \n           "))
Aligned Sequences: (sum/UR068, summand/UR063)
subunit a: (W - W), (AB - AB), (GG - GG), (W:GG - W:GG), (W - _), (AB - _), (AB:GG - _), (W:KB - _), (W - W), (AB - MB), 
           (GG - GG), (W:KB - W:GG), (W - W), (AB - MB), (GG - GG), (W:KB - W:GG), (W - W), (AB - MB), (GG - GG), (W:KB - W:GG)
subunit b: (W - W), (AB - MB), (GG - GG), (W:GG - W:GG), (W - W), (AB - AB), (AB:GG - GG), (W:KB - W:KB), (W - W), 
           (AB - AB), (GG - GG), (W:KB - W:KB), (W - W), (AB - AB), (GG - GG), (W:KB - W:KB), (W - W), (AB - W:AB), (GG - GG), 
           (W:KB - _)
subunit c: (W - W), (AB - AB), (GG - GG), (W:GG - _), (W - W), (AB - AB), (AB:GG - _), (W:KB - GG), (W - W), (AB - AB), 
           (GG - GG), (W:KB - W:KB), (W - W), (AB - AB), (GG - GG), (W:KB - W:KB), (W - W), (AB - MB), (GG - GG), (W:KB - W:KB)
subunit d: (W - W), (AB - MB), (GG - GG), (W:GG - _), (W - W), (AB - MB), (AB:GG - GG), (W:KB - W:KB), (W - W), (AB - AB), 
           (GG - GG), (W:KB - W:KB), (W - W), (AB - AB), (GG - GG), (W:KB - W:KB), (W - W), (AB - AB), (GG - GG), (W:KB - W:KB)
subunit e: (W - W), (AB - AB), (GG - GG), (W:GG - W:KB), (W - W), (AB - AB), (AB:GG - GG), (W:KB - W:KB), (W - W), 
           (AB - AB), (GG - GG), (W:KB - W:KB), (W - W), (AB - AB), (GG - GG), (W:KB - W:KB), (W - W), (AB - AB), (GG - GG), 
           (W:KB - W:KB)
subunit f: (W - W), (AB - AB), (GG - GG), (W:GG - W:KB), (W - W), (AB - AB), (AB:GG - GG), (W:KB - W:KB), (W - W), 
           (AB - AB), (GG - GG), (W:KB - W:KB), (W - W), (AB - AB), (GG - GG), (W:KB - W:KB), (W - W), (AB - AB), (GG - GG), 
           (W:KB - _)

Using this alignment, we can create a tabular “excel” view similar to Figure 6 of the article, with empty cords noted as “null”:

KHIPU # RECORDED CORDS
UR068 34-53 p34 p35 p36 p37 p38 p39 p40 p41 p42 p43 p44 p45 p46 p47 p48 p49 p50 p51 p52 p53
20 W AB GG W:GG W AB AB:GG W:KB W AB GG W:KB W AB GG W:KB W AB GG W:KB
20 2 0 1 0 8 1 1 0 8 1 3 1 57 5 6 2 1236 46 59 10
UR063 (Sum) 2 0 1 0 9 1 1 0 8 2 3 1 57 6 4 1 1247 46 44 6
UR063
subunit_a 20 0 0 1 0 2 0 0 0 1 0 0 1 9 1 1 0 127 12 9 1
subunit_b 18 0 0 0 0 1 0 0 0 2 0 1 0 12 1 1 null cord 229 6 13 null cord
subunit_c 18 0 0 0 null cord 1 0 0 0 1 1 0 0 9 2 1 0 148 7 2 null cord
subunit_d 20 2 0 0 0 3 1 0 0 1 1 0 0 12 1 0 0 367 8 8 2
subunit_e 20 0 0 0 0 2 0 1 0 2 0 1 0 10 1 0 1 157 7 12 3
subunit_f 15 null cord null cord null cord null cord 0 0 0 0 1 0 1 0 5 0 1 0 219 6 0 null cord
subunit_a 20 W AB GG W:GG W MB GG W:GG W MB GG W:GG W MB GG W:GG W MB GG W:GG
subunit_b 18 W AB GG W:KB W AB GG W:KB W AB GG W:KB W W:AB GG null cord W AB GG null cord
subunit_c 18 W AB GG null cord W AB GG W:KB W AB GG W:KB W MB GG W:KB W MB GG null cord
subunit_d 20 W MB GG W:KB W AB GG W:KB W AB GG W:KB W AB GG W:KB W AB GG W:KB
subunit_e 20 W AB GG W:KB W AB GG W:KB W AB GG W:KB W AB GG W:KB W AB GG W:KB
subunit_f 15 null cord null cord null cord null cord W AB GG W:KB W AB GG W:KB W AB GG W:KB W AB GG null cord

Adding these default null cords to UR063 and then splicing UR068 on it’s right shows some of the Sums for XXUR068_UR063 that cross the splicing boundary. We are especially interested, as the above table shows, in the pendant sums by index :

Code
khipu_name = 'XXUR063_UR068'
pps_count = f"{num_crossed_relations(khipu_name, pendant_pendant_sums_df, left_khipu_num_clusters=6)[0]}"
ppsc_count = f"{num_crossed_relations(khipu_name, pendant_pendant_color_sums_df, left_khipu_num_clusters=6)[0]}"
ipps_count = f"{num_crossed_relations(khipu_name, indexed_pendant_sums_df, left_khipu_num_clusters=6)[0]}"
print(f"| {khipu_name} | {ipps_count} | {pps_count} | {ppsc_count} |")
| XXUR063_UR068 | 3 | 4 | 4 |
# Crossing Ascher Sums
Khipu # Crossing Pendant Pendant Sums by Index # Crossing Pendant Pendant Sums # Crossing Pendant Pendant Sums by Color
Click on name to see symbolic image Click on number to see Ascher Summation Diagram Click on number to see Ascher Summation Diagram Click on number to see Ascher Summation Diagram
XXUR063_UR068 3 4 4
Code
print("INDEXED PENDANT SUMS CROSSING SPLICE BOUNDARY FOR XXUR063_UR068:")
(_, relation_reps) = num_crossed_relations('XXUR063_UR068', indexed_pendant_sums_df, left_khipu_num_clusters=6)
for rep in relation_reps: print(f"    {rep}")
INDEXED PENDANT SUMS CROSSING SPLICE BOUNDARY FOR XXUR063_UR068:
        Sum Cord [p162]: W@[14, 8]:8 SummandString: W@[0, 8]:1 + W@[1, 8]:2 + W@[2, 8]:1 + W@[3, 8]:1 + W@[4, 8]:2 + W@[5, 8]:1
        Sum Cord [p166]: W@[14, 12]:57 SummandString: W@[0, 12]:9 + W@[1, 12]:12 + W@[2, 12]:9 + W@[3, 12]:12 + W@[4, 12]:10 + W@[5, 12]:5
        Sum Cord [p171]: AB@[14, 17]:46 SummandString: MB@[0, 17]:12 + AB@[1, 17]:6 + MB@[2, 17]:7 + AB@[3, 17]:8 + AB@[4, 17]:7 + AB@[5, 17]:6

The pendant-pendant-sum by index summation finds three of the significant relationships (8, 57, and 46 above). Since the default search is to eliminate anything less than five, 6 of the matches are missed (2,1,1,1,3,1). Also, when you look at the actual matches it missed, you will notice that a few of them are off by 1 or more. A more permissive matcher, instead of the exact one used here, would find additional signal, at the cost of additional noise.

Despite the missing sums, like an X-ray, this case study of splicing and evaluating the resulting summation relationships reveals how the two khipus are related to each other. The case study also reveals that the existing narrow constraints of summation matches may need to be relaxed when searching for these kinds of relationships.