Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sanity checks for simulations: disconnected species, extinct species & disconnected graphs #151

Open
1 of 5 tasks
Tracked by #137
alaindanet opened this issue Apr 11, 2024 · 17 comments · May be fixed by #152
Open
1 of 5 tasks
Tracked by #137

Sanity checks for simulations: disconnected species, extinct species & disconnected graphs #151

alaindanet opened this issue Apr 11, 2024 · 17 comments · May be fixed by #152
Assignees

Comments

@alaindanet
Copy link
Contributor

alaindanet commented Apr 11, 2024

The goal would be to warn users that the ecological network simulations can have properties that can create problems for interpreting the results or computing output metrics.

Those problematic features are:

  • Disconnected species:

    • a consumer whose resources/preys are all dead (i.e. their biomass is 0)
    • a producer whose the consumers/predators are all dead (i.e. their biomass is 0)
  • Disconnected graphs:

    • Two disconnected trophic chains
  • Tasks:

    • Functions for disconnected species
    • Function for disconnected graphs
    • Message/Warning implemented in simulate
    • Write a workaround doc for user to simulate dynamics until there are no more extinct or disconnected species.
    • Expose processing metrics (CV, trophic levels, ...)

A set of function for disconnected species:

"""
kill_disconnected_species(A; alive_species = nothing, bm = nothing)

Returns a vector of species biomass, all the disconnected and extinct species being set to a
biomass of 0.

# Arguments:

  - `A`: Adjacency matrix
  - `alive_species`: vector of alive species indices. See [`living_species`](@ref)
  - `bm`: vector of species biomass

# Examples

ti = [
 0 0 0 0;
 0 0 0 0;
 1 0 0 0;
 0 1 0 0
]

bm = [1, 1, 3, 4]
alive = [1, 3, 4]
kill_disconnected_species(ti, alive_species = [1, 2, 3, 4], bm = bm) == [1, 1, 3, 4]
kill_disconnected_species(ti, alive_species = [], bm = bm) == [0, 0, 0, 0]
kill_disconnected_species(ti, alive_species = [1, 3, 4], bm = bm) == [1, 0, 3, 0]
kill_disconnected_species(ti, alive_species = [1, 2, 4], bm = bm) == [0, 1, 0, 4]
kill_disconnected_species(ti, alive_species = [1, 2, 4], bm = bm) == [0, 1, 0, 4]

# Filter species biomass and then adjacency matrix

## Case 1
to = kill_disconnected_species(ti, alive_species = [1, 2, 3, 4], bm = bm)
mask = to .== 0
A = ti[mask, mask]
new_bm = bm[mask]
dim(A) == length(new_bm)

## Case 2
to = kill_disconnected_species(ti, alive_species = [1, 2, 4], bm = bm)
mask = to .== 0
A = ti[mask, mask]
new_bm = bm[mask]
dim(A) == length(new_bm)


"""


function kill_disconnected_species(A; alive_species = nothing, bm = nothing)
    bm = deepcopy(bm)
    disconnected_alive_species = check_disconnected_species(A, alive_species)
    in_disconnected = in(disconnected_alive_species)
    mask_alive_disconnected = in_disconnected.(alive_species)
    alive_to_keep = alive_species[.!mask_alive_disconnected]
    bm_to_set_to_zero = 1:length(bm) .∉ [alive_to_keep]
    bm[bm_to_set_to_zero] .= 0
    bm
end

"""
    remove_disconnected_species(A, alive_species)

# Examples

ti = [
 0 0 0 0;
 0 0 0 0;
 1 0 0 0;
 0 1 0 0
]
remove_disconnected_species(ti, [1, 2, 3])
"""
function remove_disconnected_species(A, alive_species)

    disconnected_alive_species = check_disconnected_species(A, alive_species)
    in_disconnected = in(disconnected_alive_species)
    mask_alive_disconnected = in_disconnected.(alive_species)
    to_keep = .!(mask_alive_disconnected)

    living_A = A[alive_species, alive_species]
    living_A[to_keep, to_keep]

end

