In [ ]:
%matplotlib inline

TUTORIAL coupling NPZD2 and Mussels


First create a new file by "Saving-as" NPDZ.py witha new name... lets call the new file NPZD2_Mussels.py

From now on, we are going to copy-paste from Mussels.py to the new NPZD2_Mussels.py


Copy-paste parameters from Mussels.py to NPZD2_Mussels.py Copy-paste the "Mussel" parameters just below the "NPZD2" parameters. The new code should look like this:


In [ ]:
# Parameters
    par = {}
    par['mu0']    = 0.69  
    par['kNO3']    = 0.5    
    par['kNH4']    = 0.5  
    par['alpha']    = 0.125  
    par['gmax'] = 0.6  #Original 0.6
    par['kP']       = 0.44
    par['mP']   = 0.15    
    par['tau']   = 0.005 
    par['thetaMax']   = 0.053
    par['beta']    = 0.75 
    par['lBM']    = 0.1    
    par['lE']   = 0.1
    par['mZ']  = 0.25
    par['rSD']    = 0.3 # Original 0.03
    par['rLD'] = 0.1 # # Original 0.01
    par['nmax'] = 0.05
    par['kI'] = 0.1
    par['I0']  = 0.0095
    # Parameters Mussels
    par['AE_P']    = 0.9  
    par['AE_D']    = 0.2    
    par['AE_Z']    = 0.3  
    par['Bpub']    = 0.43  
    par['Fmax_ref'] = 0.025
    par['GT']       = 0.44
    par['KTempH']   = 0.1    
    par['KTempL']   = 0.5 
    par['KSaltL']   = 0.25
    par['KOxyL']    = 0.02 
    par['KFood']    = 1.    
    par['KRE']   = 0.86
    par['OxyL']  = 17.5
    par['Rm']    = 0.002
    par['SaltL'] = 10.
    par['TempH'] = 25.
    par['TempL'] = -4.
    par['beta']  = 0.12
    par['epsilonP'] = 1.
    par['epsilonD'] = 0.5
    par['epsilonZ'] = 0.3

Copy-paste InitCond from Mussels.py to NPZD2_Mussels.py Copy-paste the "Mussel" InitCond just below the "NPZD2" InitCond.

Note that now there are duplicate InitCond... do not copy the Mussel InitCond that are already in NPZD2 (i.e. Temp, Phy, Zoo, SDet)

The new code should look like this:


In [ ]:
# Initial conditions
    InitCond = {}
    InitCond['Phy'] = 0.2
    InitCond['Zoo'] = 0.1
    InitCond['SDet'] = 1. 
    InitCond['LDet'] = 1.
    InitCond['NH4'] = 0.1
    InitCond['NO3'] = 7.
    InitCond['Temp'] = 6.
    #InitCond['O2'] = 0.5
    InitCond['Soma'] = 0.01
    InitCond['Gonad'] = 0.
    InitCond['Salt'] = 30. #Salinity
    InitCond['Oxy'] = 30. #Oxygen

Copy-paste zero vectors from Mussels.py to NPZD2_Mussels.py Copy-paste the "Mussel" zero-vectors just below the "NPZD2" zero-vectors.

Same as above, note that now there are duplicate zero-vectors... do not copy the Mussel zero-vectors that are already in NPZD2 (i.e. Temp, Phy, Zoo, SDet)

The new code should look like this:


In [ ]:
# Create zero-vectors
    Phy = np.zeros((NoSTEPS,),float) # makes a vector array of zeros (size: NoSTEPS rows by ONE column)
    Zoo = np.zeros((NoSTEPS,),float) # same as above
    SDet = np.zeros((NoSTEPS,),float) # Biomass - same as above 
    LDet = np.zeros((NoSTEPS,),float) # same as above
    NH4 = np.zeros((NoSTEPS,),float) # same as above
    NO3 = np.zeros((NoSTEPS,),float) # same as above
    I  = np.zeros((NoSTEPS,),float) # same as above
    
    mu = np.zeros((NoSTEPS,),float) # same as above
    f_I = np.zeros((NoSTEPS,),float) # same as above
    L_NO3 = np.zeros((NoSTEPS,),float) # same as above
    L_NH4 = np.zeros((NoSTEPS,),float) # same as above
    TotN = np.zeros((NoSTEPS,),float) # same as above
    
    # Mussels
    Soma = np.zeros((NoSTEPS,),float) # makes a vector array of zeros (size: NoSTEPS rows by ONE column)
    Gonad = np.zeros((NoSTEPS,),float) # same as above
    B = np.zeros((NoSTEPS,),float) # Biomass - same as above 
    L_Temp = np.zeros((NoSTEPS,),float) # same as above
    L_Salt = np.zeros((NoSTEPS,),float) # same as above
    L_Oxy = np.zeros((NoSTEPS,),float) # same as above
    L_Food = np.zeros((NoSTEPS,),float) # same as above
    F = np.zeros((NoSTEPS,),float) # same as above
    A = np.zeros((NoSTEPS,),float) # same as above
    R = np.zeros((NoSTEPS,),float) # same as above
    RE = np.zeros((NoSTEPS,),float) # same as above
    Spawning = np.zeros((NoSTEPS,),float) # same as above

