Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e6665b2

Browse files
authored
Fixes, adjustments and integrations (grimme-lab#1)
- some troubleshooting regarding xtb subproject build with meson - badges linking to Travis CI, codecov and LGTM - add additional tests for properties and error handling - make sure Results object does not get deconstructed by singlepoint
1 parent b00a65b commit e6665b2

3 files changed

Lines changed: 165 additions & 31 deletions

File tree

README.rst

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
Python API for the extended tight binding program
22
=================================================
33

4+
.. image:: https://img.shields.io/github/license/grimme-lab/xtb-python
5+
:alt: License
6+
:target: COPYING.LESSER
7+
.. image:: https://travis-ci.com/grimme-lab/xtb-python.svg?branch=master
8+
:alt: Travis CI
9+
:target: https://travis-ci.com/grimme-lab/xtb-python
10+
.. image:: https://img.shields.io/lgtm/grade/python/g/grimme-lab/xtb-python.svg
11+
:alt: LGTM
12+
:target: https://lgtm.com/projects/g/grimme-lab/xtb-python/context:python
13+
.. image:: https://codecov.io/gh/grimme-lab/xtb-python/branch/master/graph/badge.svg
14+
:alt: Codecov
15+
:target: https://codecov.io/gh/grimme-lab/xtb-python
16+
417
This repository host the Python API for the extended tight binding (``xtb``) program.
518

619
The idea of this project is to provide the ``xtb`` API for Python *without*
@@ -24,7 +37,7 @@ project, in summary it requires a Fortran and a C compiler as well as a
2437
linear algebra backend. Make yourself familiar with building ``xtb`` first!
2538

2639
Additionally this project requires a development version of Python installed.
27-
Also ensure that you have the ``numpy`` and ``cffi`` package installed,
40+
Also ensure that you have the ``numpy`` and ``cffi`` packages installed,
2841
configure the build of the extension with:
2942

3043
.. code::
@@ -33,11 +46,17 @@ configure the build of the extension with:
3346
ninja -C build install
3447
3548
If you have several versions of Python installed you can point meson with
36-
the ``-Dpy=<version>`` to the correct one.
37-
This will create the CFFI module ``xtb._libxtb``.
49+
the ``-Dpy=<version>`` option to the correct one.
50+
This will create the CFFI extension ``_libxtb`` and place it in the ``xtb``
51+
directory.
52+
53+
In case meson fails to configure or build, check the options for ``-Dla_backed``
54+
and ``-Dopenmp`` which are passed to the ``xtb`` subproject.
55+
For more information on the build with meson, follow the guide in the ``xtb``
56+
repository `here <https://github.com/grimme-lab/xtb/blob/master/meson/README.adoc>`_.
3857

39-
After creating the ``_libxtb`` extension, the python module can be installed
40-
as usual
58+
After creating the ``_libxtb`` extension, the Python module can be installed
59+
as usual with
4160

4261
.. code::
4362

tests/test_interface.py

Lines changed: 113 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,96 @@
1515
# You should have received a copy of the GNU Lesser General Public License
1616
# along with xtb. If not, see <https://www.gnu.org/licenses/>.
1717

18-
from xtb.interface import Calculator, Param
19-
from pytest import approx
18+
from xtb.interface import (
19+
XTBException,
20+
Molecule,
21+
Calculator,
22+
Results,
23+
Param,
24+
VERBOSITY_MINIMAL,
25+
)
26+
from pytest import approx, raises
2027
import numpy as np
2128

2229

30+
def test_molecule():
31+
"""check if the molecular structure data is working as expected."""
32+
33+
numbers = np.array(
34+
[6, 7, 6, 7, 6, 6, 6, 8, 7, 6, 8, 7, 6, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
35+
)
36+
positions = np.array(
37+
[
38+
[ 2.02799738646442, 0.09231312124713,-0.14310895950963],
39+
[ 4.75011007621000, 0.02373496014051,-0.14324124033844],
40+
[ 6.33434307654413, 2.07098865582721,-0.14235306905930],
41+
[ 8.72860718071825, 1.38002919517619,-0.14265542523943],
42+
[ 8.65318821103610,-1.19324866489847,-0.14231527453678],
43+
[ 6.23857175648671,-2.08353643730276,-0.14218299370797],
44+
[ 5.63266886875962,-4.69950321056008,-0.13940509630299],
45+
[ 3.44931709749015,-5.48092386085491,-0.14318454855466],
46+
[ 7.77508917214346,-6.24427872938674,-0.13107140408805],
47+
[10.30229550927022,-5.39739796609292,-0.13672168520430],
48+
[12.07410272485492,-6.91573621641911,-0.13666499342053],
49+
[10.70038521493902,-2.79078533715849,-0.14148379504141],
50+
[13.24597858727017,-1.76969072232377,-0.14218299370797],
51+
[ 7.40891694074004,-8.95905928176407,-0.11636933482904],
52+
[ 1.38702118184179, 2.05575746325296,-0.14178615122154],
53+
[ 1.34622199478497,-0.86356704498496, 1.55590600570783],
54+
[ 1.34624089204623,-0.86133716815647,-1.84340893849267],
55+
[ 5.65596919189118, 4.00172183859480,-0.14131371969009],
56+
[14.67430918222276,-3.26230980007732,-0.14344911021228],
57+
[13.50897177220290,-0.60815166181684, 1.54898960808727],
58+
[13.50780014200488,-0.60614855212345,-1.83214617078268],
59+
[ 5.41408424778406,-9.49239668625902,-0.11022772492007],
60+
[ 8.31919801555568,-9.74947502841788, 1.56539243085954],
61+
[ 8.31511620712388,-9.76854236502758,-1.79108242206824],
62+
]
63+
)
64+
filename = "xtb-error.log"
65+
message = "Expecting nuclear fusion warning"
66+
67+
# Constructor should raise an error for nuclear fusion input
68+
with raises(XTBException, match="Could not initialize"):
69+
mol = Molecule(numbers, np.zeros((24, 3)))
70+
71+
# The Python class should protect from garbage input like this
72+
with raises(ValueError, match="Dimension missmatch"):
73+
mol = Molecule(np.array([1, 1, 1]), positions)
74+
75+
# Also check for sane coordinate input
76+
with raises(ValueError, match="Expected tripels"):
77+
mol = Molecule(numbers, np.random.rand(7))
78+
79+
# Construct real molecule
80+
mol = Molecule(numbers, positions)
81+
82+
# Try to update a structure with missmatched coordinates
83+
with raises(ValueError, match="Dimension missmatch for positions"):
84+
mol.update(np.random.rand(7))
85+
86+
# Try to add a missmatched lattice
87+
with raises(ValueError, match="Invalid lattice provided"):
88+
mol.update(positions, np.random.rand(7))
89+
90+
# Try to update a structure with nuclear fusion coordinates
91+
with raises(XTBException, match="Could not update"):
92+
mol.update(np.zeros((24, 3)))
93+
94+
# Redirect API output to file
95+
mol.set_output(filename)
96+
97+
# Flush the error from the environment log
98+
mol.show(message)
99+
100+
# Reset to correct positions, Molecule object should still be intact
101+
mol.update(positions)
102+
103+
23104
def test_gfn2_xtb():
24105
"""check if the GFN2-xTB interface is working correctly."""
25106
thr = 1.0e-8
107+
thr2 = 1.0e-6
26108

27109
numbers = np.array(
28110
[6, 7, 6, 7, 6, 6, 6, 8, 7, 6, 8, 7, 6, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
@@ -83,17 +165,29 @@ def test_gfn2_xtb():
83165
[ 3.81284918e-04,-1.28923994e-04,-2.34336886e-03],
84166
]
85167
)
168+
charges = np.array([
169+
-0.05445590, -0.00457526, 0.08391889, -0.27870751, 0.11914924,
170+
-0.02621044, 0.26115960, -0.44071824, -0.10804747, 0.30411699,
171+
-0.44083760, -0.07457706, -0.04790859, -0.03738239, 0.06457802,
172+
0.08293905, 0.08296802, 0.05698136, 0.09025556, 0.07152988,
173+
0.07159003, 0.08590674, 0.06906357, 0.06926350])
86174

87175
calc = Calculator(Param.GFN2xTB, numbers, positions)
88-
res = calc.singlepoint()
176+
calc.set_verbosity(VERBOSITY_MINIMAL)
177+
assert calc.check() == 0
178+
179+
res = Results(calc)
180+
calc.singlepoint(res)
89181

90182
assert approx(res.get_energy(), thr) == -42.14746312757416
91183
assert approx(res.get_gradient(), thr) == gradient
184+
assert approx(res.get_charges(), thr2) == charges
92185

93186

94187
def test_gfn1_xtb():
95188
"""check if the GFN1-xTB interface is working correctly."""
96189
thr = 1.0e-8
190+
thr2 = 1.0e-6
97191

98192
numbers = np.array(
99193
[6, 7, 6, 7, 6, 6, 6, 8, 7, 6, 8, 7, 6, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
@@ -154,9 +248,24 @@ def test_gfn1_xtb():
154248
[ 2.89640303e-04,-2.09943109e-04,-1.35065134e-03],
155249
]
156250
)
251+
dipole = np.array([-0.81941935, 1.60912848, 0.00564382])
252+
157253

158254
calc = Calculator(Param.GFN1xTB, numbers, positions)
159-
res = calc.singlepoint()
255+
256+
res = Results(calc)
257+
258+
# check if we cannot retrieve properties from the unallocated result
259+
with raises(XTBException, match="Virial is not available"):
260+
res.get_virial()
261+
res.show("Release error log")
262+
with raises(XTBException, match="Bond orders are not available"):
263+
res.get_bond_orders()
264+
res.show("Release error log")
265+
266+
# Start calculation by restarting with result
267+
calc.singlepoint(res)
160268

161269
assert approx(res.get_energy(), thr) == -44.509702418208896
162270
assert approx(res.get_gradient(), thr) == gradient
271+
assert approx(res.get_dipole(), thr2) == dipole

xtb/interface.py

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
raise Exception("xtb C extension unimportable, cannot use C-API")
2727

2828

29+
class XTBException(Exception): ...
30+
31+
2932
class Param(Enum):
3033
"""Possible parametrisations for the Calculator class"""
3134

@@ -35,6 +38,11 @@ class Param(Enum):
3538
GFNFF = auto()
3639

3740

41+
VERBOSITY_FULL = 2
42+
VERBOSITY_MINIMAL = 1
43+
VERBOSITY_MUTED = 0
44+
45+
3846
class Environment:
3947
"""Calculation environment"""
4048

@@ -56,12 +64,12 @@ def check(self) -> int:
5664

5765
def show(self, message: str) -> None:
5866
"""Show and empty error stack"""
59-
_message = _ffi.new("char[]", message)
67+
_message = _ffi.new("char[]", message.encode())
6068
_lib.xtb_showEnvironment(self._env, _message)
6169

6270
def set_output(self, filename: str) -> None:
6371
"""Bind output from this environment"""
64-
_filename = _ffi.new("char[]", filename)
72+
_filename = _ffi.new("char[]", filename.encode())
6573
_lib.xtb_setOutput(self._env, _filename)
6674

6775
def release_output(self) -> None:
@@ -93,7 +101,7 @@ def __init__(
93101
raise ValueError("Expected tripels of cartesian coordinates")
94102

95103
if 3 * numbers.size != positions.size:
96-
raise ValueError("Dimension missmatch between numbers and postions")
104+
raise ValueError("Dimension missmatch between numbers and positions")
97105

98106
self._natoms = len(numbers)
99107
_numbers = np.array(numbers, dtype="i4")
@@ -128,7 +136,7 @@ def __init__(
128136
)
129137

130138
if self.check() != 0:
131-
raise ValueError("Could not initialize molecular structure data")
139+
raise XTBException("Could not initialize molecular structure data")
132140

133141
def __del__(self):
134142
"""Delete molecular structure data"""
@@ -145,16 +153,16 @@ def update(
145153
):
146154
"""Update coordinates and lattice parameters"""
147155

148-
if 3 * len(self) != len(positions):
149-
raise ValueError("Dimension missmatch for postions")
156+
if 3 * len(self) != positions.size:
157+
raise ValueError("Dimension missmatch for positions")
150158
_positions = np.array(positions, dtype="float")
151159

152160
if lattice is not None:
153161
if len(lattice) != 9:
154162
raise ValueError("Invalid lattice provided")
155163
_lattice = np.array(lattice, dtype="float")
156164
else:
157-
_lattice = _ffi.NULL
165+
_lattice = None
158166

159167
_lib.xtb_updateMolecule(
160168
self._env,
@@ -164,7 +172,7 @@ def update(
164172
)
165173

166174
if self.check() != 0:
167-
raise ValueError("Could not update molecular structure data")
175+
raise XTBException("Could not update molecular structure data")
168176

169177

170178
class Results(Environment):
@@ -193,47 +201,47 @@ def get_energy(self):
193201
_energy = _ffi.new("double *")
194202
_lib.xtb_getEnergy(self._env, self._res, _energy)
195203
if self.check() != 0:
196-
raise ValueError("Energy is not available")
204+
raise XTBException("Energy is not available")
197205
return _energy[0]
198206

199207
def get_gradient(self):
200208
"""Query singlepoint results object for gradient"""
201209
_gradient = np.zeros((len(self), 3))
202210
_lib.xtb_getGradient(self._env, self._res, _cast("double*", _gradient))
203211
if self.check() != 0:
204-
raise ValueError("Gradient is not available")
212+
raise XTBException("Gradient is not available")
205213
return _gradient
206214

207215
def get_virial(self):
208216
"""Query singlepoint results object for virial"""
209217
_virial = np.zeros((3, 3))
210218
_lib.xtb_getVirial(self._env, self._res, _cast("double*", _virial))
211219
if self.check() != 0:
212-
raise ValueError("Virial is not available")
220+
raise XTBException("Virial is not available")
213221
return _virial
214222

215223
def get_dipole(self):
216224
"""Query singlepoint results object for dipole"""
217225
_dipole = np.zeros(3)
218226
_lib.xtb_getDipole(self._env, self._res, _cast("double*", _dipole))
219227
if self.check() != 0:
220-
raise ValueError("Dipole is not available")
228+
raise XTBException("Dipole is not available")
221229
return _dipole
222230

223231
def get_charges(self):
224232
"""Query singlepoint results object for partial charges"""
225233
_charges = np.zeros(len(self))
226234
_lib.xtb_getCharges(self._env, self._res, _cast("double*", _charges))
227235
if self.check() != 0:
228-
raise ValueError("Charges are not available")
236+
raise XTBException("Charges are not available")
229237
return _charges
230238

231239
def get_bond_orders(self):
232240
"""Query singlepoint results object for bond orders"""
233241
_bond_orders = np.zeros((len(self), len(self)))
234242
_lib.xtb_getBondOrders(self._env, self._res, _cast("double*", _bond_orders))
235243
if self.check() != 0:
236-
raise ValueError("Bond orders are not available")
244+
raise XTBException("Bond orders are not available")
237245
return _bond_orders
238246

239247

@@ -281,20 +289,18 @@ def _load(self, param: Param):
281289
)
282290

283291
if self.check() != 0:
284-
raise ValueError("Could not load parametrisation data")
292+
raise XTBException("Could not load parametrisation data")
285293

286-
def singlepoint(self, res: Optional[Results] = None) -> Results:
287-
"""Perform singlepoint calculation"""
294+
def singlepoint(self, res: Results) -> None:
295+
"""Perform singlepoint calculation,
296+
note that the a previous result is consumed by this action"""
288297

289-
_res = Results(self) if res is None else res
290298
_lib.xtb_singlepoint(
291-
self._env, self._mol, self._calc, _res._res,
299+
self._env, self._mol, self._calc, res._res,
292300
)
293301

294302
if self.check() != 0:
295-
raise ValueError("Single point calculation failed")
296-
297-
return _res
303+
raise XTBException("Single point calculation failed")
298304

299305

300306
def _cast(ctype, array):

0 commit comments

Comments
 (0)