Writing Formatted Output

Overview

Questions

  • How do I display status information during a simulation?

  • How can I log user-defined quantities?

  • How can I write formatted output to a text file?

Objectives

  • Demonstrate user-defined log quantities.

  • Explain the use of Table to display status information during a simulation run.

  • Show that Table can write to a file.

Boilerplate Code

[1]:
import datetime

import hoomd

Define the Simulation

This tutorial executes the Lennard-Jones particle simulation from a previous tutorial. See Introducing Molecular Dynamics for a complete description of this code.

[3]:
cpu = hoomd.device.CPU()
simulation = hoomd.Simulation(device=cpu, seed=1)
simulation.create_state_from_gsd(
    filename="../01-Introducing-Molecular-Dynamics/random.gsd"
)

integrator = hoomd.md.Integrator(dt=0.005)
cell = hoomd.md.nlist.Cell(buffer=0.4)
lj = hoomd.md.pair.LJ(nlist=cell)
lj.params[("A", "A")] = dict(epsilon=1, sigma=1)
lj.r_cut[("A", "A")] = 2.5
integrator.forces.append(lj)
nvt = hoomd.md.methods.ConstantVolume(
    filter=hoomd.filter.All(), thermostat=hoomd.md.methods.thermostats.Bussi(kT=1.5)
)
integrator.methods.append(nvt)
simulation.operations.integrator = integrator

Formatted Output

The Table writer formats log quantities in human-readable text and writes them to stdout or a file. Table only supports scalar and string quantities due to the limitations of this format. This section shows you how to use Table to display status information during a simulation run.

Add Quantities to a Logger

The categories argument to Logger defines the categories that it will accept.

[4]:
logger = hoomd.logging.Logger(categories=["scalar", "string"])

Log the simulation timestep and tps quantities:

[5]:
logger.add(simulation, quantities=["timestep", "tps"])

You can also log user-defined quantities using functions, callable class instances, or class properties. For example, this class computes the estimated time remaining:

[6]:
class Status:
    def __init__(self, simulation):
        self.simulation = simulation

    @property
    def seconds_remaining(self):
        try:
            return (
                self.simulation.final_timestep - self.simulation.timestep
            ) / self.simulation.tps
        except ZeroDivisionError:
            return 0

    @property
    def etr(self):
        return str(datetime.timedelta(seconds=self.seconds_remaining))

Assign the loggable quantity using the tuple (object, property_name, flag), where flag is the string name of the flag for this quantity. (The tuple for callable objects would be (callable, flag)).

[7]:
status = Status(simulation)
logger[("Status", "etr")] = (status, "etr", "string")

Represent the namespace of your user-defined quantity with a tuple of strings - ('Status', 'etr') above. You can use any number of arbitrary strings in the tuple to name your quantity.

Display Quantities with Table

Table is a Writer that formats the quantities in a Logger into a human readable table. Create one that triggers periodically:

[8]:
table = hoomd.write.Table(trigger=hoomd.trigger.Periodic(period=5000), logger=logger)

Add it to the simulation:

[9]:
simulation.operations.writers.append(table)

Run the simulation and see the output:

[10]:
simulation.run(100000)
Simulation.timestep  Simulation.tps     Status.etr
       15000          7.88916e+03     0:00:12.041834
       20000          8.69618e+03     0:00:10.349370
       25000          8.99640e+03     0:00:09.448221
       30000          9.17050e+03     0:00:08.723628
       35000          9.30709e+03     0:00:08.058376
       40000          9.37507e+03     0:00:07.466608
       45000          9.46373e+03     0:00:06.868325
       50000          9.52414e+03     0:00:06.299782
       55000          9.56244e+03     0:00:05.751668
       60000          9.58261e+03     0:00:05.217783
       65000          9.61214e+03     0:00:04.681580
       70000          9.62463e+03     0:00:04.156005
       75000          9.63408e+03     0:00:03.632937
       80000          9.64975e+03     0:00:03.108888
       85000          9.64976e+03     0:00:02.590738
       90000          9.66390e+03     0:00:02.069558
       95000          9.68390e+03     0:00:01.548963
      100000          9.69409e+03     0:00:01.031556
      105000          9.70195e+03     0:00:00.515360
      110000          9.70874e+03        0:00:00

Later in this notebook, you are going to create another Table Writer. Remove table now to avoid confusing the two:

[11]:
simulation.operations.writers.remove(table)

Save Table Output to a File

Table writes to stdout by default. It can write to a file (or any Python file-like object with write and flush methods) instead.

Open the file to write:

[12]:
file = open("log.txt", mode="x", newline="\n")

Create a Table Writer that outputs to this file and add it to the Simulation:

[13]:
table_file = hoomd.write.Table(
    output=file, trigger=hoomd.trigger.Periodic(period=5000), logger=logger
)
simulation.operations.writers.append(table_file)

Run the simulation:

[14]:
simulation.run(100000)

You can read the file with standard tools:

[15]:
!tail log.txt
      165000          9.89210e+03     0:00:04.549084
      170000          9.90604e+03     0:00:04.037939
      175000          9.90100e+03     0:00:03.534997
      180000          9.89791e+03     0:00:03.030942
      185000          9.88818e+03     0:00:02.528271
      190000          9.89337e+03     0:00:02.021556
      195000          9.88920e+03     0:00:01.516806
      200000          9.89858e+03     0:00:01.010246
      205000          9.89846e+03     0:00:00.505129
      210000          9.89905e+03        0:00:00

In this section, you have displayed loggable quantities during a simulation run and saved them to a text file. This is the last section in the logging tutorial.