diff --git a/.docstr.yaml b/.docstr.yaml index b572cfbe..fb7f98cc 100644 --- a/.docstr.yaml +++ b/.docstr.yaml @@ -1,27 +1,11 @@ paths: - PQAnalysis -#badge: docs -#exclude: .*/test # regex +exclude: ./tests # regex verbose: 2 # int (0-3) -skip_magic: True +skip_magic: False skip_file_doc: True -skip_init: True +skip_init: False skip_class_def: False -skip_private: True +skip_private: False follow_links: True -#ignore_names_file: .*/test # regex -#fail_under: 90 -percentage_only: False -#ignore_patterns: # Dict with key/value pairs of file-pattern/node-pattern -# .*: method_to_ignore_in_all_files -# FileWhereWeWantToIgnoreAllSpecialMethods: "__.+__" -# SomeFile: -# - method_to_ignore1 -# - method_to_ignore2 -# - method_to_ignore3 -# a_very_important_view_file: -# - "^get$" -# - "^set$" -# - "^post$" -# detect_.*: -# - "get_val.*" \ No newline at end of file +percentage_only: False \ No newline at end of file diff --git a/.github/workflows/docstr.yml b/.github/workflows/docstr.yml deleted file mode 100644 index 885fe9b3..00000000 --- a/.github/workflows/docstr.yml +++ /dev/null @@ -1,102 +0,0 @@ -name: Docstring Coverage - -on: - push: - branches: [dev, main] - pull_request: - branches: ['*'] - workflow_dispatch: - -env: - RANGE: 95..100 - ENDPOINT: https://jsonbin.org/${{ github.repository_owner }}/${{ github.event.repository.name }} - TOKEN: ${{ secrets.JSONBIN_APIKEY }} - BRANCH_NAME: ${{ github.head_ref || github.ref_name}} - - -jobs: - check: - runs-on: ubuntu-latest - steps: - - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Setup Python - uses: actions/setup-python@v2 - with: - python-version: 3.x - - - name: Install docstr-coverage - run: | - pip install docstr-coverage - - - name: Get SHAs - run: | - if [[ ${{ github.event_name }} == 'push' ]]; then - echo "BASE=$(git rev-parse HEAD^)" >> $GITHUB_ENV - echo "HEAD=$(git rev-parse HEAD)" >> $GITHUB_ENV - - elif [[ ${{ github.event_name }} == 'pull_request' ]]; then - echo "BASE=${{ github.event.pull_request.base.sha }}" >> $GITHUB_ENV - echo "HEAD=${{ github.event.pull_request.head.sha }}" >> $GITHUB_ENV - - else - echo "Unexpected event trigger" - exit 1 - - fi - - - name: Get ${{ github.event.pull_request.base.sha }} coverage - run: | - git checkout $BASE - echo "BASE_COV=$(docstr-coverage PQAnalysis -p)" >> $GITHUB_ENV - - - name: Test ${{ github.event.pull_request.head.sha }} coverage - run: | - echo "$BASE coverage was: $BASE_COV%" - git checkout $HEAD - docstr-coverage --fail-under=$BASE_COV - - - name: Blame - run: | - git diff --name-only $(git merge-base $BASE $HEAD) | \ - xargs docstr-coverage --accept-empty - if: failure() - - - name: Get new coverage - run: echo "NEW_COV=$(printf "%.f" $(docstr-coverage -p))" >> $GITHUB_ENV - if: always() && github.event_name == 'push' - - - name: Set label color - run: | - if [[ $NEW_COV -ge $(echo {${{ env.RANGE }}} | awk '{print $NF;}') ]]; then - echo "COLOR=green" >> $GITHUB_ENV - - elif [[ $NEW_COV -lt $(echo {${{ env.RANGE }}} | awk '{print $1;}') ]]; then - echo "COLOR=red" >> $GITHUB_ENV - - else - echo "COLOR=orange" >> $GITHUB_ENV - - fi - if: always() && github.event_name == 'push' - - - name: Post results - run: | - echo "New coverage is: $NEW_COV%" - curl -X POST $ENDPOINT/badges/docstr-cov \ - -H "authorization: token $TOKEN" \ - -d "{ \"schemaVersion\": 1, \"label\": \"docstr-cov\", \ - \"message\": \"$NEW_COV%\", \"color\": \"$COLOR\" }" - if: always() && github.event_name == 'push' && ${{ env.BRANCH_NAME }} == 'main' - - - name: Set public endpoint - run: | - curl -X PUT $ENDPOINT/_perms -H "authorization: token $TOKEN" - if: always() && github.event_name == 'push' && ${{ env.BRANCH_NAME }} == 'main' - - - name: Show badge URL - run: echo "https://img.shields.io/endpoint?url=$ENDPOINT/badges/docstr-cov" - if: always() && github.event_name == 'push' && ${{ env.BRANCH_NAME }} == 'main' \ No newline at end of file diff --git a/PQAnalysis/cli/_argument_parser.py b/PQAnalysis/cli/_argument_parser.py index fc747db7..09d9fab8 100644 --- a/PQAnalysis/cli/_argument_parser.py +++ b/PQAnalysis/cli/_argument_parser.py @@ -13,8 +13,11 @@ from beartype.typing import Sequence +from rich_argparse import ArgumentDefaultsRichHelpFormatter import PQAnalysis.config as config # pylint: disable=consider-using-from-import # here needed to set the config attributes + +from PQAnalysis.utils.common import __header__ from PQAnalysis.traj import MDEngineFormat from PQAnalysis.io.formats import FileWritingMode from PQAnalysis._version import __version__ @@ -46,7 +49,7 @@ def __init__(self, *args, **kwargs): """ super().__init__( - formatter_class=argparse.ArgumentDefaultsHelpFormatter, + formatter_class=ArgumentDefaultsRichHelpFormatter, *args, **kwargs ) @@ -55,7 +58,7 @@ def __init__(self, *args, **kwargs): if 'prog' not in kwargs: kwargs['prog'] = self.prog.split(".")[0] super().__init__( - formatter_class=argparse.ArgumentDefaultsHelpFormatter, + formatter_class=ArgumentDefaultsRichHelpFormatter, *args, **kwargs ) diff --git a/PQAnalysis/cli/_cli_base.py b/PQAnalysis/cli/_cli_base.py new file mode 100644 index 00000000..3dff11b4 --- /dev/null +++ b/PQAnalysis/cli/_cli_base.py @@ -0,0 +1,42 @@ +""" +This module contains the abstract base class for all the CLI classes. +""" + +from abc import ABCMeta, abstractmethod + + +class CLIBase(metaclass=ABCMeta): + """ + Abstract base class for all the CLI classes. + """ + + @classmethod + @abstractmethod + def program_name(cls) -> str: + """ + Return the name of the program. + + Returns + ------- + str + The name of the program. + """ + + @classmethod + @abstractmethod + def add_arguments(cls, parser): + """ + Add the arguments to the parser. + + Parameters + ---------- + parser : _type_ + _description_ + """ + + @classmethod + @abstractmethod + def run(cls, args): + """ + Run the CLI. + """ diff --git a/PQAnalysis/cli/add_molecules.py b/PQAnalysis/cli/add_molecules.py index 65462890..e410ec95 100644 --- a/PQAnalysis/cli/add_molecules.py +++ b/PQAnalysis/cli/add_molecules.py @@ -10,7 +10,9 @@ from PQAnalysis.config import code_base_url from PQAnalysis.tools.add_molecule import add_molecule from PQAnalysis.io.formats import OutputFileFormat + from ._argument_parser import _ArgumentParser +from ._cli_base import CLIBase __outputdoc__ = """ @@ -27,181 +29,224 @@ __epilog__ += "file keys please visit " __epilog__ += f"{code_base_url}PQAnalysis.cli.add_molecules.html." __epilog__ += "\n" +__epilog__ += "\n" __doc__ += __outputdoc__ -def main(): +class AddMoleculesCLI(CLIBase): """ - Main function of the add_molecules command line tool, - which is basically just a wrapper for the add_molecules - function. For more information on the add_molecules - function please visit :py:func:`PQAnalysis.tools.add_molecule`. + Command Line Tool for Adding Molecules to Restart Files """ - parser = _ArgumentParser(description=__outputdoc__, epilog=__epilog__) + @classmethod + def program_name(cls) -> str: + """ + Returns the name of the program. + + Returns + ------- + str + The name of the program. + """ + return 'add_molecules' + + @classmethod + def add_arguments(cls, parser: _ArgumentParser) -> None: + """ + Adds the arguments to the parser. + + Parameters + ---------- + parser : _ArgumentParser + The parser to which the arguments should be added. + """ + + parser.add_argument( + 'restart_file', + type=str, + help='The restart file where the molecules should be added.' + ) + + parser.add_argument( + 'molecule_file', + type=str, + help=( + "The molecule file that contains the coordinates " + "of the molecule that should be added. Can be in " + "any format that is supported by the PQAnalysis library." + ) + ) + + parser.parse_output_file() + parser.parse_mode() + + parser.add_argument( + '--mol-file-type', + dest='mol_file_type', + type=OutputFileFormat, + default=OutputFileFormat.AUTO, + choices=OutputFileFormat.__members__.values(), + help=( + 'The file format of the molecule file. ' + 'If not specified, the file format will ' + 'be inferred from the file extension.' + ) + ) + + parser.add_argument( + "--rst-mol-desc-file", + dest='rst_mol_desc_file', + type=str, + help=( + "The moldescriptor file that is associated with the " + "restart file. If not specified, the moldescriptor " + "file will not be used." + ), + default=None + ) - parser.add_argument( - 'restart_file', - type=str, - help='The restart file where the molecules should be added.' - ) - - parser.add_argument( - 'molecule_file', - type=str, - help=( - "The molecule file that contains the coordinates " - "of the molecule that should be added. Can be in " - "any format that is supported by the PQAnalysis library." + parser.add_argument( + "--molecule-mol-desc-file", + dest='molecule_mol_desc_file', + type=str, + help=( + "The moldescriptor file that is associated with " + "the molecule file. If not specified, the moldescriptor " + "file will not be used. Can only be used if the " + "molecule file is a restart file type." + ), + default=None ) - ) - - parser.parse_output_file() - parser.parse_mode() - - parser.add_argument( - '--mol-file-type', - dest='mol_file_type', - type=OutputFileFormat, - default=OutputFileFormat.AUTO, - choices=OutputFileFormat.__members__.values(), - help=( - 'The file format of the molecule file. ' - 'If not specified, the file format will ' - 'be inferred from the file extension.' + + parser.add_argument( + "-n, --n-molecules", + dest='n_molecules', + type=int, + default=1, + help="The number of molecules that should be added to the restart file." ) - ) - - parser.add_argument( - "--rst-mol-desc-file", - dest='rst_mol_desc_file', - type=str, - help=( - "The moldescriptor file that is associated with the " - "restart file. If not specified, the moldescriptor " - "file will not be used." - ), - default=None - ) - - parser.add_argument( - "--molecule-mol-desc-file", - dest='molecule_mol_desc_file', - type=str, - help=( - "The moldescriptor file that is associated with " - "the molecule file. If not specified, the moldescriptor " - "file will not be used. Can only be used if the " - "molecule file is a restart file type." - ), - default=None - ) - - parser.add_argument( - "-n, --n-molecules", - dest='n_molecules', - type=int, - default=1, - help="The number of molecules that should be added to the restart file." - ) - - parser.add_argument( - "--max-iter", - dest='max_iter', - type=int, - default=100, - help=( - "The maximum number of iterations that should " - "be used to fit the molecule to the restart file." + + parser.add_argument( + "--max-iter", + dest='max_iter', + type=int, + default=100, + help=( + "The maximum number of iterations that should " + "be used to fit the molecule to the restart file." + ) ) - ) - - parser.add_argument( - "-c", "--cut", - dest='cut', - type=float, - default=1.0, - help=( - "The distance cutoff that should be used " - "to fit the molecule to the restart file in Angstrom." + + parser.add_argument( + "-c", "--cut", + dest='cut', + type=float, + default=1.0, + help=( + "The distance cutoff that should be used " + "to fit the molecule to the restart file in Angstrom." + ) ) - ) - - parser.add_argument( - "--max-disp", "--max-displacement", - dest='max_disp', - type=float, - default=0.1, - help=( - "The maximum displacement that should be applied " - "to the given molecule geometry relative to " - "its center of mass in percentage." + + parser.add_argument( + "--max-disp", "--max-displacement", + dest='max_disp', + type=float, + default=0.1, + help=( + "The maximum displacement that should be applied " + "to the given molecule geometry relative to " + "its center of mass in percentage." + ) ) - ) - - parser.add_argument( - "--rot", "--rotation-angle-step", - dest='rot', - type=int, - default=10, - help=( - "If the randomly placed molecule does not " - "fit into the restart file, the molecule " - "is rotated by the given angle step in degrees." + + parser.add_argument( + "--rot", "--rotation-angle-step", + dest='rot', + type=int, + default=10, + help=( + "If the randomly placed molecule does not " + "fit into the restart file, the molecule " + "is rotated by the given angle step in degrees." + ) ) - ) - - parser.add_argument( - "--topology-file", "--top-file", - dest='top_file', - type=str, - help=( - "The topology file that is associated with " - "the restart file. If not specified, " - "the topology file will not be used." - ), - default=None - ) - - parser.add_argument( - "--added-topology-file", "--added-top-file", - dest='added_top_file', - type=str, - help=( - "The topology file that is associated with " - "the molecule file. If not specified, the " - "topology file will not be used." - ), - default=None - ) - - parser.add_argument( - "--output-topology-file", "--output-top-file", - dest='output_top_file', - type=str, - help="The output topology file. If not specified, the output is printed to stdout.", - default=None - ) - - parser.parse_engine() + + parser.add_argument( + "--topology-file", "--top-file", + dest='top_file', + type=str, + help=( + "The topology file that is associated with " + "the restart file. If not specified, " + "the topology file will not be used." + ), + default=None + ) + + parser.add_argument( + "--added-topology-file", "--added-top-file", + dest='added_top_file', + type=str, + help=( + "The topology file that is associated with " + "the molecule file. If not specified, the " + "topology file will not be used." + ), + default=None + ) + + parser.add_argument( + "--output-topology-file", "--output-top-file", + dest='output_top_file', + type=str, + help="The output topology file. If not specified, the output is printed to stdout.", + default=None + ) + + parser.parse_engine() + + @classmethod + def run(cls, args): + """ + Runs the add_molecules function. + + Parameters + ---------- + args : argparse.Namespace + The parsed arguments. + """ + add_molecule( + args.restart_file, + args.molecule_file, + output_file=args.output, + molecule_file_type=args.mol_file_type, + restart_moldescriptor_file=args.rst_mol_desc_file, + molecule_moldescriptor_file=args.molecule_mol_desc_file, + number_of_additions=args.n_molecules, + max_iterations=args.max_iter, + distance_cutoff=args.cut, + max_displacement=args.max_disp, + rotation_angle_step=args.rot, + md_engine_format=args.engine, + mode=args.mode, + topology_file=args.top_file, + topology_file_to_add=args.added_top_file, + topology_file_output=args.output_top_file + ) + + +def main(): + """ + Main function of the add_molecules command line tool, + which is basically just a wrapper for the add_molecules + function. For more information on the add_molecules + function please visit :py:func:`PQAnalysis.tools.add_molecule`. + """ + parser = _ArgumentParser(description=__outputdoc__, epilog=__epilog__) + + AddMoleculesCLI.add_arguments(parser) args = parser.parse_args() - add_molecule( - args.restart_file, - args.molecule_file, - output_file=args.output, - molecule_file_type=args.mol_file_type, - restart_moldescriptor_file=args.rst_mol_desc_file, - molecule_moldescriptor_file=args.molecule_mol_desc_file, - number_of_additions=args.n_molecules, - max_iterations=args.max_iter, - distance_cutoff=args.cut, - max_displacement=args.max_disp, - rotation_angle_step=args.rot, - md_engine_format=args.engine, - mode=args.mode, - topology_file=args.top_file, - topology_file_to_add=args.added_top_file, - topology_file_output=args.output_top_file - ) + AddMoleculesCLI.run(args) diff --git a/PQAnalysis/cli/build_nep_traj.py b/PQAnalysis/cli/build_nep_traj.py index fffbd4c7..869cf95c 100644 --- a/PQAnalysis/cli/build_nep_traj.py +++ b/PQAnalysis/cli/build_nep_traj.py @@ -10,21 +10,154 @@ from PQAnalysis.io.nep.nep_writer import NEPWriter from PQAnalysis.config import code_base_url from ._argument_parser import _ArgumentParser +from ._cli_base import CLIBase __outputdoc__ = """ -This command line tool can be used to converts output of PQ of QMCFC simulations to training and test files for the Neuroevolution Potential (NEP) method. The output is written to a xyz file. +This command line tool can be used to convert output of PQ of QMCFC simulations to training and test files for the Neuroevolution Potential (NEP) method. The output is written to a xyz file. """ __epilog__ = "\n" __epilog__ += "For more information on required and optional input file keys please visit " __epilog__ += f"{code_base_url}PQAnalysis.cli.build_nep_traj.html." __epilog__ += "\n" +__epilog__ += "\n" __doc__ += __outputdoc__ +class BuildNEPTrajCLI(CLIBase): + """ + Command Line Tool for Building Neuroevolution Potential (NEP) training/test trajectories + """ + @classmethod + def program_name(cls) -> str: + """ + Returns the name of the program. + + Returns + ------- + str + The name of the program. + """ + return 'build_nep_traj' + + @classmethod + def add_arguments(cls, parser: _ArgumentParser) -> None: + """ + Adds the arguments to the parser. + + Parameters + ---------- + parser : _ArgumentParser + The parser to which the arguments should be added. + """ + parser.add_argument( + 'file_prefixes', + type=str, + nargs='+', + help='The file prefixes for all input files.' + ) + + parser.parse_output_file() + + parser.add_argument( + '--test-ratio', + type=float, + default=0.0, + help=( + "The ratio of testing frames to the total number of " + "frames, by default 0.0. If the test_ratio is 0.0 no " + "train and test files are created. If the test_ratio " + "is larger not equal to 0.0, the test_ratio is used " + "to determine the number of training and testing frames. " + "The final ratio will be as close to the test_ratio as " + "possible, but if it is not possible to have the exact " + "ratio, always the higher next higher ratio is chosen. " + "As output filenames the original filename is used with " + "the suffix _train or _test appended and the same " + "FileWritingMode as the original file is used." + ) + ) + + parser.add_argument( + '--total-ratios', + type=str, + default=None, + help=( + "The total_ratios keyword argument is used to " + "describe frame ratios including validation frames " + "in the format train_ratio:test_ratio:validation_ratio. " + "The validation_ratio is optional and if not given, " + "no validation frames are written. The total sum of " + "the integer values provided do not have to add up " + "to the total number of frames in the input trajectory " + "files. The ratios are used to determine the ratios of " + "the training, testing, and validation frames. The final" + "ratio will be as close to the given ratios as possible, " + "but if it is not possible to have the exact ratio, " + "always the next higher ratio is chosen. As output " + "filenames the original filename is used with the suffix " + "_train, _test, or _validation appended and the same " + "FileWritingMode as the original file is used. The " + "validation frames are written to a file with the " + "suffix _validation and a file with the suffix _validation.ref. " + "The _validation file contains only the coordinates and " + "box information to function as crude testing input and " + "the _validation.ref file contains all information " + "additionally provided in the original files. " + "Pay Attention: This keyword argument is mutually exclusive " + "with the test_ratio keyword argument. If both are given, " + "a ValueError is raised." + ) + ) + + parser.add_argument( + '--use-forces', + action='store_true', + default=False, + help='Whether to include forces in the output file.' + ) + + parser.add_argument( + '--use-virial', + action='store_true', + default=False, + help='Whether to include the virial in the output file.' + ) + + parser.add_argument( + '--use-stress', + action='store_true', + default=False, + help='Whether to include the stress tensor in the output file.' + ) + + parser.parse_mode() + + @classmethod + def run(cls, args): + """ + Runs the build_nep_traj function with the given arguments. + + Parameters + ---------- + args : argparse.Namespace + The parsed arguments. + """ + writer = NEPWriter(filename=args.output, mode=args.mode) + + writer.write_from_files( + file_prefixes=args.file_prefixes, + test_ratio=args.test_ratio, + total_ratios=args.total_ratios, + use_forces=args.use_forces, + use_virial=args.use_virial, + use_stress=args.use_stress + ) + + def main(): """ Main function of the build_nep_traj command line tool, which is basically @@ -33,98 +166,8 @@ def main(): """ parser = _ArgumentParser(description=__outputdoc__, epilog=__epilog__) - parser.add_argument( - 'file_prefixes', - type=str, - nargs='+', - help='The file prefixes for all input files.' - ) - - parser.parse_output_file() - - parser.add_argument( - '--test-ratio', - type=float, - default=0.0, - help=( - "The ratio of testing frames to the total number of " - "frames, by default 0.0. If the test_ratio is 0.0 no " - "train and test files are created. If the test_ratio " - "is larger not equal to 0.0, the test_ratio is used " - "to determine the number of training and testing frames. " - "The final ratio will be as close to the test_ratio as " - "possible, but if it is not possible to have the exact " - "ratio, always the higher next higher ratio is chosen. " - "As output filenames the original filename is used with " - "the suffix _train or _test appended and the same " - "FileWritingMode as the original file is used." - ) - ) - - parser.add_argument( - '--total-ratios', - type=str, - default=None, - help=( - "The total_ratios keyword argument is used to " - "describe frame ratios including validation frames " - "in the format train_ratio:test_ratio:validation_ratio. " - "The validation_ratio is optional and if not given, " - "no validation frames are written. The total sum of " - "the integer values provided do not have to add up " - "to the total number of frames in the input trajectory " - "files. The ratios are used to determine the ratios of " - "the training, testing, and validation frames. The final" - "ratio will be as close to the given ratios as possible, " - "but if it is not possible to have the exact ratio, " - "always the next higher ratio is chosen. As output " - "filenames the original filename is used with the suffix " - "_train, _test, or _validation appended and the same " - "FileWritingMode as the original file is used. The " - "validation frames are written to a file with the " - "suffix _validation and a file with the suffix _validation.ref. " - "The _validation file contains only the coordinates and " - "box information to function as crude testing input and " - "the _validation.ref file contains all information " - "additionally provided in the original files. " - "Pay Attention: This keyword argument is mutually exclusive " - "with the test_ratio keyword argument. If both are given, " - "a ValueError is raised." - ) - ) - - parser.add_argument( - '--use-forces', - action='store_true', - default=False, - help='Whether to include forces in the output file.' - ) - - parser.add_argument( - '--use-virial', - action='store_true', - default=False, - help='Whether to include the virial in the output file.' - ) - - parser.add_argument( - '--use-stress', - action='store_true', - default=False, - help='Whether to include the stress tensor in the output file.' - ) - - parser.parse_mode() + BuildNEPTrajCLI.add_arguments(parser) args = parser.parse_args() - writer = NEPWriter(filename=args.output, mode=args.mode) - - writer.write_from_files( - file_prefixes=args.file_prefixes, - test_ratio=args.test_ratio, - total_ratios=args.total_ratios, - use_forces=args.use_forces, - use_virial=args.use_virial, - use_stress=args.use_stress - ) + BuildNEPTrajCLI.run(args) diff --git a/PQAnalysis/cli/continue_input.py b/PQAnalysis/cli/continue_input.py index 9ec17570..cec81596 100644 --- a/PQAnalysis/cli/continue_input.py +++ b/PQAnalysis/cli/continue_input.py @@ -8,6 +8,8 @@ from PQAnalysis.io import InputFileFormat, continue_input_file from PQAnalysis.config import code_base_url from ._argument_parser import _ArgumentParser +from ._cli_base import CLIBase + __outputdoc__ = """ @@ -23,6 +25,68 @@ __epilog__ += "For more information on required and optional input file keys please visit " __epilog__ += f"{code_base_url}PQAnalysis.cli.continue_input.html." __epilog__ += "\n" +__epilog__ += "\n" + + +class ContinueInputCLI(CLIBase): + """ + Command Line Tool for Extending PQ MD Simulation Input Files + """ + @classmethod + def program_name(cls) -> str: + """ + Returns the name of the program. + + Returns + ------- + str + The name of the program. + """ + return 'continue_input' + + @classmethod + def add_arguments(cls, parser: _ArgumentParser) -> None: + """ + Adds the arguments to the parser. + + Parameters + ---------- + parser : _ArgumentParser + The parser to which the arguments should be added. + """ + parser.parse_input_file() + + parser.add_argument( + '-n', '--number', + type=int, + default=1, + help='The number of times the input file should be continued.' + ) + + parser.add_argument( + '--input-format', + type=str, + default='PQ', + help='The format of the input file. Default is PQ.' + ) + + @classmethod + def run(cls, args): + """ + Runs the command line tool. + + Parameters + ---------- + args : argparse.Namespace + The parsed arguments. + """ + input_format = InputFileFormat(args.input_format) + + continue_input_file( + args.input_file, + args.number, + input_format + ) def main(): @@ -32,24 +96,9 @@ def main(): the continue_input_file function please visit :py:func:`PQAnalysis.io.api.continue_input_file`. """ parser = _ArgumentParser(description=__outputdoc__, epilog=__epilog__) - parser.parse_input_file() - - parser.add_argument( - '-n', '--number', - type=int, - default=1, - help='The number of times the input file should be continued.' - ) - - parser.add_argument( - '--input-format', - type=str, - default='PQ', - help='The format of the input file. Default is PQ.' - ) - args = parser.parse_args() + ContinueInputCLI.add_arguments(parser) - input_format = InputFileFormat(args.input_format) + args = parser.parse_args() - continue_input_file(args.input_file, args.number, input_format) + ContinueInputCLI.run(args) diff --git a/PQAnalysis/cli/gen2xyz.py b/PQAnalysis/cli/gen2xyz.py index 258f3f61..329e02aa 100644 --- a/PQAnalysis/cli/gen2xyz.py +++ b/PQAnalysis/cli/gen2xyz.py @@ -10,6 +10,7 @@ from PQAnalysis.io import gen2xyz from PQAnalysis.config import code_base_url from ._argument_parser import _ArgumentParser +from ._cli_base import CLIBase __outputdoc__ = """ @@ -24,10 +25,73 @@ __epilog__ += "For more information on required and optional input file keys please visit " __epilog__ += f"{code_base_url}PQAnalysis.cli.gen2xyz.html." __epilog__ += "\n" +__epilog__ += "\n" __doc__ += __outputdoc__ +class GEN2XYZCLI(CLIBase): + """ + Command Line Tool for Converting GEN Files to XYZ Files + """ + @classmethod + def program_name(cls) -> str: + """ + Returns the name of the program. + + Returns + ------- + str + The name of the program. + """ + return 'gen2xyz' + + @classmethod + def add_arguments(cls, parser: _ArgumentParser) -> None: + """ + Adds the arguments to the parser. + + Parameters + ---------- + parser : _ArgumentParser + The parser to which the arguments should be added. + """ + parser.add_argument( + 'gen_file', + type=str, + help='The gen file to be converted.' + ) + + parser.parse_output_file() + + parser.add_argument( + '--nobox', + action='store_true', + help='Do not print the box.' + ) + + parser.parse_engine() + parser.parse_mode() + + @classmethod + def run(cls, args): + """ + Runs the command line tool. + + Parameters + ---------- + args : _ArgumentParser + The arguments that were parsed by the parser. + """ + gen2xyz( + gen_file=args.gen_file, + output=args.output, + print_box=not args.nobox, + md_format=args.engine, + mode=args.mode, + ) + + def main(): """ Main function of the gen2xyz command line tool, which is basically just @@ -36,23 +100,8 @@ def main(): """ parser = _ArgumentParser(description=__outputdoc__, epilog=__epilog__) - parser.add_argument('gen_file', type=str, - help='The gen file to be converted.') - - parser.parse_output_file() - - parser.add_argument('--nobox', action='store_true', - help='Do not print the box.') - - parser.parse_engine() - parser.parse_mode() + GEN2XYZCLI.add_arguments(parser) args = parser.parse_args() - gen2xyz( - gen_file=args.gen_file, - output=args.output, - print_box=not args.nobox, - md_format=args.engine, - mode=args.mode, - ) + GEN2XYZCLI.run(args) diff --git a/PQAnalysis/cli/main.py b/PQAnalysis/cli/main.py new file mode 100644 index 00000000..a76d5603 --- /dev/null +++ b/PQAnalysis/cli/main.py @@ -0,0 +1,61 @@ +""" +A command line interface for the PQAnalysis package. +""" + +from PQAnalysis.config import code_base_url + +from .xyz2gen import XYZ2GENCLI +from .traj2qmcfc import Traj2QMCFCCLI +from .traj2box import Traj2BoxCLI +from .rst2xyz import Rst2XYZCLI +from .rdf import RDFCLI +from .gen2xyz import GEN2XYZCLI +from .continue_input import ContinueInputCLI +from .add_molecules import AddMoleculesCLI +from .build_nep_traj import BuildNEPTrajCLI +from ._argument_parser import _ArgumentParser + +__outputdoc__ = """ + +This is the command line interface for the PQAnalysis package. +""" + +__epilog__ = "\n" +__epilog__ += "For more information on required and optional input file keys please visit " +__epilog__ += f"{code_base_url}PQAnalysis.cli.html." +__epilog__ += "\n" +__epilog__ += "\n" + + +def main(): + """ + The main function of the PQAnalysis command line interface. + """ + parser = _ArgumentParser(description=__outputdoc__, epilog=__epilog__) + + subparsers = parser.add_subparsers( + dest='cli_command', + ) + + sub_parser_dict = { + AddMoleculesCLI.program_name(): AddMoleculesCLI, + BuildNEPTrajCLI.program_name(): BuildNEPTrajCLI, + ContinueInputCLI.program_name(): ContinueInputCLI, + GEN2XYZCLI.program_name(): GEN2XYZCLI, + RDFCLI.program_name(): RDFCLI, + Rst2XYZCLI.program_name(): Rst2XYZCLI, + Traj2BoxCLI.program_name(): Traj2BoxCLI, + Traj2QMCFCCLI.program_name(): Traj2QMCFCCLI, + XYZ2GENCLI.program_name(): XYZ2GENCLI, + } + + for key, value in sub_parser_dict.items(): + sub_parser = subparsers.add_parser(key, help=value.__doc__) + value.add_arguments(sub_parser) + + args = parser.parse_args() + + if args.cli_command in sub_parser_dict: + sub_parser_dict[args.cli_command].run(args) + else: + parser.print_help() diff --git a/PQAnalysis/cli/rdf.py b/PQAnalysis/cli/rdf.py index db00aa9f..82c268af 100644 --- a/PQAnalysis/cli/rdf.py +++ b/PQAnalysis/cli/rdf.py @@ -10,6 +10,7 @@ from PQAnalysis.analysis.rdf.rdf_input_file_reader import input_keys_documentation from PQAnalysis.config import code_base_url from ._argument_parser import _ArgumentParser +from ._cli_base import CLIBase __outputdoc__ = """ @@ -24,6 +25,7 @@ __epilog__ += "For more information on required and optional input file keys please visit " __epilog__ += f"{code_base_url}PQAnalysis.cli.rdf.html." __epilog__ += "\n" +__epilog__ += "\n" __doc__ += __outputdoc__ __doc__ += "For more information on the general the " @@ -34,6 +36,48 @@ __doc__ += input_keys_documentation +class RDFCLI(CLIBase): + """ + Command Line Tool for RDF Analysis + """ + @classmethod + def program_name(cls) -> str: + """ + Returns the name of the program. + + Returns + ------- + str + The name of the program. + """ + return 'rdf' + + @classmethod + def add_arguments(cls, parser: _ArgumentParser) -> None: + """ + Adds the arguments to the parser. + + Parameters + ---------- + parser : _ArgumentParser + The parser to which the arguments should be added. + """ + parser.parse_input_file() + parser.parse_engine() + + @classmethod + def run(cls, args): + """ + Runs the command line tool. + + Parameters + ---------- + args : argparse.Namespace + The arguments parsed by the parser. + """ + rdf(args.input_file, args.engine) + + def main(): """ The main function of the RDF analysis command line tool, @@ -42,9 +86,9 @@ def main(): visit :py:func:`PQAnalysis.analysis.rdf.api.rdf`. """ parser = _ArgumentParser(description=__outputdoc__, epilog=__epilog__) - parser.parse_engine() - parser.parse_input_file() + + RDFCLI.add_arguments(parser) args = parser.parse_args() - rdf(args.input_file, args.engine) + RDFCLI.run(args) diff --git a/PQAnalysis/cli/rst2xyz.py b/PQAnalysis/cli/rst2xyz.py index 862d5a8b..b77fb132 100644 --- a/PQAnalysis/cli/rst2xyz.py +++ b/PQAnalysis/cli/rst2xyz.py @@ -11,6 +11,7 @@ from PQAnalysis.io import rst2xyz from PQAnalysis.config import code_base_url from ._argument_parser import _ArgumentParser +from ._cli_base import CLIBase __outputdoc__ = """ @@ -25,10 +26,73 @@ __epilog__ += "For more information on required and optional input file keys please visit " __epilog__ += f"{code_base_url}PQAnalysis.cli.rst2xyz.html." __epilog__ += "\n" +__epilog__ += "\n" __doc__ += __outputdoc__ +class Rst2XYZCLI(CLIBase): + """ + Command Line Tool for Converting Restart Files to XYZ Files + """ + @classmethod + def program_name(cls) -> str: + """ + Returns the name of the program. + + Returns + ------- + str + The name of the program. + """ + return 'rst2xyz' + + @classmethod + def add_arguments(cls, parser: _ArgumentParser) -> None: + """ + Adds the arguments to the parser. + + Parameters + ---------- + parser : _ArgumentParser + The parser to which the arguments should be added. + """ + parser.parse_output_file() + + parser.add_argument( + 'restart_file', + type=str, + help='The restart file to be converted.' + ) + + parser.add_argument( + '--nobox', + action='store_true', + help='Do not print the box.' + ) + + parser.parse_engine() + parser.parse_mode() + + @classmethod + def run(cls, args): + """ + Runs the command line tool. + + Parameters + ---------- + args : argparse.Namespace + The arguments parsed by the parser. + """ + rst2xyz( + restart_file=args.restart_file, + output=args.output, + print_box=not args.nobox, + md_format=args.engine, + mode=args.mode, + ) + + def main(): """ Main function of the rst2xyz command line tool, which is basically just a wrapper @@ -37,29 +101,8 @@ def main(): """ parser = _ArgumentParser(description=__outputdoc__, epilog=__epilog__) - parser.parse_output_file() - - parser.add_argument( - 'restart_file', - type=str, - help='The restart file to be converted.' - ) - - parser.add_argument( - '--nobox', - action='store_true', - help='Do not print the box.' - ) - - parser.parse_engine() - parser.parse_mode() + Rst2XYZCLI.add_arguments(parser) args = parser.parse_args() - rst2xyz( - restart_file=args.restart_file, - output=args.output, - print_box=not args.nobox, - md_format=args.engine, - mode=args.mode, - ) + Rst2XYZCLI.run(args) diff --git a/PQAnalysis/cli/traj2box.py b/PQAnalysis/cli/traj2box.py index b8b4e4ab..7b423673 100644 --- a/PQAnalysis/cli/traj2box.py +++ b/PQAnalysis/cli/traj2box.py @@ -9,6 +9,7 @@ from PQAnalysis.config import code_base_url from PQAnalysis.io import traj2box from ._argument_parser import _ArgumentParser +from ._cli_base import CLIBase __outputdoc__ = """ @@ -28,10 +29,67 @@ __epilog__ += "For more information on the VMD file format please visit " __epilog__ += f"{code_base_url}PQAnalysis.io.formats.html#PQAnalysis.io.formats.VMDFileFormat." __epilog__ += "\n" +__epilog__ += "\n" __doc__ += __outputdoc__ +class Traj2BoxCLI(CLIBase): + """ + Command Line Tool for Converting Trajectory Files to Box Files + """ + @classmethod + def program_name(cls) -> str: + """ + Returns the name of the program. + + Returns + ------- + str + The name of the program. + """ + return 'traj2box' + + @classmethod + def add_arguments(cls, parser: _ArgumentParser) -> None: + """ + Adds the arguments to the parser. + + Parameters + ---------- + parser : _ArgumentParser + The parser to which the arguments should be added. + """ + parser.parse_output_file() + + parser.add_argument( + 'trajectory_file', + type=str, + nargs='+', + help='The trajectory file(s) to be converted.' + ) + + parser.add_argument( + '--vmd', + action='store_true', + help='Output in VMD format.' + ) + + parser.parse_mode() + + @classmethod + def run(cls, args): + """ + Runs the command line tool. + + Parameters + ---------- + args : argparse.Namespace + The arguments parsed by the parser. + """ + traj2box(args.trajectory_file, args.vmd, args.output, args.mode) + + def main(): """ Main function of the traj2box command line tool, which is basically just a @@ -40,21 +98,8 @@ def main(): """ parser = _ArgumentParser(description=__outputdoc__, epilog=__epilog__) - parser.parse_output_file() - - parser.add_argument( - 'trajectory_file', - type=str, - nargs='+', - help='The trajectory file(s) to be converted.' - ) - - parser.add_argument( - '--vmd', - action='store_true', - help='Output in VMD format.' - ) + Traj2BoxCLI.add_arguments(parser) args = parser.parse_args() - traj2box(args.trajectory_file, args.vmd, args.output) + Traj2BoxCLI.run(args) diff --git a/PQAnalysis/cli/traj2qmcfc.py b/PQAnalysis/cli/traj2qmcfc.py index 0e4004e2..8d2764ea 100644 --- a/PQAnalysis/cli/traj2qmcfc.py +++ b/PQAnalysis/cli/traj2qmcfc.py @@ -9,6 +9,7 @@ from PQAnalysis.config import code_base_url from PQAnalysis.io import traj2qmcfc from ._argument_parser import _ArgumentParser +from ._cli_base import CLIBase __outputdoc__ = """ @@ -24,10 +25,60 @@ __epilog__ += "For more information on required and optional input file keys please visit " __epilog__ += f"{code_base_url}PQAnalysis.cli.traj2qmcfc.html." __epilog__ += "\n" +__epilog__ += "\n" __doc__ += __outputdoc__ +class Traj2QMCFCCLI(CLIBase): + """ + Command Line Tool for Converting PQ to QMCFC Trajectory Files + """ + @classmethod + def program_name(cls) -> str: + """ + Returns the name of the program. + + Returns + ------- + str + The name of the program. + """ + return 'traj2qmcfc' + + @classmethod + def add_arguments(cls, parser: _ArgumentParser) -> None: + """ + Adds the arguments to the parser. + + Parameters + ---------- + parser : _ArgumentParser + The parser to which the arguments should be added. + """ + parser.parse_output_file() + + parser.add_argument( + 'trajectory_file', + type=str, + help='The trajectory file to be converted.' + ) + + parser.parse_mode() + + @classmethod + def run(cls, args) -> None: + """ + Runs the command line tool. + + Parameters + ---------- + args : _Namespace + The arguments from the command line. + """ + traj2qmcfc(args.trajectory_file, args.output) + + def main(): """ Main function of the traj2qmcfc command line tool, which is basically just a @@ -36,9 +87,8 @@ def main(): """ parser = _ArgumentParser(description=__outputdoc__, epilog=__epilog__) - parser.parse_trajectory_file() - parser.parse_output_file() + Traj2QMCFCCLI.add_arguments(parser) args = parser.parse_args() - traj2qmcfc(args.trajectory_file, args.output) + Traj2QMCFCCLI.run(args) diff --git a/PQAnalysis/cli/xyz2gen.py b/PQAnalysis/cli/xyz2gen.py index ca854809..770336a0 100644 --- a/PQAnalysis/cli/xyz2gen.py +++ b/PQAnalysis/cli/xyz2gen.py @@ -10,6 +10,7 @@ from PQAnalysis.config import code_base_url from PQAnalysis.io import xyz2gen from ._argument_parser import _ArgumentParser +from ._cli_base import CLIBase __outputdoc__ = """ @@ -21,10 +22,77 @@ __epilog__ += "For more information on required and optional input file keys please visit " __epilog__ += f"{code_base_url}PQAnalysis.cli.xyz2gen.html." __epilog__ += "\n" +__epilog__ += "\n" __doc__ += __outputdoc__ +class XYZ2GENCLI(CLIBase): + """ + Command Line Tool for Converting XYZ Files to GEN Files + """ + @classmethod + def program_name(cls) -> str: + """ + Returns the name of the program. + + Returns + ------- + str + The name of the program. + """ + return 'xyz2gen' + + @classmethod + def add_arguments(cls, parser: _ArgumentParser) -> None: + """ + Adds the arguments to the parser. + + Parameters + ---------- + parser : _ArgumentParser + The parser to which the arguments should be added. + """ + parser.add_argument( + 'xyz_file', + type=str, + help='The gen file to be converted.' + ) + + parser.parse_output_file() + + parser.add_argument( + 'periodic', + choices=[True, False, None], + default=None, + help=( + 'If True, the box is printed. If False, the box is not printed. ' + 'If None, the box is printed if it is present in the xyz file.' + ) + ) + + parser.parse_engine() + parser.parse_mode() + + @classmethod + def run(cls, args): + """ + Runs the command line tool. + + Parameters + ---------- + args : _Namespace + The arguments from the command line. + """ + xyz2gen( + xyz_file=args.xyz_file, + output=args.output, + periodic=args.periodic, + md_format=args.engine, + mode=args.mode, + ) + + def main(): """ Main function of the xyz2gen command line tool, which is basically just @@ -33,33 +101,8 @@ def main(): """ parser = _ArgumentParser(description=__outputdoc__, epilog=__epilog__) - parser.add_argument( - 'xyz_file', - type=str, - help='The gen file to be converted.' - ) - - parser.parse_output_file() - - parser.add_argument( - 'periodic', - choices=[True, False, None], - default=None, - help=( - 'If True, the box is printed. If False, the box is not printed. ' - 'If None, the box is printed if it is present in the xyz file.' - ) - ) - - parser.parse_engine() - parser.parse_mode() + XYZ2GENCLI.add_arguments(parser) args = parser.parse_args() - xyz2gen( - xyz_file=args.xyz_file, - output=args.output, - periodic=args.periodic, - md_format=args.engine, - mode=args.mode, - ) + XYZ2GENCLI.run(args) diff --git a/pyproject.toml b/pyproject.toml index 4fe6c7d2..c937f0df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,7 @@ dependencies = [ "decorator", "argcomplete", "unum", + "rich-argparse", ] [project.optional-dependencies] @@ -56,6 +57,7 @@ docs = [ version_file = "PQAnalysis/_version.py" [project.scripts] +pqanalysis = "PQAnalysis.cli.main:main" traj2box = "PQAnalysis.cli.traj2box:main" traj2qmcfc = "PQAnalysis.cli.traj2qmcfc:main" rst2xyz = "PQAnalysis.cli.rst2xyz:main" diff --git a/tests/cli/test_traj2box.py b/tests/cli/test_traj2box.py index 79dbd792..987484ab 100644 --- a/tests/cli/test_traj2box.py +++ b/tests/cli/test_traj2box.py @@ -34,6 +34,7 @@ def test_main(test_with_data_dir): output="test_box.dat", log_file=None, logging_level="INFO", + mode='w' ) ) def main_box_file(mock_args): @@ -49,6 +50,7 @@ def main_box_file(mock_args): output="test_box.vmd.xyz", log_file=None, logging_level="INFO", + mode='w' ) ) def main_vmd(mock_args):