File: xtb.py

package info (click to toggle)
avogadrolibs 1.97.0-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 36,184 kB
  • sloc: cpp: 115,988; ansic: 2,212; python: 1,163; perl: 321; sh: 83; makefile: 46
file content (106 lines) | stat: -rw-r--r-- 3,097 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#!/usr/bin/python3

#  This source file is part of the Avogadro project.
#  This source code is released under the 3-Clause BSD License, (see "LICENSE").

import argparse
import json
import sys
import os
from shutil import which
import tempfile
import subprocess


def getMetaData():
    # before we return metadata, make sure xtb is in the path
    if which("xtb") is None:
        return {}  # Avogadro will ignore us now

    metaData = {}
    metaData["inputFormat"] = "mol"  # could be other formats, but this is fine
    metaData["identifier"] = "GFN2"
    metaData["name"] = "GFN2"
    metaData["description"] = "Calculate atomic partial charges using GFN2 and xtb"
    metaData["charges"] = True
    metaData["potential"] = False
    metaData["elements"] = "1-86"  # up to Radon
    return metaData


def charges():
    # Avogadro will send us the mol file as stdin
    # we need to write it to a temporary file

    # get the whole file
    mol = sys.stdin.read()

    fd, name = tempfile.mkstemp(".mol")
    os.write(fd, mol.encode())
    os.close(fd)

    # run xtb
    xtb = which("xtb")
    if xtb is None:  # we check again
        return ""

    # for now, ignore the output itself
    tempdir = tempfile.mkdtemp()
    output = subprocess.run(
        [xtb, name], stdout=subprocess.PIPE, cwd=tempdir, check=True
    )
    # instead we read the "charges" file
    result = ""
    with open(tempdir + "/" + "charges", "r", encoding="utf-8") as f:
        result = f.read()

    # try to cleanup the temporary files
    os.remove(name)
    for filename in os.listdir(tempdir):
        try:
            os.remove(tempdir + "/" + filename)
        except:
            continue
    # and try to cleanup the directory
    try:
        os.rmdir(tempdir)
    except:
        pass

    # write the charges to stdout
    return result


def potential():
    # at the moment, xtb doesn't have a good way to do this
    # and the method shouldn't be called anyway

    # if your plugin has a potential, you can return it here
    # .. you'll get JSON with the file and the set of points
    #   e.g. { "xyz" : "xyz file contents", "points" : [ x,y,z, x,y,z, ... ] }
    #    or  { "sdf" : "sdf file contents", "points" : [ x,y,z, x,y,z, ... ] }
    # .. and you print the list of potentials to stdout
    return ""


if __name__ == "__main__":
    parser = argparse.ArgumentParser("GFN2 partial charges")
    parser.add_argument("--display-name", action="store_true")
    parser.add_argument("--metadata", action="store_true")
    parser.add_argument("--charges", action="store_true")
    parser.add_argument("--potential", action="store_true")
    parser.add_argument("--lang", nargs="?", default="en")
    args = vars(parser.parse_args())

    if args["metadata"]:
        print(json.dumps(getMetaData()))
    elif args["display_name"]:
        name = getMetaData().get("name")
        if name:
            print(name)
        else:
            raise RuntimeError("xtb is unavailable")
    elif args["charges"]:
        print(charges())
    elif args["potential"]:
        print(potential())