Computer-based Simulation Modelling for Anthropologists
Michael D. Fischer

Simulation Home

Introduction

Implementation

The examples are represented in the programming language Prolog (see the Prolog
appendix
for description of basic features; Brako 1986 is a good introductory text. For a
description of prolog aimed specifically at kinship analysis see this part of the Kinship
project
).

Prolog has a number of properties that makes it very useful for qualitative and non-
deterministic simulations, and is weakest in quantitative simulations. There are some
problems with Prolog, because although we will find it relatively easy to represent the
sub-models and their interaction in Prolog, it is often difficult or cumbersome to evaluate
the results.


We can define our basic object type in Prolog by using a person fact:
person(age,sex,status) which we repeat for every person in our population, where age is
either a numerical age, sex is male or female, and status is married or unmarried. These
could be read from a data file, created by an initialising program module (in Prolog a
program module is called a predicate) , or typed in. We could then use a very simple (and
unrealistic) marriage rule, 'each unmarried male at or beyond the age of 18 will marry the
first female encountered  who is 11 years of age or older  and who is between 5 and 7
years younger than the male'.

  /* Database */

  person(abdul, 24, male, unmarried).
person(rubina,18,female, unmarried).
/* ... */
person(zarina, 22, female, unmarried).

  /* Rules */

  marry_all :- /* marry all eligible people */
  marry(Male, Female), /* marry a couple */
  fail. /* forces evaluation of  next couple */
  marry_all. /* so that marry_all will succeed  after all marriages  */

  marry(Id_male, Id_female) :- /* marry people if eligible */
  eligible(Id_male, Id_female),
change_marital_status(Id_male,Id_female).

  eligible(Id_male, Id_female) :- /* check eligibility for marriage */
  person(Id_male,Age_male,male,unmarried), /* unmarried male*/
  person(Id_female,Age_female,female,unmarried), /* unmarried female */
  age_check(Age_male,Age_female).

  age_check(Age_male,Age_female) :-  /* check to see if ages are compatible */
  Age_male >= 18,
Age_male - Age_female <= 7,
Age_male - Age_female >= 5.

  change_marital_status(Id_male,Id_female) :- /* change from unmarried to married status */
  retract(person(Id_male,Age_male,male,unmarried)), /* remove old entry */
  assert(person(Id_male,Age_male,male,married)), /* add updated information */
retract(person(Id_female,Age_female,female,unmarried)), /* ditto */
assert(person(Id_female,Age_female,female,married)).

The predicate marry_all will attempt to marry everyone in the population according to the
defined criteria. This does not mean that everyone who is eligible for marriage will be
married at the end, because of demographic restrictions of the initial population. One
problem with this example, especially from an anthropological perspective is that all males
and females are interchangeable with all other males and females respectively. There is no
mechanism to take account of kinship or other relationships, not even such primitive
aspects such as sibling-hood! We can accommodate by adding avoidance for half and full
siblings: person(Id,Age,Sex,Marital,Father,Mother)

  marry(Id_male, Id_female) :- /* marry a couple */
  eligible(Id_male, Id_female),
change_marital_status(Id_male,Id_female).

  eligible(Id_male, Id_female) :-
  is_male(Id_male),
not(is_married(Id_male)),
is_female(Id_female), not(is_married(Id_female),
  not(are_siblings(Id_male,Id_female)),
  age_check(Id_male,Id_female).

  is_male(Id) :- person(Id,_,male,_,_,_).
  is_female(Id) :- person(Id,_,female,_,_,_).
  is_married(Id) :- person(Id,_,_,married,_,_).
get_age(Id,Age) :- person(Id,Age,_,_,_,_).
get_father(Id,Father) :- person(Id,_,_,_,Father,_).
get_mother(Id,Mother) :- person(Id,_,_,_,_,Mother).
are_siblings(Id1,Id2) :- get_Father(Id1,Father), get_father(Id2,Father).
are_siblings(Id1,Id2) :- get_mother(Id1,Mother), get_mother(Id2,Mother).

  age_check(Id_male,Id_female) :-
  get_age(Id_male,Age_male),
get_age(Id,female,Age_female),
Age_male >= 18,
Age_male - Age_female <= 7,
Age_male - Age_female >= 5.

  change_marital_status(Id_male,Id_female) :-
  retract(person(Id_male,Age_male,male,unmarried,Mf,Mm)),
  assert(person(Id_male,Age_male,male,married,Mf,Mm)),
  retract(person(Id_female,Age_female,female,unmarried,Ff,Fm)),
  assert(person(Id_female,Age_female,female,married,Ff,Fm)).

From here we can elaborate the code further to include a absolute preference for FBD (eg if
a FBD is available marry her, else marry someone else) by replacing the marry predicate
with the following predicates:

  marry(Id_male,Id_female) :- marry_fbd(Id_male,Id_female).

  marry(Id_male,Id_female) :- marry_other(Id_male,Id_female).

  marry_fbd(Id_male, Id_female) :- /* marry folks */
  is_fbd(Id_male,Id_female),
eligible(Id_male, Id_female),
change_marital_status(Id_male,Id_female).

  marry_other(Id_male, Id_female) :- /* marry folks */
  eligible(Id_male, Id_female),
change_marital_status(Id_male,Id_female).

  is_fbd(Id_male,Id_female) :-
  get_father(Id_male,Mf),
get_father(Id_female,Ff),
are_siblings(Mf,Ff).

From this we can see that we can alter and elaborate the model to represent what we want.
For example, if we want to include the consideration of obligations, we first need a model
of obligation, then a representation of obligation, and finally a check to see at the time of
the marriage decision if an obligation might affect marriage choice. Along the same lines,
we might want in the code above to add a deference of the marriage decision until some
upper age boundary, say 25. If the male is not already married at 25 he must marry
someone, even if a fbd is not available. Or we could add a routine to see if there is the
prospect of a fbd becoming available, and if she has an obligation to marry him. In other
words, if we can model and represent some aspect of the situation, it can be included in the
simulation. One obvious problem with the above example is that we have provided no
method of actually monitoring or otherwise getting information about what is happening.
Before the simulation is designed it is important to decide what information you are
seeking to answer which questions. As with other computing applications, the simulation
is a transformational method for relating Input to Output. A difference here is that we are
interested in the set of transactions that lead to this transformation.

Monitoring the simulation depends on what data is affected. In this case we have limited
data to monitor, since all that is changing is the marital status, and we are not recording
who the marriages are to. For example, we can count the married and unmarried people,
by gender and age group using the following:


  count_people(Gender, Low_age, Hi_age, Marital_status, Count) :-
  retract(total(C)), fail.
  count_people(Gender, Low_age, Hi_age, Marital_status, Count) :-
  person(Name,Age, Gender, Marital_status, Father, Mother),
Age >= Low_age,
Age =< Hi_age,
total(C),
retract(total(C),
C1 is C + 1,
assert(total(C)),
fail.
  count_people(Gender, Low_age, Hi_age, Marital_status, Count) :-
  total(Count).

The first definition of count_people deletes any prior count. total could be any name. It
then fails so that the second definition will be tried. The second definition matches the
criteria you are counting by; eg 'count_people(female,15,20,married,Count)'. It fails at the
end so that the next person will be examined. The third definition simply reports the result.
This structure works because Prolog always tries the next possible case if it fails. Since we
record a case that matches before we fail, we can keep a count. retract and assert are
Prolog predicates which add and remove 'facts' from the database.

If we were to keep track of who was married by extending the person structure, then we
could also count the number of fbd who were married.

Generating Behaviour: Independent Events

If we simply need to generate events, such as rainfall, which are independent of other
elements in the simulation then a statistical/probabilistic model is often best, especially if
the simulation is one which will be run many times to establish its overall behaviour. Thus
is usually the case because most social situation involve a great deal of uncertainty, and
often the only sensible method of investigating them is to look at a range of solutions. If
the event we want to model has a numeric value, such as rainfall, and we have several
years of recorded data for rainfall, we can often simply use the mean and standard
deviation of the rainfall to generate a value  If we only require a few categories, we can
break the probabilities into a table, and select from that. Different distributions suit
different kinds of data. For example, disease events are often better sampled from a
possion distribution than a normal or binomial distribution.

For example, we can elaborate the simulation by operating it on an annual cycle.
To do this we need to do three things. First we must age everyone one year, we must have
some mortality, and we must have some births. Aging is easy:

age_people :-
  person(Name,Age, Gender, Marital_status, Father, Mother),
retract(person(Name,Age, Gender, Marital_status, Father, Mother)),
New_age is Age + 1,
asserta(person(Name,New_age, Gender, Marital_status, Father, Mother)),
fail.
age_people.

The others can be simulated in simple cases by applying probabilities of births to women
of child-bearing age, with appropriate weights for married and unmarried women, and
applying mortality to each person based on age.