Skip to content

Commit

Permalink
v1.0.3
Browse files Browse the repository at this point in the history
  • Loading branch information
Ladme committed Sep 25, 2023
1 parent e14a956 commit b726b87
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 58 deletions.
46 changes: 23 additions & 23 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "gcenter"
authors = ["Ladislav Bartos <[email protected]>"]
description = "Center Any Group in a Gromacs Trajectory"
version = "1.0.2"
version = "1.0.3"
license = "MIT"
edition = "2021"
repository = "https://github.com/Ladme/gcenter"
Expand All @@ -15,7 +15,7 @@ categories = ["command-line-utilities", "science"]
backitup = "0.1.1"
clap = { version = "4.4.3", features = ["derive"] }
colored = "2.0.4"
groan_rs = "0.2.0"
groan_rs = "0.3.1"
thiserror = "1.0.48"

[dev-dependencies]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Center your chosen group within a Gromacs trajectory or structure file effortles

`gcenter` can accurately center atom groups, even when they span multiple molecules that may extend beyond the box boundaries. `gcenter` does not employ connectivity information, so it doesn't require a tpr file as input. `gcenter` exclusively supports orthogonal simulation boxes.

`gcenter` supports gro and pdb structure files and xtc and trr trajectories and it can autodetect protein residues. Use VMD-like [groan selection language](https://docs.rs/groan_rs/0.1.0/groan_rs/#groan-selection-language) to select groups of atoms to center.
`gcenter` supports gro and pdb structure files and xtc and trr trajectories and it can autodetect protein residues. Use VMD-like [groan selection language](https://docs.rs/groan_rs/latest/groan_rs/#groan-selection-language) to select groups of atoms to center.

## Installation

Expand Down
89 changes: 57 additions & 32 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::io::{self, Write};
use std::path::Path;
use thiserror::Error;

use groan_rs::errors::GroupError;
use groan_rs::files::FileType;
use groan_rs::prelude::*;

Expand All @@ -29,22 +30,24 @@ pub struct Args {
short = 'c',
long = "structure",
help = "Input structure file",
long_help = "Path to a gro or pdb file containing the system structure. If a trajectory is also provided, the coordinates from the structure file will be ignored."
long_help = "Path to a gro or pdb file containing the system structure. If a trajectory is also provided, the coordinates from the structure file will be ignored.",
value_parser = validate_structure_type,
)]
structure: String,

#[arg(
short = 'f',
long = "trajectory",
help = "Input trajectory file",
long_help = "Path to an xtc or trr file containing the trajectory to be manipulated. If not provided, the centering operation will use the structure file itself."
long_help = "Path to an xtc or trr file containing the trajectory to be manipulated. If not provided, the centering operation will use the structure file itself.",
value_parser = validate_trajectory_type,
)]
trajectory: Option<String>,

