drone-comp / uatlib

Simulation Library for Urban Airspace Tradable Permit Model
https://drone-comp.github.io/uatlib/
MIT License
0 stars 0 forks source link

Trying alternative simulation order. #9

Open verri opened 1 month ago

verri commented 1 month ago

I notice a bug in the current simulation loop when choosing no_agents_t as stop condition.

Now the order is:

The thing is, imagine if the factory would still generate new agents in the next iteration, however all agents in the current iteration have stopped. Since we are checking the stop condition at the end, the simulation would stop and the factore is never called. Thus, simulation is terminated before the user expects it to terminate.

I propose the following order:

Consequence: now when the factory provides more agents, the simulation do not stop early. However, if one chooses time_threshold as an stop condition, some agents are created but never run. But user can learn the last run iteration by checking the last call of simulation_callback.

verri commented 1 month ago

I am not totally satisfied with this order, though...

@JorgeLuizFranco @vitorcurtis-ita any ideas?

verri commented 1 month ago

Discussing with @vitorcurtis-ita , we realized that it does make sense to simulation_status_callback be called in the case of t_end + 1. The ideia is that the user might want to check the book status after the simulation has finished (at iteration t_end). It is a little bit useless the last call of factory in the case of time_threshold stop condition, but since the factory knows t, the user can easily avoid unnecessary creations.

verri commented 1 month ago

@JorgeLuizFranco any thoughts?

JorgeLuizFranco commented 1 month ago

@verri , from what I understood in this solution some agents are going to be created and not run. I think this is expected since this means that the t_chosen was not sufficient for the number of agents generated. Thus, simulation_callback is how the user is going to know which agents were run and which not, right? This is a good solution, but from what I read in the code, the user is going to be responsible for creating the simulation_callback to do this, probably it would be good if we had a default function that does this if the user does not want to waste time in creating its own.

vitorcurtis-ita commented 1 month ago

Sorry guys, I have not read the code yet. So my contributions will be minimal. @JorgeLuizFranco, I guess this line prevents simulation_callback from being called if it's not defined: https://github.com/drone-comp/uatlib/blob/30a8650d48478e1669c15eff0891283a451bac84/include/uat/simulation.hpp#L203

Is that what you mean?

verri commented 1 month ago

@JorgeLuizFranco we already provide a way for the user to avoid generating useless agents. Just use a generator function that does not generate anything agents after t_end.

But, answering your question, if the user wants to write a simpler factory and deal with the agents in the simulation callback, it would work as well.

verri commented 1 month ago

Just notice a nice symmetry in this proposal.

Both

#include <uat/simulation.hpp>
#include <iostream>

int main() {
  uat::simulate<std::monostate>({
    .stop_criterion = uat::stop_criterion::time_threshold_t{3},
    .simulation_callback = [](uat::uint_t t, const auto&, auto) {
      std::cout << t << '\n';
    },
  });
}

and

#include <uat/simulation.hpp>
#include <iostream>

struct do_nothing : uat::agent<std::monostate> {
  auto stop(uat::uint_t, int) -> bool override { return true; }
};

int main() {
  uat::simulate<std::monostate>({
    .factory = [](uat::uint_t t, int) -> std::vector<uat::any_agent> {
      if (t <= 3) {
        std::vector<uat::any_agent> agents;
        agents.emplace_back(do_nothing{});
        return agents;
      }
      return {};
    },
    .stop_criterion = uat::stop_criterion::no_agents_t{},
    .simulation_callback = [](uat::uint_t t, const auto& status, auto) {
      std::cout << t << ' ' << status.active_count() << '\n';
    },
  });
}

call simulation_callback five times, for t = 0, 1, 2, 3, and 4.

verri commented 1 month ago

Even nicer version of the second example:

#include <uat/simulation.hpp>
#include <iostream>

struct do_nothing : uat::agent<std::monostate> {
  auto stop(uat::uint_t, int) -> bool override { return true; }
};

int main() {
  uat::simulate<std::monostate>({
    .factory = [](uat::uint_t t, int) -> std::vector<do_nothing> {
      if (t > 3)
        return {};
      return {do_nothing{},};
    },
    .stop_criterion = uat::stop_criterion::no_agents_t{},
    .simulation_callback = [](uat::uint_t t, const auto& status, auto) {
      std::cout << t << ' ' << status.active_count() << '\n';
    },
  });
}

with the modifications I have just pushed.

verri commented 1 month ago

Symmetry is such a beautiful thing.

verri commented 1 month ago

@JorgeLuizFranco take a look at the last commit. One of the reasons I chose _t for the factory type is that it has the potential to become more complex over time even keeping compatibility.