expdef: name:Reproduction of benthic foraminifera info:~ Basic information about this simulation: www.framsticks.com/foraminifera Technical information: Genes and parameter values which control reproduction are stored in data->genes and data->lifeparams fields. genes: genes which are not encoded in Ff genotype: min_repro_energy - Minimum energy necessary for reproduction hibernation - Defines foram behavior in the case of no nutrients lifeparams: Physiological parameters of foraminifera: max_energy_level - maximum energy level reached so far gen - generation: 0 haploid, 1 diploid species - species: 0 not hibernating 1 hibernating hibernated - 0/1 foram is/isn't hibernated reproduce - 0/1 foram isn't/is ready for reproduction ~ code:~ global nutrientenergywaiting; global reprocounter; global colors; global chambers; global o; global max_chamber_energ; global dir_change; global movePerStep; @include "foraminifera.inc" // -------------------------------- experiment begin -------------------------------- function onExpDefLoad() { // define genotype and creature groups GenePools.clear(); Populations.clear(); GenePools[0].name = "Unused"; var pop = Populations[0]; pop.name = "Forams"; pop.en_assim = 0; pop.nnsim = 0; pop.enableperf = 1; pop.death = 1; pop.energy = 1; pop.selfmask = 0x10001; pop.othermask = 0x20001; //pop.selfmask = 0x20002; pop.othermask = 0x10002; pop.perfperiod = 25; pop = Populations.addGroup("Nutrients"); pop.nnsim = 0; pop.enableperf = 0; pop.death = 1; pop.energy = 1; pop.selfmask = 0x20002; pop.othermask = 0x10000; //pop.othermask = 0x10002; pop = Populations.addGroup("ReticulopodiaNutrients"); pop.nnsim = 0; pop.enableperf = 0; pop.death = 0; pop.energy = 0; pop.selfmask = 0x20002; pop.othermask = 0x10000; //world SignalView.mode = 1; World.wrldwat = 200; World.wrldsiz = micronsToFrams(40000); World.wrldbnd = 1; ExpParams.stress = 1; ExpParams.creath = -0.99; //just above the bottom ExpParams.autorestart = 0; //time ExpParams.secPerStep = 60; ExpParams.foramSpeedMmPerMin = 0.05; movePerStep = getMovePerStep(); //ExpParams.visualize = 1; //uncomment to visualize reticulopodia and indicate nutrients positions //ExpParams.logging = 1; //uncomment to enable logging simulation parameters to log files //reproduction ExpParams.foramPop = 10; ExpParams.crossprob = 0; ExpParams.mutationprob = 0; ExpParams.repro_time = 20; reprocounter = 0; //morphology dir_change = 30000; ExpParams.zone1_range = micronsToFrams(1000); ExpParams.zone2_range = micronsToFrams(3000); init_chambers(); ExpParams.chamber_proculus_haplo = micronsToFrams(50); ExpParams.chamber_difference_haplo = 0.0; ExpParams.chamber_proculus_diplo = micronsToFrams(20); ExpParams.chamber_difference_diplo = 0.2; max_chamber_energ = [Vector.new(), Vector.new()]; for (var j = 0; j < 2; j++) { for (var i = 0; i < chambers[0].size; i++) { max_chamber_energ[j].add(((energyFromVolume(getProperty(j, "chamber_proculus")) + energyFromVolume(getProperty(j, "chamber_proculus") + (i) * getProperty(j, "chamber_difference")))*(i+1))/2); } } //energetics ExpParams.min_repro_energ_haplo = 4; ExpParams.min_repro_energ_diplo = 6; ExpParams.e_meta = 0.00005; ExpParams.energy_hib = 0.000025; ExpParams.energy_move = 0.00005; ExpParams.energies0_haplo = max_chamber_energ[0][0] - 0.001*max_chamber_energ[0][0]; ExpParams.energies0_diplo = max_chamber_energ[1][0] - 0.001*max_chamber_energ[1][0]; ExpParams.feedtrans = 0.125; ExpParams.e_repro_cost_haplo = 0.7; ExpParams.e_repro_cost_diplo = 0.3; //nutrients ExpParams.nutrientsize = micronsToFrams(10); ExpParams.energy_nut = 100 * energyFromVolume(ExpParams.nutrientsize); ExpParams.nutrientPop = 1; ExpParams.feedrate = 100; nutrientenergywaiting = 0; ExpState.totaltestedcr = 0; ExpState.nutrient = ""; } @include "standard_placement.inc" function energyFromVolume(radius) { return 4.0/3.0*Math.pi*Math.pow(radius,3); } function getMovePerStep() { return micronsToFrams((ExpParams.foramSpeedMmPerMin/60)*1000)*ExpParams.secPerStep; } function micronsToFrams(micrometers) { return micrometers*0.025; } function framsToMicrons(framsworldunits) { return framsworldunits/0.025; } function getProperty(gen, prop_id) { var ploid = "haplo"; if (gen == 1) ploid = "diplo"; return ExpParams.[prop_id + "_" + ploid]; } function addInitialForam(species, i) { var geno = createForamGenotype(0, species, 0); var cr = Populations[0].add(geno); cr.name = "Initial creature" + species + "_" + i; placeCreatureRandomly(cr, 0, 0); cr.energy0 = getProperty(0, "energies0"); cr.energy = cr.energy0; setGenotype({"opt" : 0, "cr" : cr, "species" : species}); } function onExpInit() { Populations[0].clear(); Populations[1].clear(); Populations[2].clear(); //reticulopodia and nutrients for (var i = 0; i < ExpParams.foramPop; i++) { addInitialForam(0, i); addInitialForam(1, i); } o = Populations[0][0].getMechPart(0).orient.clone(); ExpState.totaltestedcr = 0; nutrientenergywaiting = ExpParams.energy_nut; } function onExpLoad() { for (var pop in Populations) pop.clear(); Loader.addClass(sim_params.*); Loader.setBreakLabel(Loader.BeforeUnknown, "onExpLoad_Unknown"); Loader.run(); Simulator.print("Loaded " + Populations[0].size + " Forams and " + Populations[1].size + " nutrient objects"); } function onExpLoad_Unknown() { if (Loader.objectName == "org") // saved by the old expdef { var g = Genotype.newFromString(""); Loader.currentObject = g; Interface.makeFrom(g).setAllDefault(); Loader.loadObject(); var cr = Populations[0].add(g); if (cr != null) { //cr.rotate(0,0,Math.rnd01*Math.twopi); if ((typeof(g.data->genes) == "Vector") && (g.data->genes.size >= 3)) { // [x,y,energy] cr.move(g.data->genes[0] - cr.center_x, g.data->genes[1] - cr.center_y, 0); cr.energy = g.data->genes[2]; } else { cr.move(Math.rnd01 * World.wrldsiz - cr.center_x, Math.rnd01 * World.wrldsiz - cr.center_y, 0); } } } else if (Loader.objectName == "Creature") { Loader.currentObject = CreatureSnapshot.new(); Loader.loadObject(); Populations[0].add(Loader.currentObject); } } function onExpSave() { File.writeComment("saved by '%s.expdef'" % Simulator.expdef); var tmpvec = [], i; for(var cr in Populations[1]) tmpvec.add([cr.center_x, cr.center_y, cr.energy]); ExpState.nutrient = tmpvec; File.writeObject(sim_params.*); ExpState.nutrient = null; //vectors are only created for saving and then discarded for (var cr in Populations[0]) File.writeObject(cr); } // -------------------------------- experiment end -------------------------------- // -------------------------------- foram begin ----------------------------------- function setForamMeta(cr, gen) { //cr.idleen = (ExpParams.e_meta * max_chamber_energ[gen][Math.min(lastChamberNum(cr), max_chamber_energ[gen].size-1)])*ExpParams.secPerStep; cr.idleen = (ExpParams.e_meta * cr.energy)*ExpParams.secPerStep; } function lastChamberNum(cr) { return cr.numparts-1; } function onForamsBorn(cr) { setForamMeta(cr, 1); if (ExpParams.visualize == 1) { var ret = Populations[2].add("//0\np:sh=3,sx=0.01,sy="+ExpParams.zone1_range+",sz="+ExpParams.zone1_range+",ry=1.57,vr=1.0,1.0,1.0"); cr.data->reticulopodiacreature = ret; } } function placeRandomlyNotColliding(cr) { var retry = 100; //try 100 times while (retry--) { placeCreatureRandomly(cr, 0, 0); if (!cr.boundingBoxCollisions(0)) return cr; } Populations[0].delete(cr); } function visualization(cr) { var has_ret = 0; if (cr.data->reticulopodiacreature != null) { if (Populations[2].findUID(cr.data->reticulopodiacreature.uid) != null) { has_ret = 1; } } return has_ret; } function foramGrow(cr, chamber_num) { if ((chamber_num+1) < chambers[cr.data->lifeparams->species].size) { var geno = createForamGenotype(cr.data->lifeparams->gen, cr.data->lifeparams->species, chamber_num+1); var cr2 = Populations[0].add(geno); cr2.energy0 = cr.energy; cr2.energy = cr2.energy0; setGenotype({"cr" : cr2, "parent_genes" : cr.data->genes, "parent_lifeparams" : cr.data->lifeparams, "opt" : 2}); cr2.moveAbs(cr.center_x - cr2.size_x / 2, cr.center_y - cr2.size_y / 2, cr.pos_z); setForamMeta(cr2, cr2.data->lifeparams->gen); if (visualization(cr)) { Populations[2].delete(cr.data->reticulopodiacreature); } Populations[0].delete(cr); } } function stepToNearest(cr) { var p = cr.getMechPart(0); var n = cr.signals.receiveSet("nutrient", ExpParams.zone2_range); //if signals are received find the source of the nearest if (n.size > 0) { var i; var mp; var distvec = XYZ.new(0, 0, 0); var dist; var mindist = 100000000000.0; var mindistvec = null; var eating = 0; for (i = 0; i < n.size; i++) { mp = n[i].value.getMechPart(0); distvec.set(mp.pos); distvec.sub(p.pos); dist = distvec.length; if (dist < ExpParams.zone1_range) { if (n[i].value != null) { energyTransfer(cr, n[i].value); eating = 1; } } else if (eating == 0 && cr.data->lifeparams->hibernated == 0 && dist < mindist) { mindist = dist; mindistvec = distvec.clone(); } } if (!eating && cr.data->lifeparams->hibernated == 0) { mindistvec.normalize(); mindistvec.scale(-1*movePerStep); cr.localDrive = mindistvec; moveEnergyDec(cr); } return 1; } else { return 0; } } function moveEnergyDec(cr) { if (cr.data->lifeparams->hibernated == 0) { //cr.energy_m += (ExpParams.energy_move * max_chamber_energ[cr.data->lifeparams->gen][Math.min(lastChamberNum(cr), (max_chamber_energ[cr.data->lifeparams->gen].size)-1)])*ExpParams.secPerStep; cr.energy_m += (ExpParams.energy_move * cr.energy)*ExpParams.secPerStep; } } function foramMove(cr) { //TODO moving inside sediment? //adjustment in z axis cr.moveAbs(cr.pos_x, cr.pos_y, 0); //are there any nutrients in zone 1 or 2? { var moved = stepToNearest(cr); //TODO weighted sum of distance and energy if (moved==1) { return; } } //no nutrients in zone 2 var hibernation = 0; if (cr.data->lifeparams->gen == 0) hibernation = cr.data->genes->hibernation; else hibernation = cr.data->genes[0]->hibernation; //hibernation if (hibernation == 1) { reverseHib(cr); cr.localDrive = XYZ.new(0,0,0); } //random move else if (cr.lifespan%(dir_change/ExpParams.secPerStep) == 0) { cr.data->lifeparams->dir = randomDir(); cr.localDrive = cr.data->lifeparams->dir; moveEnergyDec(cr); } else { cr.localDrive = cr.data->lifeparams->dir; } } function randomDir() { var dir = (Math.rndUni(-ExpParams.zone2_range, ExpParams.zone2_range), Math.rndUni(-ExpParams.zone2_range, ExpParams.zone2_range), 0); dir.normalize(); dir.scale(-1*movePerStep); return dir; } function energyTransfer(cr1, cr2) { cr1.localDrive = XYZ.new(0,0,0); var e = ExpParams.feedtrans*ExpParams.secPerStep; //TODO efficiency dependent on age e = Math.min(cr2.energy, e) + 0.0000001; //Simulator.print("transferring "+e+" to "+cr1.name+" from "+cr2.name+" ("+cr2.energy+")"); cr2.energy_m = cr2.energy_m + e; cr1.energy_p = cr1.energy_p + e; if (cr1.data->lifeparams->hibernated == 1) { reverseHib(cr1); } } function reverseHib(cr) { if (cr.data->lifeparams->hibernated == 1) { setForamMeta(cr, cr.data->lifeparams->gen); //unhibernate } else { //cr.idleen = (ExpParams.energy_hib * max_chamber_energ[cr.data->lifeparams->gen][Math.min(lastChamberNum(cr), (max_chamber_energ[cr.data->lifeparams->gen].size)-1)])*ExpParams.secPerStep; //hibernate cr.idleen = (ExpParams.energy_hib * cr.energy)*ExpParams.secPerStep; //hibernate } cr.data->lifeparams->hibernated = 1 - cr.data->lifeparams->hibernated; } function onForamsStep(cr) { cr.getMechPart(0).orient.set(o); if (visualization(cr)) { cr.data->reticulopodiacreature.moveAbs(cr.center_x-ExpParams.zone1_range, cr.center_y-ExpParams.zone1_range, cr.center_z-ExpParams.zone1_range-getProperty(cr.data->lifeparams->gen, "chamber_proculus")); } if (deathConditions(cr) == 1) { Populations[0].kill(cr); return; } foramMove(cr); var repro = foramReproduce(cr); if (repro == 1) { return; } cr.data->lifeparams->max_energy_level = Math.max(cr.energy, cr.data->lifeparams->max_energy_level); if (lastChamberNum(cr) <= chambers[0].size-1) { if ((cr.data->lifeparams->max_energy_level >= max_chamber_energ[cr.data->lifeparams->gen][lastChamberNum(cr)])) { foramGrow(cr, lastChamberNum(cr)); } } } function deathConditions(cr) { if ((cr.energy <= getProperty(cr.data->lifeparams->species,"e_death_level")) || (Math.rnd01 < ExpParams.hunted_prob)) return 1; else return 0; } function onForamsDied(cr) { if (visualization(cr)) { Populations[2].delete(cr.data->reticulopodiacreature); } //fossilization var geno = GenePools[0].add(cr.genotype); geno.data->genes = cr.data->genes; geno.data->lifeparams = cr.data->lifeparams; if (ExpParams.logging == 1) Simulator.print("\"" + cr.name + "\" died..."); ExpState.totaltestedcr++; } // --------------------------------foram end ------------------------------------- // -------------------------------- nutrient begin -------------------------------- function createNutrientGenotype(nutrientsize, zone1_range) { return "//0\np:sh=3,sx="+nutrientsize+",sy="+nutrientsize+",sz="+nutrientsize+",ry=1.5,vr=0.0,1.0,0.0"; } function onNutrientsStep(cr) { cr.moveAbs(cr.pos_x % World.wrldsiz, cr.pos_y % World.wrldsiz, 0.5); } function addNutrient() { var cr = Populations[1].add(createNutrientGenotype(ExpParams.nutrientsize, ExpParams.zone1_range)); cr.name = "Nutrients"; cr.idleen = 0; cr.energy0 = ExpParams.energy_nut; cr.energy = cr.energy0; cr.signals.add("nutrient"); cr.signals[0].value = cr; placeCreatureRandomly(cr, 0, 0); if (ExpParams.visualize == 1) { var nutsize = ExpParams.nutrientsize*10; var nut = Populations[2].add("//0\np:sh=2,sx="+nutsize+",sy="+nutsize+",sz="+nutsize+",ry=1.5,vr=0.0,1.0,0.0"); cr.data->reticulopodiacreature = nut; nut.moveAbs(cr.pos_x-1.5*nutsize, cr.pos_y-1.5*nutsize, 0.5); } } function onNutrientsDied(cr) { if (visualization(cr)) { Populations[2].delete(cr.data->reticulopodiacreature); } } function nutrientGrowth() { nutrientenergywaiting = nutrientenergywaiting + 1; if (nutrientenergywaiting > ExpParams.feedrate/ExpParams.secPerStep) { for (var i = 0; i < ExpParams.nutrientPop; i++) { addNutrient(); } nutrientenergywaiting = 0.0; Simulator.checkpoint(); } } // -------------------------------- nutrient end -------------------------------- // -------------------------------- step begin -------------------------------- function onStep() { nutrientGrowth(); if (ExpParams.logging == 1) { createStatistics(); } //reproduction -------------------------------------------- reprocounter += 1; if (reprocounter*ExpParams.secPerStep > ExpParams.repro_time) { reprocounter = 0; reproduce_parents(0); reproduce_parents(1); } //check for extinction ----------------------------------------------- if (Populations[0].size == 0) { if (ExpParams.autorestart) { Simulator.print("no more creatures, restarting..."); onExpInit(); } else { Simulator.print("no more creatures, stopped."); Simulator.stop(); } } if (ExpParams.maxSteps > 0) { if (Simulator.stepNumber >= ExpParams.maxSteps) Simulator.stop(); } } function createStatistics() { var number = [[0, 0],[0,0]]; // [species][gen] var e_inc = [[0, 0],[0,0]]; var e_nut = 0.0; for (var i = 0; i < Populations[0].size; i++) { var cr = Populations[0].get(i); var gen = cr.data->lifeparams->gen; var species = cr.data->lifeparams->species; number[species][gen] = number[species][gen] + 1; e_inc[species][gen] = e_inc[species][gen] + cr.energy; } for (var i = 0; i < Populations[1].size; i++) { var cr = Populations[1].get(i); e_nut += cr.energy; } var log_numbers = [number[1][0], number[1][1], number[0][0], number[0][1], Populations[1].size]; var log_energies = [e_inc[1][0], e_inc[1][1], e_inc[0][0], e_inc[0][1], e_nut]; log(log_numbers, "forams_log.txt"); log(log_energies, "energies_log.txt"); } function log(tolog, fname) { var f = File.appendDirect(fname, "forams data"); f.writeString("" + Simulator.stepNumber); for (var i = 0; i < tolog.size; i++) { f.writeString(";" + tolog[i]); } f.writeString("\n"); f.close(); } // -------------------------------- step end -------------------------------- @include "standard_events.inc" ~ prop: id:visualize name:Show reticulopodia and nutrients type:d 0 1 0 group:Foraminifera prop: id:maxSteps name:Stop after the given number of simulation steps type:d 0 1000000 0 prop: id:foramSpeedMmPerMin name:Speed of foraminfera in mm/min type:f 0.1 flags: 16 group:Foraminifera prop: id:secPerStep name:Seconds per simulation step type:f 60.0 flags: 16 group:Foraminifera prop: id:e_repro_cost_haplo name:Cost of reproduction type:f 0.1 0.9 0.5 group:Foraminifera prop: id:e_repro_cost_diplo name:Cost of reproduction type:f 0.1 0.9 0.3 group:Foraminifera prop: id:chamber_proculus_haplo name:Size of proculus type:f group:Foraminifera prop: id:chamber_proculus_diplo name:Size of proculus type:f group:Foraminifera prop: id:chamber_difference_haplo name:Difference in size between subsequent chambers type:f group:Foraminifera prop: id:chamber_difference_diplo name:Difference in size between subsequent chambers type:f group:Foraminifera prop: id:hunted_prob name:Probability of being hunted type:f 0 1 0 group:Forminifera prop: id:zone1_range name:Zone 1 range type:f 0 200 group:Foraminifera prop: id:zone2_range name:Zone 2 range type:f 0 3000 group:Foraminifera prop: id:colors name:Haploid and diploid colors type:x group:Foraminifera prop: id:min_repro_energ_haplo name:Min reproduction energy of forams type:f group:Foraminifera prop: id:min_repro_energ_diplo name:Min reproduction energy of forams type:f group:Foraminifera prop: id:repro_prob name:Probability of reproduction type:f 0 1 0.8 group:Foraminifera prop: id:energies0_haplo name:Energy of offspring from diploid forams type:f group:Foraminifera prop: id:energies0_diplo name:Energy of offspring from diploid forams type:f group:Foraminifera prop: id:e_death_level_haplo name:Minimal level of energy to sustain life of haploid type:f 0 20 4 group:Foraminifera prop: id:e_death_level_diplo name:Minimal level of energy to sustain life of diploid type:f 0 20 0.25 group:Foraminifera prop: id:energy_hib name:Energy used for hibernation during one step type:f 0 1 0.001 group:Foraminifera prop: id:energy_move name:Energy used for movement during one step type:f 0 20 0.001 group:Foraminifera prop: id:min_vol name:Minimal volume for reproduction type:f 100 900 100 group:Foraminifera prop: id:max_size name:Maximal size type:d 1 10 5 group:Foraminifera prop: id:foramPop name:Initial forams population size type:d 1 1000 100 group:Foraminifera prop: id:crossprob name:Crossover probability type:f 0 1 0 group:Foraminifera prop: id:mutationprob name:Mutation probability type:f 0 1 0 group:Foraminifera prop: id:e_meta name:Idle metabolism type:f 0 1 group:Energy help:Each stick consumes this amount of energy in one time step prop: id:feedrate name:Feeding rate type:f 0 1000000 group:Energy help:How fast energy is created in the world prop: id:energy_nut name:Nutrient energy type:f 0 1000 group:Energy prop: id:feedtrans name:Ingestion multiplier type:f 0 100 group:Energy prop: id:nutrientsize name:Nutrient size type:f 0.1 0.9 0.1 group:Energy prop: id:nutrientPop name:Nutrient population size group:Energy type:d 1 1000 10 prop: id:stress name:Environmental stress type:d 0 1 1 group:World prop: id:repro_trigger name:Reproduction trigger type:d 0 1 1 group:World prop: id:repro_time name:Time before reproduction type:d 0 10000 prop: id:creath name:Creation height type:f -1 50 help:~ Vertical position (above the surface) where new Forams are revived. Negative values are only used in the water area: 0 = at the surface -0.5 = half depth -1 = just above the bottom~ state: id:nutrient name:Nutrient locations help:vector of vectors [x,y,energy] type:x flags:32 prop: id:autorestart name:Restart after extinction help:Restart automatically this experiment after the last creature has died? type:d 0 1 state: id:notes name:Notes type:s 1 help:~ You can write anything here (it will be saved to the experiment file)~ state: id:totaltestedcr name:Evaluated Forams help:Total number of the Forams evaluated in the experiment type:d flags:16 prop: id:logging name:Log statistics to file type:d 0 1 0