#[arg(
short = 'n',
long = "index",
help = "Input index file (default: index.ndx)",
help = "Input index file [default: index.ndx]",
long_help = "Path to an ndx file containing groups associated with the system.\n\n[default: index.ndx]"
)]
index: Option<String>,
Expand All @@ -60,7 +63,7 @@ pub struct Args {
#[arg(
short = 'r',
long = "reference",
help = "Group to center (default: Protein)",
help = "Group to center",
default_value = "Protein",
long_help = "Specify the group to be centered. Use VMD-like 'groan selection language' to define the group. This language also supports ndx group names."
)]
Expand All @@ -69,7 +72,7 @@ pub struct Args {
#[arg(
short = 's',
long = "step",
help = "Write every <STEP>th frame (default: 1)",
help = "Write every <STEP>th frame",
default_value_t = 1,
requires = "trajectory",
long_help = "Center and write only every <STEP>th frame of the trajectory to the output file. This option is only applicable when a trajectory file is provided."
Expand Down Expand Up @@ -125,10 +128,10 @@ pub struct Args {
/// Errors originating directly from `gcenter`.
#[derive(Error, Debug, PartialEq, Eq)]
pub enum RunError {
#[error("{} structure file '{}' is the same file as the output file", "error:".red().bold(), .0.yellow())]
IOMatchStructure(String),
#[error("{} trajectory file '{}' is the same file as the output file", "error:".red().bold(), .0.yellow())]
IOMatchTrajectory(String),
#[error("{} invalid value '{}' for '{}': output path matches input path\n\nFor more information, try '{}'.", "error:".red().bold(), .0.yellow(), "--output <OUTPUT>".bold(), "--help".bold())]
IOMatch(String),
#[error("{} invalid value '{}' for '{}': unsupported file extension\n\nFor more information, try '{}'.", "error:".red().bold(), .0.yellow(), "--output <OUTPUT>".bold(), "--help".bold())]
OutputUnsupported(String),
#[error("{} reference group '{}' is empty", "error:".red().bold(), .0.yellow())]
EmptyReference(String),
#[error("{} no protein atoms autodetected", "error:".red().bold())]
Expand All @@ -141,20 +144,44 @@ pub enum RunError {
UnsupportedFileExtension(String),
}

/// Validate that the structure is gro or pdb file.
fn validate_structure_type(s: &str) -> Result<String, String> {
match FileType::from_name(s) {
FileType::GRO | FileType::PDB => Ok(s.to_owned()),
_ => Err(String::from("unsupported file extension")),
}
}

/// Validate that the trajectory is xtc or trr file.
fn validate_trajectory_type(s: &str) -> Result<String, String> {
match FileType::from_name(s) {
FileType::XTC | FileType::TRR => Ok(s.to_owned()),
_ => Err(String::from("unsupported file extension")),
}
}

/// Check that the input and output files are not identical.
/// This protects the user from accidentaly overwriting their data.
/// Check that the output file has the correct file extension.
fn sanity_check_files(args: &Args) -> Result<(), RunError> {
if args.trajectory.is_none() {
if args.structure == args.output {
return Err(RunError::IOMatchStructure(args.structure.to_string()));
return Err(RunError::IOMatch(args.structure.to_string()));
}
} else if *args.trajectory.as_ref().unwrap() == args.output {
return Err(RunError::IOMatchTrajectory(
return Err(RunError::IOMatch(
args.trajectory.as_ref().unwrap().to_string(),
));
}

Ok(())
let output_type = FileType::from_name(&args.output);

match (&args.trajectory, output_type) {
(None, FileType::GRO | FileType::PDB) => Ok(()),
(None, _) => Err(RunError::OutputUnsupported(args.output.clone())),
(Some(_), FileType::XTC | FileType::TRR) => Ok(()),
(Some(_), _) => Err(RunError::OutputUnsupported(args.output.clone())),
}
}

/// Center the reference group and write an output gro or pdb file.
Expand Down Expand Up @@ -289,15 +316,13 @@ fn print_options(args: &Args, system: &System, dim: &Dimension) {
/// Perform the centering.
pub fn run() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
sanity_check_files(&args)?;

if !args.silent {
let version = format!("\n >> gcenter {} <<\n", env!("CARGO_PKG_VERSION"));
println!("{}", version.bold());
}

// check that the input file is not the same as the output file
sanity_check_files(&args)?;

// construct a dimension; if no dimension has been chosen, use all of them
let dim: Dimension = match [args.xdimension, args.ydimension, args.zdimension].into() {
Dimension::None => Dimension::XYZ,
Expand Down Expand Up @@ -349,25 +374,25 @@ pub fn run() -> Result<(), Box<dyn std::error::Error>> {

// select reference atoms
let autodetect = match system.group_create("Reference", &args.reference) {
Ok(_) => false,
Err(e) => {
if &args.reference == "Protein" {
if !args.silent {
println!(
"{} group '{}' not found. Autodetecting protein atoms...\n",
"warning:".yellow().bold(),
"Protein".yellow()
);
}

system
.group_create("Reference", "@protein")
.expect("gcenter: Fatal Error. Autodetection failed.");
true
} else {
return Err(e);
// ignore group overwrite
Ok(_) | Err(GroupError::AlreadyExistsWarning(_)) => false,
// if the reference group is 'Protein' and such group does not exist, try autodetecting the protein atoms
Err(GroupError::InvalidQuery(_)) if &args.reference == "Protein" => {
if !args.silent {
println!(
"{} group '{}' not found. Autodetecting protein atoms...\n",
"warning:".yellow().bold(),
"Protein".yellow()
);
}

match system.group_create("Reference", "@protein") {
Ok(_) | Err(GroupError::AlreadyExistsWarning(_)) => true,
Err(_) => panic!("gcenter: Fatal Error. Autodetection failed."),
}
}
// propagate all the other errors
Err(e) => return Err(Box::from(e)),
};

// check that the reference group is not empty
Expand Down
Loading

0 comments on commit b726b87

Please sign in to comment.