From Arithmetic to Algebra - The Discovery of Equality, Equations and Double-Entry Accounting in Khipus


by Ashok Khosla and AgustĂ­n Da Fieno Delucchi, September 2026

KH0696
KH0696 - Photo by Mackinley FitzPatrick, Click on image for a larger view

Abstract

This article first surveys existing scholarship on khipu arithmetic. Then, with the addition of new khipu “operators”, we transform simple khipu addition and subtraction to allow for a more complete algebraic structure that provides equational capabilities.

Previous work by Manuel Medrano and Ashok Khosla generalized the work of Marcia and Robert Ascher to confirm and identify numerous types of addition and subtraction operations. Our previous algebraic structure had a 0 (an unknotted cord), a 1, and the operators ‘+’ and ‘-’, i.e., it had the full algebraic signature of an additive abelian group. In effect, the group had one operator, addition, with its inverse, subtraction.

\(Result \Leftarrow \displaystyle\sum_{p_x}^{p_y}\)

What khipu arithmetic did not have was an equal sign and the ability to express equations of equality (a ring). The discovery of an equality operator transforms previous simple arithmetic to the next level of algebraic structure. For example equations like this are now possible:

    \(A + B = C + D\)     where \(A,\ B,\ C,\ D\ =\ \displaystyle\sum_{p_x}^{p_y}\ or\ Cord\ {p_i}\)

We propose two candidate khipu signs:

  1. An equality marker = , noted by an Equal Sum cord whose value matches both right and left-handed summand ranges. Such a marker can be interpreted as representing an equality relation, i.e. A+B = C+D.
  2. A set of optional parenthetical delimiters, ( and ), noted by Figure-8-Knots, that mark those summand ranges

Combining established addition signs with the new signs, we demonstrate an illustration of these signs’ interogative power by using an Equal Sum equality marker on khipu KH0696 to yield a clear, legible template for Double-Entry Accounting logic, including crosstabs, encoded within its structure. Corpus-wide analyses show that for larger khipus, multiple Equal Sums exist, with the sums forming a network graph of interrelated equations.

0. Introduction - Equal Sums and Double Entry Accounting

A estos pueblos del camino vienen ĂĄ servir todos los caçiques co- marcanos [
] [los contadores] tienen depĂłssito de leña Ă© mahiz Ă© de todo lo demĂĄs, Ă© cuentan por unos nudos en unas cuerdas de lo que cada caçique ha traydo. E quando nos avian de traer algunas cargas de leña Ăș ovejas Ăł mahiz Ăł chicha, quitaban de lo nudos de los que lo tenian ĂĄ cargo, Ă© anudĂĄbanlo en otra parte: de manera que en todo tienen muy grand cuenta Ă© raçon.

All the local caçiques come to these towns along the road to serve [
] [the accountants] have a store of firewood and maize and everything else, and they count by some knots in ropes what each caçique has brought. And when they brought us a few loads of firewood or sheep or maize or chicha, they removed the knots from those who had it in charge, and tied it elsewhere: so that in everything they have a very great account and reason.

Pizarro, Hernando. (1533). “A los señores oydores de la audiencia real de su magestad.”

Double-Entry Accounting, the famous system of accounting created by Italian merchants in the 13th through 15th century, is built on the fundamental equation of:

    Assets = Equities + Liabilities

Hernando Pizarro’s account above is a statement of the double-entry accounting equation in practice.  How might this be done in a khipu?

To effect this, first we need two arithmetical “operators”;  a   +   (plus sign), and an   =   (equal sign).  The plus sign has been well documented by Medrano and Khosla in their article on Ascher Summations, and on the Khipu Field Guide. What we need now is an Equal sign.

First, lets step back and look at the history of khipu arithmetic.

1. The Arithmetical Operators, +, -, ×, Ă·, √, (,), and =

1.1    +      Plus: The Discovery of Addition

1.1.1 Locke, Ascher, and Clindaniel

Leland Locke first noted the use of numeric summations in khipu in 1923. Locke observed that a top cord’s value was the sum of the values of the cords that were tied to it. That idea was later expanded by Robert and Marcia Ascher in 1975, who discovered numerous summations in the 200+ khipus they documented in their Databooks. In Jon Clindaniel’s Harvard PhD Thesis, on the Incahuasi khipus, he proposed that white cords were involved in subtraction. While this turned out not to be the case (the reading was the mirror inverse - it was addition), it was a novel use of color as a sign being posited for addition.

1.1.2 Ascher Pendant Sum Types

In 2025, Manuel Medrano and Ashok Khosla, extrapolated and generalized the most common summation relationships based on Marcia and Robert Aschers’ observations of khipus in their Databooks. Medrano and Khosla define Ascher Pendant Sums as a pendant cord that is the sum of a range of other pendants. Three common types of Sums exist, and they occur in two directions, Left versus Right. The three common types are Pendant Pendant Sums, Indexed Pendant Sums, and Colored Pendant Sums.

  • Pendant Pendant Sums have the sum cord equal to the sum of the values of a set of contiguous summand cords, for example:

    G1P1 = G3P5 + G4P1 + G4P2    (A subset of contiguous pendant cords from Group 3 and 4)

  • Indexed Pendant Sums have the sum cord equal to the sum of the values of the indexed (position of the cord in its group) summand cords, for example:

    G1P4 = G3P4 + G4P4 + G5P4    (Every 4th pendant from Groups 3,4, and 5)

  • Colored Pendant Sums have the sum cord equal to the sum of the values of colored summand cords across groups, regardless of their position, for example:

    G1PRed = G3PRed + G4PRed + G5PRed    (Every Red pendant from Groups 3,4, and 5)

These three types of sums often exist simultaneously. A visual inspection of their network graphs can guide the reveal of these summation type layers and their juxtapositions. Indexed pendant sums and colored pendant sums are often used as sum hierarchies - for example, the Purucucho khipu hierarchy studied by Carrie Brezine in Brezine2005 and the restatement of KH0082/AS069 and KH0083/AS070 by Karen Thompson in Thompson2018.

1.1.3 Ascher Sum Directions

Sums can be in two directions: Right-Handed and Left-Handed. A Right-Handed sum has the summands are to the right of the sum cord (think of your right-handed thumb summing the values of the right hand fingers). The obverse, Left-Handed Sums, have the summands (the left fingers) to the left of the left thumb (the sum).

Make a fist, then turn your hands so the thumbs are sticking in and facing each other.

A Right-Handed Sum has 10 = 1+2+3+4 with the sum on the right-thumb, and the fingers representing 1,2,3,4. Right-Handed Sum

Right-handed sums are conventionally colored as Red (using the mnemonic R for Red/Right-Handed)

\(RightHandedSum = \displaystyle\sum_{i,j=A}^{B} G_i P_j\)

A Left-Handed Sum has 1+2+3+4 = 10 with the sum on the left-thumb, and the fingers representing 1,2,3,4. Left-Handed Sum

Left-handed sums are conventionally colored as Blue.

\(\sum_{i,j=A}^{B} G_i P_j = LeftHandedSum\)

As you will come to see, sum directions are important - like links in a spreadsheet, they provide a network graph of summations. That network graph can be analyzed to understand the structure of the khipu. Here for example is the pendant-pendant sum network graph for KH0696. Notice all the arrows, both red and blue, that link into g4p1.

KH0696 Sum Network Graph
Click on image for a larger view

1.1.4 Sum Signs

Medrano and Khosla observed that the sum cord was most commonly White. In addition, since it often was of a higher value than the often neighboring summand cords, it could be more easily visually identified as a sum. However, markers for summand ranges (which were not visually as obvious) remained undiscovered until the authors’ discovery of Figure-8-Knots as summand range markers.

1.2    -      Minus: The Discovery of Subtraction

The Aschers also noticed that some khipus had pendant-subsidiary differences. For example, KH0031 has a property that relates adjacent pendants to their neighbor and their neighbor’s subsidiary.

In group 1, wherever the pendant value is larger than its subsidiary’s value, the difference is the value of another pendant in the group. That is:

\(P_{i}-P_{i,s1}=P_{j}for(i,j)=(5,4),(7,8),(9,10),(10,4)\)

By contrast, in group 8, whenever the pendant value is smaller than its subsidiary’ s value, the difference is the value of another pendant in the group. Namely,

\(P_{i,s1}-P_{i}=P_{j}for(i,j)=(1,2),(2,1),(4,1)\)

This pattern, constrained, by nearest neighbor, not just by any other pendant in the group, was described by Medrano & Khosla and is now called a pendant-subsidiary difference relationship. Although subtractions are uncommon in the KFG corpus (~5% of the total of pendant pendant sums, indexed pendant sums, and colored pendant sums), pendant-subsidiary difference relationships occur with enough frequency to be considered a subtraction operator. Nonetheless, the corpus is clearly telling us to be cautious when framing a khipu equation as a subtraction. It may be masquerading, with it’s true nature being an addition, not a subtraction.

1.3    (,)      Range: The Discovery of Figure 8 Knots as Parenthetical Summand Range Markers

In Medrano and Khosla’s article, left hanging, in the Notes section, was note 6:

Many of the sets of summand cords in KH0468 also either begin or end with the value one, designated by a so-called figure eight knot.

This observation was later investigated by the authors. It was discovered that figure-8-knots were used as optional left and/or right parentheses to mark summand regions. Since the focus of this article is on the algebra of Equal Sums, we will not delve deeply into the details of Figure-8-Knots as summand range markers. However, considerable elaboration of Figure-8-Knots as summand markers is provided in the KhipuFieldGuide page dedicated to Figure-8-Knots.

1.4    Ă—, Ă·, √      Times, Divide & Square Root: The (Still Missing) Discovery of Higher Order Arithmetic

There is little actual evidence of multiplication and division represented in the literature. Numerous instance exist in Ascher’s Databooks, of muliplication and division by 2 and 10 exist, but the authors have not uncovered actual khipu examples of generalized multiplication or division where three cords exist, x, y, and z, and x*y=z or x/y=z, where x,y,and z are all non-trivial values. For example, when we simultaneously search for:

  • Three contiguous pendant cords
  • Any two of the cords are factors, and the third is the product
  • The values are non-trivial

We find only 9 examples in our entire KFG database:

  1. KH0075 p11=7, p12=35, p13=5
  2. KH0081 p46=39, p47=3, p48=117
  3. KH0130 p7=52, p8=2, p9=26
  4. KH0156 p174=28, p175=4, p176=7
  5. KH0303 p25=156, p26=13, p27=12
  6. KH0304 p25=156, p26=13, p27=12 (khipu identical to KH0303)
  7. KH0326 p105=7, p106=119, p107=17
  8. KH0476 p46=36, p47=6, p48=6
  9. KH0600 p107=4, p108=52, p109=13

Urton has proposed that taxation by a fixed amount existed in the Incahuasi khipus KH0503, KH0504, KH0505, and KH0514. There is no multiplication, however in these khipus, just a pre-multiplication step with the result shown in the khipu as a “tax” on the state.

Ascher has noted that KH0229(see notes 9-11) has cords that are multiples of 17, and two numbers that as ratios approximate the sqrt of 2.

1.5    =      Equals: The Discovery of Equal Sums

Enabled by new Data Visualization techniques, it has been discovered that sum cords can simultaneously participate as a Left-Handed and Right-Handed sum. Named Equal Sums, khipus with equal sums exhibit intriguing characteristics that are described in this study.

The basic idea of an Equal Sum is that given a set of pendant cords, PL1, PL2,PL3, PLn, on the left, and a set of pendant cords, on the right, PR1, PR2,PR3, PRn that the sum of ΣPLeft i, and the sum of ΣPRight j are = (“equal”). Note that the number of summands on the left does not have to equal the number of summands on the right, just that their sums are equal.

\[ \sum_{Left_i = left_A}^{left_B} Cord Value[Left_i] = Equal Sum Cord = \sum_{Right_j = right_C}^{right_D} Cord Value[Right_j] \]

where the cord ordinals B-A >=2 and D-C >=2 (ie. there must be at least two summands on each side)

