Program Listing for File simulation.cc

Return to documentation for file (core/simulation.cc)

#include "lupnt/core/simulation.h"

#include <fmt/format.h>

#include <cstdlib>

#include "lupnt/conversions/time_conversions.h"
#include "lupnt/core/asset_factory.h"
#include "lupnt/core/data_logger.h"
#include "lupnt/core/file.h"
#include "lupnt/core/logger.h"
#include "lupnt/measurements/channel.h"

namespace lupnt {

  // Simulation *****************************************************
  Simulation::Simulation(Config& config) {
    int _ = std::system("clear");

    config_ = config;
    name_ = config_["name"] ? config_["name"].as<std::string>() : GetId();
    Setup();
  }

  void Simulation::Setup() {
    if (setup_complete_) return;
    setup_complete_ = true;
    // Output
    auto output_dir = GetOutputDir(name_);
    auto output_file = output_dir / (name_ + ".h5");
    DataLogger::SetOutputFile(output_file);
    Logger::Info(fmt::format("Output file: {}", output_file.string()), "Simulation");

    Logger::Debug("Creating Simulation", "Simulation");

    // Log level
    if (config_["log_level"]) {
      Logger::SetLogLevel(
          enum_cast<Logger::LogLevel>(config_["log_level"].as<std::string>()).value());
      Logger::Info(fmt::format("Log level set to {}", config_["log_level"].as<std::string>()),
                   "Simulation");
    }

    // Cesium
    if (config_["cesium_port"]) {
      cesium_viewer_ = MakePtr<CesiumViewer>(config_["cesium_port"].as<int>());
      Logger::Debug("CesiumViewer started", "Simulation");
    } else {
      Logger::Debug("CesiumViewer disabled", "Simulation");
    }

    // Epoch
    if (config_["epoch"]) SetLupntEpoch(GregorianToTime(config_["epoch"].as<std::string>()));

    // Duration
    if (config_["duration"]) {
      std::string duration_str = config_["duration"].as<std::string>();
      int hours = 0, minutes = 0;
      double seconds = 0.0;
      sscanf(duration_str.c_str(), "%d:%d:%lf", &hours, &minutes, &seconds);
      duration_ = hours * 3600 + minutes * 60 + seconds;
      Logger::Debug(
          fmt::format("Simulation duration {:02}:{:02}:{:06.3f}", hours, minutes, seconds),
          "Simulation");
    }

    // Channels
    if (config_["channels"]) {
      Logger::Debug(fmt::format("Creating {} channels", config_["channels"].size()), "Simulation");
      for (const auto& channel_item : config_["channels"]) {
        Config channel_config(channel_item.second);
        if (!channel_config["name"]) channel_config["name"] = channel_item.first.as<std::string>();
        channel_config["name"] = name_ + "/" + channel_config["name"].as<std::string>();
        channels_[channel_config["name"].as<std::string>()] = Channel::FromConfig(channel_config);
      }
      Logger::Debug(fmt::format("Created {} channels", channels_.size()), "Simulation");
    }

    // Agents
    if (config_["agents"]) {
      Logger::Debug(fmt::format("Creating {} agents", config_["agents"].size()), "Simulation");
      for (const auto& agent_item : config_["agents"]) {
        Config agent_config(agent_item.second);
        if (!agent_config["name"]) agent_config["name"] = agent_item.first.as<std::string>();
        agent_config["name"] = name_ + "/" + agent_config["name"].as<std::string>();
        std::string agent_class = agent_config["class"].as<std::string>();
        Logger::Debug(
            fmt::format("Creating {} {}", agent_class, agent_config["name"].as<std::string>()),
            "Simulation");
        agents_[agent_config["name"].as<std::string>()]
            = AssetFactory<Agent, Config&>::Create(agent_class, agent_config);
      }
      Logger::Debug(fmt::format("Created {} agents", agents_.size()), "Simulation");
    }

    // Constellations
    if (config_["constellations"]) {
      Logger::Debug(fmt::format("Creating {} constellations", config_["constellations"].size()),
                    "Simulation");
      for (const auto& constellation_item : config_["constellations"]) {
        Config constellation_config(constellation_item.second);
        if (!constellation_config["name"])
          constellation_config["name"] = constellation_item.first.as<std::string>();
        constellation_config["name"] = name_ + "/" + constellation_config["name"].as<std::string>();
        std::string constellation_class = constellation_config["class"].as<std::string>();
        Logger::Debug(fmt::format("Creating {} {}", constellation_class,
                                  constellation_config["name"].as<std::string>()),
                      "Simulation");
        constellations_[constellation_config["name"].as<std::string>()]
            = MakePtr<Constellation>(constellation_config);
      }
      Logger::Debug(fmt::format("Created {} constellations", constellations_.size()), "Simulation");
    }

    // Setup agents
    Logger::Debug(fmt::format("Setting up {} agents", agents_.size()), "Simulation");
    for (auto& agent : agents_) {
      agent.second->SetSimulation(this);
      agent.second->Setup();
    }
    Logger::Debug(fmt::format("Set up {} agents", agents_.size()), "Simulation");

    // Setup constellations
    Logger::Debug(fmt::format("Setting up {} constellations", constellations_.size()),
                  "Simulation");
    for (auto& constellation : constellations_) {
      constellation.second->SetSimulation(this);
      constellation.second->Setup();
    }
    Logger::Debug(fmt::format("Set up {} constellations", constellations_.size()), "Simulation");

    // Run
    if (cesium_viewer_) cesium_viewer_->Run();

    // Save config
    std::string config_path = output_dir / "config.yaml";
    std::ofstream out_file(config_path);
    out_file << YAML::Dump(config_);
    out_file.close();
    Logger::Info(fmt::format("Saved config to {}", config_path), "Simulation");
  }