"""
    check_disconnected_species(A, alive_species)

# Examples

ti = [
 0 0 0 0;
 0 0 0 0;
 1 0 0 0;
 0 1 0 0
]

check_disconnected_species(ti, [1, 2, 3]) == [2]
check_disconnected_species(ti, [1, 2, 4]) == [1]
check_disconnected_species(ti, [2, 3, 4]) == [3]
check_disconnected_species(ti, [1, 3, 4]) == [4]
check_disconnected_species(ti, [1, 2, 3, 4]) == []

"""
function check_disconnected_species(A, alive_species; verbose = false)
    # Get binary matrix
    A = A .> 0
    living_A = A[alive_species, alive_species]
    cons = sum.(eachrow(A)) .!= 0
    prod = sum.(eachrow(A)) .== 0


    alive_cons = cons[alive_species]
    alive_prod = prod[alive_species]

    species_with_no_pred = sum.(eachcol(living_A)) .== 0
    species_with_no_prey = sum.(eachrow(living_A)) .== 0

    disconnected_prod = alive_prod .&& species_with_no_pred
    disconnected_cons = alive_cons .&& species_with_no_prey

    if sum([disconnected_prod; disconnected_cons]) > 0 & verbose
        println("There are $(sum(disconnected_prod)) disconnected producers
            and $(sum(disconnected_cons)) consumers.")
    end

    alive_species[ disconnected_prod .|| disconnected_cons ]
end

The goal is that simulate produces a useful message when there is a disconnected species. For example, in that case when there is a disconnected consumer:

# Disconnected consumer that goes extinct, all fine 
A = [0 0 0 0; 0 0 0 0; 1 0 0 0; 0 1 0 0]
foodweb = FoodWeb(A);
params = ModelParameters(foodweb);
B0 = [0.5, 0, .5, .5];

julia> sol = simulate(params, B0);
┌ Info: Species [4] went extinct at time t = 38.37898034897611. 
└ 2 out of 4 species are extinct.

julia> check_disconnected_species(get_parameters(sol).network.A, living_species(sol).idxs,
                                  verbose = true
                                 )
Int64[]

julia> kill_disconnected_species(get_parameters(sol).network.A;
                                 alive_species = living_species(sol).idxs,
                                 bm = biomass(sol).species
                                )
4-element Vector{Float64}:
 0.19199402785494718
 0.0
 0.33605893664276615
 0.0

# Disconnected producers
B0 = [0.5, 0.5, 0, .5];
sol = simulate(params, B0);

check_disconnected_species(get_parameters(sol).network.A, living_species(sol).idxs,
                           verbose = true
                          )
kill_disconnected_species(get_parameters(sol).network.A;
                          alive_species = living_species(sol).idxs,
                          bm = biomass(sol).species
                         )
julia> check_disconnected_species(get_parameters(sol).network.A, living_species(sol).idxs,
                                  verbose = true
                                 )
There are 1 disconnected producers
            and 0 consumers.
1-element Vector{Int64}:
 1

julia> kill_disconnected_species(get_parameters(sol).network.A;
                                 alive_species = living_species(sol).idxs,
                                 bm = biomass(sol).species
                                )
There are 1 disconnected producers
            and 0 consumers.
4-element Vector{Float64}:
 0.0
 0.19194243058968705
 0.0
 0.36856095125952454

On top of sanity checks, we can also provide a snippet in the doc to enable users to run the simulations until there are no more extinct species or disconnected species:

global ti = false
global i = 1
global remove_disconnected = false 
global starting_bm = [0.5, 0.5, 0, .5];
#starting_bm = rand(richness(foodweb))

while ti != true
    println("Iteration = $i.")
    output = simulate(params, starting_bm)

    # Retrieve network and alive species
    old_A = get_parameters(output).network.A
    alive_species = living_species(output).idxs
    # Check
    disconnected_species = check_disconnected_species(old_A, alive_species)
    ti = length(disconnected_species) == 0
    if ti == true
        println("No disconnected species, all fine.")
        return output
    end

    # Build the vector of biomasses
    biomass_vector = biomass(output).species
    killed_species = kill_disconnected_species(old_A;
                                               alive_species = alive_species,
                                               bm = biomass_vector)
    mask_sp_to_keep = killed_species .!= 0.0
    idxs = 1:size(old_A, 1)
    idxs_to_keep = idxs[mask_sp_to_keep]

    # Rebuilding network or set disconnected species to 0
    if remove_disconnected == true
        A = old_A[mask_sp_to_keep, mask_sp_to_keep]
        starting_bm = biomass_vector[mask_sp_to_keep]
        foodweb = FoodWeb(A);
        params = ModelParameters(foodweb);
        println("Rebuilding model without disconnected species.")
    else
        starting_bm = killed_species
        A = A
        println("Keep the same model without disconnected species.")
    end
    i = i + 1
end
@iago-lito
Copy link
Collaborator

Thank you @alaindanet. IIUC this would solve a substantial part of #137, right?

@iago-lito iago-lito self-assigned this Apr 11, 2024
@iago-lito
Copy link
Collaborator

iago-lito commented Apr 16, 2024

Hi @alaindanet! We've just discussed this with @ismael-lajaaiti this morning and I think we're almost good to go :) Here are the specifications I offer for the feature, which should clarify a few things that need to be clarified first IMO. Don't panic: this is mostly about naming things just to be sure we don't get confused ;)

1. Static checks on the "foodweb graph"

The directed graph representing the Foodweb is a static graph, fixed, constant throughout the simulation. As such, if it's connected at t=0, then it will stay connected until t=tmax no matter which species get extinct. So the checks discussed here are not checks against this static graph.

As it turns out, checks against this static graph used to be performed but I'm not sure whether they still are. I can suggest a behaviour like the following:

julia> fw = Foodweb(A=[...])
ERROR: The given matrix describes a disconnected graph.
This error is meant to protect you from unintentionally working on disconnected foodwebs.
But you can construct disconnected foodwebs by setting `allow_disconnected=true`.

IIUC this is slightly out-of-scope for the current issue (because it's not even about simulation), but maybe we can fit it in anyway ;)

2. Dynamics checks on the "biomasses graph"

The checks discussed here are performed against another graph that does not exactly represent the foodweb, but the effective interactions between live species. Let's call it the biomass graph (?). The biomass graph is a dynamic graph, as it changes over time, during simulation, depending on the biomasses vector $B(t)$:

At any time $t$, the biomass graph is the foodweb graph with all species nodes $i$ such that $B_i(t)=0$ removed.

3. Three different kinds of checks.

Since the word disconnected has a precise formal meaning in graph theory, I would warn against using it in the three different situations you describe. Here is how I offer referring to them instead. The "problematic features" to be detected are:

Disconnection

The biomass graph is (or has become) a disconnected graph, meaning that its number of connected components is above 1. For instance, this happens when A becomes extinct in the following foodweb:

  A
 ↙ ↘
B   C
↓   ↓
D   E

Then the resulting biomass graph becomes made of two disconnected components B → D and C → E.

Isolated producers

One or several producers (=nodes without outgoing edges in the foodweb graph) exhibit the special property that there is no incoming edges to this producer in the biomass graph. For instance, when A becomes extinct in either of the two following foodwebs, then B and C become isolated producers:

  A          A
 ↙ ↘         ↓
B   C        B

Note

The existence of isolated producers does not imply disconnection. For example, in the right situation above, when A becomes extinct, the biomass graph becomes only constituted of B: so it's a connected graph with 1 connected component constituted of 1 isolated producer.

Starving consumers

One or several consumers (=nodes with outgoing edges in the foodweb graph) exhibit the special property that there is no directed path from this consumer to a producer in the biomass graph. For instance, when D becomes extinct in the the following foodweb, then A, B and C become starving consumers:

   A      E
 ↙   ↘  ↙ |
B     C   |
 ↘   ↙    ↓
   D      F

Note

Again, the existence of starving consumers does not imply disconnection. In the above, the biomass graph remains connected after D becomes extinct.

Warning

I understand that the biomass of starving consumers is expected to become zero short-term. However, I have a strong opinion that it would be wrong to force it to become instantaneously zero with anything akin to a kill_starving_consumers() function. In the above example, we can see that the biomasses of starving consumers still influence all other biomasses in the simulation. I would rather let them become extinct "the natural way" to not produce artefacts in the simulation outcomes. Possibly with the kind of "recipe" you are offering, to e.g. simulate until there are no more starving producers in the biomass graph.

4. Suggested feature

julia> out = simulate(model, ..)
INFO: The biomass graph at the end of simulation contains 3 disconnected components:
   - Connected component 1:
     - 2 producers
     - 3 consumers
     - 4 trophic links
   - Connected component 2:
      - 2 producers /!\ including 1 isolated producer /!\
      - 1 consumer
      - 1 trophic link
   - Connected component 3:
       - 3 producers /!\ including 2 isolated producers /!\
       - 4 consumers /!\ including 3 starving consumers /!\
       - 4 trophic links
This message is meant to attract your attention regarding the meaning
of downstream analyses depending on the simulated biomasses values.
You can silent it with `show_unusual_biomass_graph_properties=false`.

julia> list_connected_components(out)
3-element Vector{BiomassConnectedComponent}:
 Component(producers = [:A, :B, :C], consumers = [:D, :E])
 Component(producers = [:F, :G], consumers = [:H], isolated_producers = [:F])
 Component(producers = [:M, :N, :O], consumers = [:I, :J, :K, :L], isolated_producers = [:N, :O], starving_consumers = [:I, :J, :K])

.. or something?

@alaindanet
Copy link
Contributor Author

alaindanet commented Apr 16, 2024

Thank you so much @iago-lito for the clarification,
that is really neat!

I agree with your semantic about disconnected graphs. If I understand well,
disconnection is a graph property. In the case where the graph is disconnected,
the graph has then several components.

I really like the naming "isolated" and may these are our three rules:

  • A graph should be connected, i.e. having one component
  • A consumer should have an outgoing vertice (starving consumer)
  • A producer should have an ingoing vertice (isolated producer)

or

  • A graph should be connected, i.e. having one component
  • A consumer should be connected to a producer node (starving consumer)
  • A producer should be connected to a consumer node (isolated producer)

Really cool, got it about the non-disconnection of the graph following D
extinction:

   A      E
 ↙   ↘  ↙ |
B     C   |
 ↘   ↙    ↓
   D      F
  • One component with two starving consumers:
             A      E
~~~~~~     ↙   ↘  ↙ |
D dies    B     C   |
~~~~~~              ↓
                    F

I really like the terminology and I agree to go with it!

About the killing of disconnected species, I have a less strong opinion than you
about the impact on the dynamics. The problem is a starving consumer can
take ages to die if it has a really slow metabolism, but it would be the choice
of the user. In the example that you are providing, I would expect the
extinction sequence C, B, and A, the extinction of C and B should be
really fast, but the extinction of A could take ages.

The middle ground that I could think of is that we provide to kill only isolated
species that are not resources for other species, so that we do not impact the
dynamics but still enable to reach a steady state pretty quickly. In your
example, I would kill A only if B and C are already extinct. So we could
provide a function that kill isolated producers and consumers, but not the
starving consumers that are still part of a bigger component.

I really like that you suggest to return the components because there is not an
unique way to pick one component that we would like to pick for further
simulations. So, I find really cool to let the user to pick a component that he
wishes to continue with.

In the simulation, I think that the message that you suggest is great but may
be that it could be less detailled such as:

julia> out = simulate(model, ..)
INFO: The biomass graph at the end of simulation contains 3 disconnected
components, 3 isolated producers, 3 starving consumers.
This might affect the meaning of of downstream analyses. For more information,
see: `diagnose()`.
You can silent this message with `show_unusual_biomass_graph_properties=false`.

julia> diagnose(out)
The biomass graph at the end of simulation contains 3 disconnected components:
   - Connected component 1:
     - 2 producers
     - 3 consumers
     - 4 trophic links
   - Connected component 2:
      - 2 producers /!\ including 1 isolated producer /!\
      - 1 consumer
      - 1 trophic link
   - Connected component 3:
       - 3 producers /!\ including 2 isolated producers /!\
       - 4 consumers /!\ including 3 starving consumers /!\
       - 4 trophic links
You can retrieve components with `list_connected_components()` and know more
about the problems with those graphs in the dedicated vignette: https://xxx

julia> list_connected_components(out)
3-element Vector{BiomassConnectedComponent}:
 Component(producers = [:A, :B, :C], consumers = [:D, :E])
 Component(producers = [:F, :G], consumers = [:H], isolated_producers = [:F])
 Component(producers = [:M, :N, :O], consumers = [:I, :J, :K, :L], isolated_producers = [:N, :O], starving_consumers = [:I, :J, :K])

@iago-lito
Copy link
Collaborator

iago-lito commented Apr 18, 2024

Good @alaindanet! I'll start implementing this tomorrow :)

A graph should be connected, i.e. having one component

To be clear about should here: there is not much we can do to guarantee that a biomass graph won't be disconnected by the end of a simulation. So I guess you mean "should be connected so as not to trigger the @info message".

A consumer should have an outgoing vertice (starving consumer)

This is not equivalent to there is no directed path from this consumer to a producer in the biomass graph. For instance, I think that A should be considered a starving consumer when D dies in the following situation.

   A      E
 ↙   ↘  ↙ |
B     C   |
 ↘   ↙    ↓
  xDx     F

So yeah, I would say that there are not two (as you wrote) but three starving consumers in the above: A, B and C: because they are all expected to die from starvation before equilibrium is reached.

A consumer should be connected to a producer node (starving consumer).

This is still not equivalent to there is no directed path from this consumer to a producer in the biomass graph either. For example in the above again: consumer B is starving even though it is connected to producer F. But if you want to state it this way, you can try "A consumer should be strongly connected to a producer node". [EDIT]: well no, this would also not be equivalent, sorry (see message below) ^ ^"

A producer should be connected to a consumer node (isolated producer)

This is indeed equivalent to there is no incoming edges to this producer in the biomass graph.

The middle ground that I could think of is that we provide to kill only isolated
species that are not resources for other species, so that we do not impact the
dynamics but still enable to reach a steady state pretty quickly.

IIUC you are suggesting that kill_*() functions could only kill species represented with very special-cased nodes in the biomass graph. So, if user asks:

  • Kill isolated producers.
  • Kill all species in connected components that only contains starving consumers.

So, when D dies in the following:

   A      E
 ↙   ↘  ↙ |
B     C   |
 ↘   ↙    ↓
  xDx     F    G

Then we can offer to kill G (on demand), but not A, B and C because their dynamics still influence every other biomasses.

And then when C dies:

   A      E
 ↙   ↘  ↙ |
B    xCx  |
          ↓
          F

Then only we can offer to kill A and B (on demand), because they belong to a connected component with only starving consumers inside.

the message that you suggest is great but may be that it could be less detailled

Sure, whatever, as long as user is warned ;)