This restriction of two summands is artificial (favoring precision over recall in our search for Equal Sums). Consequently, more than one summand must be represented from either side. This means the Equal Sum cord is representing a multiterm equations. i.e.

Given two equations:

1.) \(A + B + C = D + E\) 2.) \({p_1} = A + B + C\)

then, because these are equations, substitution and cancellation are possible. i.e.

    \(A + B + C + K = D + E + K\)
    \(\Rightarrow A + B + C = D + E\)    Cancel Ks
    \(\Rightarrow A + B + C - (D + E) = 0\)    Rearrange terms
    \(\Rightarrow {p_1} = D + E\)    Substitution

These types of equal sums exist for:

  • Pendant-Pendant Sums, where the sum pendant is the summation of a set of contiguous pendant summands,
  • Colored-Pendant Sums, where the sum pendant is the summation of a set of contiguous by color in a group pendant summands
  • Indexed-Pendant Sums, where the sum pendant is the summation of a set of contiguous by index in a group pendant summands
Code
import math
import random
from random import choices

import numpy as np
import pandas as pd
from pandas import Series, DataFrame

# Plotly
import plotly
from plotly.offline import iplot, init_notebook_mode 
import plotly.graph_objs as go
import plotly.express as px
import plotly.figure_factory as ff
plotly.offline.init_notebook_mode(connected = False)

import utils_loom as uloom
import utils_khipu as ukhipu
import qollqa_chuspa as qc
from collections import Counter
from utils_math import get_statistics
Code
from fieldmark_ascher_pendant_pendant_sum import FieldmarkPendantPendantSum
from fieldmark_ascher_colored_pendant_sum import FieldmarkColoredPendantSum
from fieldmark_ascher_indexed_pendant_sum import FieldmarkIndexedPendantSum
from fieldmark_equal_sums import FieldmarkEqualSums

(khipu_dict, all_khipus) = qc.fetch_khipus()

equal_sums_fieldmark = FieldmarkEqualSums()
equal_sums_fieldmark_df = equal_sums_fieldmark.fieldmark_df()
equal_sums_relations_df = equal_sums_fieldmark.relations_df()
Code
do_print = False
for aKhipu in all_khipus:
    pendant_cords = aKhipu.pendant_cords()

    for cord in pendant_cords[:-2]:
        next_cord = pendant_cords[pendant_cords.index(cord)+1]
        next_next_cord = pendant_cords[pendant_cords.index(next_cord)+1]
        A = cord.knotted_value
        B = next_cord.knotted_value
        C = next_next_cord.knotted_value
        A_name = cord.pendant_name
        B_name = next_cord.pendant_name
        C_name = next_next_cord.pendant_name
        non_trivial_values = all((x%10)!=0 and (x!=1) for x in [A, B, C])
        at_least_one_non_trivial_product = max([A,B,C]) > 25
        multiplier_exists = ((A==B*C) or (B==A*C) or (C==A*B))
        if (non_trivial_values and at_least_one_non_trivial_product and multiplier_exists):
            if do_print: print(f"Found multiplier: {aKhipu.kfg_name()} {A_name}={A}, {B_name}={B}, {C_name}={C}")
Code
do_print = False
for aKhipu in all_khipus:
    pendant_cords = aKhipu.pendant_cords()

    for cord in pendant_cords[:-2]:
        next_cord = pendant_cords[pendant_cords.index(cord)+1]
        thiscords_subs = cord.subsidiary_cords
        A = cord.knotted_value
        B = next_cord.knotted_value
        for C_cord in thiscords_subs:
            C = C_cord.knotted_value
            C_name = C_cord.pendant_name
            non_trivial_values = all((x%10)!=0 and (x!=1) for x in [A, B, C])
            at_least_one_non_trivial_product = max([A,B,C]) > 25
            multiplier_exists = ((A==B*C) or (B==A*C) or (C==A*B))
            if (non_trivial_values and at_least_one_non_trivial_product and multiplier_exists):
                if do_print: print(f"Found multiplier: {aKhipu.kfg_name()} {A_name}={A}, {B_name}={B}, {C_name}={C}")

2. Case Studies

Now that we have an idea of what an Equal Sum is, lets look at some examples.

2.1. Case Study 1 - KH0258 - Sum Arrangements with Mixed Verso/Recto Attachments

An obvious use of Equal Sums would be to indicate a distinction between moieties as outlined by Manuel Medrano in his Santa Valley Khipu article using mixed Verso/Recto attachments. A simple example might be to ensure that the two moeities contributed equally. Statistics, confirm that Equal Sums do often associate with mixed Verso/Recto attachments. Despite 27% of the KFG database lacking attachment information, a search of the KFG database reveals several candidate khipus with Equal Sums using mixed Verso/Recto attachments.

KH0258 serves as an example. KH0258, a Chachapoyas khipu, has a left-hand section of all Verso cords, followed by right-hand section of all Recto cords, spanning groups 13 to 31. As you can see the main action is in group 7 and group 12, just before the transition from verso to recto.

New data visualization approaches, adapted from the algorithms of map label placement (too many labels, not enough space) now enable direct overlay of summations on khipus. These summation drawings are, in-effect, X-rays that reveal the khipu’s ‘internal’ structure. In the diagram below, Equal Sums are highlighted in yellow, and MultiSummands, those cords involved in more than one sum relationship, are highlighted in green.

Examine g12p2 which has a value of 218 and sums 17 verso cords on the left and 14 recto cords on the right.


Click on image for a larger view

2.1. Case Study 1 - KH0258 - Sum Arrangements with Mixed Verso/Recto Attachments

An obvious use of Equal Sums would be to indicate a distinction between moieties as outlined by Manuel Medrano in his Santa Valley Khipu article using mixed Verso/Recto attachments. A simple example might be to ensure that the two moeities contributed equally. Statistics shown below, confirm that Equal Sums do often associate with mixed Verso/Recto attachments. Despite 27% of the KFG database lacking attachment information, a search of the KFG database reveals several candidate khipus with Equal Sums using mixed Verso/Recto attachments.

KH0258 serves as an example. KH0258, a Chachapoyas khipu, has a left-hand section of all Verso cords, followed by right-hand section of all Recto cords, spanning groups 13 to 31. As you can see the main action is in group 7 and group 12, just before the transition from verso to recto.

New data visualization approaches, adapted from the algorithms of map label placement (too many labels, not enough space) now enable direct overlay of summations on khipus. These summation drawings are, in-effect, X-rays that reveal the khipu’s ‘internal’ structure. In the diagram below, Equal Sums are highlighted in yellow, and MultiSummands, those cords involved in more than one sum relationship, are highlighted in green.

Examine g12p2 which has a value of 218 and sums 17 verso cords on the left and 14 recto cords on the right.


Click on image for a larger view

Code
do_print = False
num_non_matching_sets = 0
num_matching_sets = 0
for index, row in equal_sums_relations_df.iterrows():
    kfg_name = row['kfg_name']
    khipu = khipu_dict[kfg_name]
    left_handed_summands = khipu.cords_from_sum_string(row['left_handed_summand_string'])
    right_handed_summands = khipu.cords_from_sum_string(row['right_handed_summand_string'])
    left_handed_attachments = [cord.attachment for cord in left_handed_summands]
    right_handed_attachments = [cord.attachment for cord in right_handed_summands]
    if not (any([x == 'U' for x in left_handed_attachments]) or any([x == 'U' for x in right_handed_attachments])):
        num_matching_sets += 1
        if (set(left_handed_attachments) != set(right_handed_attachments)):
            num_non_matching_sets += 1
            sum_cord = khipu.find_cord_named(row['sum_cord_name'])
            sum_group_name = sum_cord.group_name
            sum_value = sum_cord.knotted_value
            if do_print: print(f"{kfg_name} - {sum_group_name}:{sum_value} - {len(left_handed_attachments)} != {len(right_handed_attachments)}")


#if do_print: 
# print(f"Percent of Equal Sums with non matching attachments: {uloom.percent_info(num_non_matching_sets, len(equal_sums_relations_df))}")

2.2. Case Study 2 - KH0696

Our second case study of an equal cord is KH0696, a small khipu of 38 pendants that has been well-studied by most affiliates of the KhipuFieldGuide including the lead author. We just saw, in KH0258, a case of Verso cords then Recto cords. Once again, the layout here is similar, and it is near these Verso/Recto boundaries that the interesting action occurs.

KH0696 has common arithmetic “signs” such as one white Equal Sum cord, g4p1, serving as a loop pendant. Let’s examine this Equal Sum cord and see what it tells us.

KH0696 Summap
Click on image for a larger view

2.2.1. G4P1

This khipu has an equality cord, g4p1 (we’ll call it T), which is equal to the left-handed sum of all the pendant cords that preceed it, and the sum of two cords on its right g5p1:68 and g5p2:81.

     \[\displaystyle\sum_{g_1 p_1}^{g_3 p_8} \Rightarrow T(g_{4} p_{1:149}) \Leftarrow g_5 p_{1:68} + g_5 p_{2:81}\]

Examine g5p1:68. Its summands span the Center of the khipu — g1p14 through g3p2. There’s a span of pendant to the Left of this pendant span and a span of pendants to the Right.

KH0696 spans
Click on image for a larger view

Let L (Left) be the sum of the pendants between p1 — p12 (g1p1 to g1p12). (12 cords)
Let LK be the value of the pendant just to the right of L p13 (g1p13). (1 cord)
Let C (Center) be the sum of the pendants between p14 to p25 (g1p14 to g3p2). (12 cords) This is also the value of right-hand summand g5p1.
Let RK be the value of the pendant preceeding R p26(g3p3). (1 cord)
Let R (Right) be the sum of the pendants p27 — p31 (g3p4 to g3p8) (5 cords)

    \(51 = L \Leftarrow \displaystyle\sum_{g_1 p_1}^{g_1 p_{12}}\)
    \(6 = L_K \Leftarrow {g_1 p_{13}}\)
    \(68 = C \Leftarrow \displaystyle\sum_{g_1 p_{14}}^{g_3 p_2}\)
    \(7 = R_K \Leftarrow {g_3 p_3}\)
    \(17 = R \Leftarrow \displaystyle\sum_{g_3 p_4}^{g_3 p_8}\)

We know for the left-handed summands:
    L:51 + LK:6 + C:68 + Rk:7 + R:17 = T:149

We also know that for the two right-handed summands:
    T:149 = g5p1:68 + g5p2:81
    g5p1:68 = C:68
    g5p2:81 = LK:6 + C:68 + RK:7

Substituting and simplifying:
\(L_{:51} + L_{k:6} + C_{:68} + R_{k:7} + R_{:17} = 149 = g_5p_1{:68} + g_5p_2{:81}\)
\(L_{:51} + L_{k:6} + C_{:68} + R_{k:7} + R_{:17} = C_{:68} + (L_{k:6} + C_{:68} + R_{k:7})\)
\(L_{:51} + R_{:17} = C_{:68}\)     Canceling the C’s and LK and RK
\(C_{:68} = L_{:51} + R_{:17}\)

This matches a classic statement of double-entry accounting patterns.

Assets        =  Equity   + Liabilities
CCenterSum =  LLeftSum + RRightSum

You can now see how double-entry accounting might be implemented in a khipu with the left, center, and right summands as the equity, assets, and liabilities, and individual cords representing accounts such as peanuts, peppers, potatoes, etc.

But wait, there’s more!
Examination of the white pendant cord groups g4 and g5 by Karen Thompson reveals that they are crosstab sums of information from the Left, Center, and Right summands.

  • g4p1:149 is a crosstab sum of (L:51 + LK:6) + C:68 + (RK:7 + R:17)
  • g5p1:68 is a crosstab sum of C:68
  • g5p2:81 is a crosstab sum of (L:51 + LK:6) + (RK:7 + R:17).
    This elegant result derives from C = L + R and g5p2 = C + LK + RK
  • g5p3:33 is a crosstab sum of g1p3 to g1p12 - note that g5p3:33 and the next cord g5p4:8 are colored Brown, not White.
  • g5p4:8 + g5p5:27 is a crosstab sum of (L:51 + R:17) - g5p3:33
  • g5p6:13 is a crosstab sum of LK:6 + RK:7