  void Simulation::Precompute() {}

  void Simulation::Schedule(Real time, const std::function<void(Real)>& func, Real freq,
                            Event::Priority priority) {
    Event e;
    e.time = time;
    e.frequency = freq;
    e.priority = priority;
    e.callback = func;
    Schedule(e);
  }

  void Simulation::Schedule(const Event& e) {
    if (!running_) {
      Logger::Debug(fmt::format("Scheduling event at time {}", e.time), "Simulation");
    } else {
      Logger::Debug(fmt::format("Scheduling event at time {}", e.time), "Simulation", time_);
    }
    queue_.push(e);
  }

  void Simulation::Run() {
    Logger::Info("Simulation started", "Simulation");
    running_ = true;
    auto bar = Logger::GetProgressBar(100, "", "Simulation");
    while (!queue_.empty() && queue_.top().time <= duration_ + EPS) {
      Event e = queue_.top();
      queue_.pop();
      time_ = e.time;
      e.callback(e.time);
      if (e.frequency > 0.0) {
        Real time_next = e.time + 1.0 / e.frequency;
        if (time_next <= duration_ + EPS) {
          e.time = time_next;
          queue_.push(e);
        }
      }
      bar->Update(static_cast<int>(time_ / duration_ * 100.0));
    }
    bar->Finish();
    Logger::Info("Simulation finished", "Simulation");

    DataLogger::Flush();
    if (cesium_viewer_) cesium_viewer_->Stop();
  }

  Channel* Simulation::GetChannel(const std::string& name) {
    if (channels_.find(name) != channels_.end()) {
      return channels_[name].get();
    } else if (channels_.find(name_ + "/" + name) != channels_.end()) {
      return channels_[name_ + "/" + name].get();
    } else {
      LUPNT_CHECK(false, fmt::format("Channel {} not found", name), "Simulation");
    }
  }
  Agent* Simulation::GetAgent(const std::string& name) {
    if (agents_.find(name) != agents_.end()) {
      return agents_[name].get();
    } else if (agents_.find(name_ + "/" + name) != agents_.end()) {
      return agents_[name_ + "/" + name].get();
    } else {
      LUPNT_CHECK(false, fmt::format("Agent {} not found", name), "Simulation");
    }
  }

  void Simulation::Subscribe(const std::string& topic,
                             std::function<void(const std::any&)> callback) {
    subscribers_[topic].push_back(std::move(callback));
  }

  void Simulation::Publish(double time, const std::string& topic, const std::any& message) {
    auto it = subscribers_.find(topic);
    if (it != subscribers_.end()) {
      for (auto& callback : it->second) {
        Schedule(
            time, [callback, message](Real) { callback(message); }, Event::SINGLE_EVENT,
            Event::Priority::APPLICATION);
      }
    }
  }
}  // namespace lupnt