From 19bd55709cfd311a797bc2fda051d81158798636 Mon Sep 17 00:00:00 2001 From: harryswift01 Date: Fri, 14 Mar 2025 16:49:34 +0000 Subject: [PATCH 1/5] Introduction of a YAML configuration file for CodeEntropy: - Modularize 'main_mcc.py' to separate configuration and input stages - Functions include: 'load_config", 'setup_argsparse' and 'merge_configs' - Improved error handling by adding specific checks on inputs that are required - Improve error handling to catch and raise specific errors with clear messages - Ensure YAML configuration values are correctly merged with CLI argument - Add detailed comments to explain the purpose and functionality of each section --- CodeEntropy/main_mcc.py | 293 +++++++++++++--------------------------- config.yaml | 38 ++++++ 2 files changed, 133 insertions(+), 198 deletions(-) create mode 100644 config.yaml diff --git a/CodeEntropy/main_mcc.py b/CodeEntropy/main_mcc.py index 234cefe..f80a3e9 100644 --- a/CodeEntropy/main_mcc.py +++ b/CodeEntropy/main_mcc.py @@ -5,6 +5,7 @@ # import numpy as np import pandas as pd +import yaml from CodeEntropy import EntropyFunctions as EF from CodeEntropy import LevelFunctions as LF @@ -13,6 +14,79 @@ # from datetime import datetime +def load_config(file_path): + """Load YAML configuration file.""" + with open(file_path, "r") as file: + return yaml.safe_load(file)["arguments"]["CodeEntropy"] + + +def setup_argparse(): + """Set up argument parser with required arguments.""" + parser = argparse.ArgumentParser( + description="Override YAML defaults with CLI arguments" + ) + parser.add_argument( + "-f", + "--top_traj_file", + nargs="+", + help="Path to Structure/topology file followed by Trajectory file(s)", + ) + parser.add_argument( + "--selection_string", type=str, help="Selection string for CodeEntropy" + ) + parser.add_argument( + "--start", type=int, help="Start analysing the trajectory from this frame index" + ) + parser.add_argument( + "--end", type=int, help="Stop analysing the trajectory at this frame index" + ) + parser.add_argument( + "--step", + type=int, + help="Interval between two consecutive frames to be read index", + ) + parser.add_argument( + "--bin_width", type=int, help="Bin width in degrees for making the histogram" + ) + parser.add_argument( + "--tempra", type=float, help="Temperature for entropy calculation (K)" + ) + parser.add_argument( + "--verbose", type=bool, help="True/False flag for noisy or quiet output" + ) + parser.add_argument("--thread", type=int, help="How many multiprocess to use") + parser.add_argument( + "--outfile", help="Name of the file where the output will be written" + ) + parser.add_argument( + "--resfile", + help="Name of the file where the residue entropy output will be written", + ) + parser.add_argument( + "--mout", help="Name of the file where certain matrices will be written" + ) + return parser + + +def merge_configs(args, config): + """Merge CLI arguments with YAML configuration.""" + for key, value in config.items(): + if getattr(args, key) is None: + setattr( + args, + key, + ( + value["default"] + if isinstance(value, dict) and "default" in value + else value + ), + ) + for key, value in vars(args).items(): + if value is not None: + config[key] = value + return config + + def main(): """ Main function for calculating the entropy of a system using the multiscale cell @@ -20,215 +94,38 @@ def main(): """ try: - parser = argparse.ArgumentParser( - description=""" - CodeEntropy-POSEIDON is a tool to compute entropy using the - multiscale-cell-correlation (MCC) theory and force/torque covariance - methods with the ablity to compute solvent entropy. - Version: - 0.3.1; - - Authors: - Arghya Chakravorty (arghya90), - Jas Kalayan (jkalayan), - Donald Chang, - Sarah Fegan - Ioana Papa; - - Output: - *.csv = results from different calculateion, - *.pkl - Pickled reduced universe for further analysis, - *.out - detailed output such as matrix and spectra""" - ) - - parser.add_argument( - "-f", - "--top_traj_file", - required=True, - dest="filePath", - action="store", - nargs="+", - help="Path to Structure/topology file (AMBER PRMTOP, GROMACS TPR which " - "contains topology and dihedral information) followed by Trajectory " - "file(s) (AMBER NETCDF or GROMACS TRR) you will need to output the " - "coordinates and forces to the same file. Required.", - ) - parser.add_argument( - "-l", - "--selectString", - action="store", - dest="selection_string", - type=str, - default="all", - help="Selection string for CodeEntropy such as protein or resid, refer to " - "MDAnalysis.select_atoms for more information.", - ) - parser.add_argument( - "-b", - "--begin", - action="store", - dest="start", - help="Start analysing the trajectory from this frame index. Defaults to 0", - default=0, - type=int, - ) - parser.add_argument( - "-e", - "--end", - action="store", - dest="end", - help="Stop analysing the trajectory at this frame index. Defaults to -1 " - "(end of trajectory file)", - default=-1, - type=int, - ) - parser.add_argument( - "-d", - "--step", - action="store", - dest="step", - help="interval between two consecutive frames to be read index. " - "Defaults to 1", - default=1, - type=int, - ) - parser.add_argument( - "-n", - "--bin_width", - action="store", - dest="bin_width", - default=30, - type=int, - help="Bin width in degrees for making the histogram of the dihedral angles " - "for the conformational entropy. Default: 30", - ) - parser.add_argument( - "-k", - "--tempra", - action="store", - dest="temp", - help="Temperature for entropy calculation (K). Default to 298.0 K", - default=298.0, - type=float, - ) - parser.add_argument( - "-v", - "--verbose", - action="store", - dest="verbose", - default=False, - type=bool, - help="True/False flag for noisy or quiet output. Default: False", - ) - parser.add_argument( - "-t", - "--thread", - action="store", - dest="thread", - help="How many multiprocess to use. Default 1 for single core execution.", - default=1, - type=int, - ) - parser.add_argument( - "-o", - "--out", - action="store", - dest="outfile", - default="outfile.out", - help="Name of the file where the output will be written. " - "Default: outfile.out", - ) - parser.add_argument( - "-r", - "--resout", - action="store", - dest="resfile", - default="res_outfile.out", - help="Name of the file where the residue entropy output will be written. " - "Default: res_outfile.out", - ) - parser.add_argument( - "-m", - "--mout", - action="store", - dest="moutfile", - default=None, - help="Name of the file where certain matrices will be written " - "(default: None).", - ) - - parser.add_argument( - "-c", - "--cutShell", - action="store", - dest="cutShell", - default=None, - type=float, - help="include cutoff shell analysis, add cutoff distance in angstrom " - "Default None will ust the RAD Algorithm", - ) - parser.add_argument( - "-p", - "--pureAtomNum", - action="store", - dest="puteAtomNum", - default=1, - type=int, - help="Reference molecule resid for system of pure liquid. " "Default to 1", - ) - parser.add_argument( - "-x", - "--excludedResnames", - dest="excludedResnames", - action="store", - nargs="+", - default=None, - help="exclude a list of molecule names from nearest non-like analysis. " - "Default: None. Multiples are gathered into list.", - ) - parser.add_argument( - "-w", - "--water", - dest="waterResnames", - action="store", - default="WAT", - nargs="+", - help="resname for water molecules. " - "Default: WAT. Multiples are gathered into list.", - ) - parser.add_argument( - "-s", - "--solvent", - dest="solventResnames", - action="store", - nargs="+", - default=None, - help="include resname of solvent molecules (case-sensitive) " - "Default: None. Multiples are gathered into list.", - ) - parser.add_argument( - "--solContact", - action="store_true", - dest="doSolContact", - default=False, - help="Do solute contact calculation", - ) - + config = load_config("config.yaml") + parser = setup_argparse() args = parser.parse_args() + config = merge_configs(args, config) + + # Check for required arguments + if not config.get("top_traj_file"): + raise ValueError( + "The 'top_traj_file' argument is required but not provided." + ) + if not config.get("selection_string"): + raise ValueError( + "The 'selection_string' argument is required but not provided." + ) + except argparse.ArgumentError: print("Command line arguments are ill-defined, please check the arguments") raise + except ValueError as e: + print(e) + raise # REPLACE INPUTS - print("printing all input") + print("Printing all input") for arg in vars(args): - print(" {} {}".format(arg, getattr(args, arg) or "")) + print(" {}: {}".format(arg, getattr(args, arg) or "")) # startTime = datetime.now() # Get topology and trajectory file names and make universe - tprfile = args.filePath[0] - trrfile = args.filePath[1:] + tprfile = args.top_traj_file[0] + trrfile = args.top_traj_file[1:] u = mda.Universe(tprfile, trrfile) # Define bin_width for histogram from inputs diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..2f193af --- /dev/null +++ b/config.yaml @@ -0,0 +1,38 @@ +--- + +arguments: + CodeEntropy: + top_traj_file: + required: True + default: [] + selection_string: + type: str + default: "all" + start: + type: int + default: 0 + end: + type: int + default: -1 + step: + type: int + default: 1 + bin_width: + type: int + default: 30 + tempra: + type: float + default: 298.0 + verbose: + type: bool + default: False + thread: + type: int + default: 1 + outfile: + type: str + default: "outfile.out" + resfile: + default: "res_outfile.out" + mout: + default: null \ No newline at end of file From 3bc5250ef3ac4ed9c53ea6d445e9f1b3ea757bbe Mon Sep 17 00:00:00 2001 From: harryswift01 Date: Fri, 14 Mar 2025 17:09:42 +0000 Subject: [PATCH 2/5] Addition of 'force_partitioning' and 'waterEntropy' arguments --- CodeEntropy/main_mcc.py | 4 ++++ config.yaml | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CodeEntropy/main_mcc.py b/CodeEntropy/main_mcc.py index f80a3e9..f165bf3 100644 --- a/CodeEntropy/main_mcc.py +++ b/CodeEntropy/main_mcc.py @@ -65,6 +65,10 @@ def setup_argparse(): parser.add_argument( "--mout", help="Name of the file where certain matrices will be written" ) + parser.add_argument( + "--force_partitioning", + ) + parser.add_argument("--waterEntropy") return parser diff --git a/config.yaml b/config.yaml index 2f193af..6263b1a 100644 --- a/config.yaml +++ b/config.yaml @@ -35,4 +35,10 @@ arguments: resfile: default: "res_outfile.out" mout: - default: null \ No newline at end of file + default: null + force_partitioning: + type: float + default: 0.5 + waterEntropy: + type: bool + default: False \ No newline at end of file From 88c0bc02f5879f5a6d57093a201611f52fae1598 Mon Sep 17 00:00:00 2001 From: harryswift01 Date: Mon, 17 Mar 2025 16:12:08 +0000 Subject: [PATCH 3/5] Refactor YAML Configuration for CodeEntropy: - Simplified the YAML input structure to make it more user-friendly and flexible. - The YAML configuration now supports multiple job runs using a dictionary format, allowing easier scaling for future additions like multiprocessing. - Changed the YAML format to store run configurations in a dictionary for better internal management. - If no configuration file is provided or if the config is empty, the logic defaults to a single run using the provided CLI arguments. - Ensured that necessary arguments (e.g., `top_traj_file`, `selection_string`) are validated, even when falling back to CLI arguments. - Added tests to ensure the new behavior works as expected, including cases where no YAML file is provided and CLI arguments are used instead. --- .coveragerc | 3 + CodeEntropy/main_mcc.py | 213 ++++++++------ config.yaml | 57 +--- tests/test_EntropyFunctions/test_main_mcc.py | 287 +++++++++++++++++++ 4 files changed, 436 insertions(+), 124 deletions(-) create mode 100644 .coveragerc create mode 100644 tests/test_EntropyFunctions/test_main_mcc.py diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..ab7ca80 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,3 @@ +[run] +omit = + .vscode-server/* \ No newline at end of file diff --git a/CodeEntropy/main_mcc.py b/CodeEntropy/main_mcc.py index f165bf3..86a7f43 100644 --- a/CodeEntropy/main_mcc.py +++ b/CodeEntropy/main_mcc.py @@ -1,5 +1,6 @@ import argparse import math +import os import MDAnalysis as mda @@ -13,82 +14,122 @@ # from datetime import datetime +arg_map = { + "top_traj_file": { + "type": str, + "nargs": "+", + "help": "Path to Structure/topology file followed by Trajectory file(s)", + "default": [], + }, + "selection_string": { + "type": str, + "help": "Selection string for CodeEntropy", + "default": "all", + }, + "start": { + "type": int, + "help": "Start analysing the trajectory from this frame index", + "default": 0, + }, + "end": { + "type": int, + "help": "Stop analysing the trajectory at this frame index", + "default": -1, + }, + "step": { + "type": int, + "help": "Interval between two consecutive frames to be read index", + "default": 1, + }, + "bin_width": { + "type": int, + "help": "Bin width in degrees for making the histogram", + "default": 30, + }, + "tempra": { + "type": float, + "help": "Temperature for entropy calculation (K)", + "default": 298.0, + }, + "verbose": { + "type": bool, + "help": "True/False flag for noisy or quiet output", + "default": False, + }, + "thread": {"type": int, "help": "How many multiprocess to use", "default": 1}, + "outfile": { + "type": str, + "help": "Name of the file where the output will be written", + "default": "outfile.out", + }, + "resfile": { + "type": str, + "help": "Name of the file where the residue entropy output will be written", + "default": "res_outfile.out", + }, + "mout": { + "type": str, + "help": "Name of the file where certain matrices will be written", + "default": None, + }, + "force_partitioning": {"type": float, "help": "Force partitioning", "default": 0.5}, + "waterEntropy": {"type": bool, "help": "Calculate water entropy", "default": False}, +} + def load_config(file_path): """Load YAML configuration file.""" + if not os.path.exists(file_path): + raise FileNotFoundError(f"Configuration file '{file_path}' not found.") + with open(file_path, "r") as file: - return yaml.safe_load(file)["arguments"]["CodeEntropy"] + config = yaml.safe_load(file) + + # If YAML content is empty, return an empty dictionary + if config is None: + config = {} + + return config def setup_argparse(): - """Set up argument parser with required arguments.""" + """Setup argument parsing dynamically based on arg_map.""" parser = argparse.ArgumentParser( - description="Override YAML defaults with CLI arguments" - ) - parser.add_argument( - "-f", - "--top_traj_file", - nargs="+", - help="Path to Structure/topology file followed by Trajectory file(s)", - ) - parser.add_argument( - "--selection_string", type=str, help="Selection string for CodeEntropy" - ) - parser.add_argument( - "--start", type=int, help="Start analysing the trajectory from this frame index" - ) - parser.add_argument( - "--end", type=int, help="Stop analysing the trajectory at this frame index" - ) - parser.add_argument( - "--step", - type=int, - help="Interval between two consecutive frames to be read index", + description="CodeEntropy: Entropy calculation with MCC method." ) - parser.add_argument( - "--bin_width", type=int, help="Bin width in degrees for making the histogram" - ) - parser.add_argument( - "--tempra", type=float, help="Temperature for entropy calculation (K)" - ) - parser.add_argument( - "--verbose", type=bool, help="True/False flag for noisy or quiet output" - ) - parser.add_argument("--thread", type=int, help="How many multiprocess to use") - parser.add_argument( - "--outfile", help="Name of the file where the output will be written" - ) - parser.add_argument( - "--resfile", - help="Name of the file where the residue entropy output will be written", - ) - parser.add_argument( - "--mout", help="Name of the file where certain matrices will be written" - ) - parser.add_argument( - "--force_partitioning", - ) - parser.add_argument("--waterEntropy") + + for arg, properties in arg_map.items(): + kwargs = {key: properties[key] for key in properties if key != "help"} + parser.add_argument(f"--{arg}", **kwargs, help=properties.get("help")) + return parser -def merge_configs(args, config): +def merge_configs(args, run_config): """Merge CLI arguments with YAML configuration.""" - for key, value in config.items(): - if getattr(args, key) is None: - setattr( - args, - key, - ( - value["default"] - if isinstance(value, dict) and "default" in value - else value - ), - ) - for key, value in vars(args).items(): - if value is not None: - config[key] = value - return config + if run_config is None: + run_config = {} + + if not isinstance(run_config, dict): + raise TypeError("run_config must be a dictionary or None.") + + # Step 1: Merge YAML configuration into args + for key, value in run_config.items(): + if getattr(args, key, None) is None: + setattr(args, key, value) + + # Step 2: Set default values for any missing arguments from `arg_map` + for key, params in arg_map.items(): + if getattr(args, key, None) is None: + setattr(args, key, params.get("default")) + + # Step 3: Override with CLI values if provided + for key in arg_map.keys(): + cli_value = getattr(args, key, None) + if cli_value is not None: + run_config[key] = cli_value + + return args def main(): @@ -96,35 +137,43 @@ def main(): Main function for calculating the entropy of a system using the multiscale cell correlation method. """ - try: config = load_config("config.yaml") - parser = setup_argparse() - args = parser.parse_args() - config = merge_configs(args, config) - # Check for required arguments - if not config.get("top_traj_file"): + if config is None: raise ValueError( - "The 'top_traj_file' argument is required but not provided." - ) - if not config.get("selection_string"): - raise ValueError( - "The 'selection_string' argument is required but not provided." + "No configuration file found, and no CLI arguments were provided." ) - except argparse.ArgumentError: - print("Command line arguments are ill-defined, please check the arguments") - raise + parser = setup_argparse() + args, unknown = parser.parse_known_args() + + # Process each run in the YAML configuration + for run_name, run_config in config.items(): + if isinstance(run_config, dict): + # Merging CLI arguments with YAML configuration + args = merge_configs(args, run_config) + + # Ensure necessary arguments are provided + if not getattr(args, "top_traj_file"): + raise ValueError( + "The 'top_traj_file' argument is required but not provided." + ) + if not getattr(args, "selection_string"): + raise ValueError( + "The 'selection_string' argument is required but not provided." + ) + + # REPLACE INPUTS + print(f"Printing all input for {run_name}") + for arg in vars(args): + print(f" {arg}: {getattr(args, arg) or ''}") + else: + print(f"Run configuration for {run_name} is not a dictionary.") except ValueError as e: print(e) raise - # REPLACE INPUTS - print("Printing all input") - for arg in vars(args): - print(" {}: {}".format(arg, getattr(args, arg) or "")) - # startTime = datetime.now() # Get topology and trajectory file names and make universe diff --git a/config.yaml b/config.yaml index 6263b1a..aee4538 100644 --- a/config.yaml +++ b/config.yaml @@ -1,44 +1,17 @@ --- -arguments: - CodeEntropy: - top_traj_file: - required: True - default: [] - selection_string: - type: str - default: "all" - start: - type: int - default: 0 - end: - type: int - default: -1 - step: - type: int - default: 1 - bin_width: - type: int - default: 30 - tempra: - type: float - default: 298.0 - verbose: - type: bool - default: False - thread: - type: int - default: 1 - outfile: - type: str - default: "outfile.out" - resfile: - default: "res_outfile.out" - mout: - default: null - force_partitioning: - type: float - default: 0.5 - waterEntropy: - type: bool - default: False \ No newline at end of file +run1: + top_traj_file: + selection_string: + start: + end: + step: + bin_width: + tempra: + verbose: + thread: + outfile: + resfile: + mout: + force_partitioning: + waterEntropy: diff --git a/tests/test_EntropyFunctions/test_main_mcc.py b/tests/test_EntropyFunctions/test_main_mcc.py new file mode 100644 index 0000000..fa6d023 --- /dev/null +++ b/tests/test_EntropyFunctions/test_main_mcc.py @@ -0,0 +1,287 @@ +import argparse +import unittest +from unittest.mock import MagicMock, mock_open, patch + +from CodeEntropy.main_mcc import ( + arg_map, + load_config, + main, + merge_configs, + setup_argparse, +) + + +class test_maincc(unittest.TestCase): + """ + Unit tests for the main functionality of CodeEntropy. + """ + + def setUp(self): + """ + Set up test environment. + """ + self.config_file = "config.yaml" + self.code_entropy = main + + def tearDown(self): + """ + Clean up after each test. + """ + return super().tearDown() + + def setup_file(self, mock_file): + """ + Mock the contents of a configuration file. + """ + mock_file.return_value = mock_open( + read_data="--- \n \nrun1:\n " + "top_traj_file: ['/path/to/tpr', '/path/to/trr']\n " + "selection_string: " + "'all'\n " + "start: 0\n " + "end: -1\n " + "step: 1\n " + "bin_width: 30\n " + "tempra: 298.0\n " + "verbose: False\n " + "thread: 1\n " + "outfile: 'outfile.out'\n " + "resfile: 'res_outfile.out'\n " + "mout: null\n " + "force_partitioning: 0.5\n " + "waterEntropy: False" + ).return_value + + @patch("builtins.open", new_callable=mock_open) + @patch("os.path.exists", return_value=True) + def test_load_config(self, mock_exists, mock_file): + """ + Test loading a valid configuration file. + """ + self.setup_file(mock_file) + + config = load_config(self.config_file) + + self.assertIn("run1", config) + self.assertEqual( + config["run1"]["top_traj_file"], ["/path/to/tpr", "/path/to/trr"] + ) + + @patch("builtins.open", side_effect=FileNotFoundError) + def test_load_config_file_not_found(self, mock_file): + """ + Test loading a configuration file that does not exist. + """ + with self.assertRaises(FileNotFoundError): + load_config(self.config_file) + + @patch("CodeEntropy.main_mcc.load_config", return_value=None) + def test_no_cli_no_yaml(self, mock_load_config): + """ + Test behavior when no CLI arguments and no YAML file are provided. + Should raise an exception or use defaults. + """ + + with self.assertRaises(ValueError) as context: + self.code_entropy() + + self.assertTrue( + "No configuration file found, and no CLI arguments were provided." + in str(context.exception) + ) + + def test_invalid_run_config_type(self): + """ + Test that passing an invalid type for run_config raises a TypeError. + """ + args = MagicMock() + invalid_configs = ["string", 123, 3.14, ["list"], {("tuple_key",): "value"}] + + for invalid in invalid_configs: + with self.assertRaises(TypeError): + merge_configs(args, invalid) + + @patch( + "argparse.ArgumentParser.parse_args", + return_value=MagicMock( + top_traj_file=["/path/to/tpr", "/path/to/trr"], + selection_string="all", + start=0, + end=-1, + step=1, + bin_width=30, + tempra=298.0, + verbose=False, + thread=1, + outfile="outfile.out", + resfile="res_outfile.out", + mout=None, + force_partitioning=0.5, + waterEntropy=False, + ), + ) + def test_setup_argparse(self, mock_args): + """ + Test parsing command-line arguments. + """ + parser = setup_argparse() + args = parser.parse_args() + self.assertEqual(args.top_traj_file, ["/path/to/tpr", "/path/to/trr"]) + self.assertEqual(args.selection_string, "all") + + def test_cli_overrides_defaults(self): + """ + Test if CLI parameters override default values. + """ + parser = setup_argparse() + args = parser.parse_args( + ["--top_traj_file", "/cli/path", "--selection_string", "cli_value"] + ) + self.assertEqual(args.top_traj_file, ["/cli/path"]) + self.assertEqual(args.selection_string, "cli_value") + + def test_yaml_overrides_defaults(self): + """ + Test if YAML parameters override default values. + """ + run_config = {"top_traj_file": ["/yaml/path"], "selection_string": "yaml_value"} + args = argparse.Namespace() + merged_args = merge_configs(args, run_config) + self.assertEqual(merged_args.top_traj_file, ["/yaml/path"]) + self.assertEqual(merged_args.selection_string, "yaml_value") + + def test_cli_overrides_yaml(self): + """ + Test if CLI parameters override YAML parameters correctly. + """ + parser = setup_argparse() + args = parser.parse_args( + ["--top_traj_file", "/cli/path", "--selection_string", "cli_value"] + ) + run_config = {"top_traj_file": ["/yaml/path"], "selection_string": "yaml_value"} + merged_args = merge_configs(args, run_config) + self.assertEqual(merged_args.top_traj_file, ["/cli/path"]) + self.assertEqual(merged_args.selection_string, "cli_value") + + def test_merge_configs(self): + """ + Test merging default arguments with a run configuration. + """ + args = MagicMock( + top_traj_file=None, + selection_string=None, + start=None, + end=None, + step=None, + bin_width=None, + tempra=None, + verbose=None, + thread=None, + outfile=None, + resfile=None, + mout=None, + force_partitioning=None, + waterEntropy=None, + ) + run_config = { + "top_traj_file": ["/path/to/tpr", "/path/to/trr"], + "selection_string": "all", + "start": 0, + "end": -1, + "step": 1, + "bin_width": 30, + "tempra": 298.0, + "verbose": False, + "thread": 1, + "outfile": "outfile.out", + "resfile": "res_outfile.out", + "mout": None, + "force_partitioning": 0.5, + "waterEntropy": False, + } + merged_args = merge_configs(args, run_config) + self.assertEqual(merged_args.top_traj_file, ["/path/to/tpr", "/path/to/trr"]) + self.assertEqual(merged_args.selection_string, "all") + + @patch("argparse.ArgumentParser.parse_args") + def test_default_values(self, mock_parse_args): + """ + Test if argument parser assigns default values correctly. + """ + default_args = {arg: params["default"] for arg, params in arg_map.items()} + mock_parse_args.return_value = MagicMock(**default_args) + parser = setup_argparse() + args = parser.parse_args() + for arg, params in arg_map.items(): + self.assertEqual(getattr(args, arg), params["default"]) + + @patch( + "argparse.ArgumentParser.parse_args", return_value=MagicMock(top_traj_file=None) + ) + def test_missing_required_arguments(self, mock_args): + """ + Test behavior when required arguments are missing. + """ + parser = setup_argparse() + args = parser.parse_args() + with self.assertRaises(ValueError): + if not args.top_traj_file: + raise ValueError( + "The 'top_traj_file' argument is required but not provided." + ) + + def test_invalid_argument_type(self): + """ + Test handling of invalid argument types. + """ + parser = setup_argparse() + with self.assertRaises(SystemExit): + parser.parse_args(["--start", "invalid"]) + + @patch( + "argparse.ArgumentParser.parse_args", return_value=MagicMock(start=-1, end=-10) + ) + def test_edge_case_argument_values(self, mock_args): + """ + Test parsing of edge case values. + """ + parser = setup_argparse() + args = parser.parse_args() + self.assertEqual(args.start, -1) + self.assertEqual(args.end, -10) + + @patch("builtins.open", new_callable=mock_open, read_data="--- \n") + @patch("os.path.exists", return_value=True) + def test_empty_yaml_config(self, mock_exists, mock_file): + """ + Test behavior when an empty YAML file is provided. + Should use defaults or raise an appropriate error. + """ + config = load_config(self.config_file) + + self.assertIsInstance(config, dict) + self.assertEqual(config, {}) + + @patch( + "builtins.open", + new_callable=mock_open, + read_data=b"--- \n top_traj_file: ['/path/to/tpr', '/path/to/trr'] \n", + ) + @patch("os.path.exists", return_value=True) + @patch("MDAnalysis.Universe") + @patch("gettext.translation", return_value=MagicMock()) + def test_run(self, mock_translation, mock_universe, mock_exists, mock_file): + """ + Test the execution of the main function with the necessary CLI argument. + """ + with patch( + "sys.argv", + ["CodeEntropy", "--top_traj_file", "/path/to/tpr", "/path/to/trr"], + ): + self.setup_file(mock_file) + mock_universe.return_value.trajectory = MagicMock() + main() + + +if __name__ == "__main__": + unittest.main() From 531b58a2aa969ce01f05ac789bd4245f1f8ceb18 Mon Sep 17 00:00:00 2001 From: harryswift01 Date: Mon, 17 Mar 2025 16:22:55 +0000 Subject: [PATCH 4/5] Removal of .coveragerc file and included PyYAML as a dependency in the pyproject.toml file --- .coveragerc | 3 --- pyproject.toml | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index ab7ca80..0000000 --- a/.coveragerc +++ /dev/null @@ -1,3 +0,0 @@ -[run] -omit = - .vscode-server/* \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 88ae2e6..76635b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ dependencies = [ "mdanalysis==2.8.0", "pandas==2.2.3", "psutil==5.9.5", + "PyYAML==5.4", ] [project.urls] From ad0af422da677135216c3c415a9516b2b4d75456 Mon Sep 17 00:00:00 2001 From: harryswift01 Date: Mon, 17 Mar 2025 16:26:32 +0000 Subject: [PATCH 5/5] Updated version of PyYAML --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 76635b9..696e1a7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ dependencies = [ "mdanalysis==2.8.0", "pandas==2.2.3", "psutil==5.9.5", - "PyYAML==5.4", + "PyYAML==6.0.2", ] [project.urls]