Skip to content

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

Description

@alaindanet

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

Activity

  1. iago-lito commented on Apr 11, 2024

    @iago-lito
    Collaborator

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

  2. self-assigned this
    on Apr 11, 2024
  3. iago-lito commented on Apr 16, 2024

    @iago-lito
    Collaborator

    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?

  4. alaindanet commented on Apr 16, 2024

    @alaindanet
    CollaboratorAuthor

    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])
  5. iago-lito commented on Apr 18, 2024

    @iago-lito
    Collaborator

    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 ;)

  6. iago-lito commented on Apr 18, 2024

    @iago-lito
    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. ^ ^"

  7. alaindanet commented on Apr 18, 2024

    @alaindanet
    CollaboratorAuthor

    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! 🧑‍🏭

  8. iago-lito commented on Apr 18, 2024

    @iago-lito
    Collaborator

    @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?

  9. alaindanet commented on Apr 18, 2024

    @alaindanet
    CollaboratorAuthor

    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 =)

  10. ismael-lajaaiti commented on Apr 18, 2024

    @ismael-lajaaiti
    Collaborator

    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?

  11. alaindanet commented on Apr 18, 2024

    @alaindanet
    CollaboratorAuthor

    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

  12. ismael-lajaaiti commented on Apr 18, 2024

    @ismael-lajaaiti
    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?

  13. iago-lito commented on Apr 19, 2024

    @iago-lito
    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.

  14. iago-lito commented on Apr 19, 2024

    @iago-lito
    Collaborator

    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.

    ?

  15. iago-lito commented on Apr 19, 2024

    @iago-lito
    Collaborator

    Good. What about nutrients? ^ ^"

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

    ?

  16. 6 remaining items

  17. ismael-lajaaiti commented on Apr 9, 2025

    @ismael-lajaaiti
    Collaborator

    Concerning the warning disconnected species at the end of simulations, I noticed that producers competing with one another are warned as disconnected, whereas they're actually connected. @iago-lito are species considered (dis-)connected wrt trophic links only?

    MWE

    fw = Foodweb([0 0; 0 0])
    m = default_model(fw, ProducersCompetition([1 0.2; 0.2 1]))
    simulate(m, [1, 1], 1_000)
  18. iago-lito commented on Apr 10, 2025

    @iago-lito
    Collaborator

    Yupe, that's a bug :( Non-trophic interactions are supposed to be taken into account, see for example:

    fw = Foodweb([0 0; 0 0])
    m = default_model(fw, NonTrophicLayers(facilitation_A = [0 1; 0 0]))
    simulate(m, [1, 1], 1_000)

    Thank you for reporting @ismael-lajaaiti. Working on this rn.

  19. iago-lito commented on Apr 24, 2025

    @iago-lito
    Collaborator

    Just so you know, this has just been fixed on the dev branch @ismael-lajaaiti :)

  20. iago-lito commented on Jul 16, 2025

    @iago-lito
    Collaborator

    This has actually been featured for a while now @alaindanet @ismael-lajaaiti, what else needs to be done here before we close?

  21. ismael-lajaaiti commented on Jul 17, 2025

    @ismael-lajaaiti
    Collaborator

    On my side, all is good there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions