Skip to content

Instantly share code, notes, and snippets.

@Elmuti
Created November 6, 2022 01:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Elmuti/8184c7eea69ac5d34afe3c70a95ec44d to your computer and use it in GitHub Desktop.
Save Elmuti/8184c7eea69ac5d34afe3c70a95ec44d to your computer and use it in GitHub Desktop.
local PreBakedDeathFunction = {}
local random = math.random
local ceil = math.ceil
local floor = math.floor
local randomseed = math.randomseed
local prevActual = 0
local prevWeeks = 0
randomseed(1557007091) --os.time()
random(); random(); random()
--16/4/19
--r-strategy simulation
--23/4/19
--r-strategy simulation expanded with population strata
--24/4/19
--first run for 1000 years (52000 weeks), results: 54.6 s with 1465 agents (i5 2500k)
--basedeathfactor was 0.0018 and basefertilityfactor was 0.008
--r-strategy simulation expanded to multiple repeated simulations with efficient logging
--28/4/19
--multiple simulation implemented (with inferior rolling average method) (will be replaced with superior full-averaging method)
--todo notes
--implement average agent pregnancies tracking (28/4/19)
--implement age strata size tracking (28/4/19)
--29/4/19
--multiple simulation (superior full average method) implemented
--mortality calculators
--(0.6(x+2)^2)(0.01(x-2)^2)(0.9(x-2)^2)+0.01
--big local list
local ResultsFileName = "C:/Users/Elmeri/Desktop/GregorSimData/GregorData" --output file name (in general)
local pop_num = 1000 --number of agents in the start
local desired_length = 1000 --length in years to simulate
local desired_simulations = 5 --number of simulations
local verboseoutput = true --should the output include text (true) or just be numbers (false)
local runSingle = false --set to true if a single simulation is desired
local prerepro = 0.35 --percent of population in the pre-reproductive stage
local repro = 0.6 --percent of population in the reproductive stage of life
local postrepro = 0.05 --percent of population in the post-reproductive stage
--small local list
--internal locals
local pop = {} --table to contain the agents
local week = 0 --counts weeks, for ingest into Excel
local basedeathfactor = 0.0011
local basefertilityfactor = 0.007
local sim_length = (desired_length*52) --simulation length in weeks
--local pop_actual = pop_num
local max_pop_size = 10000 --maximum population size to terminate the simulation
local simSettingsFile = (os.time().."RStratSimSettings.txt") --simulation settings log file
local sim_interval = 10 --prints a progress message every n simulations to make sure the program is working as intended
local sim_number = 0 --keeps track of the current number of simulations
local sim_time_length = 0.83 --length in seconds of a single simulation per 1000 weeks in that simulation, to calculate estimated times
local simSingleOutput = "C:/Users/Elmeri/Desktop/GregorSimData/GregorData"
local preResults = {}
--sim length estimator
print("Expected run time (in seconds) "..( ((sim_length/1000) * sim_time_length) * desired_simulations))
os.execute("title GregorPopSim - Running "..desired_simulations.." simulations, each "..sim_length.." weeks")
--various verbose text
if desired_simulations ~= 0 then
print("Warning, multiple simulations ("..desired_simulations..") will be run!")
end
if simSingleOutput == nil then
print("Critical! Special simulation output folder not available!")
end
print("The program will now present various mission critical variables, and write them into a log file")
print("Strata: "..prerepro.." "..repro.." "..postrepro)
print("Factors: "..basedeathfactor.." "..basefertilityfactor)
print("Simulations: "..sim_length.." "..desired_simulations)
local F99 = io.open(simSettingsFile,"w")
F99:write("This is the settings log file for the simulation on "..os.date().."\n")
F99:write("Strata: "..prerepro.." "..repro.." "..postrepro.."\n")
F99:write("Factors: "..basedeathfactor.." "..basefertilityfactor.."\n")
F99:write("Simulations: "..sim_length.." "..desired_simulations.."\n")
F99:write("Agent characteristics")
print("script head complete "..os.clock().."\n")
local function PreBakeDeathCharts()
local before = os.clock()
for i = 1, 5200 do --assume 100 years is maximum age for pre-baked death function charts
local age = i/1000
PreBakedDeathFunction[i] = 0.1 * ( (0.6 * (age+0.1)^2) * (0.01 * (age-2.5)^2) * (0.9 * (age-2)^2) + basedeathfactor)
end
local timetaken = os.clock() - before
print("Took "..timetaken.." seconds to pre-bake death function chart")
end
PreBakeDeathCharts()
print("Press ENTER to start simulation")
--io.read()
--agent types
--[[table positions agentID,
age,
death boolean for clean up,
age at reproductive maturity,
post-reproductive maturity,
pregnant boolean,
%chance of pregnancy,
week of pregnancy,
gestation period,
post-gestational regeneration (multiplier of gestation time),
post-gestational stage boolean,
numpregnancies,
is reproductive age (adult) boolean,
internal boolean to smooth out pregnancies at the start,
]]
local basicAgent = {
ID = IDcount;
Age = 0;
Dead = false;
Repro = 780;
PRepro = 2080;
Pregnant = false;
FertilityRoll = basefertilityfactor;
WeekP = 0;
Gestation = 38;
PGestation = 0.5;
PGestational = false;
PGestationalWeek = 0;
Pregnancies = 0;
Adult = false;
Burnin = false;
}
F99:write("Repro: "..basicAgent.Repro.."\n")
F99:write("PRepro: "..basicAgent.PRepro.."\n")
F99:write("Gestation "..basicAgent.Gestation.."\n")
F99:write("PGestation "..basicAgent.PGestation.."\n")
F99:close()
--samity checks
if prerepro+repro+postrepro ~= 1 then
print("The simulation cannot be started as the population strata do not sum to 1!")
print(prerepro)
print(repro)
print(postrepro)
print(prerepro+repro+postrepro)
error("Script has been halted!")
end
--script start
--local F = io.open(ResultsFile,"w")
--F:write("week \t deaths \t population \t births \t pregnancies \t burnins \t avg age")
local verboseLine = "week \t deaths \t population \t births \t pregnancies \t avg age"
--local F2 = io.open((os.time().."PopStart.txt"),"w")
--F2:write("agent \t age")
--establish the table
local function deepcopy(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[deepcopy(orig_key)] = deepcopy(orig_value)
end
setmetatable(copy, deepcopy(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
end
return copy
end
local function createAgents(numAgents,typeAgent,creationType)
if creationType == "SimStart" then
local IDcount = 0
local prereprocount = ceil(pop_num * prerepro)
local reprocount = ceil(pop_num * repro)
local postreprocount = ceil(pop_num * postrepro)
os.execute("cls")
print("Currently running simulation #"..sim_number.." with agent configuration "..prereprocount.."/"..reprocount.."/"..postreprocount.." for a total of "..(prereprocount+reprocount+postreprocount).." agents")
print("Previous simulation: "..prevActual.." agents and "..prevWeeks.." weeks")
print("Total time running so far: ".. os.clock())
reprocount = reprocount + prereprocount
postreprocount = postreprocount + reprocount
for i = 1, numAgents do
if ((prereprocount > i) and (i >= 0)) then
local copy = deepcopy(typeAgent)
copy.Age = random(0,basicAgent.Repro)
IDcount = IDcount + 1
table.insert(pop,copy)
--F2:write("\n"..IDcount.."\t"..copy.Age)
elseif ((reprocount > i) and (i >= prereprocount)) then
local copy = deepcopy(typeAgent)
copy.Age = random(basicAgent.Repro,basicAgent.PRepro)
local numReproAgents = ceil(pop_num * repro)
local burninRoll = random()
if burninRoll < 0.9 then
copy.Burnin = true
end
IDcount = IDcount + 1
table.insert(pop,copy)
--F2:write("\n"..IDcount.."\t"..copy.Age)
elseif ((postreprocount > i) and (i >= reprocount)) then
local copy = deepcopy(typeAgent)
copy.Age = random(basicAgent.PRepro,3000)
IDcount = IDcount + 1
table.insert(pop,copy)
--F2:write("\n"..IDcount.."\t"..copy.Age)
end
end
elseif creationType == "Birth" then
for i = 1, numAgents do
table.insert(pop,deepcopy(typeAgent))
end
end
--F2:close()
end
--simulation
local function simulate(dsrlngth,startPop)
local pop_actual = startPop
--print(dsrlngth)
for i = 1, dsrlngth do
if pop_actual > max_pop_size or pop_actual <= 10 then
--createLastLog()
local time_length = os.clock()
--print("The simulation has ended after "..time_length.." seconds with "..pop_actual.." agents and "..week.." weeks")
prevActual = pop_actual
prevWeeks = week
break
end
--print(pop_actual)
local deaths = 0
local births = 0
local pregnants = 0
local burnins = 0
local checkavg = 0
--age all the agents
for numAgent, agent in pairs(pop) do
agent.Age = agent.Age + 1
--DEATH FUNCTION
if random() <= PreBakedDeathFunction[agent.Age] then
agent.Dead = true
deaths = deaths + 1
end
end
--remove dead ones to prevent dead ones from being pregnant
for numAgent, agent in pairs(pop) do
if agent.Dead == true then
table.remove(pop,numAgent)
end
end
--count burnin agents during burnin period
if week < 781 then
for numAgent, agent in pairs(pop) do
if agent.Burnin == true then
burnins = burnins + 1
end
end
else end
--make agents pregnant, advance their fetal ages, check for whether it is time for birth, check whether the agent can be returned to a fertile status (post-gestational period)
for numAgent, agent in pairs(pop) do
--check for Burnin and adjust the agent accordingly
if agent.Burnin == true or (week > dsrlngth+1) then
if week > dsrlngth then
agent.Burnin = false
else
local chance_unburn = random()
if chance_unburn < (1/burnins) then
agent.Burnin = false
end
end
end
--check for pregnant or post-gestational
if agent.Pregnant == true then
pregnants = pregnants + 1
--check for whether its delivery time
if agent.WeekP >= agent.Gestation then
agent.Pregnant = false
agent.Pregnancies = agent.Pregnancies + 1
agent.PGestational = true
agent.WeekP = 0
createAgents(1,basicAgent,"Birth")
births = births + 1
end
agent.WeekP = agent.WeekP + 1
elseif agent.PGestational then
--check for whether the post-birth recovery period is over
if agent.PGestationalWeek >= (agent.Gestation * agent.PGestation ) then
agent.PGestational = false
agent.PGestationalWeek = 0
end
agent.PGestationalWeek = agent.PGestationalWeek + 1
elseif not agent.Pregnant and not agent.PGestational and not agent.Burnin then
--calculate chance of pregnancy and manipulate the agent accordingly
if agent.Age > agent.Repro and agent.Age < agent.PRepro then
if random() < agent.FertilityRoll then
agent.Pregnant = true
else
agent.Pregnant = false
end
end
end
checkavg = checkavg + agent.Age
end
pop_actual = (pop_actual - deaths) + births
local checkavg2 = floor(checkavg/pop_actual)
--print(checkavg2)
--createLogLine(week,deaths,pop_actual,births,pregnants,burnins,checkavg2) [single simulation]
--[[if sim_number == 0 then
local startlogline = (week.."\t"..deaths.."\t"..pop_actual.."\t"..births.."\t"..pregnants.."\t"..burnins.."\t"..checkavg2)
preResults[week] = startlogline
elseif sim_number > 0 then
createLogLine2(week,deaths,pop_actual,births,pregnants,burnins,checkavg2)
end --deprecated multiple simulations]]
if runSingle == false then
local currentLogLine = tostring((week.."\t"..deaths.."\t"..pop_actual.."\t"..births.."\t"..pregnants.."\t"..checkavg2))
--preResults[week] = currentLogLine
table.insert(preResults,currentLogLine)
end
week = week + 1
checkavg = 0
end
end
--increment logging
local function doAverage(num1,num2)
local doneAverage = ((num1+num2)/2)
return doneAverage
end
local function createLogLine2(whatWeek, numDead, numPopulation, numBorn, numPregnant, numBurnin, numAvg)
local loglineOld = preResults[whatWeek]
local llOstrings = splitString(loglineOld,"\t")
print(llOstrings)
local logline = tostring((whatWeek.."\t"..(doAverage(llOstrings[2],numDead)).."\t"..(doAverage(llOstrings[3],numPopulation)).."\t"..(doAverage(llOstrings[4],numBorn)).."\t"..(doAverage(llOstrings[5],numPregnant)).."\t"..(doAverage(llOstrings[6],numBurnin)).."\t"..(doAverage(llOstrings[7],numAvg))))
preResults[whatWeek] = logline
end
local function splitString(inputstr, sep)
if sep == nil then
sep = "%s"
end
local t={}
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
table.insert(t, str)
end
return t
end
local function createSimTable(weekLength)
--tracking for 7 datapoints
local newLine = "0 \t 1 \t 2 \t 3 \t 4 \t 5"
for i = 1, weekLength do
table.insert(preResults,newLine)
--print(newLine)
end
print("Data gathering table with "..weekLength.." weeks has been prepared! "..os.clock())
end
local function writeWholeLog(simNumber)
--F:write("week \t deaths \t population \t births \t pregnancies \t burnins \t avg age")
local currentResult = (ResultsFileName..simNumber..".txt")
local F9 = io.open(currentResult,"w")
for i = 1, sim_length do
F9:write("\n"..tostring(preResults[i]))
end
F9:close()
end
local function createLogLine(numDead, numPopulation, numBorn, numPregnant, numBurnin, numAvg)
local logline
logline = ("\n"..week.."\t"..numDead.."\t"..numPopulation.."\t"..numBorn.."\t"..numPregnant.."\t"..numBurnin.."\t"..numAvg)
F:write(logline)
end
local function createLastLog()
if verboseoutput == false then return end
local lastlogline
lastlogline = ("\n*At week "..week.." the simulation ended with "..pop_actual.." agents")
--F = io.open(ResultsFile,"w")
F:write(lastlogline)
--F:close()
end
--simulation
--setup simulation
--createAgents(pop_num,basicAgent,"SimStart")
--[[do simulation (single)
simulate(sim_length)
writeWholeLog()
print("In "..os.clock().." seconds "..desired_simulations.." simulations with a length of "..desired_length.." years were run and the averages reported in "..tostring(ResultsFile))
if os.clock() > 60 then
local minutes = os.clock()/60
print(minutes.." minutes")
end
if os.clock() > 3600 then
local hours = os.clock()/3600
print(hours.." hours")
end
F:close()]]
--do simulation (multiple)
local function runSimulations(howMany,howLong)
--createSimTable(howLong)
local lastTime = os.clock()
for X = 1, howMany do
preResults = {}
pop = {}
createAgents(pop_num,basicAgent,"SimStart")
--createSimTable(howLong)
week = 0
simulate(howLong,pop_num)
writeWholeLog(sim_number)
sim_number = sim_number + 1
local completion = (X/desired_simulations)*100
if desired_simulations < 51 then
print(completion.."% complete")
else
local checkInteger = completion/sim_interval
if completion == checkInteger then
local currentTime = (os.clock() - lastTime)
print(completion.."% complete (simulation "..sim_number..") in time "..currentTime.." seconds")
end
end
end
end
runSimulations(desired_simulations,sim_length)
--writeWholeLog()
print("In "..os.clock().." seconds "..desired_simulations.." simulations with a length of "..desired_length.." years were run and the averages reported in "..tostring(ResultsFile))
if os.clock() > 60 then
local minutes = os.clock()/60
print(minutes.." minutes")
end
if os.clock() > 3600 then
local hours = os.clock()/3600
print(hours.." hours")
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment