MUSIC manager

The MUSICManager class in NEST provides a comprehensive interface for managing interactions with the MUSIC (MUlti-Simulation Coordinator) interface. It handles port registration and coordinates communication between NEST and other applications via MUSIC. It uses MPI for parallel communication and ensures proper initialization and cleanup. The code structure uses maps to track ports and their associated handlers, allowing dynamic registration of input proxies.

Overview of steps

Initialization:

  • init_music: Initializes the MUSIC setup with MPI thread settings.

Runtime Management:

  • enter_runtime: Enters the runtime mode, sets up ports, and initializes the runtime if not already done.

  • advance_music_time: Advances the simulation time by one tick.

Port Management:

  • register_music_in_port: Registers an input port for MUSIC, tracking the number of input proxies.

  • unregister_music_in_port: Unregisters a port, removing it if no proxies are connected.

  • set_music_in_port_acceptable_latency: Sets the acceptable latency for a port.

  • set_music_in_port_max_buffered: Sets the maximum number of buffered events for a port.

  • publish_music_in_ports_: Publishes all registered input ports for external connections.

Event Handling:

  • register_music_event_in_proxy: Registers an event handler for a specific port and channel, linking it to a simulation node.

  • register_music_rate_in_proxy: Registers a rate-based input handler for a port and channel.

  • update_music_event_handlers: Updates all event handlers with new time boundaries, allowing them to process events within the specified timeframe.

Relationships wth other managers and components

Table 3 Interactions with other managers and components

Manager/Component

Role in MUSICManager Operations

Example Interaction

KernelManager

  • Initializes and finalizes the MUSICManager during simulation setup and shutdown.

