NATS simulation

para-atm includes capabilities to facilitate running the NATS simulation from within Python. These capabilities are provided by the paraatm.io.nats module. The following functionality is provided:

  • Boilerplate code to automatically start and stop the Java virtual machine, and to prevent it from being started multiple times
  • Behind-the-scenes path handling, so that the NATS simulation does not need to be run from within the NATS installation directory
  • A utility function to retrieve NATS constants from the Java environment
  • Return trajectory results directly as a pandas DataFrame

The functions for interfacing with NATS are subject to change. Currently, the code has been tested with NATS 1.7beta and NATS 1.8beta on Ubuntu Linux.

Creating a NATS simulation

Creation of a NATS simulation in para-atm is done by writing a class that derives from the NatsSimulationWrapper class. This is best understood through an example. The complete code for the following example is available at tests/nats_gate_to_gate.py, and it is based on DEMO_Gate_To_Gate_Simulation_SFO_PHX_beta1.7.py from the NATS samples directory.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 from paraatm.io.nats import NatsSimulationWrapper, NatsEnvironment

 class GateToGate(NatsSimulationWrapper):
     def simulation(self):

         NATS_SIMULATION_STATUS_PAUSE = NatsEnvironment.get_nats_constant('NATS_SIMULATION_STATUS_PAUSE')
         NATS_SIMULATION_STATUS_ENDED = NatsEnvironment.get_nats_constant('NATS_SIMULATION_STATUS_ENDED')

         natsStandalone = NatsEnvironment.get_nats_standalone()

         simulationInterface = natsStandalone.getSimulationInterface()
         # ...

In this example, Line 3 defines the GateToGate class as a subclass of NatsSimulationWrapper class. Then, the simulation() method is defined. This is where the user’s code for setting up and running the NATS simulation should go. Notice that lines 6 and 7 use the get_nats_constant() method to retrieve specific constants from the Java environment, which are used later in the simulation code.

Line 9 gets a reference to the natsStandalone instance, which is then used to access other simulation objects. The bulk of the remaining code follows the example file that is included with NATS.

As compared to the NATS sample file, some key differences in this implementation are:

  • from NATS_Python_Header import * is not used (in general, import * is not advisable)
  • Calls to JClass('NATSStandalone') and clsNATSStandalone.start() are not needed, as they are handled automatically by the NatsSimulationWrapper class
  • NatsEnvironment.get_nats_standalone() is used to retrieve a reference to the NATS standalone environment, which has already been started by the wrapper class
  • Cleanup calls for natsStandalone.stop() and shutdownJVM() are not needed, as they are automatically handled as well
  • NATS constants are retrieved using the utility function get_nats_constant(), as opposed to importing the constants from NATS_Python_Header.py, where each constant is manually defined

Running the NATS simulation

Once the user-defined class deriving from NatsSimulationWrapper has been created, the simulation is executed by creating an instance of the class and calling its __call__() method. This method will handle various setup behind the scenes, such as starting the JVM, creating the NATSStandalone instance, and preparing the current working directory. Once the simulation is prepared, the user’s simulation() method is called automatically. The output file is automatically created by communicating with the user-defined write_output() method, and the trajectory results are stored as a DataFrame in the 'trajectory' key of the returned dictionary.

For example, the GateToGate simulation class defined above could be invoked as:

1
2
g2g_sim = GateToGate()
df = g2g_sim()['trajectory']

Here, line 1 creates an instance of the GateToGate class. Line 2 executes the simulation, passing no arguments (note that the () operator invokes the __call__ method). The return value of g2g_sim() is a dictionary, and we retrieve the value of the 'trajectory' key, which is a DataFrame that stores the resulting trajectory data. Note that Line 2 is just shorthand for:

results = g2g_sim()
df = results['trajectory']

Additional keyword arguments provided to __call__() are passed on to simulation(). This makes it possible to create a simulation instance that accepts parameter values. For example:

1
2
3
4
5
6
7
class MySim(NatsSimulationWrapper):
    def simulation(self, my_parameter):
        # .. Perform simulation using the value of my_parameter