This interpretation is strong for KH0696 because the overlapping relations produce a specific three-part decomposition and crosstab structure. However, the existence of an Equal Sum alone does not establish that every such Equal Sum encodes a similar accounting pattern.

2.2.2 Khipus similar to KH0696

Let’s continue to examine khipus with similar equal sums. First lets find all pendant-pendant equal sums, where one of the handed sums has 2 summands and the other is >= 4 summands.

Khipus with One Equal Sum, With # Summands == 2, And # Other Side Summands >= 4:

KFG Name # Pendants Group Name # Delta Summands
KH0696 38 g4p1 29
KH0305 73 g11p1 6
KH0546 58 g4p1 5

Unfortunately, none of these khipus have the same double-entry accounting pattern we just reviewed. However, when we expand the search to two Equal sums, we find a similar example, KH0447, whose second Equal Sum Cord g9p4 has a similar pattern:


Click on image for a larger view

As we can see from the Equal Sums histogram below, khipus with multiple equal sums exist. How might that be used to encode information?

Code
do_debug = False

if do_debug:
    ak001 = khipu_dict['KH0696']
    
    def pendant_cord_sum_range(aKhipu, startcordname, endcordname):
        return sum([theCord.knotted_value for theCord in aKhipu.find_pendant_cord_range(startcordname, endcordname)])

    print(f"L:  {pendant_cord_sum_range(ak001, 'g1p1', 'g1p12')=}")
    print(f"Lk: {pendant_cord_sum_range(ak001, 'g1p13', 'g1p13')=}")
    print(f"C:  {pendant_cord_sum_range(ak001, 'g1p14', 'g3p2')=}")
    print(f"Rk: {pendant_cord_sum_range(ak001, 'g3p3', 'g3p3')=}")
    print(f"R:  {pendant_cord_sum_range(ak001, 'g3p4', 'g3p8')=}")
    print(f"{51+6+68+7+17=}\n")
    # print("57+24 must equal 13+68")

    # ak001 = khipu_dict['KH0696']
    # pendant_cords = ak001.pendant_cords()
    # for cord in pendant_cords:
    #     print(f"{cord.pendant_name=} {cord.group_name=}")

2.2.1. G4P1

This khipu has an equality cord, g4p1 (we’ll call it T), which is equal to the left-handed sum of all the pendant cords that preceed it, and the sum of two cords on its right g5p1:68 and g5p2:81.

    \(\displaystyle\sum_{g_1 p_1}^{g_3 p_8} \Rightarrow T(g{4} p{1}_{:149}) \Leftarrow g_5 p_{1:68} + g_5 p_{2:81}\)

Examine g5p1:68. Its summands span the Center of the khipu — g1p14 through g3p2. There’s a span of pendant to the Left of this pendant span and a span of pendants to the Right.

KH0696 spans

Let L (Left) be the sum of the pendants between p1 — p12 (g1p1 to g1p12). (12 cords)
Let LK be the value of the pendant just to the right of L p13 (g1p13). (1 cord)
Let C (Center) be the sum of the pendants between p14 to p25 (g1p14 to g3p2). (12 cords) This is also the value of right-hand summand g5p1.
Let RK be the value of the pendant preceeding R p26(g3p3). (1 cord)
Let R (Right) be the sum of the pendants p27 — p31 (g3p4 to g3p8) (5 cords)

    \(51 = L \Leftarrow \displaystyle\sum_{g_1 p_1}^{g_1 p_12}\)


    \(6 = L_K \Leftarrow {g_1 p_13}\)


    \(68 = C \Leftarrow \displaystyle\sum_{g_1 p_14}^{g_3 p_2}\)


    \(7 = R_K \Leftarrow {g_3 p_3}\)


    \(17 = R \Leftarrow \displaystyle\sum_{g_3 p_4}^{g_3 p_8}\)


We know for the left-handed summands:
    L:51 + LK:6 + C:68 + Rk:7 + R:17 = T:149

We also know that for the two right-handed summands:
    T:149 = g5p1:68 + g5p2:81
     g5p1:68 = C:68
     g5p2:81 = LK:6 + C:68 + RK:7

Substituting and simplifying:
\(L_{:51} + L_{k:6} + C_{:68} + R_{k:7} + R_{:17} = 149 = g_5p_1{:68} + g_5p_2{:81}\)
\(L_{:51} + L_{k:6} + C_{:68} + R_{k:7} + R_{:17} = C_{:68} + (L_{k:6} + C_{:68} + R_{k:7})\)
\(L_{:51} + R_{:17} = C_{:68}\)     Canceling the C’s and LK and RK
\(C_{:68} = L_{:51} + R_{:17}\)

This is a double-entry accounting pattern!

Assets        =  Equity   + Liabilities
CCenterSum =  LLeftSum + RRightSum

This is an example of how double-entry accounting might be implemented in a khipu with the left, center, and right summands as the equity, assets, and liabilities, and individual cords representing accounts such as peanuts, peppers, potatoes, etc.

But wait, there’s more!
Examination of the white pendant cord groups g4 and g5 by Karen Thompson reveals that they are crosstab sums of information from the Left, Center, and Right summands.

  • g4p1:149 is a crosstab sum of (L:51 + LK:6) + C:68 + (RK:7 + R:17)
  • g5p1:68 is a crosstab sum of C:68
  • g5p2:81 is a crosstab sum of (L:51 + LK:6) + (RK:7 + R:17).
    This elegant result derives from C = L + R and g5p2 = C + LK + RK
  • g5p3:33 is a crosstab sum of g1p3 to g1p12
  • g5p4:8 + g5p5:27 is a crosstab sum of (L:51 + R:17) - g5p3:33
  • g5p6:13 is a crosstab sum of LK:6 + RK:7

2.2.2 Khipus similar to KH0696

Let’s continue to examine khipus with similar equal sums. First lets find all pendant-pendant equal sums, where one of the handed sums is 2 and the other is >= 4.

Code
one_equal_sum_df = equal_sums_fieldmark_df[equal_sums_fieldmark_df['num_equal_sums']==1] # type: ignore
Code
one_equal_df = equal_sums_fieldmark_df[equal_sums_fieldmark_df['num_equal_sums']==1]# type: ignore
one_equal_kfg_names = sorted(one_equal_df['kfg_name'].unique())

one_equal_relations_df = equal_sums_relations_df[equal_sums_relations_df['kfg_name'].isin(one_equal_kfg_names)]# type: ignore
Code
min_spread = 4
def is_double_entry(aRow):
    return ((aRow.num_left_summands == 2) or (aRow.num_right_summands == 2)) and (abs(aRow.delta_num_summands) >= min_spread)
double_entry_mask = one_equal_relations_df.apply(is_double_entry, axis=1)
double_entry_df = one_equal_relations_df[double_entry_mask]
sorted_double_entry_df = double_entry_df.sort_values(
    by="delta_num_summands",
    key=lambda col: col.abs(),
    ascending=False
)

# print(f"{len(sorted_double_entry_df)} One Equal Sum Khipus exist with # summands == 2, and # summands >= {min_spread}")
# sorted_double_entry_df.head(5)
Code
# Many of the equal sums are in KH0082/AS069 - the monster Chilean Khipu. We're going to ignore AS069
do_print = False
if do_print:
    print("| KFG Name |  # Pendants | Group Name | # Delta Summands | ")
    print("| -------- | --------------------------------------------- | --------------------------- |--------------------------- |")

    for row in sorted_double_entry_df.head(7).itertuples():
        kfg_name = row.kfg_name
        if kfg_name != "KH0082":
            pendant_cord_name = row.sum_cord_name
            theKhipu = khipu_dict[kfg_name]
            equal_sum_cord = theKhipu.find_cord_named(pendant_cord_name)
            group_name = equal_sum_cord.group_name
            num_pendants = theKhipu.num_pendant_cords()
            image_url = f"../fieldmarks/pendant_pendant_sum/html/{kfg_name}_B_sumkhipu.html"

            print(f"| <a target=\"__blank\" href=\"{image_url}\">{kfg_name}</a> | {num_pendants} | {group_name} | {abs(row.delta_num_summands)} |")

Khipus with One Equal Sum, With # Summands == 2, And # Other Side Summands >= 4:

KFG Name # Pendants Group Name # Delta Summands
KH0696 38 g4p1 29
KH0305 73 g11p1 6
KH0546 58 g4p1 5

Unfortunately, none of these khipus have the same double-entry accounting pattern we just reviewed. However, when we expand the search to two Equal sums, we find a similar example, KH0447, whose second Equal Sum Cord g9p4 has a similar pattern:


Click on image for a larger view

As we can see from the Equal Sums histogram below, khipus with multiple equal sums exist. How might that be used to encode information?

2.3. Case Study 3 - KH0232 - Pyramid Sum Arrangements

A look at a summap displays how equal sums work. In a summap, khipu pendant cords are drawn, abstractly as columns, from left to right in the conventional order. Then a row at a time, based on the sum cord index/ordinal, a sum relationship is drawn.

Below is the summap of KH0232. Equal Sums are indcated by a yellow field. MultiSummands, those cords used as summands by more than one sum relationship, are overlaid with a green field.


Click on image for a larger view

Note especially, the Equal Sum cords p32 through p39 in the white middle #7 group. This khipu exhibits classic summation fieldmarks. As noted, in Medrano and Khosla’s article, many of the sums totals lie on groups starting with a white cord. The summands are marked by figure-eight knot boundary markers as noted in Figure 8 Knots with Ascher Summations.

This khipu exhibits what we would expect of equal sum’s occuring in khipus


  • The sums occur in the middle, there being more opportunities for BOTH left and right handed sums in the middle, than on the edges.
  • The sums increase in the middle.

These khipus exhibit a “pyramidal” arrangement of sums, with the maximum sum value being roughly in the middle, by building up sums from both sides, towards the center. It is as if the left third of the khipu and the right third were independent khipus that were then tied together with a middle khipu with the additions. In fact, Ascher’s notes for KH0232 are quite intriguing on this aspect.

2.4. Case Study 4 - KH0057 - MultiSummands and Waterfall Sum Arrangements

Now let’s look at another khipu KH0057.


Click on image for a larger view

This khipu exhibits a “waterfall” arrangement of sums. In this case, for example, the longest path is [‘p1’, ‘p27’, ‘p35’, ‘p51’, ‘p57’]. P1 the final sum, and contains as it summands p27, which in turn contains as it summands p35 
. all the way to p57. Waterfall khipus tend to have the biggest sum be at p1 or close to that location.

Although waterfalls and pyramid arrangements are visually different, it could be argued that they are topologically similar, and the visual architecture is dependent on the khipukamayoq. We will return to this below.

3. Exploratory Data Analysis

3.1 Database

This study is based on 711 khipus contained in the Khipu Field Guide.

The database is highly heterogenous, and consists of everything from “sheep herder” khipus to the 8 meter long suspected Bolivian khipu KH0082. However, the database also contains anomalous khipus such as Canuto khipus or khipus consisting of solely one cord. Many of the khipus show broken cords, unmeasured values such as spin or cord attachment, etc. Consequently, we should expect large standard deviations in any statistic we measure. An overview of the database quality is available at the “Completeness/Anomalous” table at the Building the KFG Database page

3.2 Investigations

The following investigations are performed.

  1. Khipus by Number of Equal Sums
  2. What are Common Fieldmarks associated with Equal Sums?
  3. Equal Sums by Number of Pendant Cords
  4. Equal Sums by Mean Pendant Cord Value
  5. Equal Sums by Number of Summands (Left vs Right)
  6. Equal Sums by Color
  7. Equal Sums by Banded vs Seriated
  8. Equal Sums by Recto vs Verso Attachments
  9. Equal Sums by Number of Multi-Summands

3.1. Khipus by Number of Equal Sums

Code
pps_fieldmark = FieldmarkPendantPendantSum()
cps_fieldmark = FieldmarkColoredPendantSum()
ips_fieldmark = FieldmarkIndexedPendantSum()
equal_sums_fieldmark = FieldmarkEqualSums()

