1.1 INTRODUCTION: THERMODYNAMICS AND STATISTICAL MECHANICS OF THE PERFECT GAS
Ludwig Boltzmann, who spent much of his life studying statistical mechanics, died in 1906, by his own hand. Paul Ehrenfest, carrying on the work, died similarly in 1933. Now it is our turn to study statistical mechanics.
Perhaps it will be wise to approach the subject cautiously. We will begin by considering the simplest meaningful example, the perfect gas, in order to get the central concepts sorted out.
States of Matter, by David Goodstein
Appendix 3 - Pendant Pendant Sum Monte Carlo Simulation
In the evaluation of Ascher Pendant Pendant Sum relationships in the KFG, the following conclusions were made:
The percentage of right sums to left sums was 54%/46%
Roughly 70% of a khipuโs non-zero pendants are involved in being a sum or a summand.
1.8 sums exist for every pendant cord (including 0 pendant cords)
The average handedness for a khipu tended towards 0, with some khipus having extreme handedness (indicative of misjoined khipus)
With these conclusions in mind letโs compare the KFG pendant pendant sum distribution to a completely random distribution to see what we learn. Of special interest:
The number of sums per cord
The number of summands per sum
The total number of left vs right sums
The mean and std deviation of sum values
The mean and std deviation of sum handedness
To compare existing KFG khipus with simulated random khipus, we build a straw man khipu using either:
Values from the KFG (ie. the pendant cord values of each khipu) or
Values from a numerical distribution such as a uniform, bin sampled, or other distribution.
1. Building The Strawman Infrastructure
Since the strawman infrastructure is somewhat lengthy, it has been included at the end of this page.
2. Creating the Sample Distributions:
The obvious distribution to compare against is the Existing KFG:
Uniform - The simplest random distribution is a random uniform distribution between the min and max values of the KFG
Bin Sample - A distribution which very closely resembles that of the KFG is to pick cords at random from the existing KFG. This technique is also known as Bootstrapping
Jittered Bin - A distribution which more closely resembles that of the KFG is to pick cords at random from the existing KFG, and then add a random proportional offset (i.e. a jitter)
Thus we have one reference distribution, the KFG, and three random distributions, random uniform, random KFG bin samples and random jittered KFG bin samples. These distributions, and how they are created are detailed here.
Due to limits on page length, and on the amount of graphics plotly can draw, each of these distributions is created and examined on itโs own separate page.
For each distribution we create the sample distribution and then compare it with the existing KFG distribution
3. Conclusions and Insights
Examining the distributions reveals the following insights:
The number of sums is a function of the number of pendants and the sample distribution
Random values sampled from a random distribution (uniform, jittered, etc.) are separable from the existing KFG distribution by having
Larger mean sum values
Sums that are farther away from the summands (ie. a larger handedness)
Fewer summands
Fewer sums per pendant.
When the values are sampled from the existing KFG the number of sums INCREASES. However these sums usually have very few summands (relative to the existing KFG distribution), and a high mean value, indicating it is a large number plus a small number.
Random values of any kind tend to have an equal number of left and right sums, unlike the existing KFG distribution which tends towards a rough 60/40 split.
4. Source Code for Examination
4.1 Pendant Summer
Code
# To build the Strawman Khipu we use the same Pendant Summer that the pendant-pendant sum fieldmark uses.# We add the ability to calculate handedness based on cord position instead of cluster position#+======================================================================================================================+#| |#| Pendant Summer Class |#| |#+======================================================================================================================+class PendantSummer():""" Builds a list of contiguous sum ranges. Given: an ordered list of pendant cord values (knotted values) Returns a list of tuples (summand index, (start index, end_index)) where the summand index is the index of the summand in the list of pendant cord values. and the start/end index is the range pendant_cord_values[start_index:end_index] (i.e. end_index is +1...) The tuples are organized by right handed sums, and left handed sums or are None if no sum is found. Use: foo = PendantSummer('MY_KHIPU_NAME', [1, 0,3,4...]) # pendant cord values foo.right_sums # Can be none foo.left_sums # Can be none """def__init__(self, name, pendant_vals):self.name = nameself.right_pendant_vals = np.array(pendant_vals)self.left_pendant_vals = np.array(pendant_vals[::-1])self.num_pendants =len(pendant_vals)## Output values are stored here. (self.right_sums, self.right_handedness) =self.search_right_sums() (self.left_sums, self.left_handedness) =self.search_left_sums()self.num_left_sums =len(self.left_sums)self.num_right_sums =len(self.right_sums)self.num_sums =self.num_left_sums+self.num_right_sumsdef search_right_sums(self):self.right_sum_partials = [np.cumsum(self.right_pendant_vals[i:]) for i inrange(0,self.num_pendants)] right_sums = {}for summand_index inrange(self.num_pendants-2):# FILTER/REMOVE ANY SUMS <= 10ifself.right_pendant_vals[summand_index] >10:if found_search :=self.search_sum_at(summand_index, self.right_pendant_vals, self.right_sum_partials): right_sums[summand_index] = found_searchif right_sums: right_sums =self.filter_sums(right_sums) if right_sums: right_sums = {key: val for key, val insorted(right_sums.items(), key =lambda keyval: keyval[0])} right_handedness = []if right_sums:for (summand_index, sum_range) in right_sums.items(): (start_index, end_index) = sum_range handed_distance = (start_index+(end_index-1))/2- summand_index right_handedness.append(handed_distance)return (right_sums, right_handedness)def search_left_sums(self):self.left_sum_partials = [np.cumsum(self.left_pendant_vals[i:]) for i inrange(0,self.num_pendants)] left_sums = {}for summand_index inrange(self.num_pendants-2):# FILTER/REMOVE ANY SUMS <= 10ifself.left_pendant_vals[summand_index] >10:if found_search :=self.search_sum_at(summand_index, self.left_pendant_vals, self.left_sum_partials): (reversed_start_index, reversed_end_index) = found_search start_index =self.num_pendants-reversed_end_index end_index =self.num_pendants-reversed_start_index summand_index = (self.num_pendants-summand_index)-1 left_sums[summand_index] = (start_index,end_index)if left_sums: left_sums =self.filter_sums(left_sums) if left_sums: left_sums = {key: val for key, val insorted(left_sums.items(), key =lambda keyval: keyval[0])} left_handedness = []if left_sums:for (summand_index, sum_range) in left_sums.items(): (start_index, end_index) = sum_range handed_distance = (start_index+(end_index-1))/2- summand_index left_handedness.append(handed_distance)return (left_sums, left_handedness)def search_sum_at(self, summand_index, pendant_vals, sum_partials): search_val = pendant_vals[summand_index]for start_index inrange(summand_index +1, self.num_pendants):if pendant_vals[start_index] >0: cum_sum_partial = np.where(sum_partials[start_index]==search_val)iflen(cum_sum_partial[0]): end_index = cum_sum_partial[0][0] + start_index +1return (start_index, end_index) if end_index-start_index >=2elseNonereturnNonedef filter_sums(self, sums):# Note that we have already filtered to remove any searches whose sum cord <= 10# And any searches whose cord = 0 filtered_sums = {}for (summand_index, sum_range) in sums.items(): (start_index, end_index) = sum_range sum_cord =self.right_pendant_vals[summand_index]# Here we filter things like 5=1+1+1+1+1 keep_sum = sum_cord > (end_index-start_index)if keep_sum: filtered_sums[summand_index] = sum_rangereturn filtered_sumsdef search_cords_that_sum(aKhipu): left_sum_cord_tuples = [] right_sum_cord_tuples = [] pendant_cords = aKhipu.pendant_cords() pendant_vals = [aCord.knotted_value() for aCord in pendant_cords] pendant_summer = PendantSummer(aKhipu.name(), pendant_vals) right_sum_cord_tuples =[] left_sum_cord_tuples =[]for (summand_index, sum_range) in pendant_summer.right_sums.items(): right_sum_cord_tuples.append((pendant_cords[summand_index], pendant_cords[sum_range[0]:sum_range[1]]))for (summand_index, sum_range) in pendant_summer.left_sums.items(): left_sum_cord_tuples.append((pendant_cords[summand_index], pendant_cords[sum_range[0]:sum_range[1]]))return (left_sum_cord_tuples, right_sum_cord_tuples)
4.2 Strawman Khipu
Code
from statistics import mean, stdevclass StrawmanKhipu:def__init__(self, name, source, pendant_values):self.name = nameself.source = sourceself.pendant_values = pendant_valuesself.summer = PendantSummer(self.name, self.pendant_values)def num_nonzero_pendants(self):returnlen([aVal for aVal inself.pendant_values if aVal >0])def num_sums(self):returnself.summer.num_sumsdef num_right_sums(self):returnself.summer.num_right_sumsdef num_left_sums(self):returnself.summer.num_left_sumsdef num_sums_per_nonzero_pendant(self):returnfloat(self.num_sums())/float(self.num_nonzero_pendants()) ifself.num_nonzero_pendants() >0else0.0def num_cords_per_sum(self):returnfloat(self.num_nonzero_pendants())/float(self.num_sums()) ifself.num_sums() >0else0.0def mean_cord_value(self): the_mean = mean(self.pendant_values) ifself.pendant_values else0.0return the_meandef stdev_cord_value(self): the_stdev = stdev(self.pendant_values) iflen(self.pendant_values)>1else0.0return the_stdevdef mean_sum_value(self):ifself.summer.right_sums: right_indices =self.summer.right_sums.keys() # Summand Indices right_cord_vals = [self.pendant_values[i] for i in right_indices]else: right_cord_vals = []ifself.summer.left_sums: left_indices =self.summer.left_sums.keys() # Summand Indices left_cord_vals = [self.pendant_values[i] for i in left_indices]else: left_cord_vals = [] all_cord_vals = right_cord_vals+left_cord_vals the_mean = mean(all_cord_vals) if all_cord_vals else0.0return the_meandef stdev_sum_value(self):ifself.summer.right_sums: right_indices =self.summer.right_sums.keys() # Summand Indices right_cord_vals = [self.pendant_values[i] for i in right_indices]else: right_cord_vals = []ifself.summer.left_sums: left_indices =self.summer.left_sums.keys() # Summand Indices left_cord_vals = [self.pendant_values[i] for i in left_indices]else: left_cord_vals = [] all_cord_vals = right_cord_vals+left_cord_vals the_stdev = stdev(all_cord_vals) iflen(all_cord_vals)>1else0.0return the_stdevdef mean_num_summands(self): num_summands = []for (summand_index, sum_range) inself.summer.left_sums.items(): num_summands.append(sum_range[1]-sum_range[0])for (summand_index, sum_range) inself.summer.right_sums.items(): num_summands.append(sum_range[1]-sum_range[0])return mean(num_summands) if num_summands else0.0def stdev_num_summands(self): num_summands = []for (summand_index, sum_range) inself.summer.left_sums.items(): num_summands.append(sum_range[1]-sum_range[0])for (summand_index, sum_range) inself.summer.right_sums.items(): num_summands.append(sum_range[1]-sum_range[0])return stdev(num_summands) iflen(num_summands)>1else0.0def mean_right_handedness(self):return mean(self.summer.right_handedness) ifself.summer.right_handedness else0.0def stdev_right_handedness(self):return stdev(self.summer.right_handedness) iflen(self.summer.right_handedness)>1else0.0def mean_left_handedness(self):return mean(self.summer.left_handedness) ifself.summer.left_handedness else0.0def stdev_left_handedness(self):return stdev(self.summer.left_handedness) iflen(self.summer.left_handedness)>1else0.0def dataframe_tuple(self):return (self.name, self.source, self.summer.num_pendants, self.mean_cord_value(), self.stdev_cord_value(), self.num_right_sums(), self.num_left_sums(), self.num_sums(), self.mean_num_summands(), self.stdev_num_summands(),self.mean_sum_value(), self.stdev_sum_value(), self.num_sums_per_nonzero_pendant(),self.mean_right_handedness(), self.stdev_right_handedness(),self.mean_left_handedness(), self.stdev_left_handedness())@staticmethoddef dataframe_columns():return ["name", "source", "num_pendants", "mean_cord_value", "stdev_cord_value","num_right_sums", "num_left_sums", "num_sums", "mean_num_summands", "stdev_num_summands","mean_sum_value", "stdev_sum_value", "num_sums_per_nonzero_pendant", "mean_right_handedness", "stdev_right_handedness", "mean_left_handedness", "stdev_left_handedness"]