#################################################################################################################
# ewstools
# Description: Python package for computing, analysing and visualising 
# early warning signals (EWS) in time-series data
# Author: Thomas M Bury
# Web: https://www.thomasbury.net/
# Code repo: https://github.com/ThomasMBury/ewstools
# Documentation: https://ewstools.readthedocs.io/
#
# The MIT License (MIT)
#
# Copyright (c) 2019 Thomas Bury
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#################################################################################################################


#---------------------------------
# Import relevant packages
#--------------------------------

# For numeric computation and DataFrames
import numpy as np
import pandas as pd

def simulate_ricker(tmax=500, tburn=100,
                    r=0.75, k=10, h=0.75,
                    F=[0,2.7],
                    sigma=0.02,
                    x0=0.8):
    '''
    Run a numerical simulation of the Ricker model
    with a Holling Type II harvesting term and additive white noise.
    Allows for linearly increasing/decreasing harvesting rate.
    Default parameter configuration takes model through a Fold bifurcation.
    
    Model details are available in this paper https://royalsocietypublishing.org/doi/10.1098/rsif.2016.0845.

    Parameters
    ----------
    tmax : int, optional
        Number of time steps. The default is 500.
    tburn : int, optional
        Number of time steps to use as a burn in period
        to remove transients. The default is 100.
    r : float or list, optional
        Intrinsic growth rate. Can be provided as a list containing the start
        and end value if linear change is desired. The default is 0.75.
    k : float, optional
        Population carrying capacity. The default is 10.
    h : float, optional
        Half-saturation constant of the harvesting expression. The default is 0.75.
    F : float or list, optional
        Maximum harvesting rate. Can be provided as a list containing the start
        and end value if linear change is desired. The default is 0.
    sigma : float, optional
        Noise amplitude. The default is 0.02.
    x0 : float, optional
        Initial condition. The default is 0.8.

    Returns
    -------
    pd.Series
        Trajectory indexed by time.  

    '''
    
    # Define the map
    def de_fun(x,r,k,f,h,xi):
        return x*np.exp(r*(1-x/k)+xi) - f*x**2/(x**2+h**2)
            
        
    # Initialise arrays for time and state values
    t = np.arange(tmax)
    x = np.zeros(tmax)
    
    # Get r values, that may be linearly changing
    if type(r) == float:
        rvals = np.ones(tmax)*r
    else:
        rvals = np.linspace(r[0], r[1], tmax)
    
    # Get F values, that may be linearly changing
    if type(F) == float:
        Fvals = np.ones(tmax)*F
    else:
        Fvals = np.linspace(F[0], F[1], tmax)
    
    # Array of noise values (normal random variables with variance sigma^2)
    dW_burn = np.random.normal(loc=0, scale=sigma, size = tburn) # burn-in period
    dW = np.random.normal(loc=0, scale=sigma, size = tmax) # monitored period
    
    # Run burn-in period starting from intiial condition x0
    for i in range(int(tburn)):
        x0 = de_fun(x0,rvals[0],k,Fvals[0],h,dW_burn[i])
    
    # State value post burn-in period. Set as starting value.
    x[0]=x0
    
    # Run simulation
    for i in range(tmax-1):
        x[i+1] = de_fun(x[i],rvals[i],k,Fvals[i],h,dW[i])
        # Make sure that state variable stays >= 0
        if x[i+1] < 0:
            x[i+1] = 0
            
    # Store in pd.Series indexed by time
    series = pd.Series(data=x, index=t)
    series.index.name='time'
    
    return series