pps_fieldmark_df = pps_fieldmark.relations_df()
cps_fieldmark_df = cps_fieldmark.relations_df()
ips_fieldmark_df = ips_fieldmark.relations_df()
equal_sums_fieldmark_df = equal_sums_fieldmark.relations_df()    

pps_equal_sum_dict = {aKhipu.kfg_name(): pps_fieldmark.num_equal_sums(aKhipu.kfg_name()) for aKhipu in all_khipus}
cps_equal_sum_dict = {aKhipu.kfg_name(): cps_fieldmark.num_equal_sums(aKhipu.kfg_name()) for aKhipu in all_khipus}
ips_equal_sum_dict = {aKhipu.kfg_name(): ips_fieldmark.num_equal_sums(aKhipu.kfg_name()) for aKhipu in all_khipus}

pps_equal_sum_khipus = {aKhipu for aKhipu in all_khipus if pps_equal_sum_dict[aKhipu.kfg_name()] > 0}
cps_equal_sum_khipus = {aKhipu for aKhipu in all_khipus if cps_equal_sum_dict[aKhipu.kfg_name()] > 0}
ips_equal_sum_khipus = {aKhipu for aKhipu in all_khipus if ips_equal_sum_dict[aKhipu.kfg_name()] > 0}
Code
equal_sum_fieldmark_df = equal_sums_fieldmark.fieldmark_df()
equal_sum_relations_df = equal_sums_fieldmark.relations_df()
# equal_sum_fieldmark_df.head(10)
Code
matching_khipus_df = equal_sum_fieldmark_df[equal_sum_fieldmark_df['num_equal_sums']>0]  # type: ignore
num_equal_sum_khipus = len(matching_khipus_df)   

print(f"Number of khipus with at least 1 Equal Sum: {uloom.percent_info(num_equal_sum_khipus,len(all_khipus))}")   
at_least_4_matching_khipus_df = matching_khipus_df[matching_khipus_df['num_equal_sums']>=4]
num_4_equal_sum_khipus = len(at_least_4_matching_khipus_df)   
#print(f"Number of khipus with at least 4 equal sums: {num_4_equal_sum_khipus}") 
at_least_10_matching_khipus_df = matching_khipus_df[matching_khipus_df['num_equal_sums']>=10]
#print(f"Number of khipus with at least 10 equal sums: {len(at_least_10_matching_khipus_df)}") 
#matching_khipus_df.head(3)
#print()

num_all_khipu_equal_sums = len(equal_sum_relations_df) # type: ignore
num_pps_khipu_equal_sums = len(equal_sum_relations_df[equal_sum_relations_df['fieldmark_name']=="pendant_pendant_sum"])  # type: ignore
num_cps_khipu_equal_sums = len(equal_sum_relations_df[equal_sum_relations_df['fieldmark_name']=="colored_pendant_sum"])  # type: ignore
num_ips_khipu_equal_sums = len(equal_sum_relations_df[equal_sum_relations_df['fieldmark_name']=="indexed_pendant_sum"])  # type: ignore

print(f"Number of All Equal Sums: {num_all_khipu_equal_sums}")
print(f"Number of Pendant-Pendant Equal Sums: {uloom.percent_info(num_pps_khipu_equal_sums, num_all_khipu_equal_sums)}") 
print(f"Number of Colored-Pendant Equal Sums: {uloom.percent_info(num_cps_khipu_equal_sums, num_all_khipu_equal_sums)}")
print(f"Number of Indexed-Pendant Equal Sums: {uloom.percent_info(num_ips_khipu_equal_sums, num_all_khipu_equal_sums)}")

# Build a histogram of the number of equal sums per khipu using plotly histogram
def num_khipus_with_equal_sums(min_num_equal_sums):
    return len(equal_sum_fieldmark_df[equal_sum_fieldmark_df['num_equal_sums'] == min_num_equal_sums])  # type: ignore
num_equal_sums_histogram = equal_sum_fieldmark_df['num_equal_sums'].value_counts().sort_index()  # type: ignore
max_num_equal_sums = num_equal_sums_histogram.index[-1]

num_equal_sums = [i for i in range(0, max_num_equal_sums + 1)]
num_khipus_with_equal_sums = [num_khipus_with_equal_sums(num_sums) for num_sums in num_equal_sums]  # type: ignore
num_khipus_with_equal_sums_df = pd.DataFrame({'num_equal_sums': num_equal_sums, 'num_khipus': num_khipus_with_equal_sums})
fig = (px.bar(num_khipus_with_equal_sums_df, x="num_equal_sums", y="num_khipus", log_y=True,
                   title="Histogram of Number of Equal Sums per Khipu")
        .update_layout(xaxis_title="Number of Equal Sums", 
            yaxis_title="Number of Khipus (log scale)", 
            height=600, 
            font=dict(family="ETBookOT"))
        .show())
Number of khipus with at least 1 Equal Sum: 27% (194 of 711)
Number of All Equal Sums: 2084
Number of Pendant-Pendant Equal Sums: 59% (1228 of 2084)
Number of Colored-Pendant Equal Sums: 33% (694 of 2084)
Number of Indexed-Pendant Equal Sums: 8% (162 of 2084)

3.2 What are Common Fieldmarks Associated with Equal Sums

Code
# Useful functions to collect the statistics about a khipu's equal sums for a given fieldmark
def equal_color_map_df(aKFGName, aFieldmark):
    theKhipu = khipu_dict[aKFGName]
    aFieldmark_df = aFieldmark.relations_df()
    equal_sums_df = equal_sums_fieldmark.relations_df()
    equal_relations_df = equal_sums_df[equal_sums_df['kfg_name'] == aKFGName]
    print(len(equal_relations_df))
    khipu_records = []
 
    for index, aEqualSumRelation in equal_relations_df.iterrows():
        left_handed_summand_string = aEqualSumRelation['left_handed_summand_string']
        right_handed_summand_string = aEqualSumRelation['right_handed_summand_string']
        left_handed_summand_string = aEqualSumRelation['left_handed_summand_string']
        right_handed_summand_string = aEqualSumRelation['right_handed_summand_string']
        left_handed_cords = []
        right_handed_cords = [] 
        if left_handed_summand_string and isinstance(left_handed_summand_string, str) and len(left_handed_summand_string) > 0:
            left_handed_cords = aFieldmark.cords_from_sum_string(theKhipu, left_handed_summand_string)
        if right_handed_summand_string and isinstance(right_handed_summand_string, str) and len(right_handed_summand_string) > 0:   
            right_handed_cords = aFieldmark.cords_from_sum_string(theKhipu, right_handed_summand_string)

        left_handed_cord_colors = [cord.main_color() for cord in left_handed_cords]
        # left_counter = Counter(left_handed_cord_colors)
        #count_left_handed_cords_colors = ", ".join([f"{color}({count})" for color, count in left_counter.items()])   
        num_left_handed_cord_colors = len(set(left_handed_cord_colors))
        
        right_handed_cord_colors = [cord.main_color() for cord in right_handed_cords]
        # right_counter = Counter(right_handed_cord_colors)
        #count_right_handed_cords_colors = ", ".join([f"{color}({count})" for color, count in right_counter.items()])
        num_right_handed_cord_colors = len(set(right_handed_cord_colors))
        khipu_records.append({'kfg_name':aEqualSumRelation['kfg_name'],
                              'sum_cord_name':aEqualSumRelation['sum_cord_name'],
                              'left_handed_summand_string':left_handed_summand_string,
                              'right_handed_summand_string':right_handed_summand_string,
                              'num_left_handed_cord_colors':num_left_handed_cord_colors,
                              'num_right_handed_cord_colors':num_right_handed_cord_colors,
                              'left_handed_cord_colors':','.join(left_handed_cord_colors),
                              'right_handed_cord_colors':','.join(right_handed_cord_colors),
                              'num_recto_cords':theKhipu.num_recto_cords(),
                              'num_verso_cords':theKhipu.num_verso_cords(),
                            })    
    
    equal_color_map_df = pd.DataFrame(khipu_records)
    return equal_color_map_df

def equal_sum_statistics(aKFGName, aFieldmark):
    aKhipu = khipu_dict[aKFGName]
    equal_sum_cord_names = aFieldmark.equal_sums(aKFGName)
    if len(equal_sum_cord_names) == 0:
        return None
    equal_sum_cords = [aKhipu[aCordName] for aCordName in equal_sum_cord_names]
    last_sum_cord_ordinal = int((ukhipu.sort_cord_names_by_ordinal(equal_sum_cord_names)[-1])[1:])
    equal_sum_cord_values = [aCord.knotted_value for aCord in equal_sum_cords]
    
    similar_cord_values_counter = [(value, count) for value,count in Counter(equal_sum_cord_values).most_common() if count > 1]
    num_similar = sum([count for value, count in similar_cord_values_counter])
    percent_similar = 100.0*num_similar/len(equal_sum_cord_values)
    
    cord_deltas = [0] + [equal_sum_cord_values[i] - equal_sum_cord_values[i-1] for i in range(1, len(equal_sum_cord_values))]
    similar_cord_deltas_counter = [(value, count) for value,count in Counter(cord_deltas).most_common() if count > 1]
    num_similar_deltas = sum([count for value, count in similar_cord_deltas_counter])
    percent_similar_deltas = 100.0*num_similar_deltas/len(cord_deltas)
        
    cord_val_stats = get_statistics(equal_sum_cord_values)
    min_cord_value = cord_val_stats['min']
    mean_cord_value = cord_val_stats['mean']
    max_cord_value = cord_val_stats['max']
    stddev_cord_value = cord_val_stats['stddev']
    value_spread = cord_val_stats['spread']

    cord_delta_stats = get_statistics(cord_deltas)
    min_cord_delta = cord_delta_stats['min']
    mean_cord_delta = cord_delta_stats['mean']
    max_cord_delta = cord_delta_stats['max']
    stddev_cord_delta = cord_delta_stats['stddev']
    delta_spread = cord_delta_stats['spread']

    num_banded_groups = aKhipu.num_banded_groups()
    num_seriated_groups = aKhipu.num_seriated_groups()
    more_seriated = num_seriated_groups > num_banded_groups

    has_mixed_attachments = aKhipu.has_mixed_attachments()

    return {
        'kfg_name': aKhipu.kfg_name(),
        'fieldmark_name': aFieldmark.shortname, # 'pendant_pendant_sums' or 'colored_pendant_sums
        'num_pendant_cords': aKhipu.num_pendant_cords(),
        'num_equal_sums': len(equal_sum_cord_values),
        'last_sum_cord_ordinal': last_sum_cord_ordinal, 
        'cord_names': f"{equal_sum_cord_names}", #Convert list to string
        'cord_values': f"{equal_sum_cord_values}", #Convert list to string
        'similar_cord_values': similar_cord_values_counter,
        'num_similar': num_similar,
        'percent_similar': percent_similar,
        'min_cord_value': min_cord_value, 
        'mean_cord_value': mean_cord_value, 
        'max_cord_value': max_cord_value, 
        'stddev_cord_value': stddev_cord_value, 
        'value_spread': value_spread,
        'cord_deltas': f"{cord_deltas}", #Convert list to string
        'similar_cord_deltas': similar_cord_deltas_counter,
        'num_similar_deltas': num_similar_deltas,
        'percent_similar_deltas': percent_similar_deltas,
        'min_cord_delta': min_cord_delta, 
        'mean_cord_delta': mean_cord_delta,
        'max_cord_delta': max_cord_delta, 
        'stddev_cord_delta': stddev_cord_delta, 
        'delta_spread': delta_spread,

        'num_seriated_groups':num_seriated_groups,
        'num_banded_groups':num_banded_groups,
        'more_seriated':more_seriated, 
        
        'has_mixed_attachments': has_mixed_attachments,
        } 

