Fibonacci soup

There’s a meme floating around on places such as /r/ProgrammerHumor and /r/mathmemes:

But what properties (mathematically) would such a soup have after many days? Specifically, if we start with two distinct soups and every day produce a new one that is a mix of soups from the previous two days, what kind of soup will we ultimately end up with? Turns out the solution is a neat application of first-year university maths so let’s dig in!

The recipe

Fibonacci soup is very simple to make:1

  1. On day 00 serve soup 00 (e.g. spinach)
  2. On day 11 serve soup 11 (e.g. pumpkin)
  3. On day nn serve a soup consisting of a fixed proportion x[0,1]x\in[0,1] of soup from n2n-2 days ago and a proportion 1x1-x of soup from the day before2

There is only one parameter xx which we could think of as “chef’s choice” which ultimately determines what kind of soup you get as nn grows large. Since on any given day a soup is a mixture of at most 2 initial soups (ingredients), we can focus on the evolution of the proportion of the first soup (served on day 00)3. Denoting the proportion of the first soup on day nn by pnp_n we have the following recurrence:

p0=1p1=0pn=xpn2+(1x)pn1. \begin{aligned} p_0 &= 1 \\ p_1 &= 0 \\ p_n &= xp_{n-2}+(1-x)p_{n-1}. \end{aligned}

We can now work out a few simple cases “by hand” starting with the easy cases of x=0x=0 and x=1x=1. If x=0x=0 then that means on day nn the soup is just the same as the one served on day n1n-1:

The arrows here indicate that recurrence relationship is only between adjacent days. So you only get one chance at spinach soup on day 0 otherwise it’s pumpkin soup forever after!

On the other hand if x=1x=1, the soup on day nn is the same soup as served on day n2n-2:

The situation here is the complete opposite—the menu is constantly alternating between the two initial soups and there is no innovation from the chef.

Finally, let’s see what happens if x=0.5x=0.5:

On day 2 the soup is an equal mix of spinach and pumpkin, but this biases the proportions towards pumpkin on the following days. As a result the proportion of spinach seems to approach some fixed value that’s noticeably less than one half.

To find out how the proportions of the initial soups evolve for any xx we can either take the programmer’s approach or do some maths.

Programmer’s solution

We don’t have to think too much here—take the recurrence relation, write a function to iterate it and watch as results appear. In Python this might look something like this:

def soup(x = 0.5):
    p_0, p_1 = 1, 0
    while True:
      yield p_0
      p_0, p_1 = p_1, x * p_0 + (1-x) * p_1

N = 10
for n, p in zip(range(N), soup()):
    print(n, p)
0 1
1 0
2 0.5
3 0.25
4 0.375
5 0.3125
6 0.34375
7 0.328125
8 0.3359375
9 0.33203125

Looks like for x=0.5x=0.5 the proportion of spinach soup is approaching 1/31/3.

If we run this for a selection of values of xx we obtain the following plot:4

We notice several things:

  • Lower xx results in much lower final proportion of soup 00 (spinach)
  • Any value of x(0,1)x\in(0, 1) seems to converge to some final proportion of the initial soups
  • Higher values of xx result in wild oscillations in the early days where on one day the majority ingredient might be spinach but on the following it would be pumpkin
  • Higher values of xx take much longer to converge (for x=0.99x=0.99 a hundred days are not enough) to a stable proportion, but they do seem to converge eventually

Nevertheless, to work out exactly what the final proportions of the initial soups are we need to turn to maths.

Mathematician’s solution

What we really want to know is the ultimate proportion of spinach to pumpkin soup as the number of days tends to infinity, in other words limnpn\lim_{n\to\infty}p_n. To do this we need to solve the recurrence relation. The recurrence is second order (since pnp_n depends on the previous two terms of the sequence), linear (none of the terms appear as powers higher than one), homogeneous (there is no constant term), and has constant coefficients (xx and 1x1-x are independent of nn). Given this we could look up a solution in a textbook or a wikipedia article.

The usual way these types of recurrences (also know as difference equations) are solved is similar to solving first and second order differential equations—we make an ansatz (a guess) of the form of the solution, work out the consequences and show that it is indeed a solution and then appeal to some uniqueness theorem to ensure that it is the only solution.5 In the case of our difference equation, we make an ansatz that the solution must be a power of the form pnp^n.6 Taking this ansatz and plugging it into the recurrence we get:

pn(1x)pn1xpn2=0, p^n-(1-x)p^{n-1}-xp^{n-2}=0, which must hold for all n2n\geq 2. Dividing through by pn2p^{n-2} we arrive at what is called the characteristic equation:

p2(1x)px=0. p^2-(1-x)p-x=0. This is a quadratic equation in pp with discriminant (1x)2+4x=(x+1)2(1-x)^2+4x=(x+1)^2. Thus, the roots are:

p±=(1x)±x+12 p_{\pm} = \frac{(1-x)\pm|x+1|}{2} or {p+=1p=x. \begin{cases} p_{+} = 1 \\ p_{-} = -x. \end{cases}

The roots are both real, so the general solution will be:7 pn=Ap+n+Bpn=A+B(x)n, \begin{aligned} p_n &= Ap_{+}^n + Bp_{-}^n \\ &= A + B(-x)^n, \end{aligned} where the constants AA and BB are determined from the initial conditions p0p_0 and p1p_1. To find the constants, we plug in the values of p0=1p_0=1 and p1=0p_1=0 and solve the resulting system of equations:

1=A+B0=ABx. \begin{aligned} 1 &= A + B \\ 0 &= A - Bx. \end{aligned} With some algebra we find: A=xx+1B=1x+1. \begin{aligned} A &= \frac{x}{x+1} \\ B &= \frac{1}{x+1}. \end{aligned} Plugging these into the expression for pnp_n and with some simplification we finally arrive at the solution:8 pn=1x+1[x+(x)n], p_n = \frac{1}{x+1}\left[x+\left(-x\right)^{n}\right], which is valid for all n0n\geq 0 (try it yourself for n=0,1,2n=0,1,2).

Now that we have a closed-form expression for pnp_n we can analyse it’s convergence as a sequence. Fortunately it is quite easy in this case. If x=0x=0, the whole expression reduces to pn=0p_n=0 which is what we had before.9 If x=1x=1 the expression simplifies to: pn=12[1+(1)n]. p_n = \frac{1}{2}\left[1+(-1)^{n}\right]. This gives us the oscillating behaviour between 11 (spinach soup) and 00 (pumpkin soup) we also observed before.

If 0<x<10<x<1, we notice that due to the (x)n(-x)^{n} term the expression in the brackets tends to xx as nn\to\infty. Thus, limnpn=xx+1\lim_{n\to\infty}p_n=\frac{x}{x+1}. In particular, for the case of equal mixing x=0.5x=0.5 we get that eventually the ratio of spinach soup will be 13\frac{1}{3} which is what we observed qualitatively in the pie charts and on the simulation results.

A final few observations:

  • If 0<x<10<x<1, the oscillations between consecutive days are due to (x)n(-x)^n flipping between positive and negative values
  • If xx is large, the oscillations at the start are bigger as (x)n(-x)^n is bigger in absolute value but die down as nn grows
  • If xx is large, the final proportions of the soup will be close to 50:50 (because limx1xx+1=12\lim_{x\to 1}\frac{x}{x+1}=\frac{1}{2}).10

Epilogue

Even though Fibonacci soup is clearly a silly problem, surprisingly it packs enough interesting pieces to be, in my opinion, an interesting homework or exam problem for first year maths students. It touches on aspects of simulation, solving recurrences, analysing convergence of sequences, and still leaves room for further exploration (existence and uniqueness of the solution). Too bad we couldn’t taste any of the results!

Seinfeld no soup for you


  1. It does, however, assume that you already know how to make two distinct soups. ↩︎

  2. We don’t want any funny business with proportions so assume that each portion xx and 1x1-x of the two soups contain ingredients in the same proportion as the two soups before taking the portions out, i.e. uniform sampling. ↩︎

  3. At this point my friend who’s a physicist by training was naturally concerned about what happens if some of the soup is eaten or how can we ensure that every day there is soup available in the correct proportions. Since we’re approaching this mathematically we will not concern ourselves with such issues—either nobody is eating the soup or at any day there exists enough soup from one and two days ago in the correct proportions. ↩︎

  4. Since the domain of the sequence is the natural numbers together with zero, only the markers are valid points, the dotted lines are purely for highlighting the scale of oscillations between adjacent terms. ↩︎

  5. I do find this a bit unsatisfying myself but I have yet to see an exposition that proceeds differently. For differential equations, existence and uniqueness theorems are quite involved and usually postponed to year 2 or 3 of a maths degree. The situation is slightly better with difference equations, but still requires a knowledge of linear algebra. ↩︎

  6. This choice is informed by the fact that the general solution of the first order recurrence an=ran1a_n=ra_{n-1} is an=krna_n=kr^n with initial condition a0=ka_0=k↩︎

  7. One can show that if p+np_{+}^n and pnp_{-}^n are both solutions of the recurrence, so is their linear combination. This is a consequence of linearity. ↩︎

  8. You can check that it really is a solution by plugging it into the recurrence, but ain’t nobody got time for that in this post. The other thing we should really check is the uniqueness—there are no other possible solutions. But that requires a textbook and an even longer exposition—such is maths! ↩︎

  9. For n=0,1n=0,1, the expression evaluated for general xx is independent of xx which gives us the correct initial conditions p0=1p_0=1 and p1=0p_1=0. Alternatively, plugging in x=0x=0 first, we can appeal to assigning the value 00=10^0=1 to keep the expression consistent for p0p_0. For more info see here↩︎

  10. We have to be a little careful in how we take limits, here we take the limit nn\to\infty first and then observe how the limit changes as a function of xx. This does not mean that pn=12p_n=\frac{1}{2} for x=1x=1 as the limit nn\to\infty is different (in fact, it doesn’t exist as we get the oscillating behaviour). ↩︎