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
enumabove and thematchexpression.
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_workfloware 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) producestarget/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.