@iago-lito
Copy link
Collaborator

you can try "A consumer should be strongly connected to a producer node"

Wops, sorry that is also incorrect. A strongly connected component necessarily contains directed cycles, which excludes producers as they have no outgoing edges. Maybe the only good definition of a starving consumer remains there is no directed path from this consumer to a producer in the biomass graph. ^ ^"

@alaindanet
Copy link
Contributor Author

That sounds marvelous @iago-lito! Thank you for the clarification about the definition of starving consumers and to have taken the time to explain more 🙏
All good for me and excited to see that coming! 🧑‍🏭

@iago-lito
Copy link
Collaborator

iago-lito commented Apr 18, 2024

@ismael-lajaaiti @alaindanet I can think of another possible artefact of kill_disconnected_starving_consumers(..). As you have suggested @alaindanet, starving consumers may have low metabolic/death rate, so they may be long to die "the natural way" (thus the apparent need for that function). However, in this scenario again:

   A      E
 ↙   ↘  ↙ |
B    xCx  |
 ↘   ↙    ↓
  xDx     F    

If C then D become extinct, then A and B become eligible to killing. Here is what the "natural" timeline would look like without the killing function being called:

                           (E and F biomasses reach stable equilibrium)
                           |
   (C dies)  (D dies)      |      (A dies)  (B dies, so A and B also reach their own equilibrium = 0)
    v         v            v       v         v
