This is the first of two web pages. Ostensibly, they show you haw to make a good start on a simulation modeling the ecology of a small woodland, inhabited by grass, rabbits and foxes. A Predator/ Prey Population Dynamics simulation.
The simulation won't win any prizes... yet... for being true- to- nature. But, having said that, the way the work already done has been done should mean that extending the simulation to make it more life-like is entirely feasible. It would be an interesting challenge.
The primary value of the material here will be, I think, as an example of how you could build a simulation of things like predator/ prey interaction. Things that proceed in "generations". Cellular automata, such as Conway's "Life". Traffic flows. River flooding. One thing you could tackle this way which particularly tempts me is a simulation of how age pyramids proceed... i.e. graphs showing the numbers of males and females in age-range bands, over a series of years. Many, many possibilities!
Download the sim06- Predator/ Prey Population Dynamics simulation source-code. If you just want the source-code, to adapt to your own purposes (or to follow along, as you read this essay), it is available for download, in an archive called, not very imaginatively, "sim06.zip". The .exe that arises from compilation is included in that .zip, and can be run without any further ado. It doesn't read from or write to your hard drive. You can run it directly from the .zip, without doing an extract- and- save first. (Compiled for Windows, on a Win7 machine. Download tested under Firefox.)
A few odds and ends of generally useful programming tricks and techniques also arise, as ever.
Not "as ever" will be the level of hand-holding I generally offer, particularly in tutorials of a more elementary nature. I invite you to try some of the lower "level" tutorials at my site until you are more Lazarus (or Delphi) "fluent", if you find this one goes too quickly.
Many general techniques are illustrated along the way. A compiled .exe is available so that you can play with the application without having to compile it. The Lazarus sourcecode is in the same zip file you can download. Both are there for you to play with... but I reserve all rights to any applications derived from the sourcecode. It is there to show you programming techniques, not to be the basis of a commercial product. Well... anyone else's commercial product, anyway.
"We're going", to do what I said above. Let me reiterate elements...
While it won't be a complete simulation of the rise and fall of grass, rabbit and fox populations in a woodland, it is going to illustrate some elements of how you might go about writing such a program. I hope it will fire the imagination of a reader, start them on the road to writing such a program... because it would be a fun, and worthwhile project.
Working on simulating natural processes, be they biological, chemical or physical, can bring on the pleasure to be had from better understanding the processes you are trying to model. If your model behaves at least plausibly, then you may be justified in taking some pride in having, perhaps, understood important aspects of the process.
It may help you to grasp what the woodland ecosystem model will try to simulate if first we look at something even simpler.
There was once a type of private boarding school which generally accepted pupils into "year 1", hoping they would stay for 5 years. But many would leave after year 3. As far as I recall, we had no births or predation.
The economics were quite harsh... it was important to keep each year group "full". For the sake of our discussion, we'll stipulate that the ideal number in each year was 100.
Predicting the future was helpful. Data about the past was available.
The number of people in "Year 2" for 2005 was the number of pupils present in Year 1 of 2004, PLUS any pupils (usually not many) joining the school at the Year 2 level in 2005, MINUS the number of 2005 Year 1 pupils not returning for Year 2.
It WAS trivial to create a spreadsheet to record the school's enrollment history....
With a little history, it was easy enough to calculate the net influx/ outflow for each year group, at the end of each year. Then, with not much bother, the spreadsheet could be extended into the hypothetical future, using best-guesses for the net influxes/ outflows. With that "prediction" to "sharpen the mind" of those responsible, action (or reaction) could ensue.
(Of course, for the extra work, extra "where to put your effort" information would arise if instead of merely using NET influx/ outflow, the system tracked each separately.... but that "frill" isn't necessary for our progress towards a simulation of a woodland's population dynamics)
Think a little about how you would build such a spreadsheet, and I hope it will help you get ready for what comes next.
In a very, VERY simple woodland, you might have Grass and Rabbits and Foxes. The rabbits eat the grass, the foxes eat the rabbits.
In the first year, you might have "lots" of grass, 10 rabbits, and 10 foxes. (I will call the first year "Y0" from now on. (Always best to count from zero in computer stuff. You get used to it.))
Start like that, and in the second year, you would probably have no rabbits and no foxes.... everyone has starved. Start with "lots" of grass, 1000 rabbits, and 2 foxes (a dog and a vixen), and the simulation might run longer, be more interesting.
Before we go any further, we're going to make a simplification which will help us. Instead of counting our grass plants, rabbits, and foxes, we are, from now on, merely going to weigh them all. So... we might have 20,000 kg of grass, 1000 kg of rabbits, and 20 kg of foxes.
We are further going to assume that the woodland is so large, that we won't get down to just 1 fox with no one to mate with, etc. Nor will we worry about the fact that as the rabbits become more scarce, they will become harder to find... although that is an example of an important thing to add early on, if you take the exercise further.
We're not going to worry about the seasons. We'll have a "clock" that ticks, like the school, once each year.
We're not going to divide the grass, rabbit and fox populations up into "young ones", "middle aged", and "old". Important... but not included in our plan. But! The plan has been done in a way that allows adding that "frill" in, at a later date. ("young" foxes and rabbits don't breed. "old" rabbits run less fast, and get eaten more often. If grass becomes plentiful, the rabbit population doesn't immediately rise at the rate it will at a time when the first extra youngsters become sexually mature... in a "proper", complex model (and the real world.)
Our model is simply going to go 'round and 'round the following loop....
REPEAT.... Make a note of how things stood, end of previous period. Based on the grass you have, add more. (It grew) Based on number of rabbits, decrease grass by a certain amount. Based on number of rabbits, increase number of rabbits. (They bred) Based in number of foxes, decrease number of rabbits. (They got eaten) Based on number of rabbits, increase or decrease number of foxes. (Their increase from breeding either did, or did not, exceed the deaths due to starvation.) UNTIL YOU ARE BORED (or a population crashes!)
The above is very plausible, I hope. Sadly, interpreted one way, there is something badly wrong with it!... which I discovered rather late in the day, having "finished" the first stage of this program's development!
I am going to leave the "finished" first stage as it was, and also the tutorial. DO complete your reading of it. Keep you eye open; see if you spot the flaw. In a subsequent tutorial, I am going to show you how all of my work, regardless of the design flaw, was not in vain... because I was using good program design and development techniques.
From here, remember: You are reading an account, not edited to correct the mistake, flawed (not irredeemably!) of my first attempt (for a few years, anyway!) at creating the population dynamics simulation.
Just to make things a little more fun, we could easily modify the app, to let you "play God"... well, nature preserve manager, at least.... as the loop goes 'round and 'round. You would be allowed to "inject" extra grass, or rabbits, or foxes. If we limit how MUCH you can inject, and make building the biggest populations (maximum total biomass) in a fixed number of years, it becomes a little game, along the way.
But first, the basics. This is what sim06, "finished" looks like...
(Apologies, if it still remains, for the non-functioning label "Click here..." at the upper left. The "click here to copy..." button works fine!)
The image above gives you an idea of where we are going. Eventually, of course, you would "fancy things up" with graphical displays. The controls are laid out more or less in the order they were added to the form, so that you can see the history of the development. They are not, I assure you, how I would lay them out, otherwise, of course.
Near the beginning, the memo just scrolled each time you clicked "Go forward one year", with the same numbers over and over! I built up from something that had, and could display, the status quo.
Of course, a bunch of initializing has to be done. This is in two procedures... InitOnce and InitToRestartSim. The first is things that have to happen when the app is first launched. The second is for things that have to be set back to starting values if, umm, you want to restart the sim. Both are called by the FormActivate event handler. (Do not try to put them in the FormActivate event handler... If you include what I did, you will be trying to write to a memo before the memo exists, and get an error, but not, maybe when you would expect to. (I got a SIGSEGV error when I tried to close the app.)
For the sake of a few bytes, I wasn't going to risk data overflow, so all the biomasses are stored in type LongWord variables. lwBM_grass, lw_BM_rabbit, lwBM_fox. (The plural forms of the two latter should be assumed... they hold the "rabbit" biomass, which you might more intuitively think of as "the biomass of the rabbitS"... but I begrudge the extra character.)
New lines for the memo (meReportData) will be added at THE TOP, and push older lines down the page. As I may want to add lines from various places, I've parceled up the small amount of code required in the simple procedure AddToTopOfMemo.
The font of the memo (and of the label with the column headings) was set to a mono-spaced font, to keep the columns of figures aligned vertically.
Remember, please... the point of this is NOT to produce the latest, greatest population dynamics simulator. Rather, it is to demonstrate some programming principles. Not least of which is "Build a framework that is robust. One which can gradually be developed, enhanced, made to incorporate more details."
Working from such a framework is useful in many, many programming exercises.
You can't just start coding without careful thought about where you are going. But it pays to start "small", and build gradually.
So... onward with building our population dynamics simulation.
The initial biomasses for the grass, rabbit, fox were established during InitToRestartSim
Now we have to arrange for our organisms to feed, and to die.
In the pseudo-code outline, you may have noticed "Make a note of how things stood, end of previous period."
This is a common necessity in programs of this sort. It arises thus....
Early on, we are going to revise the biomass of grass present. And after that, we are going to do a calculation on the feeding success of the rabbits including as a factor, the grass available to them. But we don't want to be using the NEW grass biomass. We want access to what the grass biomass was at the end of last year.
Hence, we will create the following....
lwBM_grassPrev, lwBM_rabbitPrev, lwBM_foxPrev:longword;
We will fill them, at the beginning of the loop, from lwBM_grass, lwBM_rabbit and lwBM_fox.
=== Building the app===
Quite early, I had something which produced the following. The numbers for the biomass of grass, rabbit and fox were not yet changing, but at least I had my form, and a way to display the numbers. That was a big help when I came to try to get the program to generate them!
At one point, at the heart of the code, I had...
procedure Tsim006f1.DoYear; begin MakeNoteOfPreviousBiomasses; GrassAdd; GrassReduce; RabbitsAdd; RabbitsReduce; FoxesAdd; FoxesReduce; ReportBiomasses; end;//DoYear procedure Tsim006f1.GrassAdd; begin end;//GrassAdd procedure Tsim006f1.GrassReduce; begin end;//GrassReduce procedure Tsim006f1.RabbitsAdd; begin end;//RabbitsAdd procedure Tsim006f1.RabbitsReduce; begin end;//RabbitsReduce procedure Tsim006f1.FoxesAdd; begin end;//FoxesAdd procedure Tsim006f1.FoxesReduce; begin end;//FoxesReduce
Pretty boring and dull, you might say! But I like "boring and dull", if it will do the job!
Fewer hidey holes for bugs and gremlins. Easier to "keep your place", as you build the app up.
Starting with an empty skeleton also lets you take care of the sundry "trivial" tedious typos in the "mere framework" before your mind needs all its gray cells for "the clever bits".
I won't go into all the detail of how every one of the "Add"/"Reduce" routines were developed, but perhaps some detail in a few cases will be of interest.
For this simple demo, I thought I would just increase the amount of grass each year by a simple 2%.
First I had...
THAT wouldn't fly, because you can't use a real number (1.02) on in integer, if you want to store the answer in an integer-type variable.
So then I tried....
lwBM_grass:=(lwBM_grassPrev*102) mod 100;
... feeling quite smug as I typed that over my earlier decision to use a really "big" data type (one capable of storing very large numbers) for the grass biomass variable.
"Of course", I should have used "div", not "mod".
Once all of that had been sorted out, the grass biomass went up nicely, 2% per year, compounded.
Oh. But then I noticed that the starting values weren't appearing in the memo. Trivial? Probably. But untidy. And untidy is BAD... and often comes back to haunt you.
See why I like to break things down to a "ridiculous" level, and take it one tiny step at a time? Imaging if I'd been trying to do something complicated at this point, AND contend with the "trivial" odds and ends?
The final form of even this simple routine illustrates another general principle. The final form is...
lwBM_grass:=(lwBM_grassPrev*wGrassGR) mod 1000;
... where wGrassGR is an integer, set to 1020 elsewhere. "Set"- for now. The day may come when I want to be able to vary the growth rate. Suppose, for instance, we switch to making the "tick" of the model be a month? At that time, we will be able to build into the model the fact that the grass grows more quickly in the summer than in the winter, won't we?
Even if growth rate remains a constant, it is always good to "extract" all the places where a constant is set. Have them in one place. E.g. a routine called "SetConstants"! Given that there will be many "constants" in this program, and the fact that when I think of the "far horizon", I imagine I might vary things like the grass growth rate, I think in this case, for now (the early stage of the journey), I'll set up a routine, to be called during InitToRestartSim, called EstablishInitialRates. (If they don't change, it doesn't matter what you might be inferring from "initial". You can quickly search the code to discover that the final rate is the same as the initial. And in any case, there is the comment....
procedure Tsim006f1.EstablishInitialRates; //Some of these rates may be static, may remain unchanged // over the whole execution of the program's code. //ppk: Parts per thousand. I.e. 1020 is equivalent to 102 percent begin wGrassGR:=1020;//ppk, grass growth per cycle. end;//EstablishInitialRates;
And yes... I did change the formula in GrassAdd...
lwBM_grass:=(lwBM_grassPrev*wGrassGR) div 1000;
... to make finer control of the rate possible.
Once that was in place, which took a little while, the time taken was repaid when I came to write GrassReduce, because GrassGrow was a useful starting point... although I was annoyed because I'd originally called the rate at which the grass was added "wGrassRG". I went back and changed that to "wGrassRA" in the code and here in the tutorial, because it was a better name. Using that would make all the "rate" names follow a logical pattern. (And then was unhappy again, because the "R" in the name is easily confused... some of the Rs mean rate, others meant "change"... so changed the "R"s for "change" to "D"s for "Delta", an old fashioned way to designate "a change rate"... so, finally (I hope), the rate of addition (by growth, in the simple model (and probably the final)) to the grasses biomass is in wGrassAD, and the rate at which the grass biomass reduces (but rabbits eating it) is in wGrassRD.
Initially, the formula was....
But then I "had" to "fancy it up" (and make related changes elsewhere) to allow for the "rate" to be set in 100ths of biomass units.
Note, by the way, that grass is "added" merely by multiplying the old biomass by a constant, greater than 1.
On the other hand, the grass biomass after the rabbits have grazed is calculated by subtracting a number for what the rabbits ate from the old biomass, that number being calculated by multiplying the number of rabbits by their "eating rate" (wGrassRD). (The rabbit's eating rate... grass per rabbit... is just the other side of the coin of the rate the grass's biomass reduces.)
Whew! Lots of detail, recently... but there are lots of devils in programming, and you know where devils are found.
You may be pleased to learn that the fortunes of the rabbits and foxes were, for our simple wants, (relatively) easily dealt with by fairly similar code.
Ta da!... and example of how a good "general framework" can save you work: good, and, even better, mean that you don't have to tackle each element of a puzzle as entirely new and different. Complete with new and different ways to Go Wrong.
Sadly, there was a fly in the ointment... the reduction of fox numbers had to be dealt with differently from how we dealt with the reduction of rabbits or grass... for reasons which you should be able to identify for yourself.
Happily, it was just a matter of slightly adapting the "GrassAdd" idea, and using a rate of less than one, to create a useable initial "FoxReduce".
So! A basic population dynamics simulation was "finished", and "working".
I then began playing with the rates, expecting different outcomes... and didn't get them. For instance, changing the rate of fox reduction didn't change the pattern of the changes in fox biomass. Which is really odd, in more ways than one.
As I said in the "stop press" earlier, I've LEFT this as it was... and, in the next tutorial, have written up how I went from the flawed simulation to one that works better! I hope you will read on!....
That's a lot done... even if bits were flawed! Now we'll go on, fix the flaws, and ass some "fancy bits"... in lt3Ng-2.htm.
I hope you liked Part 1, whether you feel the need of Part 2 or not, and will recommend via social media (see top of page), etc. Feedback welcome other than "needs more graphics". (I know that!)
Search across all my sites with the Google search...
Page tested for compliance with INDUSTRY (not MS-only) standards, using the free, publicly accessible validator at validator.w3.org. Mostly passes. There were two "unknown attributes" in Google+ button code. Sigh.
....... P a g e . . . E n d s .....