Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The action Binary

The action binary (in src/bin/action.rs) implements the command line interface that executes the simulate action on directories in the workspace.

Note

At this time, the template supports actions that operate only on a single directory at a a time. A future update will enable bundled actions that operate on many directories within a single SLURM job.

Parse the Command Line

You need some way to tell action what to do. In production, you may be running thousands of different action instances at once, so a command line interface is essential. By default, row will launch commands of the form:

action simulate {directory}

The template action uses clap to parse command line options of this form into a struct:

#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Cli {
    #[command(subcommand)]
    command: Commands,

    #[command(flatten)]
    verbose: Verbosity<InfoLevel>,
}

#[derive(Subcommand)]
enum Commands {
    Simulate { directory: PathBuf },
}

fn main() -> anyhow::Result<()> {
    let options = Cli::parse();

The Subcommand is not strictly necessary in a binary that executes only one action. The template uses it so that you can easily add more actions (subcommands) as needed (e.g. analyze, render, …).

Tip

row of course allows you to implement these other actions in another language of your preference. You do not have to write all your workflow actions in Rust.

Configure env_logger

Did you notice all the info! and debug! messages throughout the code? These macros pass messages to the log crate, which does nothing by itself. Once you configure a logging backend, the messages will print to stdout (or wherever the backend delivers them). The env_logger crate a backend that allows fine-grained customization at run time (e.g. you could hide messages from hoomd-mc and see messages from your own crate). The verbose option configures what level is visible from the command line (which can be overridden by the RUST_LOG environment variable). By default, warning! and info! messages are shown. Pass -q or -qq to hide info and warning respectively. Pass -v to enable debug! messages and -vv to enable trace!.

let log_level = match options.verbose.log_level_filter() {
    LevelFilter::Off => "off",
    LevelFilter::Error => "error",
    LevelFilter::Warn => "warn",

    LevelFilter::Info => "info",
    LevelFilter::Debug => "debug",
    LevelFilter::Trace => "trace",
};

env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(log_level))
    .format_timestamp(None)
    .init();

Execute the Chosen Action

Next, action switches to the workspace directory (row names directories relative to workspace/) and calls simulate_one:

    set_current_dir("workspace").context("error switching to directory `workspace`")?;

    match &options.command {
        Commands::Simulate { directory } => simulate_one(directory)?,
    }

Tip

Add any new subcommands to both the enum above and the match expression.

To see the log messages in action, execute:

$ target/release/action simulate 524ac2c93c6db72af79821edd021696b

You should see:

[INFO  hoomd_workflow::simulate] Step 0 / 100000 (0%)
[INFO  hoomd_workflow::simulate] Step 1000 / 100000 (1%)
[INFO  hoomd_workflow::simulate] Step 2000 / 100000 (2%)

Try adding -v, -vv, or -q to the command and see how the output changes.

Note

The messages generated by hoomd_workflow are suggestions only. Feel free to modify them as you see fit for your own workflows.

Error Handling

Notice that main returns an anyhow::Result and checks for errors with ?. In this way, any errors that occur in methods called by main (or methods called by those methods, and on down the chain) will propagate all the way to the top, unless then are recovered from. When main returns an Err, anyhow prints a human readable form of the error message.

To see an error message, execute:

$ target/release/action simulate not-a-directory

You should see:

Error: error switching to job directory `not-a-directory`

Caused by:
    No such file or directory (os error 2)

The error switching to job directory came from a call to .context. anyhow lists the original error message(s) under Caused by.

Troubleshooting Difficult Errors

What if action prints an error that has a less obvious cause? For example, after changing one of the .rs files, the command:

$ target/release/action simulate 524ac2c93c6db72af79821edd021696b

produced the output:

Error: error initializing model in `524ac2c93c6db72af79821edd021696b`

Caused by:
    -0.1 is not greater than 0

Obviously, the problem is that some -0.1 needs to be larger than 0. The context tells us that this error occurs during model initialization. It would help to know exactly where the error occurred, so you could tell which -0.1 to change.

Like most programming environments, Rust can give you a backtrace. You set the environment variable RUST_BACKTRACE=1 to opt-in. Normally, Rust backtraces are only issued on a panic, but anyhow enables them for errors as well. Furthermore, you must build in a development mode (omit the --release option) or else you will not see file names or line numbers. With the same code modification as above, the command:

$ RUST_BACKTRACE=1 cargo run -- simulate 524ac2c93c6db72af79821edd021696b

produces the output:

Error: error initializing model in `524ac2c93c6db72af79821edd021696b`

Caused by:
    -0.1 is not greater than 0

Stack backtrace:
   0: std::backtrace_rs::backtrace::libunwind::trace
             at /rustc/4a4ef493e3a1488c6e321570238084b38948f6db/library/std/src/../../backtrace/src/backtrace/libunwind.rs:117:9
   1: std::backtrace_rs::backtrace::trace_unsynchronized
             at /rustc/4a4ef493e3a1488c6e321570238084b38948f6db/library/std/src/../../backtrace/src/backtrace/mod.rs:66:14
   2: std::backtrace::Backtrace::create
             at /rustc/4a4ef493e3a1488c6e321570238084b38948f6db/library/std/src/backtrace.rs:331:13
   3: anyhow::error::<impl core::convert::From<E> for anyhow::Error>::from
             at ${HOME}/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.102/src/backtrace.rs:10:14
   4: <core::result::Result<T,F> as core::ops::try_trait::FromResidual<core::result::Result<core::convert::Infallible,E>>>::from_residual
             at ${HOME}/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/result.rs:2189:27
   5: hoomd_workflow::model::LennardJonesModel::new
             at ${HOME}/devel/hoomd-workflow/src/model.rs:101:58
   6: hoomd_workflow::simulate::get_model
             at ${HOME}/devel/hoomd-workflow/src/simulate.rs:42:17
   7: hoomd_workflow::simulate::simulate_one
             at ${HOME}/devel/hoomd-workflow/src/simulate.rs:63:21
   8: action::main
             at ${HOME}/devel/hoomd-workflow/src/bin/action.rs:43:45
   9: core::ops::function::FnOnce::call_once
             at ${HOME}/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5
  10: std::sys::backtrace::__rust_begin_short_backtrace
             at ${HOME}/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/std/src/sys/backtrace.rs:166:18
  11: std::rt::lang_start::{{closure}}
             at ${HOME}/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/std/src/rt.rs:206:18
  12: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once
             at /rustc/4a4ef493e3a1488c6e321570238084b38948f6db/library/core/src/ops/function.rs:287:21
  13: std::panicking::catch_unwind::do_call
             at /rustc/4a4ef493e3a1488c6e321570238084b38948f6db/library/std/src/panicking.rs:581:40
  14: std::panicking::catch_unwind
             at /rustc/4a4ef493e3a1488c6e321570238084b38948f6db/library/std/src/panicking.rs:544:19
  15: std::panic::catch_unwind
             at /rustc/4a4ef493e3a1488c6e321570238084b38948f6db/library/std/src/panic.rs:359:14
  16: std::rt::lang_start_internal::{{closure}}
             at /rustc/4a4ef493e3a1488c6e321570238084b38948f6db/library/std/src/rt.rs:175:24
  17: std::panicking::catch_unwind::do_call
             at /rustc/4a4ef493e3a1488c6e321570238084b38948f6db/library/std/src/panicking.rs:581:40
  18: std::panicking::catch_unwind
             at /rustc/4a4ef493e3a1488c6e321570238084b38948f6db/library/std/src/panicking.rs:544:19
  19: std::panic::catch_unwind
             at /rustc/4a4ef493e3a1488c6e321570238084b38948f6db/library/std/src/panic.rs:359:14
  20: std::rt::lang_start_internal
             at /rustc/4a4ef493e3a1488c6e321570238084b38948f6db/library/std/src/rt.rs:171:5
  21: std::rt::lang_start
             at ${HOME}/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/std/src/rt.rs:205:5
  22: _main

The lowest level file in hoomd_workflow that triggered the error is:

   5: hoomd_workflow::model::LennardJonesModel::new
             at ${HOME}/devel/hoomd-workflow/src/model.rs:101:58

which is:

let translate = Translate::with_maximum_distance(INITIAL_MAXIMUM_DISTANCE.try_into()?);

Now you can tell that this demonstration modified INITIAL_MAXIMUM_DISTANCE to a negative value. The Translate move must have a positive value.

Tip

cargo build (without --release) produces target/debug/action.


Development of hoomd-rs is led by the Glotzer Group at the University of Michigan.

Copyright © 2024-2026 The Regents of the University of Michigan.