[Tutor] recursion sort of

Danny Yoo dyoo@hkn.eecs.berkeley.edu
Fri May 30 16:34:13 2003


> >I'm trying to write a little program on population growth (of anything,
> >say plants).  The key is that it's frequency dependent. When the size
> >is large, individuals don't do as well, and then when it's smaller,
> >they do better.  also there are 2 'types' of individuals (here called
> >n1 and n2; 'w' is how well they do)
>
> The code below is obviously not not doing what you intended, and since
> the code it what you showed us, we don't know what you *did* intend! :)

Hi again,

[My apologies for posting so much on this; it's an interesting problem!]



I think the problem is one of assignment; the program is trying to
simulate the advance of time in the simulation by doing reassignments to
the n1 and n2 variables.

The problem, though, is that this doesn't make it clean to simulate both
populations at the same time.  Nor is it trivially to go "back in time"
without saving previous results in some kind of list.



What the program ends up doing is something very serial --- it's
simulating the first population up to a certain point in time, and then
simulating the second population up to another certain point in time, and
the main problem is that we simply don't know how much time has passed.


>def run(n1, n2):
>     while n1>=501:
>         w=1
>         n1=n1*w
>     while n1<=500:
>         w=0.2
>         n1=n1*W


At this point, how do we know how many iterations of life our population
has gone through?


>     while n2>=501:
>         w=0.8
>         n2=n2*w
>     while n2<=500:
>         w=0.4
>         n2=n2*W

And the same question applies here.  There's no guarantee that the
simulation of n2 goes through the same number of time steps as the
simulation of the n1.



And that's what makes the next calculation meaningless:

>     nt=n2+n1
>     print nt

Conceptually, I'm guessing that we'd like 'nt' to sum up those two
populations, but without guaranteeing that n2 and n1 have run for equal
amounts of time, this won't give a useful answer.




If we really want to do this simulation with loops and reassignment, the
iterating variable should probably be time, not population size:

###
def simulate(number_of_steps, p1_initial, p2_initial):
    """Simulates 'number_of_steps' steps of two populations."""
    p1, p2 = p1_inital, p2_initial

    for time in range(number_of_steps):
        print "Time", time
        print "Population 1:", p1
        print "Population 2:", p2

        p1 = calculate_new_p1(p1)  ## This needs to be defined somewhere.
        p2 = calculate_new_p2(p2)  ## So does this.
###

The structure of this pseudocode is equivalent to Magnus's example.
(It's also designed in a way that will make it difficult to make it loop
infinitely.  *grin*)





But an alternative approach is to model our population growth by using
functions.  For example:


###
def f(time):
    if time == 0:
        return 0
    return f(time-1) + 1
###


is a function that's represents a linear growth, sorta like:

             /
           /
         /
       /
     /
---------------------> time
     0 1 2 3 4




Another one we can do is:

###
def g(time):
    return 2 * math.sin(time / 3.0)
###


which looks a little bit more bouncy:


        ..        ..
       .  .      .
      .    .    .
            .  .
             ..


What's nice about modeling populations with functions is that we can
compose them together: we can make a new function that is a combination of
the two others:


###
>>> import math
>>> def f(time):
...     if time == 0:
...         return 0
...     return f(time-1) + 1
...
>>> def g(time):
...     return 2 * math.sin(time / 3.0)
...
>>> def compose_add(function1, function2):
...     def new_function(time):
...         return function1(time) + function2(time)
...     return new_function
...
>>> fg = compose_add(f, g)
###



'fg' is a function that represents the sum of the functions 'f' and 'g'.
If we graph these together, we'll see that the behavior of fg is sort of a
blend between f and g, exhibiting both linear growth but with a slight
oscillating component.


###
>>> for i in range(20):
...     print i, fg(i)
...
0 0.0
1 1.65438939359
2 3.23673960614
3 4.68294196962
4 5.94387580273
5 6.9908159155
6 7.81859485365
7 8.44617176348
8 8.91454525327
9 9.28224001612
10 9.61886407425
11 9.99744590282
12 10.4863950094
13 11.1419709975
14 12.0020901658
15 13.0821514507
16 14.3733412169
17 15.8436035165
18 17.4411690036
19 19.1002540198
###



And this is sorta neat, for 10 lines of code.  *grin* And it becomes easy
to use this to start adding other functions together:

###
>>> ff = compose_add(f, f)
>>> ff(0)
0
>>> ff(1)
2
>>> ff(2)
4
>>> ff(3)
6
>>> ff(4)
8
###


If you have any questions on this, please feel free to ask!