Simulate cultural interactions using Go and Python
Model and simulate how culture disseminates using Go and Jupyter Notebook
Nothing shouts culture more than visiting relatives during Chinese New Year. From celebrations and customs to decorations and food (it’s always about food), everything is red and loud in your face. When I was young, I thought this is what being Chinese is all about but as I grew older I realise there’s a lot more to it.
I grew up in Malaysia and Singapore, in the Malay archipelago and growing up, I always thought Chinese New Year was a purely Chinese thing. Then I learnt that the Japanese, Koreans, Tibetans, Mongolians and Vietnamese also celebrate it. And the Chinese in China don’t even call it that, instead they call it the Spring Festival.
After a while I learnt that some of the customs which I always thought are totally Chinese, turned out to be not as straightforward as it seems. For example, lions aren’t native to China so why is lion dance a major part of Chinese New Year celebrations? What I always assumed to be staples of Chinese New Year – eating bak kwa and giving mandarin oranges are not practised in China in many places. And the perennial pineapple tart as a Chinese New Year snack is apparently purely a local practise, a crossover from the peranakans.
Taking that further, if you observe our daily lives you’d realize that the everyday culture you’re used to is likely be a hodge-podge mix of multiple cultures. What you identify as a national or local culture is probably an eclectic mixture of a number of other cultures, which in turn are also a mix of even more other cultures.
For example, take laksa, a popular spicy noodle soup dish in Singapore, Malaysia, Indonesia and southern Thailand. Its origins are believed to have been from Chinese coastal settlements, which took on a local twist as Chinese immigrants introduced local ingredients and cooking methods. And as local ingredients go, this can be coconut milk (resulting in variants of curry laksa), tamarind (resulting in variants of assam laksa), sambal belacan, various species of fish and even lemongrass! This results in a delightful and dizzying variety of variants that are simply edible embodiment of the local cultural mix.
Similarly, we find linguistic equivalents in Singlish, an English-based creole language spoken in Singapore, containing words originating from English, Malay, various dialects of Chinese and Indian languages, and also in Bahasa Rojak a Malaysian pidgin language formed by code-switching between English, Malay, various dialects of Chinese and Indian languages in Malaysia. If we dig deeper, various things we take for granted as being iconic to a culture, often have origins in other cultures for e.g. English tea (which came from China) and Zen Buddhism (popularized in the West through Japanese Zen, which came from Chan Buddhism, which originated in China and was influenced by Taoism, but in turn is a school of Mahayana Buddhism which came from India).
As Stephen Hawkings wrote in the opening to A Brief History of Time — it’s turtles all the way down.
Modeling cultural interactions
How people and cultures interact and influence each other is the subject of numerous research. Various social scientists, anthropologists and biologists have studied cultural changes, came up with plausible theories and have written papers to describe these theories.
I’ve created a similar agent-based model based partly on the rules described on Axelrod’s paper. I used Go to create the simulation and Python to do a bit of analysis of the results.
The model I used is based on two very simple premises, similar to Axelrod’s model:
Cultures that are similar to each other are more likely to interact that cultures that are not
When cultures interact they become more alike each other
Using these two premises, I built a model using a square grid. Each cell in the grid represents a culture.
In the simulation, each culture has 6 features, where a feature is an attribute of a culture like food, language, religion etc.
Each feature in turn has 16 different traits, which are the different possible values of the feature.
As you probably realize, this is obviously modeled to make it easier for me to describe the grid in hexadecimal colors.
Let’s see how the simulation works. Each cell (culture) has 8 neighbors, unless it is a corner or cell.
At every interval, the simulation randomly picks n number of cultures. The selected culture is compared with its neighbors, feature to feature. The difference between one culture and another, which I call the cultural difference, is the probability that one culture will influence the other. If there is cultural influence, the simulation chooses a random trait to copy to the other culture, making it more similar to each other.
In the example below, take 2 cultures A and B that are next to each other (i.e. they are neighbors). Now compare them feature to feature. In the figure below, the difference between two features, or feature difference is 3. Note that we’re only looking for the difference between the two features, so we are getting the absolute number, and not simple subtracting one number from another.
Next, we add up all the features differences between the two cultures to get a cultural difference. In the figure below, we get a cultural difference of 34 (if we step back a bit you’d realize that we’ve just reduced the difference between two cultures to a single number, but that’s the nature of modeling).
A quick arithmetic check will tell us that if two cultures are completely different from each other, their cultural difference is 96 (16 traits x 6 features), and if they are exactly the same, their cultural difference is 0. Therefore, the probability that there will be cultural exchange is:
Finally, if cultural exchange does happen, the simulation will randomly pick one feature and copy the trait value from a culture to another. In this case, culture A’s feature 3 is copied to culture B.
Let’s take a look at the simulation code next.
The simulation in Go
The simulation is written in Go (no surprises here) and it creates a series of images of the grid as described above. These images are shown one after another in quick succession to show how the simulated cultures change with each tick of the simulation.
Data
Let’s start with the data. The img variable stores the image, which is a image.RGBA from the standard library. This is the variable that will be used to display the image. The cells variable is a slice of Cell structs that represent the simulation grid.
We have 4 parameters that we can tweak for the simulation — the width is the size of the grid, coverage is the percentage of the simulation grid that will have cultures, interactions represent the number of interactions the simulation does per tic, numTicks is just how long to run the simulation.
We also store 3 sets of simulation data that we can use for analysis afterwards, fdistances represent the average distances between features for all cells in the grid, uniques is the number of unique cultures left and changes is the number of cultural changes that actually happened — interactions is how many times we do, but changes is how many times it actually happened.
Cells
A Cell in the simulation has a position X and Y, a radius R which describes how big it is and a Color which is the color of the cell, with the values 0x1A2B3C. The struct Cell has a getRGB method that returns the color integer (a number that represents the color) and also a setRGB method that sets the Color of the cell with a color integer.
To create a cell, we use the createCell function that takes in the position x and y as well as a color integer. Finally we also have a function that creates a random grid, called createPopulation. The createPopulation creates the simulation grid based on the coverage parameter, which specifies what percentage of the grid is populated by cultures.
Before I start the main simulation loop, I will first create the grid using createPopulation. For very loop in the simulation, I will capture the distance dist, the number of unique cultures uniq and the number of changes chg.
Then for each loop, I pick a number of cells randomly and get them to interact with their neighbours as described above.
At the end of each loop, I get the distance, uniques and changes and store it into the fdistances, uniques and changes respectively. These uses a number of functions that calculates the values accordingly.
The simulation also uses a number of functions to help it to figure out which are its neighbours.
Now that we have the simulation, we need to display it.
Image
As in the previous posts, I use the draw2dimg library to draw the grid. The draw function draws the grid at each tick. Each cell is a circle with the given color.
The printImage function does exactly that — it prints out the image to the console and the saveImage function is used to save the last image of the simulation.
Save simulation data
The whole purpose of the simulation is to acquire data, and this is done using the saveData function. This function saves 3 types of data — the simulation data stored in fdistances, uniques and changes. It also saves the final state of the simulation (really just the grid data at the last tick) as well as the final image snapshot of the grid in a PNG file.
Running the simulation
And that’s our simulation. If we run it, we should get something like this:
And after the simulation ends, we should get 3 files — the log file with the simulation data, the cells file with the final tick grid data, and the final snapshot image.
Analysing the simulation data
If you just look at the simulation grid itself, you can figure out a few things. The GIF is 8x the actual speed (for the impatient) but you can clearly see that while it begins with a random layout, over time the clusters form, grow and expand then also vanish.
Let’s look at the data closely. At the end of the simulation we saved data in a log-nxx-tyy-wzz.csv file. As mentioned this file will contain the feature distance, the number of unique cultures and the number of changes. I used Jupyter Notebook to do some simple analysis on the data.
Different number of cultural interactions
I did 4 simulations, each simulation increasing the number of interactions from 100, 500, 1000 and 1500, while retaining the size of the simulation grid at a width of 36 cells. I run this simulation for 500 ticks.
First, I use Pandas to read the data from the CSV file into a data frame. Then I convert the columns accordingly to first take the feature distance, then the number of uniques and finally the number of changes.
Cultural differences
As we can see, the distance between features drop quickly at the beginning of the simulation, then the drop slows and eventually tapers to a long tail. I ran this simulation for only 500 ticks, even if we run it for longer periods, the feature distances don’t actually drop to 0. What does it mean?
Since the feature distance is really about how different cultures are from each other, it means that when interaction happens, initially the cultures get closer to each other and become more alike. However the cultural differences don’t disappear altogether.
The differences in the number of interactions tells us that if more interactions happen, the cultures before closer to each other, which is kind of what we expect as well.
Next let’s look at the number of unique cultures.
Unique cultures
From the chart you can see the number of unique cultures drop over time as they interact with each other. The more interactions happen, the more likely the cultures become like each other and eventually become the same.
Note that this doesn’t necessarily mean one culture assimilates the other, it only means both of the become more similar, as you can sometimes see from the simulation, and a dominant culture can change over time as well.
Cultural changes
Finally if we take a look at the actual number of changes, they remain quite stable most of the time, hovering between 15–20% of the number of interactions. This tells us that despite the changing number of unique cultures and even as the feature distances become smaller, there is always some level of change happening
This analysis is based on just changing the number of interactions. Let’s look at what happens if we change the coverage of the simulation grid. What this parameter changes is the number of neighbours each culture interacts with by reducing the total number of cultures in the whole simulation grid.
Different number of neighbours
The analysis is the same, the only difference is really changing the source files. In this case we start off with 20% coverage of the grid with cultures, going to 40%, then 60%, 80% and finally the entire grid is covered with cultures.
Cultural differences
Let’s start with looking at the feature distances comparing the different coverages. The feature distances changes as the coverages change look pretty uniform, as the coverage increases, i.e. as cultures have more neighbours to interact with, the feature differences are greater at steady state. However as you can see, the initial drop is much greater when cultures have more neighbours.
Unique cultures
Let’s take a look at the number of unique cultures next. The chart aligns with the earlier observation, as the coverage increases, so does the number of unique cultures, even as the numbers decrease over time. However you might also notice that as the coverage increases, the number of unique cultures drop faster as well.
Cultural changes
Finally let’s look at the number of changes. As expected, with more coverage and more neighbours to interact with, more cultural changes happen. We also can see that changes are quite consistent over time, even as the number of unique cultures as well as feature distances drop.
Observations
From the simulation we can observe the following:
The number of unique cultures converge and becomes fewer over time. This is not unexpected, after all we do expect cultures to interact and become more and more alike each other until they become the same. You can see this from the clustering of the cultures on the simulation grid. This is what Axelrod referred to as local convergence.
The number of unique cultures never actually drop to 1 even after a long time. The cultures remain different from each other. This is what Axelrod refers to as global polarisation.
The cultures become more alike over time as feature differences drop. However after an initial precipitious drop, the cultural difference more or less flattens out, even as the number of unique cultures continue dropping. What this tells us is that even as cultures interact, it doesn’t means the we’ll all become the same — cultural differences still do remain.
A last interesting observation is that even though cultures assimilate as they interact and some cultures appear dominant, the dominance is not perpetual. In fact, the fact that changes are more or less constant throughout the simulation means that the cultures are always changing.
Agent-based simulations are like scientific experiments in which you can create the perfect environment and agents for your scenarios and in which you can tweak parameters quickly and find out the possible outcomes. We can isolate and reduce a complicated situation into its elemental form and perform the experiments that we want. In a way this helps us to understand complexity and it’s unpredictable outcome a little bit better.
A final note — while I discussed this in the context of cultural exchanges, what I’ve shown here is a computer simulation with a limited set of controlled parameters. Models or simulations should be taken as what they are, simply models that are not true representatives of reality.