MUSICManager` is included in kernel_manager.h, and its initialize()/finalize() methods are called.

MPIManager

  • Manages MPI communication and finalization.

MPI_Finalize() is called in music_finalize(), and MPI_THREAD_FUNNELED is used in init_music().

NodeManager

  • Registers nodes (e.g., proxies) with MUSIC ports and channels.

register_music_event_in_proxy() and register_music_rate_in_proxy() take nest::Node* pointers.

EventDeliveryManager

  • Implicitly involved in delivering events to nodes (via SpikeEvent).

MusicEventHandler::update() calls channelmap_[channel]->handle(se), which may involve event delivery.

ConnectionManager

  • Provides minimum delay for rate data buffering (indirect dependency).

MusicRateInHandler uses connection_manager.get_min_delay() (from earlier code).

MusicEventHandler

  • Manages event-based communication with MUSIC ports.

MusicEventHandler is part of music_event_in_portmap_ and handles event delivery.

MusicRateInHandler

  • Manages rate-based communication with MUSIC ports.

MusicRateInHandler is part of music_rate_in_portmap_ and handles continuous data.

Class diagram

        classDiagram
  class MUSICManager {
    +std::map<std::string, MusicPortData> music_in_portlist_
    +std::map<std::string, MusicEventHandler> music_event_in_portmap_
    +std::map<std::string, MusicRateInHandler> music_rate_in_portmap_
    +MUSIC::Setup* music_setup
    +MUSIC::Runtime* music_runtime
    +MUSICManager()
    +void initialize(const bool)
    +void finalize(const bool)
    +void set_status(const DictionaryDatum&)
    +void get_status(DictionaryDatum&)
    +void init_music(int*, char***)
    +void enter_runtime(double)
    +void music_finalize()
    +MPI::Intracomm communicator()
    +MUSIC::Setup* get_music_setup()
    +MUSIC::Runtime* get_music_runtime()
    +void advance_music_time()
    +void register_music_in_port(std::string)
    +void unregister_music_in_port(std::string)
    +void register_music_event_in_proxy(std::string, int, nest::Node*)
    +void register_music_rate_in_proxy(std::string, int, nest::Node*)
    +void set_music_in_port_acceptable_latency(std::string, double)
    +void set_music_in_port_max_buffered(std::string, int)
    +void publish_music_in_ports_()
    +void update_music_event_handlers(Time const&, long, long)
  }

  class MusicEventHandler {
      +std::vector< nest::Node* > channelmap_
      +std::vector< std::priority_queue<double> > eventqueue_
      +std::vector<unsigned int> indexmap_
      +MUSIC::EventInput* music_port_
      +MUSIC::PermutationIndex* music_perm_ind_
      +std::string portname_
      +double acceptable_latency_
      +int max_buffered_
      +bool published_
      +MusicEventHandler()
      +MusicEventHandler(std::string, double, int)
      +~MusicEventHandler()
      +void register_channel(size_t, nest::Node*)
      +void publish_port()
      +void operator()(double, MUSIC::GlobalIndex)
      +void update(Time const&, long, long)
  }

  class MusicRateInHandler {
      // Assume similar structure to MusicEventHandler for rate-based ports
      // (specific details may vary based on implementation)
  }

  class MusicPortData {
      +size_t n_input_proxies
      +double acceptable_latency
      +int max_buffered
      +MusicPortData(size_t, double, int)
      +MusicPortData()
  }


  class Node["nest::Node"]


  MUSICManager--MusicEventHandler: manages
  MUSICManager--Node: interacts with
  MusicEventHandler--Node: interacts with
  MusicPortData--MUSICManager: part of
  MUSICManager--MusicRateInHandler: manages
    

Detailed operation sequence

  • Initialization MUSICManager::initialize(const bool)

    This function is called to initialize the MUSICManager.

  • Set up MUSIC MUSICManager::init_music(int* argc, char** argv[])

    This function is called to initialize the MUSIC setup. It creates a MUSIC::Setup object with the provided arguments and MPI thread level. Example code:

    void MUSICManager::init_music(int* argc, char** argv[]) {
        int provided_thread_level;
        music_setup = new MUSIC::Setup(*argc, *argv, MPI_THREAD_FUNNELED, &provided_thread_level);
    }
    
  • Enter MUSIC runtime MUSICManager::enter_runtime(double h_min_delay)

    This function is called to enter the MUSIC runtime. It publishes music in ports, logs a message indicating the entry into the MUSIC runtime, and creates a MUSIC::Runtime object if it doesn’t already exist. Example code:

    void MUSICManager::enter_runtime(double h_min_delay) {
        publish_music_in_ports_();
        std::string msg = String::compose("Entering MUSIC runtime with tick = %1 ms", h_min_delay);
        LOG(M_INFO, "MUSICManager::enter_runtime", msg);
    
        if (music_runtime == 0) {
            music_runtime = new MUSIC::Runtime(music_setup, h_min_delay * 1e-3);
        }
    }
    
  • Update MUSIC event handlers MUSICManager::update_music_event_handlers(Time const& origin, const long from, const long to)

    This function is called to update all music event and rate in handlers. It iterates through the event and rate handlers and updates them with the given origin, from, and to values. Example code:

    void MUSICManager::update_music_event_handlers(Time const& origin, const long from, const long to) {
        for (std::map<std::string, MusicEventHandler>::iterator it = music_event_in_portmap_.begin();
             it!= music_event_in_portmap_.end();
             ++it) {
            it->second.update(origin, from, to);
        }
    
        for (std::map<std::string, MusicRateInHandler>::iterator it = music_rate_in_portmap_.begin();
             it!= music_rate_in_portmap_.end();
             ++it) {
            it->second.update(origin, from, to);
        }
    }
    
  • Advance MUSIC time MUSICManager::advance_music_time()

    This function is called to advance the music time by calling the tick() method on the MUSIC::Runtime object. Example code:

    void MUSICManager::advance_music_time() {
        music_runtime->tick();
    }
    
  • Finalize MUSIC MUSICManager::music_finalize()

    This function is called to finalize the MUSICManager. It finalizes the MUSIC runtime if it exists, deletes the MUSIC::Runtime object, and if MPI is enabled, it finalizes MPI. Example code:

    void MUSICManager::music_finalize() {
        if (music_runtime == 0) {
            music_runtime = new MUSIC::Runtime(music_setup, 1e-3);
        }
    
        music_runtime->finalize();
        delete music_runtime;
    
        #ifdef HAVE_MPI
            MPI_Finalize();
        #endif
    }
    

MPI relationship

The MUSICManager and MPI work together to enable distributed simulations in NEST. The MUSICManager initializes MPI, retrieves an MPI communicator, and ensures proper finalization. It uses MPI for communication between processes, allowing for seamless integration of MUSIC into the simulation. This setup ensures that the simulation can scale across multiple processors, leveraging the power of distributed computing.

MPI Initialization:

The MUSICManager initializes MPI during the simulation setup. This is typically done in the init_music method, where it creates a MUSIC::Setup object with MPI thread support.

void MUSICManager::init_music(int* argc, char** argv[]) {
  int provided_thread_level;
  music_setup = new MUSIC::Setup(*argc, *argv, MPI_THREAD_FUNNELED, &provided_thread_level);
}

MPI Communication:

The MUSICManager uses MPI for communication between different processes in a distributed simulation. It retrieves an MPI communicator from the MUSIC setup and uses it for various operations, such as publishing ports and handling events.

MPI::Intracomm MUSICManager::communicator() {
  return music_setup->communicator();
}

Finalization:

The MUSICManager ensures that MPI is properly finalized during the simulation shutdown. This is done in the music_finalize method, which calls MPI_Finalize() if necessary.

void MUSICManager::music_finalize() {
  if (music_runtime == 0) {
    music_runtime = new MUSIC::Runtime(music_setup, 1e-3);
  }
  music_runtime->finalize();
  delete music_runtime;
#ifdef HAVE_MPI
  MPI_Finalize();
#endif
}

Functions

class MUSICManager : public nest::ManagerInterface

Encapsulate all calls to MUSIC.

Public Functions

virtual void initialize(const bool) override

Prepare manager for operation.

After this method has completed, the manager should be completely initialized and “ready for action”.

See also

finalize()

Note

Initialization of any given manager may depend on other managers having been initialized before. KernelManager::initialize() is responsible for calling the initialization routines on the specific managers in correct order.

Parameters:

adjust_number_of_threads_or_rng_only – Pass true if calling from kernel_manager::change_number_of_threads() or RandomManager::get_status() to limit operations to those necessary for thread adjustment or switch or re-seeding of RNG.

virtual void finalize(const bool) override

Take down manager after operation.

After this method has completed, all dynamic data structures created by the manager shall be deallocated and containers emptied. Plain variables need not be reset.

See also

initialize()

Note

Finalization of any given manager may depend on other managers not having been finalized yet. KernelManager::finalize() is responsible for calling the initialization routines on the specific managers in correct order, i.e., the opposite order of initialize() calls.

Parameters:

adjust_number_of_threads_or_rng_only – Pass true if calling from kernel_manager::change_number_of_threads() to limit operations to those necessary for thread adjustment.

virtual void set_status(const DictionaryDatum&) override

Set the status of the manager.

See also

get_status()

virtual void get_status(DictionaryDatum&) override

Retrieve the status of the manager.

See also

set_status()

Note

This would ideally be a const function. However, some managers delay the update of internal variables up to the point where they are needed (e.g., before reporting their values to the user, or before simulate is called). An example for this pattern is the call to update_delay_extrema_() right at the beginning of ConnectionManager::get_status().

MUSICManager()
void init_music(int *argc, char **argv[])
void enter_runtime(double h_min_delay)

Enter the runtime mode.

This must be done before simulating. After having entered runtime mode ports cannot be published anymore.

Parameters:

h_min_delay – is the length of a time slice, after which communication should take place.

void advance_music_time()

Advance the time of music by 1 simulation step.

void music_finalize()