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 Dyamics 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 and 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           7265.44980     0:00:13.075584
       20000           7928.10708     0:00:11.352016
       25000           8212.82940     0:00:10.349661
       30000           8347.23914     0:00:09.584007
       35000           8415.05520     0:00:08.912598
       40000           8442.75984     0:00:08.291128
       45000           8493.75873     0:00:07.652678
       50000           8532.47563     0:00:07.031957
       55000           8563.96280     0:00:06.422260
       60000           8571.47951     0:00:05.833299
       65000           8598.56480     0:00:05.233432
       70000           8627.01996     0:00:04.636595
       75000           8636.41757     0:00:04.052606
       80000           8647.71201     0:00:03.469126
       85000           8653.31308     0:00:02.889067
       90000           8664.12848     0:00:02.308368
       95000           8659.11603     0:00:01.732278
      100000           8663.85839     0:00:01.154220
      105000           8670.02276     0:00:00.576700
      110000           8674.88580        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           8794.29467     0:00:05.116954
      170000           8809.97435     0:00:04.540308
      175000           8808.18898     0:00:03.973575
      180000           8803.84588     0:00:03.407602
      185000           8793.11174     0:00:02.843135
      190000           8808.44379     0:00:02.270549
      195000           8812.89983     0:00:01.702050
      200000           8815.85406     0:00:01.134320
      205000           8819.50353     0:00:00.566925
      210000           8823.62751        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.