Code
['W:AB:KB']
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:
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.
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).
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.
What is UR231A’s primary cord?
['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:
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:
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.
# 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'}
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.
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?
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
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 |
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:
Again. let’s examine the subsequent sum relationships and crossings
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 |
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 |
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?
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:
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]
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)
# 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 :
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 |
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 |
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.