Setup
The following code implements the algorithm described in this and this paper. The first paper describes how the evolution of a fish population can be simulated, while the second paper introdues the phenomenon of 'fishing' into the simulation. The end goal of both papers is to show that one can have a stable fish population, introduce "responsible" fishing such that the total number of fish in the population decreases, but remains stable. The last step is then to add "irresponsible" fishing and to show that small changes in the fishing rate (fished fish per year) can have drastical results on the amount of fish that survive.
Code
The code is a reimplementation int Kotlin of C++ code I wrote some time ago. I picked up Kotlin just recently and I was struggeling with how inheritance worked here and how exactly to deal with static variables. It works as intended.
The code consists of three classes genome
, animal
and population
(as well as a derivied class) and the main
function that actually performs the simulation (this plot summerizes the results).
genome.kt
package penna
import java.util.*
typealias age_t = Int
class Genome{
/* Genome Class for the Penna simulation.
* The genome class has two private members:
* 1) 'genome_size_' is of type 'age_t' and static. It represents the length of the
* genome and therefore later the maximum age of the animal in question.
* 'agt_t' is set to 'int' since it needs to be bigger than 0 and an
* element of the whole numbers.
* 2) The actual genome is represented by a bitset called 'genome_' of length
* 'genome_size_'.
*/
private var genes = BitSet(genome_size)
init { genes.set(0, genome_size, false) }
/* PRE: 'this' needs to be a valid Genome instance.
* POST: switch exactly 'mutation_rate_' many instances of
* of the child's genome_.
*/
fun mutate(){
val indices: MutableList<Int> = (0..genome_size).toMutableList()
indices.shuffle()
for(k in 0..mutation_rate_){
genes.flip(indices[k])
}
}
/* PRE: 'this' is a valid genome instance and 'age' is smaller or equal to genome_size
* POST: Counts all the "bad genes" in genome_ up to the 'age'-th entry.
* A gene is bad if the entry in the BitSet is set to 'true'.
*/
fun countBad(age: age_t): Int {
return genes.get(0, age).cardinality()
}
companion object{
var genome_size: Int = 64
fun setMutationRate(age: age_t) { mutation_rate_ = age }
private var mutation_rate_: age_t = 0
}
}
animal.kt
package penna
import kotlin.random.Random.Default.nextDouble
class Animal(){
/* Animal class for the Penna simulation.
* The Animal class has several private members:
* 1) 'mutation_rate_', 'reproduction_age_' and 'threshold_' are all parameters
* that stay constant for all animals of a population.
* The respective values can all be retrieved and set with the corresponding
* get and set functions.
* 2) 'age_' represents the current age of the animal. By default construction it is set to 0.
* 3) 'genome_' is a Genome class instance in which we will save the genome of an animal.
* When constructed all genes are set to be good (aka false).
* 4) 'pregnant_' is a variable of type bool and tells you if the animal is currently pregnant.
* The status of each animal can be retrieved via the member function isPregnant().
*/
// Default constructor
private var age = 0
private var genome: Genome = Genome()
private var pregnant: Boolean = false
constructor(mum_genes: Genome): this(){
age = 0
genome = mum_genes
pregnant = false
}
fun isPregnant(): Boolean { return pregnant }
fun age(): Int {
return age
}
/* PRE: 'this' is a valid animal instance.
* POST: Returns true if the animal is dead, otherwise false.
* An animal is dead if:
* 1) age_ > max_age
* 2) count_bad(age_) > threshold_
*/
fun isDead(): Boolean { return age > max_age || genome.countBad(age) > threshold }
/* PRE: 'mother' is pregnant.
* POST: The following things are done in this order:
* 1) set the mothers pregnancy to false.
* 2) create an Animal instance with the mothers genome_
* 3) 'mutate' the child's genome.
*/
fun giveBirth(): Animal {
assert(pregnant)
pregnant = false
val childGenome = genome
childGenome.mutate()
return Animal(childGenome)
}
/* PRE: 'this' has to be a valid Animal instance
* POST: Grow the animal by one year:
* 1) age_++
* 2) set pregnant_ to true with probability_to_get_pregnant_.
*/
fun grow() {
assert(!this.isDead())
age++
if (age > reproductionAge && !pregnant){
if(nextDouble(0.0,1.0) <= probabilityToGetPregnant){
pregnant = true
}
}
}
companion object{
private var probabilityToGetPregnant: Double = 0.0
private var reproductionAge: age_t = 0 // Age at which Animals start reproduction
private var threshold: age_t = 0 // More than this many mutations kills the Animal
var max_age: age_t = Genome.genome_size
fun setReproductionAge(num: age_t){ reproductionAge = num }
fun setThreshold(num: age_t){ threshold = num }
fun setProbabilityToGetPregnant(num: Double){ probabilityToGetPregnant = num }
}
}
population.kt
package penna
import kotlin.random.Random.Default.nextDouble
open class Population(private var nMax: Int, nZero: Int) {
/* Class to simulate a population of Animal objects.
* nMax: The upper limit of the population size
* nZero: The starting number of the population
*/
protected var population: MutableList<Animal> = ArrayList()
init {
for(k in 0 until nZero){
population.add(Animal())
}
}
fun size(): Int {
return population.size
}
/* PRE: ---
* POST: Performs one step in the simulation:
* 1) Age all animals by calling Animal::grow()
* 2) Remove all animals that:
* 2.1) are dead ( by using Animal::isDead() )
* 2.2) if there are more than nMax many Animals in the population
* 2.3) regardless of the above, kills an animal with probability population.size()/nMax
* 3) Generate offspring by calling Animal::give_birth on the pregnant Animals in population and
* appending it to population.
*/
open fun step() {
// Age all animals
population.forEach { it.grow() }
// Remove dead ones
population.removeIf{ this.size() / nMax.toDouble() >= 1.0 ||
nextDouble(0.0,1.0) < this.size() / nMax.toDouble() ||
it.isDead()
}
// Generate offspring
val parents: MutableList<Animal> = population.filter { it.isPregnant() }.toMutableList()
val babies : MutableList<Animal> = ArrayList()
for(animal in parents){
babies.add(animal.giveBirth())
}
population.addAll(babies)
}
}
class FishingPopulation(nMax: Int, nZero: Int, fishingProb: Double, fishingAge: Int) : Population(nMax, nZero) {
/* Derived class of Population to realize the Fishing aspect of the Discussion.
* fishingProb: is the probability with which one fish will die due to fishing
* fishingAge: the age at which a fish can die due to fishing
*/
private var fishProb: Double = 0.0
private var fishAge: Int = 0
init {
fishProb = fishingProb
fishAge = fishingAge
}
// Change the two Parameters on the fly when necessary
fun changeFishing(fishingProb: Double, fishingAge: Int){
fishProb = fishingProb
fishAge = fishingAge
}
/* Essentially the same function as Population::step(). We only perform the fishing in addition by removing
* fish with the specified probability.
*/
override fun step() {
super.step()
super.population.removeIf { it.age() > fishAge && nextDouble(0.0,1.0) < fishProb }
}
}
main.kt
package penna
import java.io.File
fun main(){
// Set the parameters for the simulation.
Genome.genome_size = 64 // Determines the maximal age of the Animal
Genome.setMutationRate(2) // How many mutations per year can happen in the worst case
Animal.setReproductionAge(6) // Age at which Animals start reproduction
Animal.setThreshold(8) // More than this many mutations kills the Animal
Animal.setProbabilityToGetPregnant(1.0) // Animal generate offspring every year
val fish = FishingPopulation(10000, 1000, 0.0, 0)
val popSizes: MutableList<Int> = ArrayList()
for(generation in 0 until 5000){
popSizes.add(fish.size())
fish.step()
if(generation == 500) {
fish.changeFishing(0.19, 8)
}
if(generation == 3500){
fish.changeFishing(0.22,0)
}
}
File("data.txt").writeText(popSizes.toString())
}
As I said above, I'm a complete beginner when it comes to Kotlin and I have never coded in Java either, so it's very well possible that I approached the problem here completely worng... Any feedback is recommended.