-------------------------------------------------------------------> time
    ^         ^            ^       ^         ^
    t_c       t_d          t_ef    t_a       t_b = t_ab

I hear you are sometimes interested in the "time for the community to reach equilibrium". In the above, the time to reach equilibrium is:

$$t_{eq} = t_b = t_{ab}$$

But if user chooses to dismiss starving consumers A and B by forcing them to $B_A(t_d) = B_B(t_d) = 0$ without letting them die from natural starvation, then another "apparent equilibrium time" appears:

$$ t_{eq}^* = t_{ef} \neq t_{eq}$$

In this situation, does it make sense to use $t_{eq}^*$ instead of $t_{eq}$ in subsequent calculations? If so, why? If not, then maybe we should warn against using kill_disconnected_starving_consumers(..) at all?

@alaindanet
Copy link
Contributor Author

Hi @iago-lito,

in the case you are describing, I hope that TerminateSteadyState() should work as expected and so that there is no need to use kill_disconnected_starving_consumers(..) as you suggest.

kill_disconnected_starving_consumers(..) might be not useful in many case. For me, it is useful because I run stochastic simulations that have no steady state. So, I need to check at the end of the simulation if there is some disconnected species or if there was some recent extinctions that could impact post-processing. In that case I set those species to 0 biomass and I run the simulations again.

At the end, kill_disconnected_starving_consumers(..) is may be useful only when you do not use the TerminateSteadyState() callback, right? I.e. when you want a fixed number of timesteps (tmax =)

@ismael-lajaaiti
Copy link
Collaborator

ismael-lajaaiti commented Apr 18, 2024

Hey @alaindanet @iago-lito,
I have to say I'm not a fan of the kill_disconnected_starving_consumers. IMO it's very specific, comes with subtle difficulties to answer and can be easily misused. (If I understood it correctly 😅)
For me, I think the issue of starving consumers within the package framework (so no stochastic simulations), can be solved either by using TerminateSteadySate callback or with the @info message that could warn the user of the presence of starving consumers and suggest them to either run the simulation for a longer time. Or am I wrong?

@alaindanet
Copy link
Contributor Author

I totally agree with you @ismael-lajaaiti !
Priority on @info and we can always provide a code snippet later if we see that the need is there? #Waiting4UserFeedback

@ismael-lajaaiti
Copy link
Collaborator

It's just that I am afraid of adding a new layer of complexity (that is maybe not crucial, as it seems to be quite specific), while the package is already not so easy to use. However, we could document these cases of starving consumers and suggest how to deal with them?

@iago-lito
Copy link
Collaborator

Yupe. Whatever the eventual decision here, I guess we'll need find_starving_consumers and/or find_disconnected_starving_consumers. This is where the most code is required anyway, so I can go ahead and implement these even without knowing exactly what you'll want to do with them. Killing them is just a matter of:

B[find_disconnected_starving_consumers(model, B)] = 0

whether we want to do this or not is another matter which I'll let you discuss ^ ^" But I think it's always useful to ease such querying of the model.

@iago-lito
Copy link
Collaborator

iago-lito commented Apr 19, 2024

Now, I have a new 🌈 pandora box 🕷️ to open @alaindanet @ismael-lajaaiti: the definitions I have offered for disconnected components, isolated producers, starving consumers and disconnected starving consumers are only satisfying under the hypothesis that the model only contains trophic links, right?

What do these concepts become when there are non-trophic interaction layers?

For now, I can just craft something like:

julia> find_starving_consumers(model, B)
ERROR: Starving consumers are not yet defined in models with non-trophic interaction layers.

?

@iago-lito
Copy link
Collaborator

iago-lito commented Apr 19, 2024

Good. What about nutrients? ^ ^"

julia> find_isolated_producers(model, B)
ERROR: Isolated producers are not yet defined in models with explicit nutrient nodes.

?

@ismael-lajaaiti
Copy link
Collaborator

My take on that

  • disconnected components: should consider also non-trophic interactions, two components are disconnected if there is no trophic and non-trophic interactions linking them
  • isolated producers: same as above, apply also for nutrients
  • starving consumers: I believe that here non-trophic interactions do not change anything, if a consumer has no resource to feed on, it's bound to extinction (non-trophic interactions or not)
  • disconnected starving consumers: I have to confess that I didn't get the difference with the term above (sorry)

@iago-lito
Copy link
Collaborator

iago-lito commented Apr 19, 2024

Okay, summary of the current model ;)

  • The ecological model is a static, directed graph:
    • Nodes either represent species or nutrients.
    • Edges either represent trophic links eater -> eaten or various non-trophic interactions.
  • The foodweb is a restriction of the model to only species nodes and trophic links.
  • The biomass graph is a restriction of the model to only nodes with positive biomass (so it's the same but extinct species, extinct nutrients and all their incoming/outgoing edges have been removed).
  • Consumers are species nodes with outgoing trophic edges in the foodweb. Consumers are ASSUMED to never have an autonomous growth rate, so they starve if their preys starve. They are also ASSUMED to never feed on nutrients directly, so there are no consumer -> nutrient trophic edges.
  • Producers are species nodes with no outgoing trophic edges in the foodweb. Producers are ASSUMED to either have an autonomous growth rate or to have outgoing trophic edges whose targets are nutrient nodes. (Note that producer -> nutrient trophic edges don't appear in the foodweb because the foodweb is restricted to species nodes).

Now, on top of that, here are the current definitions for the various concepts discussed here:

  • Disconnected components are non-strong connected components of the biomass graph. They are interesting to consider because we can prove that the evolution of the biomasses of their nodes are independent from each other when simulating / solving the ODEs.

  • Starving consumers are consumers for which there is no directed path of trophic links to a producer in the biomass graph. They are interesting to consider because we can prove that their biomass will reach zero before an equilibrium state is reached.

  • Disconnected starving consumers are species nodes belonging to a disconnected component only containing starving consumers. They are interesting to consider because we can force their biomass to zero without modifying the final equilibrium.. /!\ although this does impact the time to reach that equilibrium!

  • Isolated producers are producers with no incoming edge in the biomass graph. They are interesting to consider because we can prove that their biomass will grow uncontrolled until they reach maximum capacity.. /!\ provided they don't feed on nutrients shared with other producers! In this respect @ismael-lajaaiti, your suggestion IIUIC is to only consider them "isolated" when they only feed from nutrients not shared with other producers? For instance: there would be no isolated producer in nopred -x producer -> nutrient <- producer <- pred. Also, I guess non-trophic interactions can also introduce dependencies between producers even if they are not predated.. so better forbid any kind of other connections?

  • ..alternate definition then: Isolated producers are producers with no incoming or outgoing edge in the biomass graph.

What does this sound like?

@iago-lito iago-lito linked a pull request Apr 19, 2024 that will close this issue
3 tasks
@iago-lito iago-lito linked a pull request Apr 19, 2024 that will close this issue
3 tasks
@alaindanet
Copy link
Contributor Author

I am really happy with those definitions!

iago-lito added a commit that referenced this issue Aug 2, 2024
These values describe the model under a topological perspective:
nodes and their neibouhring relations.
Nodes and edges are typed into various 'compartments'.
Nodes can be "removed" from topologies
while leaving tombstones to maintain indices validity.
This enables various topological analyses of the model network
like `disconnected_components()`,
`isolated_producers()` or `starving_consumers()`.
iago-lito added a commit that referenced this issue Aug 2, 2024
These values describe the model under a topological perspective:
nodes and their neibouhring relations.
Nodes and edges are typed into various 'compartments'.
Nodes can be "removed" from topologies
while leaving tombstones to maintain indices validity.
This enables various topological analyses of the model network
like `disconnected_components()`,
`isolated_producers()` or `starving_consumers()`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants