<jupyter_start><jupyter_text>Mutualism type II functional responseThis is an interactive visualization of the type II functional response model described by Algarra et al here: https://ifisc.uib-csic.es//~jramasco/text/ecol_eqs.pdf.<jupyter_code># optional for e.g. colab !git clone https://github.com/Rested/mutualism-visualisation mut_vis<jupyter_output><empty_output><jupyter_text>Imports<jupyter_code>import numpy as np
# from scipy.integrate import odeint
import matplotlib.pyplot as p
import ipywidgets as widgets
from modplot import velovect
# for colab -- from mut_vis.modplot import velovect<jupyter_output><empty_output><jupyter_text>Parameters> Animals here means some species of animal and plants means some species of plant.$r_1$ is the _intrinsic rate_ of growth of the population of animals.it is defined as$birthrate_1 - deathrate_1$.$r_2$ is the _intrinsic rate_ of growth of the population of plants.$\alpha_1$ and $\alpha_2$ are the "braking/friction" coefficient for animals and plants respectively, which can beinterpreted as the effects of inter-species competition as well as mutualism. For simplicity, in this model the effectof mutualism on $\alpha_i$ is proportional to the benefit with $c_i$ as the proportionality constant.$b_{12}$ is the rate of mutualistic interactions between animals and plants and $b_{21}$ is the rate between plants andanimals.<jupyter_code># parameters
initial_r1 = -3
initial_r2 = -10
initial_alpha1 = initial_alpha2 = 0.008
initial_b12 = initial_b21 = 0.4
initial_c1 = initial_c2 = 0.008<jupyter_output><empty_output><jupyter_text>The modelThe system dynamics with a plant species as index one and an animal species as index 2 is described by the followingdifferential equation system.$$\frac{dN^p_1}{dt} = (r_1 + b_{12}N^a_2)N^p_1 - (\alpha_1 + c_1b_{12}N^a_2){N^p_1}^2\\\frac{dN^a_2}{dt} = (r_2 + b_{21}N^p_1)N^a_2 - (\alpha_2 + c_2b_{21}N^p_1){N^a_2}^2$$$\frac{dN^p_1}{dt}$ describing how the plant population changes over time.<jupyter_code>def dX_dt(X, t=0, **params):
    animal, plant = X
    return np.array(
        [
            (params["r1"] + (params["b12"] * animal)) * plant
            - (
                (params["alpha1"] + (params["c1"] * params["b12"] * animal))
                * (plant ** 2)
            ),
            (params["r2"] + (params["b21"] * plant)) * animal
            - (
                (params["alpha2"] + (params["c2"] * params["b21"] * plant))
                * (animal ** 2)
            ),
        ]
    )<jupyter_output><empty_output><jupyter_text>Population dynamics/fixed points<jupyter_code>"""
The dynamics of the surviving population with posi-tive r
follows a decoupled logistic equation, as can be seen from(11).
Therefore, its population will tend to the limit given by a non-interacting system:
either K1=r1/α1 or K2 = r2/α2. This means that there are partial extinction
fixed points (K1, 0) or (0, K2),or both if mutualism is facultative only for species 1
(r1 > 0), only for species 2 (r2 > 0) (see Fig. 1c) or for both (r1 > 0 and r2 > 0) (see Fig. 1d).
"""
def get_fixed_points(**params):
    X_f0 = np.array([0.0, 0.0])
    K1 = params["r1"] / params["alpha1"]
    K2 = params["r2"] / params["alpha2"]
    X_f1 = np.array([K1, 0.0])
    X_f2 = np.array([0.0, K2])
    fixed_points = [X_f0]
    print(dX_dt(list(reversed(X_f1)), **params), dX_dt(list(reversed(X_f2)), **params))
    if all(dX_dt(list(reversed(X_f1)), **params) == 0):
        fixed_points.append(X_f1)
    if all(dX_dt(list(reversed(X_f2)), **params) == 0):
        fixed_points.append(X_f2)

    if params["r1"] > 0 or params["r2"] > 0:
        print(
            f"There are partial extinction points at {fixed_points[1]} and {fixed_points[2]}"
        )
    print("Trivial fixed points:", fixed_points)
    return fixed_points<jupyter_output><empty_output><jupyter_text>Non-trivial fixed points<jupyter_code>def get_non_trivial_fixed_points(r1, r2, alpha1, alpha2, b12, b21, c1, c2):
    A = (c2 * b21 * alpha1) + (c1 * b12 * b21)
    B = (alpha1 * alpha2) + (c1 * b12 * r2) - (c2 * b21 * r1) - b12 * b21
    C = -r1 * alpha2 - b12 * r2

    plant_values = np.roots([A, B, C])

    animal_values = list(
        map(lambda x: (r2 + (b21 * x)) / (alpha2 + (c2 * b21 * x)), plant_values)
    )

    non_trivial_fixed_points = list(zip(plant_values, animal_values))

    print("non trivial fixed points", non_trivial_fixed_points)

    for fp in non_trivial_fixed_points:
        print(
            "d/dt close to 0",
            dX_dt(
                reversed(list(fp)),
                r1=r1,
                r2=r2,
                alpha1=alpha1,
                alpha2=alpha2,
                b12=b12,
                b21=b21,
                c1=c1,
                c2=c2,
            ),
        )

    return non_trivial_fixed_points<jupyter_output><empty_output><jupyter_text>Flow diagrams IntuitionThe fixed points are marked as red circles. These can be thought of as points where the two populations stabilize. The colour of the arrows (or length and colour if you turn of normalization) represent intensity of the flow or how quickly the population converges to the given fixed point.We can generate the following 4 illustrative configurations by changing $r_1$ and $r_2$ (the intrinsic growth rates of each species).---> $$r_1 = -10, r_2 = -10$$In this configuration mutualism is obligatory since without it both populations would tend to the $(0, 0)$ extinction fixed points. You can demonstrate this by sliding the rate of mutualistic interacts to $b_{12} = b_{21} = 0$---> $$r_1 = -3, r_2 = -10$$Here mutualism is also obligatory for both populations but more so for the animal species. We can see this from the fixed point near $(27, 10)$.---> $$r_1 = 1, r_2 = -10$$Here mutualism is only necessary for the animal species. If there are no members of the animal species the plant species will continue thriving, whilst the animal species will go extinct.---> $$r_1 = 1, r_2 = 1$$Here mutualism is completely optional and both species can survive with or without it.  Flow diagrams<jupyter_code>xmax = ymax = 150
