#!/usr/bin/env python
import argparse
import enum
import numpy as np
import basis_set_exchange as bse
import apdft
import apdft.settings as aconf
import apdft.calculator as acalc
import apdft.physics as ap
[docs]def entry_cli():
# load configuration
parser = build_main_commandline()
conf = aconf.Configuration()
conf.from_file()
mode, modeshort, conf = parse_into(parser, configuration=conf)
# execute
if mode == "energies":
mode_energies(conf, modeshort)
else:
apdft.log.log("Unknown mode %s" % mode, level="error")
# emphasize warnings
warningcount = apdft.LOG_LEVEL_USAGE.get("warning", 0)
if warningcount > 0:
apdft.log.log(
"This run had warnings. The results might be incomplete at worst. In absence of errors, existing results are ok.",
warningcount=warningcount,
level="warning",
)
# highlight errors
errorcount = apdft.LOG_LEVEL_USAGE.get("error", 0)
returncode = 0
if errorcount > 0:
apdft.log.log(
"This run had errors. The results are not to be trusted.",
errorcount=errorcount,
level="error",
)
returncode = 1
# persist configuration
conf.to_file()
return returncode
[docs]def parse_target_list(lines):
""" Separates an explicit target list.
Accepted values: - for a missing atom, labels, nuclear charges. One target per line, comma separated."""
ret = []
for lidx, line in enumerate(lines):
res = []
for part in line.strip().split(","):
if part == "-":
res.append(0)
continue
try:
res.append(int(part))
continue
except:
pass
try:
res.append(bse.lut.element_Z_from_sym(part))
except:
apdft.log.log(
"Unknown element label in target list. Skipping entry.",
elementlabel=part,
lineno=lidx + 1,
level="warning",
)
break
if res == []:
continue
if ret != [] and len(ret[0]) != len(res):
apdft.log.log(
"Line with different number of atoms found. Skipping entry.",
lineno=lidx + 1,
level="warning",
)
continue
ret.append(res)
return np.array(ret)
[docs]def mode_energies(conf, modeshort=None):
# select QM code
calculator_options = conf.apdft_method, conf.apdft_basisset, conf.debug_superimpose
calculator = conf.energy_code.get_calculator_class()(*calculator_options)
# parse input
try:
nuclear_numbers, coordinates = apdft.read_xyz(conf.energy_geometry)
except FileNotFoundError:
apdft.log.log(
'Unable to open input file "%s".' % conf.energy_geometry, level="error"
)
return
# Parse optional targetlist
if len(conf.apdft_targets) != 0:
with open(conf.apdft_targets) as fh:
targetlist = parse_target_list(fh.readlines())
else:
targetlist = None
# call APDFT library
derivatives = ap.APDFT(
conf.apdft_maxorder,
nuclear_numbers,
coordinates,
".",
calculator,
conf.apdft_maxcharge,
conf.apdft_maxdz,
conf.apdft_includeonly,
targetlist,
)
cost, coverage = derivatives.estimate_cost_and_coverage()
if conf.debug_validation:
cost += coverage
apdft.log.log(
"Cost estimated.",
number_calculations=cost,
number_predictions=coverage,
level="RESULT",
)
if not conf.energy_dryrun:
derivatives.prepare(conf.debug_validation)
derivatives.analyse(conf.debug_validation)
[docs]def build_main_commandline(set_defaults=True):
""" Builds an argparse object of the user-facing command line interface."""
c = apdft.settings.Configuration()
parser = argparse.ArgumentParser(
description="QM calculations on multiple systems at once.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
# mode selection
modes = ["energies"]
parser.add_argument(
"mode",
choices=modes,
nargs=1,
help="Mode to use. Supported: %s" % ", ".join(modes),
metavar="mode",
)
# allow for shortcut where a mode gets one single argument of any kind
parser.add_argument("modeshort", type=str, nargs="?", help=argparse.SUPPRESS)
# options
for category in sorted(c.list_sections()):
group = parser.add_argument_group(category)
for option_name in c.list_options(category):
option = c[option_name]
choices = None
try:
if issubclass(option.get_validator(), enum.Enum):
choices = list(option.get_validator())
except TypeError:
pass
default = None
if set_defaults:
default = option.get_value()
group.add_argument(
"--%s" % option.get_attribute_name(),
type=option.get_validator(),
help=option.get_description(),
choices=choices,
default=default,
metavar="",
)
return parser
[docs]def parse_into(parser, configuration=None, cliargs=None):
""" Updates the configuration with the values specified on the command line.
Args:
parser: An argparse parser instance.
configuration: A :class:`apdft.settings.Configuration` instance. If `None`, a new instance will be returned.
args: List of split arguments from the command line.
Returns:
Mode of operation, single optional argument, updated configuration."""
if configuration is None:
configuration = apdft.settings.Configuration()
# help specified?
args = parser.parse_args(cliargs)
nodefaultparser = build_main_commandline(set_defaults=False)
args = nodefaultparser.parse_args(cliargs)
valid_options = configuration.list_options()
mode = None
modeshort = None # single argument for a mode for simplicity
for k, v in vars(args).items():
if k in valid_options:
if v is not None:
configuration[k].set_value(v)
else:
if k == "mode":
mode = v[0]
elif k == "modeshort":
modeshort = v
else:
raise ValueError("Unknown argument found.")
if mode == "energies":
if modeshort is not None:
configuration.energy_geometry = modeshort
return mode, modeshort, configuration