my_sim = MySim()
df1 = my_sim(my_parameter=1)['trajectory']
df2 = my_sim(my_parameter=2)['trajectory']

Here, the user-defined simulation() method on line 2 is defined to accept an argument, my_parameter. Once the simulation class is instantiated, repeated calls can be made using different parameter values, as shown on lines 6 and 7.

If the simulation method itself returns values, __call__() stores these in the 'sim_results' key of the dictionary that it returns. For example:

1
2
3
4
5
6
7
class MySimWithReturnVals(NatsSimulationWrapper):
    def simulation(self):
        # .. Perform simulation
        return some_data

my_sim = MySimWithReturnVals()
some_data = my_sim(return_df=False)['sim_results']

In this example, the call to my_sim() on line 7 uses the return_df=False option to suppress storing the trajectory results. However, this is not required, and both trajectory results and custom return values can be returned if needed.

The API

class paraatm.io.nats.NatsSimulationWrapper

Parent class for creating a NATS simulation instance

Users should implement the following methods in the derived class:

simulation
This method runs the actual NATS simulation. If the simulation code needs to access data files relative to the original working directory, use the NatsEnvironment.build_path() method, which will produce an appropriate path to work around the fact that NATS simulation occurs in the NATS_HOME directory.
write_output
This method writes output to the specified filename.
cleanup
Cleanup code that will be called after simulation and write_output. Having cleanup code in a separate method makes it possible for cleanup to occur after write_output. The cleanup code should not stop the NATS standalone server or the JVM, as this is handled by the NatsEnvironment class.

Once an instance of the class is created, the simulation is run by calling the instance as a function, which will go to the __call__() method. This will call the user’s simulation method, with additional pre- and post-processing steps. The JVM will be started automatically if it is not already running.

simulation()

Users must implement this method in the derived class

Assume that the jvm is already started and that it will be shutdown by the parent class.

The function may accept parameter values, which must be provided as keyword arguments when invoking __call__().

write_output(filename)

Users must implement this method in the derived class

It will be called after the simulation method and should issue the commands necessary to write the output to the specified file.

__call__(output_file=None, return_df=True, **kwargs)

Execute NATS simulation and write output to specified file

Parameters:
  • output_file (str) – Output file to write to. If not provided, a temporary file is used
  • return_df (bool) – Whether to read the output into a DataFrame and return it
  • **kwargs – Extra keyword arguments to pass to simulation call
Returns:

A dictionary with the following keys:
’trajectory’ (if return_df==True)

DataFrame with trajectory results

’sim_results’

Return value from child simulation method

Return type:

dict

class paraatm.io.nats.NatsEnvironment

Class that provides static methods to start and stop the JVM for NATS

classmethod start_jvm(nats_home=None)

Start java virtual machine and NATS standalone server

This function is called automatically by NatsSimulationWrapper, so normally there is no need for the user to call it directly.

If the JVM is already running, this will do nothing. If the JVM has already been stopped, this will raise an error, since it cannot be restarted.

This function takes care of setting the Java classpath, changing directories, starting the JVM, and starting the NATS standalone server.

Path issues with NATS are handled behind the scenes by setting the classpath and changing directories prior to starting the JVM. The original directory is remembered, and it is restored after the JVM is stopped.

Parameters:nats_home (str, optional) – Path to NATS home directory. If not provided, the NATS_HOME environment variable will be used.
classmethod stop_jvm()

Stop java virtual machine and NATS server

This also moves back to the original directory that was set prior to starting the JVM

If this function is not called manually, it will be called automatically at exit to make sure that the JVM is properly shutdown. Multiple calls are OK.

classmethod get_nats_standalone()

Retrieve reference to GNATSStandalone class instance

classmethod get_nats_constant(name)

Return the variable that stores the named NATS constant

Parameters:name (str) – Name of NATS constant to retrieve
classmethod build_path(filename)

Return a path to filename that behaves as if original directory is current working directory

This will internally convert relative paths to be relative to the original working directory (otherwise, NATS considers NATS_HOME to be the working directory).