xmin = ymin = 0
p.rcParams["figure.dpi"] = 200


def add_quiver(X1, Y1, normalize_arrows, arrow_style, colour_scheme, **params):
    DX1, DY1 = dX_dt([X1, Y1], **params)  # compute growth rate on the grid
    M = np.hypot(DX1, DY1)  # Norm of the growth rate
    if normalize_arrows:
        M[M == 0] = 1.0  # Avoid zero division errors
        DX1 /= M  # Normalize each arrows
        DY1 /= M
    if arrow_style == "Straight":
        p.quiver(
            Y1,
            X1,
            DX1,
            DY1,
            M,
            pivot="mid",
            cmap=getattr(p.cm, colour_scheme),
            zorder=2,
        )
    elif arrow_style == "Curved":
        velovect(
            p.subplot(),
            Y1.T,
            X1.T,
            DX1.T,
            DY1.T,
            color=M,
            cmap=getattr(p.cm, colour_scheme),
        )
    elif arrow_style == "Stream":
        p.streamplot(
            Y1.T,
            X1.T,
            DX1.T,
            DY1.T,
            color=M,
            cmap=getattr(p.cm, colour_scheme),
            zorder=2,
            maxlength=20,
        )


def add_scatter(fixed_points, non_trivial_fixed_points):
    p.scatter(
        np.array([x[0] for x in fixed_points + non_trivial_fixed_points]),
        np.array([x[1] for x in fixed_points + non_trivial_fixed_points]),
        c="red",
        zorder=4,
    )


@widgets.interact(
    r1=(-10, 10, 1),
    r2=(-10, 10, 1),
    alpha1=(0.001, 0.01, 0.001),
    alpha2=(0.001, 0.01, 0.001),
    c1=(0.001, 0.01, 0.001),
    c2=(0.001, 0.01, 0.001),
    b12=(-1, 1, 0.1),
    b21=(-1, 1, 0.1),
    num_points=(10, 100, 5),
    grid_toggle=["On", "Off"],
    fixed_point_toggle=["On", "Off"],
    normalize_arrows=["Yes", "No"],
    arrow_style=["Straight", "Curved", "Stream"],
    colour_scheme=[
        "binary",
        "gist_yarg",
        "gist_gray",
        "gray",
        "bone",
        "pink",
        "spring",
        "summer",
        "autumn",
        "winter",
        "cool",
        "Wistia",
        "hot",
        "afmhot",
        "gist_heat",
        "copper",
        "jet",
        "ocean",
        "gist_earth",
        "terrain",
    ],
)
def update(
    r1=initial_r1,
    r2=initial_r2,
    alpha1=initial_alpha1,
    alpha2=initial_alpha2,
    c1=initial_c1,
    c2=initial_c2,
    b12=initial_b12,
    b21=initial_b21,
    num_points=20,
    grid_toggle="On",
    fixed_point_toggle="On",
    normalize_arrows="Yes",
    arrow_style="Straight",
    colour_scheme="jet",
):

    for artist in p.gca().lines + p.gca().collections:
        artist.remove()
    x = np.linspace(xmin, xmax, num_points)
    y = np.linspace(ymin, ymax, num_points)

    X1, Y1 = np.meshgrid(x, y)

    p.title(f"r1 = {r1}, r2 = {r2}")
    p.xlabel("Number of the plant species")
    p.ylabel("Number of the animal species")
    if grid_toggle == "On":
        p.grid(zorder=-10)
    triv = get_fixed_points(
        r1=r1, r2=r2, alpha1=alpha1, alpha2=alpha2, b12=b12, b21=b21, c1=c1, c2=c2
    )
    non_triv = get_non_trivial_fixed_points(r1, r2, alpha1, alpha2, b12, b21, c1, c2)
    add_quiver(
        X1,
        Y1,
        normalize_arrows == "Yes",
        arrow_style,
        colour_scheme,
        r1=r1,
        r2=r2,
        alpha1=alpha1,
        alpha2=alpha2,
        b12=b12,
        b21=b21,
        c1=c1,
        c2=c2,
    )
    if fixed_point_toggle == "On":
        add_scatter(triv, non_triv)
    p.xlim(xmin, xmax)
    p.ylim(ymin, ymax)
    p.show()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12589517384535548, 'fraction_numerical': 0.045147898287493514, 'mean_word_length': 2.6541524459613197, 'pattern_counts': {'":': 5, '<': 29, '<?xml version=': 0, '>': 40, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '7', 'dir': 'jupyter-structured-clean-dedup', 'id': '242324', 'license': 'MIT', 'n_tokens_mistral': 3621, 'n_tokens_neox': 3373, 'n_words': 908, 'path': 'viz.ipynb', 'repo_name': 'Rested/mutualism-visualisation', 'size': '9635'}