Source code for IMDgroup.pymatgen.cli.imdg_create

# MIT License
#
# Copyright (c) 2024-2025 Inverse Materials Design Group
#
# Author: Ihor Radchenko <yantar92@posteo.net>
#
# This file is a part of IMDgroup-pymatgen package
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.


"""imdg sub-command to create new VASP inputs from scratch."""
import os
import re
import logging
import pymatgen.core as pmg
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer
from IMDgroup.pymatgen.io.vasp.sets import IMDStandardVaspInputSet

logger = logging.getLogger(__name__)


[docs] def add_args(parser): """Register subcommand arguments. Args: parser: Sub-parser from argparse. """ parser.add_argument( "what", help="""What to create (atom name+cell, mp-XXX, or file) - Atom name+cell is atom name and cell size (in ans). Example: Li 20x20x20 - mp-XXX is material project id of the structure to be fetched Example: mp-48 - file is a path to structure file that can be read by pymatgen Example: ./structure.cif """, type=str)
[docs] def create_from_mpid(mpid): """Fetch and standardise a structure from Materials Project. Args: mpid: Materials Project ID (e.g. ``mp-48``). Returns: Structure with the ``mpid`` property set. """ # cast to Structure object. from_id may return IStructure. structure = pmg.Structure.from_sites(pmg.Structure.from_id(mpid)) structure.properties['mpid'] = mpid logger.info( "Downloaded structure %s from Materials Project db:\n%s", mpid, structure ) # Sometimes, Materials Project does not return standardized structure # Force it analyzer = SpacegroupAnalyzer(structure) standard_structure = analyzer.get_primitive_standard_structure() standard_structure.properties = structure.properties if standard_structure.lattice != structure.lattice: logger.info( "Non-standardized structure converted:\n%s", standard_structure ) return standard_structure
[docs] def create_from_file(path): """Load a structure from a file. Args: path: Path to a structure file readable by pymatgen. Returns: Structure. """ structure = pmg.Structure.from_file(path) logger.info( "Loaded structure from %s:\n%s", path, structure ) return structure
[docs] def create_from_atom_name(name, size): """Create a boxed periodic structure with a single atom centred. Args: name: Element symbol. size: 3-tuple of cell dimensions in Angstrom. Returns: Structure: Boxed structure containing a single atom. """ logger.info( "Creating %fx%fx%f supercell for %s", size[0], size[1], size[2], name) molecule = pmg.Molecule([name], [[0, 0, 0]]) # cast to Structure object. from_id may return IStructure. return pmg.Structure.from_sites( molecule.get_boxed_structure(size[0], size[1], size[2]))
[docs] def create(args): """Run the create subcommand. Args: args: Parsed command-line arguments from argparse. Returns: int: Exit code (0 on success). """ if os.path.isfile(args.what): structure = create_from_file(args.what) inputset = IMDStandardVaspInputSet(structure=structure) elif match := re.match( r'([A-Z][a-z]) +([0-9]+)x([0-9]+)x([0-9]+)', args.what): structure = create_from_atom_name( match[1], [float(match[2]), float(match[3]), float(match[4])]) inputset = IMDStandardVaspInputSet( structure=structure, user_incar_settings={'SYSTEM': f'{match[1]}.boxed'}, # Single k-point user_kpoints_settings={'length': 1}) else: structure = create_from_mpid(args.what) inputset = IMDStandardVaspInputSet(structure=structure) assert inputset.incar is not None inputset.write_input(output_dir=inputset.incar['SYSTEM']) return 0