diff --git a/aiida_cp2k/cli/__init__.py b/aiida_cp2k/cli/__init__.py new file mode 100644 index 00000000..f06431b9 --- /dev/null +++ b/aiida_cp2k/cli/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +# pylint: disable=wrong-import-position,wildcard-import +"""Base command line interface module to wire up subcommands and loading the profile.""" + +import click +import click_completion + +from aiida.cmdline.params import options, types + +# Activate the completion of parameter types provided by the click_completion package +click_completion.init() + + +@click.group('aiida-cp2k', context_settings={'help_option_names': ['-h', '--help']}) +@options.PROFILE(type=types.ProfileParamType(load_profile=True)) +def cmd_root(profile): # pylint: disable=unused-argument + """CLI for the `aiida-cp2k` plugin.""" + + +from .data import cmd_structure diff --git a/aiida_cp2k/cli/data/__init__.py b/aiida_cp2k/cli/data/__init__.py new file mode 100644 index 00000000..c7f82c92 --- /dev/null +++ b/aiida_cp2k/cli/data/__init__.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- + +# pylint: disable=cyclic-import,unused-import,wrong-import-position +"""Module with CLI commands for various data types.""" + +from .. import cmd_root + + +@cmd_root.group('data') +def cmd_data(): + """Commands to create and inspect data nodes.""" + + +# Import the sub commands to register them with the CLI +from .structure import cmd_structure diff --git a/aiida_cp2k/cli/data/structure.py b/aiida_cp2k/cli/data/structure.py new file mode 100644 index 00000000..fc329aa4 --- /dev/null +++ b/aiida_cp2k/cli/data/structure.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +"""Command line utilities to create and inspect `StructureData` nodes from CP2K input files.""" + +from fractions import Fraction + +import click +import numpy as np + +from aiida.cmdline.params import options +from aiida.cmdline.utils import decorators, echo + +from . import cmd_data + + +@cmd_data.group('structure') +def cmd_structure(): + """Commands to create and inspect `StructureData` nodes from CP2K input.""" + + +@cmd_structure.command('import') +@click.argument('filename', type=click.File('r')) +@options.DRY_RUN() +@decorators.with_dbenv() +def cmd_import(filename, dry_run): + """Import a `StructureData` from a CP2K input file.""" + + # pylint: disable=import-outside-toplevel,invalid-name,too-many-locals,too-many-statements,too-many-branches + + from cp2k_input_tools.parser import CP2KInputParser + from aiida.orm.nodes.data.structure import StructureData, Kind, Site, symop_fract_from_ortho + from ase.geometry.cell import cell_to_cellpar, cellpar_to_cell + + parser = CP2KInputParser() + tree = parser.parse(filename) + + for force_eval in tree['+force_eval']: + try: + cell = force_eval['+subsys']['+cell'] + kinds = force_eval['+subsys']['+kind'] + coord = force_eval['+subsys']['+coord'] + break # for now grab the first &COORD found + except KeyError: + continue + else: + echo.echo_error('no STRUCTURE, CELL, or KIND found in the given input file') + + structure = StructureData() + + # CP2K can get its cell information in two ways: + # - A, B, C: cell vectors + # - ABC: scaling of cell vectors, ALPHA_BETA_GAMMA: angles between the cell vectors (optional) + + if 'a' in cell: + unit_cell = np.array([cell['a'], cell['b'], cell['c']]) # unit vectors given + cellpar = cell_to_cellpar(unit_cell) + elif 'abc' in cell: + cellpar = cell['abc'] + cell.get('alpha_beta_gamma', [90., 90., 90.]) + unit_cell = cellpar_to_cell(cellpar) + else: + echo.echo_error('incomplete &CELL section') + + structure.set_attribute('cell', unit_cell) + + if coord.get('scaled', False): + tmat = symop_fract_from_ortho(cellpar) + else: + tmat = np.eye(3) + + for kind in kinds: + name = kind['_'] + element = kind.get('element', name) # TODO: support getting the element from e.g. 'Fe1' + structure.append_kind(Kind(name=name, symbols=element)) + + if coord.get('units', 'angstrom').lower() != 'angstrom': + echo.echo_error('unit conversion for coordinations is not (yet) supported') + + for coordline in coord['*']: + # coordinates are a series of strings according to the CP2K schema + fields = coordline.split() + + name = fields[0] + # positions can be fractions AND they may be scaled + position = tmat @ np.array([float(Fraction(f)) for f in fields[1:4]]) + + structure.append_site(Site(kind_name=name, position=position)) + + formula = structure.get_formula() + + if dry_run: + echo.echo_success('parsed structure with formula {}'.format(formula)) + else: + structure.store() + echo.echo_success('parsed and stored StructureData<{}> with formula {}'.format(structure.pk, formula)) diff --git a/setup.json b/setup.json index c602c3b2..e784fc9a 100644 --- a/setup.json +++ b/setup.json @@ -25,9 +25,17 @@ ], "aiida.workflows": [ "cp2k.base = aiida_cp2k.workchains:Cp2kBaseWorkChain" + ], + "console_scripts": [ + "aiida-cp2k = aiida_cp2k.cli:cmd_root" ] }, "extras_require": { + "cli": [ + "click", + "click-completion", + "cp2k-input-tools" + ], "test": [ "pgtest==1.2.0", "pytest>=4.4,<5.0.0",