# MDI Driver Tutorial In this setion, we will use the MolSSI Driver Interface to perform molecular dynamics simulations with TorchANI. ANI, or ANAKIN-ME: Accurate NeurAl networK engINe for Molecular Energies, is a deep learning-based method for computing molecular energies and forces. You can read more about ANI [here](https://roitberg.chem.ufl.edu/research/).s :::{admonition} Prerequisites :class: note Before starting this tutorial, you should have already completed the [Computer Set Up](setup). It is also suggested that you have completed the [Energy Calculation Lesson](energy_calculations). ::: To complete this tutorial, clone the [starting repository](https://github.com/MolSSI-MDI/mdi-ani-workshop.git). ````{tab-set} ```{tab-item} shell :::{code-block} shell git clone https://github.com/MolSSI-MDI/mdi-ani-workshop.git ::: ``` ```` ## Examining Initial Files The starting repository contains a set of files we will need to complete this tutorial. We will be using TorchANI to compute forces on atoms given a particular molecular configuration, passing those forces to LAMMPS using MDI, then allowing LAMMPs to perform the dynamics steps. When you clone the repository, you will see the following directory structure: ``` ├── README.md ├── docker │ └── Dockerfile ├── lammps │ └── water │ ├── lammps.data │ ├── lammps.in │ └── outfiles ├── mdi-ani-tutorial │ ├── mdi-ani-tutorial.py │ └── util.py └── mdimechanic.yml ``` This is our starting set of files. This is roughly the set of files you would obtain from running the MDI CookieCutter. In this case, we have added some starting files to perform a simulation using LAMMPS. We've also added several utility functions to `util.py` that will help use complete this tutorial more easily. All of the files associated with the LAMMPS simulation are in the `lammps` directory. If you view these files, you will see a folder called `water` that contains the LAMMPS input file `lammps.in` and the LAMMPS data file `lammps.data`. This will be our starting configuration for our simulation using TorchANI. ### Python Driver Next, open the file `mdi-ani-tutorial.py` in the `mdi-ani-tutorial` directory. This file contains the Python code that will be used to run the simulation. To start, you will see the following code: ````{tab-set} ```{tab-item} mdi-ani-tutorial.py :::{code-block} python import sys import warnings # Import the MDI Library import mdi # Import MPI Library try: from mpi4py import MPI use_mpi4py = True mpi_comm_world = MPI.COMM_WORLD except ImportError: use_mpi4py = False mpi_comm_world = None # Import parser from util import create_parser, connect_to_engines if __name__ == "__main__": # Read in the command-line options args = create_parser().parse_args() mdi_options = args.mdi if mdi_options is None: mdi_options = ( "-role DRIVER -name driver -method TCP -port 8021 -hostname localhost" ) warnings.warn(f"Warning: -mdi not provided. Using default value: {mdi_options}") # Initialize the MDI Library mdi.MDI_Init(mdi_options) # Get the correct MPI intra-communicator for this code mpi_comm_world = mdi.MDI_MPI_get_world_comm() engines = connect_to_engines(1) ########################### # Perform the simulation ########################### # Send the "EXIT" command to each of the engines for comm in engines.values(): mdi.MDI_Send_Command("EXIT", comm) ::: ``` ```` The code that is present in the file to start is boilerplate code that initializes the MDI Library and connects to the engines (in this case, LAMMPS). We only need to worry about adding code in the section that says ``` ########################### # Perform the simulation ########################### ``` ### MDI Mechanic Configuration The second important file is the `mdimechanic.yml` file. This file contains the configuration for the MDI Mechanic software. MDI Mechanic is a utility tool that can perform many functions related to MDI, including launching engines and running simulations. Under the hood, MDI Mechanic uses a software called Docker to launch engines and drivers. We've already configured the `mdimechanic.yml` file to install necessary dependencies for this tutorial and launch the LAMMPS engine. At the end of the file, you will see ``` lmp -mdi "-role ENGINE -name LAMMPS -method TCP -port 8021 -hostname ani-tutorial" -l outfiles/log.lammps -in lammps.in > outfiles/water_tutorial.out ``` This is a launch command for LAMMPS. If you've used LAMMPS before, you will recognize this as the command to run a LAMMPS simulation with an additional flag `-mdi` that tells LAMMPS to connect to the MDI driver. The first time you use this repository, you will need to run `mdimechanic build` ````{tab-set} ```{tab-item} shell :::{code-block} shell mdimechanic build ::: ``` ```` To confirm that your set up is correct, you can run the following command: ````{tab-set} ```{tab-item} shell :::{code-block} shell mdimechanic run --name ani-tutorial ::: ``` ```` This command will launch the LAMMPS engine and the MDI driver. Upon successful completion of this command, you should see the following output: ``` Running a custom calculation with MDI Mechanic. ==================================================== ================ Output from Docker ================ ==================================================== Attaching to temp-ani-tutorial-1, temp-lammps-1 temp-ani-tutorial-1 exited with code 0 temp-lammps-1 exited with code 0 ==================================================== ============== End Output from Docker ============== ==================================================== Success: The driver ran to completion. ``` You will also see several files have appeared in the `lammps/water/outfiles` directory. You will see `log.lammps` and `water_tutorial.out`. Both of these will show that LAMMPS started. However, you will not see any time step information because we have not yet instructed LAMMPS to run any steps through the input file or MDI Driver. ## Getting Started with MDI Now that we have confirmed that the MDI driver and LAMMPS engine are running correctly, we can write the code that will perform the simulation. We will build this section up slowly. A key concept that we will need to understand for this section is the concept of an "engine" in our driver. If you look at the starting driver code, you will see the following line: ``` engines = connect_to_engines(1) ``` The `connect_to_engines` function is a helper function that will connect to the engines that are running. In our case, we have only one engine running, which is LAMMPS. The `engines` variable is a dictionary that contains the MDI communicator for each engine. We can examine the engines that are running by adding the following code to the `# Perform the simulation` section: ````{tab-set} ```{tab-item} python :::{code-block} python # Print the engines that are running for name, comm in engines.items(): print(f"Engine {name} is running.") ::: ``` ```` If you rerun the driver code using `mdimechanic run --name ani-tutorial`, you will see the following output: ``` Running a custom calculation with MDI Mechanic. ==================================================== ================ Output from Docker ================ ==================================================== Attaching to temp-ani-tutorial-1, temp-lammps-1 temp-ani-tutorial-1 | Engine LAMMPS is running. temp-ani-tutorial-1 exited with code 0 temp-lammps-1 exited with code 0 ==================================================== ============== End Output from Docker ============== ==================================================== Success: The driver ran to completion. ``` For convenience, we will create a variable called `lammps` to use for the rest of the tutorial. Add the following after the loop in your code: ```python lammps = engines["LAMMPS"] ``` At the end of this section, your code should look like the following: ````{tab-set} ```{tab-item} mdi-ani-tutorial.py :::{code-block} python import sys import warnings # Import the MDI Library import mdi # Import MPI Library try: from mpi4py import MPI use_mpi4py = True mpi_comm_world = MPI.COMM_WORLD except ImportError: use_mpi4py = False mpi_comm_world = None # Import parser from util import create_parser, connect_to_engines if __name__ == "__main__": # Read in the command-line options args = create_parser().parse_args() mdi_options = args.mdi if mdi_options is None: mdi_options = ( "-role DRIVER -name driver -method TCP -port 8021 -hostname localhost" ) warnings.warn(f"Warning: -mdi not provided. Using default value: {mdi_options}") # Initialize the MDI Library mdi.MDI_Init(mdi_options) # Get the correct MPI intra-communicator for this code mpi_comm_world = mdi.MDI_MPI_get_world_comm() engines = connect_to_engines(1) ########################### # Perform the simulation ########################### for name, comm in engines.items(): print(f"Engine {name} is running.") lammps = engines["LAMMPS"] # Send the "EXIT" command to each of the engines for comm in engines.values(): mdi.MDI_Send_Command("EXIT", comm) ::: ``` ```` ## Sending and Receiving Data When using MDI, we retrieve data from the engine by sending a command to the engine and then receiving the data. Data that you can send or receive from an MDI engine is defined by the [MDI Standard](https://molssi-mdi.github.io/MDI_Library/html/mdi_standard.html). You can see a full list of command at the link above, or newer docs that are under development [here](https://molssi-mdi.github.io/MDI_Library/html/mdi_standard.html). The first command we use is the `mdi.MDI_Send_Command` function. The argument to this function is the command to send and the engine we would like to send the command to. When using `MDI_Send_Command`, we use `<` to tell the engine to send data to the driver, and `>` to tell the driver to send data to the engine. For example, to get the number of atoms in the system, we first call `mdi.MDI_Send_Command("FORCES` command using the new [API Documentation](https://janash.github.io/mdi-docs/api/mdi_standard/commands/data-exchange/FORCES.html). The command to send the forces to LAMMPS is `>FORCES`. **Be sure to check the units of the forces you are sending to LAMMPS.** MDI expects atomic units (Ha/Bohr) ```{admonition} Solution :class: solution dropdown :::{code-block} python # Run the dynamics for i in range(nsteps): # Send to @FORCES node mdi.MDI_Send_Command("@FORCES", lammps) # Get the atomic coordinates mdi.MDI_Send_Command("FORCES", lammps) mdi.MDI_Send(ani_forces, 3 * natoms, mdi.MDI_DOUBLE, lammps) ::: ``` ```` After you have completed this exercise, you have successfully driven a simulation using MDI and TorchANI. You can increase your number of steps to run longer dynamics or modify the input file to change the simulation conditions. ## Exercises ### Adding a Minimization Step (Geometry Optimization) When performing molecular dynamics, you would typically do an energy minimization step (or geometry optimization) before performing dynamics. This is to ensure that the system is at a local minimum in the potential energy surface. We can add an energy minimization step to our simulation by sending our driver to the `@INIT_OPTG` node and running a minimization loop (similar to the dynamics loop). Add the ability to do a geometry optimization to your driver.