def print_equal_sum_stats(aStatsRecord):
    print(f"Khipu: {aStatsRecord['kfg_name']} - Fieldmark: {aStatsRecord['fieldmark_name']}  - # Pendant Cords: {aStatsRecord['num_pendant_cords']}")
    print(f"\tNumber of Equal Sums: {aStatsRecord['num_equal_sums']}")
    print(f"\tCord Names: {aStatsRecord['cord_names']}")
    print(f"\tCord Values: {aStatsRecord['cord_values']}")
    print(f"\tMin, Mean, Max Cord Value: {aStatsRecord['min_cord_value']:.1f} - {float(aStatsRecord['mean_cord_value']):.1f} - {aStatsRecord['max_cord_value']:.1f} +/- {aStatsRecord['stddev_cord_value']:.1f} Spread:({aStatsRecord['value_spread']:.2f})")
    print(f"\tSimilar Delta Values: {aStatsRecord['similar_cord_values']}")
    print(f"\tSimilar Delta Values: {aStatsRecord['num_similar']}:({aStatsRecord['percent_similar']:.0f}%) - {aStatsRecord['similar_cord_values']}")
    print(f"\tCord Deltas: {aStatsRecord['cord_deltas']}")
    print(f"\tMin, Mean, Max Cord Delta: {aStatsRecord['min_cord_delta']:.1f} - {aStatsRecord['mean_cord_delta']:.1f} - {aStatsRecord['max_cord_delta']:.1f} +/- {aStatsRecord['stddev_cord_delta']:.1f} Spread:({aStatsRecord['delta_spread']:.2f})")
    print(f"\tMore Seriated: {aStatsRecord['more_seriated']}, # Seriated Groups = {aStatsRecord['num_seriated_groups']}, # Banded Groups = {aStatsRecord['num_banded_groups']})")
    print(f"\tMixed Attachments: {aStatsRecord['has_mixed_attachments']}")

equal_sums_fieldmark_df = equal_sums_fieldmark.fieldmark_df() 
matching_khipus_df = equal_sums_fieldmark_df[equal_sums_fieldmark_df['num_equal_sums']>0]
def build_equal_sums_dict():
    matching_khipus_df.sort_values(by='num_equal_pps_sums', ascending=False, inplace=True)
    pps_matching_khipus = matching_khipus_df['kfg_name'].tolist()
    matching_khipus_df.sort_values(by='num_equal_cps_sums', ascending=False, inplace=True)
    cps_matching_khipus = matching_khipus_df['kfg_name'].tolist()

    pps_equal_sum_stats = dict()
    for kfg_name in pps_matching_khipus:
        if stats := equal_sum_statistics(kfg_name, pps_fieldmark):
            pps_equal_sum_stats[kfg_name] = stats

    cps_equal_sum_stats = dict()
    for kfg_name in cps_matching_khipus:
        if stats := equal_sum_statistics(kfg_name, cps_fieldmark):
            cps_equal_sum_stats[kfg_name] = stats

    return (pps_equal_sum_stats, cps_equal_sum_stats)

(pps_equal_sums_stats, cps_equal_sums_stats) = build_equal_sums_dict()
Code
from fieldmark_table import KhipuFieldmarkTable
def build_augmented_fieldmarks_df():
    # Fetch the fieldmarks dataframe
    the_fieldmark_df = KhipuFieldmarkTable().fieldmark_df()
    the_KFG_Names = the_fieldmark_df['kfg_name'].tolist()
    the_fieldmark_df.sort_values(by='mean_cord_value', ascending=True, inplace=True)
    the_fieldmark_df.set_index('kfg_name', inplace=True)
    the_fieldmark_df.drop(columns=[
                                   'similarity_index', 
                                   'pendant_pendant_sum', 'colored_pendant_sum', 'indexed_pendant_sum', 'indexed_subsidiary_sum',
                                   'cord_group_count', 'group_group_sum', 'group_sum_bands', 'percent_s_knots', 'subsidiary_cord_count', 
                                   'fanout_ratio', 'mean_cords_per_group', 'pendant_sub_neighbor', 'subsidiary_pendant_sum', 'ascher_decreasing_group',
                                   'z_cords', 
                                   ], inplace=True)
    
    return (the_KFG_Names, the_fieldmark_df)

(fieldmark_KFG_Names, augmented_fieldmark_df) = build_augmented_fieldmarks_df()

do_debug = False
if do_debug: 
    nl_char = "\n\t"
    print(f"Fieldmarks = {nl_char}{uloom.multiline(augmented_fieldmark_df.columns.tolist(), split_char=',', continuation_char=','+nl_char)}")
Code
# Create Cosine Similarity Matrix
import utils_pandas as upanda 

cosine_sim_matrix = upanda.CosineSimilarityMatrix(augmented_fieldmark_df, start_column="equal_sums", start_row="KH0239")
sorted_df = cosine_sim_matrix.sort(sort_columns = True, sort_rows = True)
sorted_columns = sorted_df.columns.tolist()
do_debug = False
if do_debug: 
        print(f"Fieldmarks sorted by Closest Match using Cosine Similarity = {nl_char}{uloom.multiline(sorted_columns, split_char=',', continuation_char=nl_char)}")
      
fig = (px.imshow(sorted_df.to_numpy().tolist(),
                labels=dict(x="Fieldmark", y="Khipu Name", ),
                x=sorted_df.columns,
                y=fieldmark_KFG_Names,
                width=944, height=2000, aspect="auto")
        .update_coloraxes(showscale=False)
        .update_layout(
                title= 'Fieldmark Features - Sorted by Cosine Similarity - Hover over line for more info',
                xaxis= dict(tickangle=270),
                xaxis_nticks=5000,
                font=dict(family="ETBookOT"))
        .show())


Because so few khipus have equal sums, we have to read this diagram carefully. As you see above recto vs verso cords light up yellow, as do percent_z_knots, indicating moeity information is present in khipus with Equal Sum Cords. We will return to this thought below.

3.3 Equal Sums by Number of Pendant Cords

As might be expected, the number of equal sums per khipu is a good indicator of the complexity of the khipu and vice versa.

Code
# Make a plotly scatter plot of the number of equal sums per khipu vs the number of pendant cords
matching_kfg_names = matching_khipus_df['kfg_name'].tolist()
num_equal_sums = [matching_khipus_df[matching_khipus_df['kfg_name']==kfg_name].num_equal_sums.tolist()[0] for kfg_name in matching_kfg_names]
num_cords = [khipu_dict[kfg_name].num_pendant_cords() for kfg_name in matching_kfg_names]
df = pd.DataFrame({'num_cords': num_cords, 'num_equal_sums': num_equal_sums, 'kfg_name': matching_kfg_names})
fig = (px.scatter(df, x=num_cords, y=num_equal_sums, log_y=True, log_x=True,
         hover_data=['kfg_name', 'num_cords', 'num_equal_sums'])
        .update_layout(title="Number of Equal Sums vs Number of Pendant Cords", 
            xaxis_title="Number of Pendant Cords (log scale)", 
            yaxis_title="Number of Equal Sums (log scale)", 
            height=600, 
            font=dict(family="ETBookOT"))
        .show())

3.4 Equal Sums by Mean Pendant Cord Value

Let’s compare Equal Sum khipus by mean cord value of non-zero pendant cord:

Code
non_zero_cord_stats = []
all_the_equal_sum_khipus = pps_equal_sum_khipus.union(cps_equal_sum_khipus).union(ips_equal_sum_khipus)
min_node_size = 6
for aKhipu in all_khipus:
    is_equal_sum_khipu = "equal_sum" if (aKhipu in all_the_equal_sum_khipus) else "non_equal_sum"
    num_equal_sums = pps_equal_sum_dict[aKhipu.kfg_name()] + cps_equal_sum_dict[aKhipu.kfg_name()] + ips_equal_sum_dict[aKhipu.kfg_name()]
    if is_equal_sum_khipu == "non_equal_sum": 
        node_size=min_node_size    
    else:
        node_size = max(min_node_size,num_equal_sums)
    khipu_record =  {
        'kfg_name': aKhipu.kfg_name(),
        'mean_non_zero_cord_value': aKhipu.mean_non_zero_pendant_cord_value(),
        'num_non_zero_pendant_cords': len(aKhipu.non_zero_pendant_cord_values()),
        'is_equal_sum_khipu': is_equal_sum_khipu,
        'num_equal_sums': num_equal_sums,
        'node_size': node_size,
    }
    non_zero_cord_stats.append(khipu_record)

non_zero_cord_stats_df = pd.DataFrame(non_zero_cord_stats)

plot_title = f"{len(all_khipus)} Khipus by # Non-Zero Pendant Cords vs. Mean Value Non-Zero Pendant Cords / Size=# Equal Sums"
fig = (px.scatter(non_zero_cord_stats_df, x="num_non_zero_pendant_cords", y="mean_non_zero_cord_value", 
                  log_y=True, log_x=True, 
                  color="is_equal_sum_khipu", color_discrete_sequence=["black", "red"],
                  size="node_size",
                  hover_name='kfg_name', hover_data=['num_non_zero_pendant_cords', 'mean_non_zero_cord_value', 'num_equal_sums'], 
                  title=plot_title,
                  width=944, height=944)
          .update_layout(showlegend=True, 
                         yaxis_title="Mean Non Zero Pendant Cord_value (Log Scale)", 
                         xaxis_title="# Pendant_Cords(Log Scale)",
                         font=dict(family="ETBookOT"))
          .update(layout_coloraxis_showscale=True).show()
       )

The above graphic illuminates how clearly special Equal Sum khipus are - they appear in larger (by # of non-zero pendant cord) khipus (above 25 cords), and they rarely exceed 500 in average cord value. This graphic fits with the above cosine similarity fieldmark sorting, which indicates the number of pendant cords is a predictor of where there is a high number of Equal Sums.

Code
from statistics import mean

def equal_sum_cord_stats(aKhipu, aFieldmark):
    num_fieldmark_sums = aFieldmark.num_sums(aKhipu.kfg_name())
    equal_sum_cord_names = aFieldmark.equal_sums(aKhipu.kfg_name())
    if len(equal_sum_cord_names) == 0:
        return None
    equal_sum_cords = [aKhipu[aCordName] for aCordName in equal_sum_cord_names]
    equal_sum_cord_values = [aCord.knotted_value for aCord in equal_sum_cords]
    
    min_cord_value = min(equal_sum_cord_values)
    mean_cord_value = mean(equal_sum_cord_values)
    max_cord_value = max(equal_sum_cord_values)
    max_cord_index = equal_sum_cord_values.index(max_cord_value)
    max_cord_location = float(equal_sum_cord_names[max_cord_index][1:])/float(aKhipu.num_pendant_cords())

    max_sum_path_length, path_sum_names = aFieldmark.maximum_sum_path(aKhipu.kfg_name())
    pendant_path_sum_names = [aKhipu.find_cord_named(sum_name).pendant_name for sum_name in path_sum_names]
    longest_path = ",".join(pendant_path_sum_names)
    final_ordinal = int(pendant_path_sum_names[-1][1:])
    normalized_final_ordinal = float(final_ordinal)/float(aKhipu.num_pendant_cords())
    
    mean_num_summands = mean([mean(aFieldmark.num_summands(aKhipu.kfg_name(), aCordName)) for aCordName in equal_sum_cord_names])
    return {
        'kfg_name': aKhipu.kfg_name(),
        'fieldmark_name': aFieldmark.shortname, # 'pendant_pendant_sums' or 'colored_pendant_sums
        'num_pendant_cords': aKhipu.num_pendant_cords(),
        'num_fieldmark_sums': num_fieldmark_sums,
        'num_equal_sums': len(equal_sum_cord_values),
        'cord_names': f"{equal_sum_cord_names}", #Convert list to string
        'cord_values': f"{equal_sum_cord_values}", #Convert list to string
        'min_cord_value': min_cord_value,
        'mean_cord_value': round(mean_cord_value,1),
        'max_cord_value': max_cord_value,
        'mean_num_summands': round(mean_num_summands,0),
        'max_sum_path_length': max_sum_path_length,
        'longest_path': longest_path,
        'final_ordinal': final_ordinal,
        'normalized_final_ordinal': normalized_final_ordinal,
        'normalized_max_cord_location': round(max_cord_location,3), 
        }

pps_khipu_equal_sum_cord_stats = [equal_sum_cord_stats(aKhipu, pps_fieldmark) for aKhipu in pps_equal_sum_khipus]
pps_khipu_equal_sum_cord_stats_df = pd.DataFrame(pps_khipu_equal_sum_cord_stats)
#pps_khipu_equal_sum_cord_stats_df.head()
Code
plot_title = "Khipus by # of Equal Sums vs. Mean Cord Value w/Size=Mean Number of Summands"
fig = (px.scatter(pps_khipu_equal_sum_cord_stats_df, x="num_equal_sums", y="mean_cord_value", log_y=True, log_x=True,
                  size="mean_num_summands", color="max_cord_value",
                  labels={"kfg_name": "KFG Name", 
                          "num_pendant_cords": "# Pendant Cords",
                          "num_equal_sums": "# Equal Sums", 
                          "max_sum_path_length": "Max Sum Path Length",
                          "mean_cord_value": "Mean Cord Value", "max_cord_value": "Max Cord Value", "mean_num_summands": "Mean # Summands per Equal Sum"
                          },
                  hover_name='kfg_name', hover_data=['num_pendant_cords', 'num_equal_sums', 'mean_cord_value', 'max_cord_value', 'mean_num_summands'], 
                  title=plot_title,
                  width=944, height=944)
          .update_layout(showlegend=True, 
                         yaxis_title="Mean Equal Sum Cord Value (Log Scale)", 
                         xaxis_title="Number of Equal Sums (Log Scale)",
                         font=dict(family="ETBookOT"))
          .update(layout_coloraxis_showscale=True)
          .show()
       )

3.5 Equal Sums by Number of Summands (Left vs Right)

Code
equal_sums_df = equal_sums_fieldmark.fieldmark_df() 
equal_sums_relations_df = equal_sums_fieldmark.relations_df()    

equal_sums_relations_df['max_num_summands'] = equal_sums_relations_df[['num_left_summands', 'num_right_summands']].max(axis=1)
equal_sums_relations_df = equal_sums_relations_df.merge(
    equal_sums_df[['kfg_name', 'num_equal_sums']].rename(columns={'num_equal_sums': 'total_equal_sums'}),
    on='kfg_name', how='left'
)

num_pendant_cords_column = [khipu_dict[kfg_name].num_pendant_cords() for kfg_name in equal_sums_relations_df['kfg_name']]
equal_sums_relations_df['num_pendant_cords'] = num_pendant_cords_column
# equal_sums_relations_df.head()
Code
fig = (px.histogram(equal_sums_relations_df, x="delta_num_summands", log_y=True, 
                   title="Histogram of Deltas of # Summands (# Right Summands - # Left Summands)")
         .update_layout(font=dict(family="ETBookOT"))
         .show())

So the majority of Equal Sums have roughly the same number of summands on each side of the equation. The slight right shift in the histogram reflects, perhaps, the same 55%/45% preponderance of right-handed vs left-handed sums witnessed in Medrano and Khosla’s article. Still, there are a substantial portion of khipus whose delta num_summand values are greater than say 20? Let’s look at the data another way by graphing left vs right summands for all the Equal Sums

Code
#equal_sums_relations_df = equal_sums_fieldmark.relations_df()

plot_title = "Equal  Sums by # Of Right vs Left Summands (Log Scale) - Size=# Equal Sums - Hover for info"
fig = (px.scatter(equal_sums_relations_df, x="num_right_summands", y="num_left_summands", 
                  log_y=True, log_x=True,
                  size="total_equal_sums", 
                  color="sum_cord_value",
                  labels={"kfg_name": "KFG Name", 
                          "sum_cord_name": "Equal  Sum Cordname",
                          "sum_cord_value": "Equal  Sum Cord Value",
                          "num_left_summands": "# Left Summands",
                          "num_right_summands": "# Right Summands",
                          "delta_num_summands": "# Right - Left Summands",
                          }, 
                  hover_name='kfg_name', hover_data=['sum_cord_name', 'sum_cord_value', 'num_left_summands', 'num_right_summands', 'delta_num_summands'], 
                  title=plot_title,
                  width=944, height=944)
          .update_layout(showlegend=True, 
                         yaxis_title="# Left Summands (Log Scale)", 
                         xaxis_title="# Right Summands (Log Scale)",
                         font=dict(family="ETBookOT"))
          .update(layout_coloraxis_showscale=True)
          .show()
       ) 
Code
asymmetric_equal_sums_df = equal_sums_relations_df[equal_sums_relations_df['abs_delta_num_summands'] > 7]
print(f"Number of Asymmetric Equal  Sums > 7 Summands: {len(asymmetric_equal_sums_df)}")
print(f"Number of Asymmetric Equal  Sum Khipus > 7 Summands: {len(asymmetric_equal_sums_df['kfg_name'].unique())}")

asymmetric_equal_sums_df.sort_values(by='abs_delta_num_summands', ascending=False, inplace=True)
top_asymmetric_kfg_names = asymmetric_equal_sums_df.head(50)['kfg_name'].unique().tolist()
print(f"{top_asymmetric_kfg_names=}")

do_print = False
if do_print:
    make_equal_sums_network_table(kfg_names=top_asymmetric_kfg_names)
Number of Asymmetric Equal  Sums > 7 Summands: 267
Number of Asymmetric Equal  Sum Khipus > 7 Summands: 74
top_asymmetric_kfg_names=['KH0082', 'KH0468', 'KH0068', 'KH0699', 'KH0240', 'KH0601', 'KH0665', 'KH0225', 'KH0143']

The list of interesting asymmetrical equal sums includes our old friend KH0468/UR231 discussed in Medrano and Khosla! So the answer is there can be both kinds of equal sums in evidence.

KFG Name # Equal Pendant-Pendant Sums Pendant-Pendant SumMap # Equal Colored-Pendant Sums Colored-Pendant SumMap # Equal Indexed-Pendant Sums Indexed-Pendant SumMap
KH0068 12 Sum-map 1 Sum-map
KH0082 85 Sum-map 125 Sum-map 26 Sum-map
KH0143 11 Sum-map 1 Sum-map
KH0225 6 Sum-map 1 Sum-map
KH0240 45 Sum-map 59 Sum-map 3 Sum-map
KH0468 39 Sum-map
KH0601 7 Sum-map 1 Sum-map 2 Sum-map
KH0665 2 Sum-map
KH0699 18 Sum-map 15 Sum-map 1 Sum-map

 

3.6 Equal Sums by Color

First, let’s look at how colors are distributed in the equal sum cords.

Code
# EDA of Equal Sums by Color

kfg_names = equal_sums_relations_df['kfg_name'].to_list()
sum_cord_names = equal_sums_relations_df['sum_cord_name'].to_list()
cord_search = zip(kfg_names, sum_cord_names)
equal_sum_cords = [khipu_dict[kfg_name].find_cord_named(sum_cord_name) for kfg_name, sum_cord_name in cord_search]
num_equal_sums = len(equal_sum_cords)

equal_sum_colors = [cord.main_color(use_brezine_colors=True) for cord in equal_sum_cords]
from collections import Counter
equal_sum_colors_counter = Counter(equal_sum_colors)

common_colors = [color for color, count in equal_sum_colors_counter.most_common(20)]
common_counts = [count for color, count in equal_sum_colors_counter.most_common(20)]

from ascher_color import AscherColor
bar_colors = { color:AscherColor(color, 0, 0).average_color() for color in common_colors }
labels = [f"{c} ({c/num_equal_sums:.0%})" for c in common_counts]
title = f"Equal Sum Colors (by % of {num_equal_sums} Equal Sums)"
# Use plotly to draw a bar chart of the equal sum colors
import plotly.express as px

fig = (px.bar(
        x=common_colors, y=common_counts, text=labels, color=common_colors,
        color_discrete_map = bar_colors
        )
        .update_layout(
            title=title,
            xaxis_title='Color',
            yaxis_title='Count',
            font=dict(family="ETBookOT")
            )
        .show()
    )

White cords predominate in the Equal Sum cords - which is expected but at a percentage of 38% of their peers, compared to 29% of overall pendant-pendant sums.

Next let’s look at the distribution of the number of different colors in a equal sum summand list.

Code
aFieldmark = equal_sums_fieldmark
full_fieldmark_df = aFieldmark.relations_df()
num_left_handed_cord_colors = full_fieldmark_df['num_left_handed_cord_colors'].values.tolist()
num_right_handed_cord_colors = full_fieldmark_df['num_right_handed_cord_colors'].values.tolist()

counter_left_handed_cord_colors = sorted(Counter(num_left_handed_cord_colors).items(),key=lambda x: x[0])
counter_right_handed_cord_colors = sorted(Counter(num_right_handed_cord_colors).items(),key=lambda x: x[0])
counter_both_handed_cord_colors = sorted(Counter(num_left_handed_cord_colors+num_right_handed_cord_colors).items(),key=lambda x: x[0])

total_x_values = [item[0] for item in counter_both_handed_cord_colors]
total_y_values = [item[1] for item in counter_both_handed_cord_colors]
total_y_text =  [f"{x:.1f} %" for x in 100.0*np.cumsum(total_y_values)/np.sum(total_y_values)]
fig = (go.Figure(data=[go.Bar(x=total_x_values, y=total_y_values, text = total_y_text)])
        .update_layout(title='Total #Colors/Equal Sum (Top of Split Bars is Cumulative Sum % of Summands Colors)', 
                       xaxis_title='# of Pendant Cord Colors in Double Sum', 
                       yaxis_title='# of Sums W/That # of Colors',
                       font=dict(family="ETBookOT"))
        .show())

So ~55% of all Equal Sums have only one color and ~75% are two colors or less!

3.7 Equal Sums by Banded vs Seriated

Code
pps_khipus = list(pps_equal_sums_stats.keys())
cps_khipus = list(cps_equal_sums_stats.keys())
print(len(pps_equal_sums_stats), len(cps_equal_sums_stats))

def khipu_group_stats(aKFGName):
    aKhipu = qc.fetch_khipu(aKFGName)
    num_banded_groups = aKhipu.num_banded_groups()
    num_seriated_groups = aKhipu.num_seriated_groups()
    more_seriated = num_seriated_groups > num_banded_groups
    return {'kfg_name':aKFGName, 'more_seriated':more_seriated, 'num_seriated_groups':num_seriated_groups, 'num_banded_groups':num_banded_groups}

def print_group_stats(aStatsList):
    for aStats in aStatsList:
        print(f"Khipu: {aStats['kfg_name']} - More Seriated: {aStats['more_seriated']}, Seriated Groups: {aStats['num_seriated_groups']}, Banded Groups: {aStats['num_banded_groups']} ")

pps_group_stats = [khipu_group_stats(kfg_name) for kfg_name in pps_khipus]  
cps_group_stats = [khipu_group_stats(kfg_name) for kfg_name in cps_khipus]
189 76
Code
pps_num_more_seriated = sum([1 for khipu in pps_group_stats if khipu['more_seriated']])
pps_group_stats.sort(key=lambda x: (x['more_seriated'], x['num_banded_groups'], x['num_seriated_groups']), reverse=False)    
percent_str = uloom.percent_info(pps_num_more_seriated, len(pps_group_stats))
print(f"For Equal Sum khipus: {percent_str} have more seriated groups/clusters than banded groups/clusters")
For Equal Sum khipus: 78% (147 of 189) have more seriated groups/clusters than banded groups/clusters

Equal sum khipus appear to be predominantly on seriated khipus. But the actual summands do not appear to be seriated. Furthermore in the fieldmark closesness test we did above, color-bands are closely associated with equal sums.

3.8 Equal Sums Association with Recto/Verso Cords - Mixed Attachments

Let’s look at percentages of khipus with mixed attachments (both recto and verso), compared to the general population of overall khipus, pendant-pendant-sum khipus and pendant-pendant sum khipus with Equal Sums:

Code
all_kfg_names = [aKhipu.kfg_name() for aKhipu in all_khipus]

the_pps_fieldmark_df = pps_fieldmark.fieldmark_df()
the_pps_khipus_df = the_pps_fieldmark_df[the_pps_fieldmark_df['num_sum_cords']>0]   
pps_kfg_names = ukhipu.kfg_order(the_pps_khipus_df['kfg_name'].tolist())
pps_khipus = [qc.fetch_khipu(aKFGName) for aKFGName in pps_kfg_names]

pps_sums_dict = {aKhipu.kfg_name(): pps_fieldmark.num_sums(aKhipu.kfg_name()) for aKhipu in all_khipus}
pps_equal_sum_dict = {aKhipu.kfg_name(): pps_fieldmark.num_equal_sums(aKhipu.kfg_name()) for aKhipu in all_khipus}
equal_khipus = [aKhipu for aKhipu in all_khipus if pps_equal_sum_dict[aKhipu.kfg_name()] > 0]
equal_kfg_names = [aKhipu.kfg_name() for aKhipu in equal_khipus]

all_recto_khipus = {aKhipu.kfg_name(): aKhipu.num_recto_cords() for aKhipu in all_khipus if aKhipu.num_recto_cords() > 0}
all_verso_khipus = {aKhipu.kfg_name(): aKhipu.num_verso_cords() for aKhipu in all_khipus if aKhipu.num_verso_cords() > 0}
all_non_attached_khipus = {kfg_name : 0 for kfg_name in all_kfg_names if not ((kfg_name in all_recto_khipus) or (kfg_name in all_verso_khipus))}
all_recto_kfg_names = list(all_recto_khipus.keys())
all_verso_kfg_names = list(all_verso_khipus.keys())
all_mixed_attachment_kfg_names = [aKhipu.kfg_name() for aKhipu in all_khipus if aKhipu.has_mixed_attachments()  ]
all_unattached_kfg_names = list(all_non_attached_khipus.keys())

print(f"Number of All Sum Khipus: {len(all_kfg_names)}")
recto_info = uloom.percent_info(len(all_recto_kfg_names), len(all_kfg_names), rounddigits=2)
print(f"Number of All Sum Khipus with Recto Cords: {recto_info}")
verso_info = uloom.percent_info(len(all_verso_kfg_names), len(all_kfg_names), rounddigits=2) 
print(f"Number of All Sum Khipus with Verso Cords: {verso_info}") 
mixed_attachment_info = uloom.percent_info(len(all_mixed_attachment_kfg_names), len(all_kfg_names), rounddigits=2)
print(f"Number of All Sum Khipus with Mixed Cord Attachments: {mixed_attachment_info}")
unattached_info = uloom.percent_info(len(all_unattached_kfg_names), len(all_kfg_names), rounddigits=2)   
print(f"Number of All Sum Khipus with Unknown Cord Attachments: {unattached_info}")
print()

pps_recto_khipus = {aKhipu.kfg_name(): aKhipu.num_recto_cords() for aKhipu in pps_khipus if aKhipu.num_recto_cords() > 0}
pps_verso_khipus = {aKhipu.kfg_name(): aKhipu.num_verso_cords() for aKhipu in pps_khipus if aKhipu.num_verso_cords() > 0}
pps_non_attached_khipus = {kfg_name : 0 for kfg_name in equal_kfg_names if not ((kfg_name in pps_recto_khipus) or (kfg_name in pps_verso_khipus))}
pps_recto_kfg_names = list(pps_recto_khipus.keys())
pps_verso_kfg_names = list(pps_verso_khipus.keys())
pps_mixed_attachment_kfg_names = [aKhipu.kfg_name() for aKhipu in pps_khipus if aKhipu.has_mixed_attachments()  ]
pps_unattached_kfg_names = list(pps_non_attached_khipus.keys())

print(f"Number of PPS Sum Khipus: {len(pps_kfg_names)}")
recto_info = uloom.percent_info(len(pps_recto_kfg_names), len(pps_kfg_names), rounddigits=2)
print(f"Number of PPS Sum Khipus with Recto Cords: {recto_info}")
verso_info = uloom.percent_info(len(pps_verso_kfg_names), len(pps_kfg_names), rounddigits=2) 
print(f"Number of PPS Sum Khipus with Verso Cords: {verso_info}") 
mixed_attachment_info = uloom.percent_info(len(pps_mixed_attachment_kfg_names), len(pps_kfg_names), rounddigits=2)
print(f"Number of PPS Sum Khipus with Mixed Cord Attachments: {mixed_attachment_info}")
unattached_info = uloom.percent_info(len(pps_unattached_kfg_names), len(pps_kfg_names), rounddigits=2)   
print(f"Number of PPS Sum Khipus with Unknown Cord Attachments: {unattached_info}")
print()

equal_recto_khipus = {aKhipu.kfg_name(): aKhipu.num_recto_cords() for aKhipu in equal_khipus if aKhipu.num_recto_cords() > 0}
equal_verso_khipus = {aKhipu.kfg_name(): aKhipu.num_verso_cords() for aKhipu in equal_khipus if aKhipu.num_verso_cords() > 0}
equal_non_attached_khipus = {kfg_name : 0 for kfg_name in equal_kfg_names if not ((kfg_name in equal_recto_khipus) or (kfg_name in equal_verso_khipus))}
equal_recto_kfg_names = list(equal_recto_khipus.keys())
equal_verso_kfg_names = list(equal_verso_khipus.keys())
equal_mixed_attachment_kfg_names = [aKhipu.kfg_name() for aKhipu in equal_khipus if aKhipu.has_mixed_attachments()  ]
equal_unattached_kfg_names = list(equal_non_attached_khipus.keys())

print(f"Number of Equal Sum Khipus: {len(equal_kfg_names)}")
recto_info = uloom.percent_info(len(equal_recto_kfg_names), len(equal_kfg_names), rounddigits=2)
print(f"Number of Equal Sum Khipus with Recto Cords: {recto_info}")
verso_info = uloom.percent_info(len(equal_verso_kfg_names), len(equal_kfg_names), rounddigits=2) 
print(f"Number of Equal Sum Khipus with Verso Cords: {verso_info}") 
mixed_attachment_info = uloom.percent_info(len(equal_mixed_attachment_kfg_names), len(equal_kfg_names), rounddigits=2)
print(f"Number of Equal Sum Khipus with Mixed Cord Attachments: {mixed_attachment_info}")
unattached_info = uloom.percent_info(len(equal_unattached_kfg_names), len(equal_kfg_names), rounddigits=2)   
print(f"Number of Equal Sum Khipus with Unknown Cord Attachments: {unattached_info}")
print()
Number of All Sum Khipus: 711
Number of All Sum Khipus with Recto Cords: 51% (364 of 711)
Number of All Sum Khipus with Verso Cords: 46% (327 of 711)
Number of All Sum Khipus with Mixed Cord Attachments: 26% (188 of 711)
Number of All Sum Khipus with Unknown Cord Attachments: 29% (208 of 711)

Number of PPS Sum Khipus: 410
Number of PPS Sum Khipus with Recto Cords: 55% (224 of 410)
Number of PPS Sum Khipus with Verso Cords: 53% (219 of 410)
Number of PPS Sum Khipus with Mixed Cord Attachments: 34% (141 of 410)
Number of PPS Sum Khipus with Unknown Cord Attachments: 10% (41 of 410)

Number of Equal Sum Khipus: 189
Number of Equal Sum Khipus with Recto Cords: 63% (119 of 189)
Number of Equal Sum Khipus with Verso Cords: 55% (104 of 189)
Number of Equal Sum Khipus with Mixed Cord Attachments: 40% (75 of 189)
Number of Equal Sum Khipus with Unknown Cord Attachments: 22% (41 of 189)

Here we see a significant increase (from 26% to 40%) of mixed attachment khipus when we move from the general base of all khipus to khipus with Equal Sums. This is added evidence that Equal Sums are allied with moiety information and/or ayni/reciprocity.

3.9 MultiSummands

We know from the fieldmark sorting above that MultiSummands most closely associate with the Equal Sum fieldmarks. What else can we learn? How closely do various measures associate?

Code
pps_sums_dict = {aKhipu.kfg_name(): pps_fieldmark.num_sums(aKhipu.kfg_name()) for aKhipu in all_khipus}
pps_equal_sum_dict = {aKhipu.kfg_name(): pps_fieldmark.num_equal_sums(aKhipu.kfg_name()) for aKhipu in all_khipus}
pps_multisummand_dict = {aKhipu.kfg_name(): pps_fieldmark.num_multisummands(aKhipu.kfg_name()) for aKhipu in all_khipus}
pps_path_length_dict = {aKhipu.kfg_name(): pps_fieldmark.maximum_sum_path(aKhipu.kfg_name())[0] for aKhipu in all_khipus}

print(f"Degrees Between Fieldmarks: # pps_sums and # equal sums  = {uloom.degrees_between_dicts(pps_equal_sum_dict, pps_sums_dict):.3}")
print(f"Degrees Between Fieldmarks: # pps multisummands and # equal_sums = {uloom.degrees_between_dicts(pps_equal_sum_dict, pps_multisummand_dict):.3}")
print(f"Degrees Between Fieldmarks: # pps multisummands and # pps_sums = {uloom.degrees_between_dicts(pps_sums_dict, pps_multisummand_dict):.3}")
print(f"Degrees Between Fieldmarks: # pps multisummands and pps_max_path_length = {uloom.degrees_between_dicts(pps_path_length_dict, pps_multisummand_dict):.3}")
Degrees Between Fieldmarks: # pps_sums and # equal sums  = 20.5
Degrees Between Fieldmarks: # pps multisummands and # equal_sums = 30.3
Degrees Between Fieldmarks: # pps multisummands and # pps_sums = 33.8
Degrees Between Fieldmarks: # pps multisummands and pps_max_path_length = 58.6

Well that’s not a useful approach.

4. Equal Sum Network Topology: Waterfall vs Pyramid vs


We can generate quick heuristics to search for Pyramid versus WaterfallEqual Sum topologies.

In his statistical review, AgustĂ­n Da Fieno Delucchi demonstrates a more generalized approach with more topologies.

4.1 Searching for Equal Sum Pyramid Topology Khipus

Code
plot_title = "Max Sum Path Length vs. Normalized Location-Max Equal Sum Cord / Size=# Equal Sums - Hover for info"
fig = (px.scatter(pps_khipu_equal_sum_cord_stats_df, x="max_sum_path_length", y="normalized_max_cord_location", 
                  size="num_equal_sums", 
                  color="max_cord_value",
                  labels={"kfg_name": "KFG Name", 
                          "num_pendant_cords": "# Pendant Cords",
                          "num_equal_sums": "# Equal Sums", 
                          "normalized_max_cord_location": "Normalized Location of Max Equal Sum Cord",
                          "max_sum_path_length": "Max Sum Path Length",
                          "mean_cord_value": "Mean Cord Value", "max_cord_value": "Max Cord Value", "mean_num_summands": "Mean # Summands per Equal Sum", 
                          "final_ordinal": "Final Ordinal in Sum Path", "max_cord_value": "Max Cord Value", "longest_path": "Longest Path"},
                  hover_name='kfg_name', hover_data=['num_pendant_cords', 'num_equal_sums', 'final_ordinal', 'max_cord_value', 'max_sum_path_length', 'longest_path'], 
                  title=plot_title,
                  width=944, height=944)
          .update_layout(showlegend=True, 
                         yaxis_title="Normalized Location of Max Equal Sum Cord", 
                         xaxis_title="Max Khipu Sum Path Length", 
                         font=dict(family="ETBookOT"))
          .update(layout_coloraxis_showscale=True).show()
       )

Hovering over the circles in the above graphic, four khipus with a high number of Equal Sums stand out as possible example “pyramid” khipus:

KFG Name Sum-map Notes
KH0081 Sum-map Clearly a “pyramid” style khipu
KH0192/UR1175 Sum-map Clearly a “pyramid” style khipu
KH0246/UR010 Sum-map Clearly a “pyramid” style khipu
KH0156/UR1140 Sum-map Not clearly a “pyramid” style khipu

4.2 Searching for Equal Sum Waterfall Topologies

Code
plot_title = "Max Sum Path Length vs. Last Ordinal in Longest Path (Size=# Equal Sums) - Hover for info"
fig = (px.scatter(pps_khipu_equal_sum_cord_stats_df, x="max_sum_path_length", y="normalized_final_ordinal", log_y=True,
                  size="num_pendant_cords",
                  color="max_cord_value",
                  labels={"kfg_name": "KFG Name", 
                          "num_pendant_cords": "# Pendant Cords",
                          "num_equal_sums": "# Equal Sums", 
                          "mean_cord_value": "Mean Cord Value", "max_cord_value": "Max Cord Value", "mean_num_summands": "Mean # Summands per Equal Sum", 
                          "normalized_final_ordinal": "Normalized Final Ordinal in Sum Path", 
                          "max_sum_path_length": "Max Sum Path Length",
                          "longest_path": "Longest Path"
                          },
                  hover_name='kfg_name', hover_data=['num_pendant_cords', 'num_equal_sums', 'normalized_final_ordinal', 'max_cord_value', 'max_sum_path_length', 'longest_path'], 
                  title=plot_title,
                  width=944, height=944)
          .update_layout(showlegend=True, 
                         yaxis_title="Normalized Ordinal Location of Final Cord in Longest Path (Log Scale)", 
                         xaxis_title="Max Khipu Sum Path Length",
                         font=dict(family="ETBookOT"))
          .update(layout_coloraxis_showscale=True).show()
       )

Hovering over the circles in the above graphic, four khipus at the bottom stand out as possible sample “waterfall” khipus. Similarly hovering over the sum path length of 4 identifies two “pyramid khipus” - KH0232, our case study, and KH0252

KFG Name Sum-map Notes
KH0435 KH0435 Sum-map Clearly a “waterfall” khipu
KH0692 KH0692 Sum-map Clearly a “waterfall” khipu
KH0232 KH0232 Sum-map Both a “waterfall” khipu, and a “pyramid” khipu sum architecture!
KH0232 KH0232 Sum-map Clearly a “pyramid” khipu
KH0252 KH0252 Sum-map Clearly a “pyramid” khipu

4.3 A Generalization of Equal Sum Topology and Statistical Review

In Agustín Da Fieno Delucchi’s independant statistical review he creates a network graph topology that includes all three sum relationships pendant-pendant, pendant-index, and pendant-color. From that he generalizes to the following topologies:

Type Example Number of Khipus
Waterfall 152
Pyramid 162
Cascade 147
Distributed 79

His interpretation of his data:

Multi-label is the norm. The most common single combination carries all five labels (50 components). Multi-root alone accounts for 93 % of components (183/196), confirming that the typical Equal Sum structure contains multiple top-level accounting entries whose underlying summand cords overlap — i.e., the weakly-connected component in the cord graph spans several simultaneous summation hierarchies rather than a single root.

Primary positional patterns. Pyramid (165) and Waterfall (153) are the most common positional labels. They co-occur in 133 of 165 Pyramid components (81 %), showing that a khipu which organizes Equal Sums around its midpoint almost always also opens with a directed grand-total chain. The two canonical archetypes are not alternatives — they are facets of the same structure.

Cascade (145, 74 %) co-occurs tightly with Waterfall (124/153 = 81 %). Deep chains (depth ≄ 3) almost always originate early in the khipu. This confirms that hierarchical depth is a feature of the Waterfall pattern, not an independent mode.

Distributed (79, 40 %). Four in ten components have Equal Sum cords spread across more than 40 % of the khipu’s physical length. Combined with the Multi-root finding, this suggests a “ledger” mode of organisation where accounting entries are distributed throughout the cord run rather than clustered at a single location.

Salience Ordering(./equal_sums_inquiry_stats.html#a-scribal-methodology-salience-ordering). Summand cord values decrease as they become farther from the sum cord. >The gradient suggests a deliberate recording convention: enter the most significant item first (adjacent to ∑), then proceed outward in descending order of magnitude. This is a form of salience ordering — structuring a list by importance rather than, say, arrival sequence or physical position. Equal Sum topologies provide an additional type of fieldmark to khipus just like cord color, or cord attachment.

5. Conclusions

This study has shown that Equal Sums:

  • Can serve as a Direct Assertion of equality, i.e., a simple balancing of sums; mathematically ∑A = ∑B . For example, we might want to ensure that the Hanan and Hurin moieties end up contributing equally. The pronounced association of Equal Sums with Recto/Verso symmetry suggests that this was a common case.

  • They can also serve as an Indirect Assertion of equality. In the above Case 1 study we saw an equal cord asserting a considerably more advanced mathematical equation: \(L_{:51} + L_{k:6} + C_{:68} + R_{k:7} + R_{:17} = C_{:68} + (L_{k:6} + C_{:68} + R_{k:7})\)

  • Since multiterm, nested equations exist, and can be identified, we now have the ability to manipulate the equations. We can now use substitution, rearrangement, cancellation, etc of terms in a khipu’s equations to understand the underlying structure of the khipu.

  • As an example, this ability allows us to search for, and find, khipus with double-entry accounting patterns, such as KH0696. Additionally, accounting practices such as crosstabs, are now visible.

  • Since we now have equations of equality, we can chain those equations together to form more complex equations. For example, Equal Sums can be chained together to form a structured topology, such as a Waterfall or Pyramid, or Cascade.

  • Similar to other khipus signs such as seriation or banding, recto vs verso, etc. these topologies provide an additional type of sign characterization to khipus in the field.

  • Finally, and perhaps the most importantly, we now have insights and techniques to allow us to map regions of a khipu by function. This section Adds these two sections, this section is a set of Crosstabs, etc.

6. Acknowledgements

The authors wish to express their gratitude to the following Khipu Field Guide Affiliates:

  • To Karen Thompson, without whom we would not have the wonderful Khipu Field Guide khipu database foundation we have now.
  • To Mackinley FitzPatrick, whose suggestion, that we use a Case Study approach, resulted in revealing the equational structures.
  • To Manuel Medrano, for framing our insights as a larger discussion about khipu algebra.
  • To Kylie Quave, for teaching us her insightful guidelines about writing, community, and joy.

7. Bibliography

  1. Ascher, Marcia, and Robert Ascher. 1978. Code of the Quipu Databook. Ann Arbor, MI: University of Michigan Press.
  2. Ascher, Marcia, and Robert Ascher. 1988. Code of the Quipu Databook II. Ithaca, NY: Ascher and Ascher.
  3. Buckmaster, Dale. 1974. “The Incan Quipu and the Jacobsen Hypothesis.” Journal of Accounting Research 12 (1): 178–181.
  4. Clindaniel, Jon. 2019. Toward a Grammar of the Inka Khipu: Investigating the Production of Non-numerical Signs. PhD dissertation, Department of Anthropology, Harvard University.
  5. Forrester, D. A. R. 1968. “The Incan Contribution to Double-Entry Accounting.” Journal of Accounting Research 6 (2): 283.
  6. Jacobsen, Lyle E. 1964. “The Ancient Inca Empire of Peru and the Double Entry Accounting Concept.” Journal of Accounting Research 2 (2): 221–228.
  7. Locke, L. Leland. 1923. The Ancient Quipu or Peruvian Knot Record. New York: American Museum of Natural History.
  8. Khosla, Ashok. 2026. The Khipu Field Guide. https://www.khipufieldguide.com.
  9. Medrano, Manuel, and Ashok Khosla. 2024. “How Can Data Science Contribute to Understanding the Khipu Code?” Journal of Latin American Antiquities, May 2024.
  10. Pizarro, Hernando. (1533) 1920. “A los señores oydores de la audiencia real de su magestad.” Informaciones sobre el antiguo PerĂș, edited by Horacio H. Urteaga, 16–180. Vol. 3. Lima: SanmartĂ­.
  11. Thompson, Karen M., 2024. A Numerical Connection Between Two Khipus. Ñawpa Pacha, pp.1-22.
  12. Thompson, Karen M. Private communication, September 8, 2026.
  13. Urton, Gary. 2005. “Khipu Archives: Duplicate Accounts and Identity Labels in the Inka Knotted String Records.” Latin American Antiquity 16 (2): 147–167.
  14. Urton, Gary. 2009. “Sin, Confession, and the Arts of Book- and Cord-Keeping: An Intercontinental and Transcultural Exploration of Accounting and Governmentality.” Comparative Studies in Society and History 51 (4): 801–831. https://doi.org/10.1017/S0010417509990144.
  15. Urton, Gary, and Alejandro Chu. 2019. “The Invention of Taxation in the Inka Empire.” Latin American Antiquity 30 (1): 1–16.

8. Appendix. An Introduction to Double Entry Accounting

8.1. The Development of Double Entry Accounting

Luca Pacioli, a Franciscan friar born around 1445 in Sansepolcro, Italy, is often called the “father of accounting,” though he didn’t actually invent double-entry bookkeeping. Italian merchants in Venice, Florence, and Genoa had been using versions of the system for roughly two centuries before he wrote about it. The earliest known example dates to a merchant’s ledger from 1299–1300. What Pacioli did was something different but equally important: in 1494 he published a massive 615-page math textbook, Summa de Arithmetica, which included a 27-page section describing this bookkeeping method in clear, systematic terms. He also chose to write in everyday Italian rather than scholarly Latin, and thanks to the recently invented printing press, his book spread across Europe far faster than any handwritten manual could have. In effect, he took a widespread but informally taught merchant practice and turned it into the first accessible instruction manual.

Double Entry Accounting is built on the fundamental equation of:
    
Assets = Equities + Liabilities


The system itself, what Pacioli called “the Method of Venice”, is built on one simple idea: every transaction has two sides, and they must always balance. If a business sells goods for cash, two things happen at once: cash goes up and revenue goes up. If a company borrows money, cash goes up and so does debt. Pacioli described three connected records for tracking this: the memoriale (a rough daily log of transactions), the giornale (a more formal journal), and the quaderno (the ledger, where entries were sorted into individual accounts). At the end of the process, a trial balance checked that total debits equaled total credits, and if they didn’t, that mismatch flagged an error or possible fraud. This was a major upgrade from the single-entry lists many merchants had used before, which made it nearly impossible to catch mistakes or get a true picture of a business’s finances.

The impact of this system went well beyond bookkeeping. Because records were now standardized and self-checking, business partners and investors could verify a company’s finances without having to simply trust the owner’s word, which made it easier for strangers to do business together and for companies to grow larger and more complex. Historians see this as one of the building blocks that helped modern capitalism expand. Pacioli’s personal life adds an interesting footnote to the story too: while working in Milan, he became close friends with Leonardo da Vinci, tutoring him in geometry while Leonardo illustrated Pacioli’s later book on the golden ratio, De Divina Proportione (1509); the only book Leonardo illustrated in his lifetime. More than 500 years later, the core logic Pacioli documented, balanced, double-sided entries, still underlies virtually every accounting system in use today, from simple spreadsheets to enterprise software.

8.2 How Double-Entry Accounting Works — An Example:

When you sell a computer in double-entry accounting, you would typically make entries to reflect the transaction in both the asset and equity sides of the accounting equation. Let’s break down the entry:

Identify the accounts involved:

  • You would credit (decrease) an asset account, such as “Inventory” (if you’re selling from your inventory).
  • You would debit (increase) an asset account, such as “Cash” or “Accounts Receivable” (if the customer pays immediately or on credit).
  • You would credit (increase) an income account, such as “Sales Revenue” or “Sales Income,” to recognize the revenue from the sale.

Determine the amounts:

  1. Debit the “Inventory” account for the cost of the computer that you sold.
  2. Credit the “Cash” account if the customer paid immediately or “Accounts Receivable” if they will pay later.
  3. Credit the “Sales Revenue” account for the selling price of the computer.

Here’s how you would list the entry:

Account Debit (+) Credit (-)
Inventory Cost of computer
Cash or Accounts Receivable Selling price of computer
Sales Revenue Selling price of computer

For example, if you sold a computer for $1,000 that originally cost you $800, and the customer paid in cash:

Account Debit (+) Credit (-)
Inventory $800
Cash $1,000
Sales Revenue $1,000

This entry reflects the decrease in inventory (debit), increase in cash (credit), and recognition of sales revenue (credit) from the computer sale.