### see output oliva.pgm

"""
Models for the simulations of the color pattern on the shells of mollusks
see also: Meinhardt,H. and Klingler,M. (1987) J. theor. Biol 126, 63-69
see also: H.Meinhardt: "Algorithmic beauty of sea shells"
(Springer Verlag) (c) H.Meinhardt, Tubingen

This is a short version of a program for the simulations of the color
patterns on tropical sea shells, here # Oliva porphyria# .
An autocatalytic activator a(i) leads to a burst-like activation
that is regulated back by the action of an inhibitor b(i). The life
time of the inhibitor is regulated via a hormone c, that is
homogeneously distributed along the growing edge. Whenever the number
of activated cells cells become too small, active cells # ain activated
until backwards waves are triggered
------------------

Translated by Bearophile from QBASIC to SSPython (for ShedSkin), V. 1.1, Feb 17 2006.
This program requires Python and ShedSkin (http://shedskin.sourceforge.net).
"""

from random import random, randint


class SavePGMlines:
    """SavePGMlines(matrix, filename): class, saves a PGM, 256 greys for each pixel, line by line.
    Values can be int or float in [0, 255]."""
    def __init__(self, filename, ny):
        self.out_file = file(filename, 'w')
        self.ny = ny
        self.nx = 0 # Unknown
        self.written_lines_count = 0 # lines written count

    def saverow(self, row):
        if self.written_lines_count:
            assert len(row) == self.nx
            assert self.written_lines_count < self.ny
        else:
            self.nx = len(row)
            # PPM header
            print >>self.out_file, "P2"
            print >>self.out_file, "# Image created with ppmlib."
            print >>self.out_file, self.nx, self.ny
            print >>self.out_file, "256"

        out_line = [""] * self.nx
        for i in xrange(self.nx):
            ipixel = int(round(row[i]))
            if ipixel >= 255:
                out_line[i] = "255"
            elif ipixel <= 0:
                out_line[i] = "0"
            else:
                out_line[i] = str(ipixel)

        print >>self.out_file, " ".join(out_line)
        self.written_lines_count +=1
        if self.written_lines_count == self.ny:
            self.out_file.close()



def oliva(nx = 600,   # Length of the computed screen matrix (number of cells)
          ny = 500,   # Height of the computed screen matrix
          kp = 12,    # number of iterations between the displays ( = lines on the screen)
          da = 0.015, # Diffusion of the activator
          ra = 0.1,   # Decay rate of the inhibitor
          ba = 0.1,   # Basic production of the activator
          sa = 0.25,  # Saturation of the autocatalysis
          db = 0.0,   # Diffusion of the inhibitor (example = 0.0)
          rb = 0.014, # Decay rate of the inhibitor
          sb = 0.1,   # Michaelis-Menten constant of the inhibition
          rc = 0.1,   # Decay rate of the hormone
          out_file_name = "oliva.pgm"):

    outPGM = SavePGMlines(out_file_name, ny)
    # ----------- Initial condition  --------------------------
    image_matrix = []   # image_matrix will become an array[ny][nx] of float
    c = 0.5             # Hormone-concentration, homogeneous in all cells
    a = [0.0] * (nx+1)  # Activator, general initial concentration
    b = [0.1] * (nx+1)  # Inhibitor, general initial concentration
    # z = fluctuation of the autocatalysis
    #z = [uniform(ra*0.96, ra*1.04) for i in xrange(nx)] # Not possible with SS yet
    z = [ra * (0.96 + 0.08 * random()) for i in xrange(nx)]

    # Seed the initially active cells, not too much close to each other
    # Example distribution: *              *         *                                        *
    i = 10
    for j in xrange(20):
        a[i] = 1.0
        i += randint(10, 60)
        if i >= nx:
            break

    # These constant factors are used again and again, therefore, they are calculated
    # only once at the begin of the calculation
    dac = 1.0 - ra - 2.0*da
    dbc = 1.0 - rb - 2.0*db
    dbcc = dbc

    for row in xrange(ny):
        # Begin of the iteration
        for niter in xrange(kp):
            # -------- Boundary impermeable
            a1 = a[0] # a1 = concentration  of the actual cell. Since this
                      # concentration is identical, no diffusion through the border.
            b1 = b[0]
            a[nx] = a[nx-1] # Concentration in a virtual right cell
            b[nx] = b[nx-1]
            bsa = 0.0  # This will carry the sum of all activations of all cells

            # ---------- Reactions  ------
            for i in xrange(nx): # i = actual cell, kx = right cell
                af = a[i] # local activator concentration
                bf = b[i] # local inhibitor concentration
                aq = z[i] * af * af / (1.0 + sa * af * af)  # Saturating autocatalysis

                # Calculation of the new activator and inhibitor concentration in cell i:
                a[i] = af * dac + da * (a1 + a[i + 1]) + aq / (sb + bf)
                # 1/BF => Action of the inhibitor
                b[i] = bf * dbcc + db * (b1 + b[i + 1]) + aq # new inhibitor conc.
                bsa += rc * af # Hormone production -> Sum of activations
                a1 = af # actual concentration will be concentration in left cell
                b1 = bf # in the concentration change of the next cell

            # New hormone concentration. 1/kx=normalization on total number of cells
            c = c * (1.0 - rc) + bsa/nx
            rbb = rb / c # rbb => Effective life time of the inhibitor

            # Change in a cell by diffusion and decay. Must be recomputed since
            # lifetime of the inhibitor changes.
            dbcc = 1.0 - 2.0*db - rbb

        # ----------- Plot-Save -------------
        outPGM.saverow( [255 * a[ix] for ix in xrange(nx)] )

if __name__ == '__main__':
    oliva()
