#!/usr/bin/env amspython
from __future__ import annotations
import json
import os
import shutil
import site
from pathlib import Path
from scm.pisa import block as input_block
from scm.pisa import key as input_key
from scm.pisa.block_subclass_string import BlockSubclassAttribute, BlockSubclassString
from scm.pisa.utils import to_valid_identifier
INPUT_DEF_FOLDER = Path(os.environ["AMSBIN"]) / "input_def"
# these dictionaries control which files in INPUT_DEF_FOLDER get turned into python class and the values determine
# the name of the classes.
ENGINE_BLOCK_FILES = {
INPUT_DEF_FOLDER / "adf.json": "ADF",
INPUT_DEF_FOLDER / "ase.json": "ASE",
INPUT_DEF_FOLDER / "band.json": "BAND",
INPUT_DEF_FOLDER / "dftb.json": "DFTB",
INPUT_DEF_FOLDER / "external.json": "External",
INPUT_DEF_FOLDER / "forcefield.json": "ForceField",
INPUT_DEF_FOLDER / "gfnff.json": "GFNFF",
INPUT_DEF_FOLDER / "hybrid.json": "Hybrid",
INPUT_DEF_FOLDER / "lennardjones.json": "LennardJones",
INPUT_DEF_FOLDER / "mlpotential.json": "MLPotential",
INPUT_DEF_FOLDER / "mopac.json": "Mopac",
INPUT_DEF_FOLDER / "pipe.json": "Pipe",
INPUT_DEF_FOLDER / "reaxff.json": "ReaxFF",
INPUT_DEF_FOLDER / "quantumespresso.json": "QuantumESPRESSO",
INPUT_DEF_FOLDER / "vasp.json": "VASP",
}
DRIVER_BLOCK_FILES = {
INPUT_DEF_FOLDER / "acerxn.json": "ACERXN",
INPUT_DEF_FOLDER / "adfnbo.json": "ADFNBO",
INPUT_DEF_FOLDER / "amsbatch.json": "AMSBatch",
INPUT_DEF_FOLDER / "ams_interactive.json": "AMSInteractive",
INPUT_DEF_FOLDER / "ams.json": "AMS",
INPUT_DEF_FOLDER / "analysis.json": "Analysis",
INPUT_DEF_FOLDER / "atomtyping.json": "AtomTyping",
INPUT_DEF_FOLDER / "chemtrayzer2.json": "Chemtrayzer2",
INPUT_DEF_FOLDER / "conductance.json": "Conductance",
INPUT_DEF_FOLDER / "conformers.json": "Conformers",
INPUT_DEF_FOLDER / "cpl.json": "CPL",
# INPUT_DEF_FOLDER / "crs.json", TODO code breaks running on this file: ''?
INPUT_DEF_FOLDER / "densf.json": "DENSF",
INPUT_DEF_FOLDER / "dftbplus.json": "DFTBPlus",
INPUT_DEF_FOLDER / "epr.json": "EPR",
INPUT_DEF_FOLDER / "fcf.json": "FCF",
INPUT_DEF_FOLDER / "glompo_genref.json": "ParAMSGenerateReference",
INPUT_DEF_FOLDER / "glompo.json": "ParAMS",
INPUT_DEF_FOLDER / "glompo_machinelearning.json": "ParAMSMachineLearning",
INPUT_DEF_FOLDER / "glompo_optimize.json": "ParAMSOptimize",
INPUT_DEF_FOLDER / "glompo_sensitivity.json": "ParAMSSensitivity",
INPUT_DEF_FOLDER / "glompo_singlepoint.json": "ParAMSSinglePoint",
INPUT_DEF_FOLDER / "green.json": "Green",
INPUT_DEF_FOLDER / "lfdft.json": "LFDFT",
INPUT_DEF_FOLDER / "lfdft_tdm.json": "LFDFT_TDM",
INPUT_DEF_FOLDER / "nao.json": "NAO",
INPUT_DEF_FOLDER / "nmr.json": "NMR",
INPUT_DEF_FOLDER / "oldfcf.json": "OldFCF",
INPUT_DEF_FOLDER / "oled-deposition.json": "OLEDDepostion",
INPUT_DEF_FOLDER / "oled-properties.json": "OLEDProperties",
INPUT_DEF_FOLDER / "pre_amsified_adf.json": "PreAmsifiedADF",
INPUT_DEF_FOLDER / "reactions_discovery.json": "ReactionsDiscovery",
INPUT_DEF_FOLDER / "redox_potential.json": "RedoxPotential",
INPUT_DEF_FOLDER / "sgf.json": "SGF",
INPUT_DEF_FOLDER / "symmetrize.json": "Symmetrize",
INPUT_DEF_FOLDER / "UnifiedChemicalSystem.json": "UnifiedChemicalSystem",
INPUT_DEF_FOLDER / "simple_active_learning.json": "SimpleActiveLearning",
}
[docs]def block_to_cls_str(block: input_block.Block, insert_underscore: bool = True) -> BlockSubclassString:
"""Generate a :class:`BlockSubclassString` object from a :class:`Block` object. Uses the __repr__ functionality to
generate a line of python for every key/block attribute of the Block to convert.
:param block: Block to convert
:type block: input_block.Block
:param insert_underscore: Whether to prefix the class name with '_'. Should be true except for the toplevel block. Used to prevent name clashes between the nested class definition and the instance attribute. Defaults to True
:type insert_underscore: bool, optional
:return: Class string object that can be used to generate a string containing python code defining the subclass that represents the block.
:rtype: BlockSubclassString
"""
attributes: list[BlockSubclassAttribute] = []
for child_key in block.keys:
cls_name, init_args = repr(child_key).split("(", 1)
attributes.append(
BlockSubclassAttribute(
child_key.name,
f"{to_valid_identifier(cls_name)}({init_args}",
input_key.type_hint(child_key),
)
)
for child_block in block.blocks:
# the block __repr__ from dataclass will have the block class
# but we need to overwrite it with the specific nested class name
# e.g. from FixedBlock(name='_PerAtomType', ...)
# to self.__PerAtomType(...)
_, init_args = repr(child_block).split("(", 1)
# nested class definitions need to be prefixed with '_'
# to prevent name clashes with the instance attributes of the same name
attributes.append(
BlockSubclassAttribute(
child_block.name,
f"self._{to_valid_identifier(child_block.name)}({init_args}",
input_block.type_hint(child_block),
)
)
cls_str = BlockSubclassString(
name=f"_{to_valid_identifier(block.name)}" if insert_underscore else to_valid_identifier(block.name),
parent=block.__class__.__name__,
attributes=attributes,
doc_str=block.comment,
)
for child_block in block.blocks:
block_cls_str = block_to_cls_str(child_block)
cls_str.insert(block_cls_str)
return cls_str
[docs]def write_block_class_to_file(block: input_block.Block, path: Path) -> None:
"""Generates python code that defines a specific block and write it to a file. Will insert the necessary imports as
well.
:param block: Block object to write to file
:type block: input_block.Block
:param path: Path to write the file to.
:type path: Path
"""
cls_str = block_to_cls_str(block, insert_underscore=False)
# TODO maybe this could be made less fragile?
# but as long as it is covered by tests, should be okay
import_line_block = "from scm.pisa.block import "
import_line_key = "from scm.pisa.key import "
block_classes = []
key_classes = []
# perhaps just hardcoding the string is more readable?
# there will not be any new block or key types often anyway
for attr in dir(input_block):
attr = getattr(input_block, attr)
if isinstance(attr, type) and issubclass(attr, input_block.Block) and attr != input_block.Block:
block_classes.append(attr.__name__)
for attr in dir(input_key):
attr = getattr(input_key, attr)
if isinstance(attr, type) and issubclass(attr, input_key.Key) and attr != input_key.Key:
key_classes.append(attr.__name__)
key_classes += ["BoolType"]
with open(path, "w", encoding="utf-8") as f:
f.write("from __future__ import annotations\n")
f.write("from pathlib import Path\n")
f.write("from typing import Iterable, Literal, Sequence\n")
f.write(import_line_block + ",".join(block_classes) + "\n")
f.write(import_line_key + ",".join(key_classes) + "\n\n")
f.write(str(cls_str))
if __name__ == "__main__":
# site packages directory differs between windows and linux platforms, better to get it programmaticallysvn st
# we try to avoid the directory in the scm virtual environment
# on linux this typically looks like: $AMSBIN/python3.8/lib/python3.8/site-packages
# and on windows it looks like: $AMSBIN/python3.8/lib/site-packages
site_packages_dir = Path([path for path in site.getsitepackages() if "venv" not in path][-1])
create_input_classes_module(destination_dir=site_packages_dir / "scm" / "input_classes", overwrite=True)