Copy-paste the section "Initializing with initial conditions" from Mussels.py to NPZD2_Mussels.py

Copy-paste the "Mussel" section just below the one for "NPZD2".

The new code should look like this:


In [ ]:
# Initializing with initial conditions
    Phy[0] = InitCond['Phy']
    Zoo[0] = InitCond['Zoo']
    SDet[0] = InitCond['SDet']
    LDet[0] = InitCond['LDet']
    NH4[0] = InitCond['NH4']
    NO3[0] = InitCond['NO3']
    # Mussels
    Soma[0] = InitCond['Soma']
    Gonad[0] = InitCond['Soma']
    B[0] = InitCond['Soma'] + InitCond['Gonad']
    Spawning[0] = 0.
    Salt = np.ones((NoSTEPS,),float) * InitCond['Salt']#Salinity
    Oxy = np.ones((NoSTEPS,),float) * InitCond['Oxy'] #Oxygen

Copy-paste "Update and step" from Mussels.py to NPZD2_Mussels.py Copy-paste the "Mussel" "Update and step" just below the one for "NPZD2".

The new code should look like this:


In [ ]:
# Update and step ----------------------------------------------------
        Phy[t+1]  = Phy[t]  + (dPhydt * dt)
        Zoo[t+1]  = Zoo[t]  + (dZoodt * dt)
        SDet[t+1] = SDet[t] + (dSDetdt * dt)
        LDet[t+1] = LDet[t] + (dLDetdt * dt)
        NH4[t+1]  = NH4[t]  + (dNH4dt * dt)
        NO3[t+1]  = NO3[t] +  (dNO3dt * dt)
        if NO3[t+1] <= 0.0001:
            offset = NO3[t+1]
            NH4[t+1] = NH4[t+1] + offset
            NO3[t+1] = NO3[t+1] - offset
        # Mussels
        Soma[t+1] = Soma[t] + (dSomadt * dt)
        Gonad[t+1] = Gonad[t] + (dGonaddt * dt) - Spawning[t]
        B[t+1] = Soma[t+1] + Gonad[t+1]

Copy-paste MAIN MODEL LOOP from Mussels.py to NPZD2_Mussels.py Copy-paste the "Mussel" MAIN MODEL LOOP just below the one for "NPZD2"

The new code should look like this:


In [ ]:
# *****************************************************************************
    # MAIN MODEL LOOP *************************************************************
    for t in range(0,NoSTEPS-1):
        muMax = par['mu0'] * (1.066 ** Temp[t]) # text
        
        f_I[t] = (par['alpha']*I[t])/(np.sqrt(muMax**2+((par['alpha']**2)*(I[t]**2)))) #Eq5
        
        L_NO3[t] = max(0,((NO3[t])/(par['kNO3']*NO3[t])) * (1/(1+(NH4[t]/par['kNH4'])))) #Eq3
        
        L_NH4[t] = max(0,NH4[t]/(par['kNH4']+NH4[t])) # Eq 4
    
        mu[t] =muMax * f_I[t] * (L_NO3[t] + L_NH4[t]) # Eq2
    
        g = par['gmax'] * (Phy[t]**2/(par['kP']+(Phy[t]**2)))
        
        n = par['nmax'] * (1 - max(0,(I[t]-par['I0'])/(par['kI']+I[t]-par['I0'])))
    
        dPhydt = (mu[t] * Phy[t]) - \
                 (g  * Zoo[t]) - \
                 (par['mP'] * Phy[t]) - \
                 (par['tau']*(SDet[t]+Phy[t])*Phy[t]) # Eq1
                 
        dZoodt = (g * par['beta'] * Zoo[t]) - \
                 (par['lBM']*Zoo[t]) - \
                 (par['lE']*((Phy[t]**2)/(par['kP']+(Phy[t]**2)))*par['beta']*Zoo[t]) - \
                 (par['mZ']*(Zoo[t]**2))#Eq10
                 
        dSDetdt = (g * (1-par['beta']) * Zoo[t]) + \
                  (par['mZ']*(Zoo[t]**2)) + \
                  (par['mP'] * Phy[t]) - \
                  (par['tau']*(SDet[t]+Phy[t])*SDet[t]) - \
                  (par['rSD']*SDet[t])
                  
        dLDetdt = (par['tau']*((SDet[t]+Phy[t])**2)) - \
                  (par['rLD']*LDet[t])
                  
        dNO3dt = -(muMax * f_I[t] * L_NO3[t] * Phy[t]) + \
                  (n * NH4[t])
                 
        dNH4dt = -(muMax * f_I[t] * L_NH4[t] * Phy[t]) - \
                  (n * NH4[t]) + \
                  (par['lBM'] * Zoo[t]) + \
                  (par['lE']*((Phy[t]**2)/(par['kP']+(Phy[t]**2)))*par['beta']*Zoo[t]) + \
                  (par['rSD']*SDet[t]) + \
                  (par['rLD']*LDet[t])


        # MUSSELS -------------------------------------------------------------
        # Calculate Temperature Limitation
        L_Temp[t] = min(max(0.,1.-np.exp(-par['KTempL']*(Temp[t]-par['TempL']))), \
                     max(0.,1.+((1.-np.exp(par['KTempH']*Temp[t]))/(np.exp(par['KTempH']*par['TempH'])-1.))))
        
        # Calculate Salinity Limitation
        L_Salt[t] = max(0.,1.-np.exp(-par['KSaltL']*(Salt[t]-par['SaltL'])))
        
        # Calculate Oxygen Limitation
        L_Oxy[t] = max(0.,1.-np.exp(-par['KOxyL']*(Oxy[t]-par['OxyL'])))
        
        # Calculate Oxygen Limitation
        L_Food[t] = (Phy[t]+Zoo[t]+SDet[t])/(par['KFood']+Phy[t]+Zoo[t]+SDet[t])
        
        # Calculate Filtration rate
        Fmax  = par['Fmax_ref']*(B[t]**(2./3.))
        
        F[t] = Fmax * L_Temp[t] * L_Salt[t] * L_Oxy[t] * L_Food[t]
        
        A[t] = F[t] * ((par['epsilonP']*par['AE_P']*Phy[t])+ \
                       (par['epsilonZ']*par['AE_Z']*Zoo[t])+ \
                       (par['epsilonD']*par['AE_D']*SDet[t]))
        
        R[t] = (par['Rm']*B[t]) + (par['beta']*A[t])
        
        RE[t] = max(0., (B[t]-par['Bpub'])/(par['KRE'] + B[t] - (2.*par['Bpub'])))
        
        # Spawning
        if Gonad[t]/B[t] < par['GT']:
            Spawning[t] = 0.
            dGonaddt = (A[t]-R[t]) * RE[t]
            dSomadt =  (A[t]-R[t]) * (1.-RE[t])
        elif Gonad[t]/B[t] >= par['GT']:         
            Spawning[t] = Gonad[t]
            dGonaddt = 0.
            dSomadt = A[t]-R[t]

Copy-paste section "Pack to output" from Mussels.py to NPZD2_Mussels.py Copy-paste the "Mussel" section just below the one for "NPZD2". Note that they are duplicates, son;t copy the duplicates (just Soma, Gonad, B, and Spawning).

The new code should look like this:


In [ ]:
# Pack output into dictionary
    output = {}
    output['time'] = time
    output['Phy'] = Phy
    output['Zoo'] = Zoo
    output['SDet'] = SDet
    output['LDet'] = LDet
    output['NH4'] = NH4
    output['NO3'] = NO3
    output['mu'] = mu
    output['f_I'] = f_I
    output['L_NO3'] = L_NO3
    output['L_NH4'] = L_NH4
    output['TotN'] = TotN
    output['Soma'] = Soma
    output['Gonad'] = Gonad
    output['B'] = B
    output['Spawning'] = Spawning

Update plot by adding to the plotting code for ax2:

ax2.plot(output['time']/365,output['B'],'r.')

The new code should look like this:


In [ ]:
ax2.plot(output['time']/365,output['Phy'],'g-')
    ax2.plot(output['time']/365,output['Zoo'],'r-')
    ax2.plot(output['time']/365,output['SDet'],'k-')
    ax2.plot(output['time']/365,output['LDet'],'k-.')
    ax2.plot(output['time']/365,output['NH4'],'m-')
    ax2.plot(output['time']/365,output['NO3'],'c-')
    ax2.plot(output['time']/365,output['TotN'],'y-')
    ax2.plot(output['time']/365,output['B'],'r.')
    ax2.set_xlabel('Time (years)')
    ax2.set_ylabel('Nitrogen (mmol N m$^{-3}$)')
    ax2.set_title('Fennel et al 2006 Model')
    plt.legend(['Phy','Zoo','SDet','LDet','NH4','NO3','TotN','B'])

Run the model so that you can see a plot.

  • Anything changed?

  • Is total nitrogen conserved?

  • Are you sure?

"B" is not accounted for in the Total Nitrogen calculation.

Add "B[t+1]" to the "Total Nitrogen calculation. The new code should look like this:


In [ ]:
# Estimate Total Nitrogen
        TotN[t+1] = Phy[t+1] + Zoo[t+1] + SDet[t+1] + LDet[t+1] + NH4[t+1] + NO3[t+1] + B[t+1]

Run the model to plot again

  • Is total nitrogen conserved?

We added Mussels to NPZD2, but they are not connected yet. "Food" is eaten by the mussels but are not taken out of the NPZD2 pool.

To connect them, we have to add extra terms to our original equations. Below, as an example, es the original Phytoplankton equation and then the new equation with an added term to account for mussel filtration.

(original from Fennel et al. 2006)

$$ \frac{\partial Phy}{\partial t} = \mu Phy - gZoo - m_P Phy - \tau (SDet -Phy)Phy$$

(new) $$ \frac{\partial Phy}{\partial t} = \mu Phy - gZoo - m_P Phy - \tau (SDet -Phy)Phy - F \epsilon_P Phy$$

To actually code the "feedback", we need to add code to Ad Feaces to LDet, eat Phy/Zoo/SDet... and excrete into NH4. The new code goes after "Spawning" but before "Update and setep. Also, we need to add spawning to Zoo.

The new code should look like this:


In [ ]:
# Spawning
        if Gonad[t]/B[t] < par['GT']:
            Spawning[t] = 0.
            dGonaddt = (A[t]-R[t]) * RE[t]
            dSomadt =  (A[t]-R[t]) * (1.-RE[t])
        elif Gonad[t]/B[t] >= par['GT']:         
            Spawning[t] = Gonad[t]
            dGonaddt = 0.
            dSomadt = A[t]-R[t]

        #Feedback to NPZD2 model
        # Faeces and Pseudofaeces
        Fae = F[t] * ((par['epsilonP']*(1-par['AE_P'])*Phy[t])+ \
                     (par['epsilonZ']*(1-par['AE_Z'])*Zoo[t])+ \
                     (par['epsilonD']*(1-par['AE_D'])*SDet[t]))
        dLDetdt = dLDetdt + Fae      
                  
        # Remove eaten Phy, Zoo and SDet from water-column
        dPhydt =  dPhydt-(F[t] *par['epsilonP']*Phy[t])
        dZoodt =  dZoodt-(F[t] *par['epsilonZ']*Zoo[t])
        dSDetdt = dSDetdt -(F[t] *par['epsilonD']*SDet[t])
        
        # Excretion into Ammonia
        dNH4dt = dNH4dt + R[t]
        

        # Update and step ----------------------------------------------------
        Phy[t+1]  = Phy[t]  + (dPhydt * dt)
        Zoo[t+1]  = Zoo[t]  + (dZoodt * dt) + Spawning[t]
        SDet[t+1] = SDet[t] + (dSDetdt * dt)
        LDet[t+1] = LDet[t] + (dLDetdt * dt)
        NH4[t+1]  = NH4[t]  + (dNH4dt * dt)
        NO3[t+1]  = NO3[t] +  (dNO3dt * dt)
        if NO3[t+1] <= 0.0001:
            offset = NO3[t+1]
            NH4[t+1] = NH4[t+1] + offset
            NO3[t+1] = NO3[t+1] - offset
        # Mussels
        Soma[t+1] = Soma[t] + (dSomadt * dt)
        Gonad[t+1] = Gonad[t] + (dGonaddt * dt) - Spawning[t]
        B[t+1] = Soma[t+1] + Gonad[t+1]

Run the model to plot again

  • Is total nitrogen conserved?

  • How many time mussels spawn in a summer?

  • Is phytoplankton eaten by the "spawned" mussel larvae? ...can you see it in the Phy series?


In [ ]: