Skip to content

Commit bd59f81

Browse files
authored
Support counter-poise calculations in Python API (#128)
1 parent 0649c1f commit bd59f81

File tree

9 files changed

+481
-303
lines changed

9 files changed

+481
-303
lines changed

python/dftd3/interface.py

Lines changed: 120 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class Structure:
5858
on invalid input, like incorrect shape / type of the passed arrays
5959
"""
6060

61-
_mol = library.ffi.NULL
61+
_mol = library.StructureHandle.null()
6262

6363
def __init__(
6464
self,
@@ -104,10 +104,10 @@ def __init__(
104104

105105
self._mol = library.new_structure(
106106
self._natoms,
107-
_cast("int*", _numbers),
108-
_cast("double*", _positions),
109-
_cast("double*", _lattice),
110-
_cast("bool*", _periodic),
107+
_numbers,
108+
_positions,
109+
_lattice,
110+
_periodic,
111111
)
112112

113113
def __len__(self):
@@ -147,8 +147,8 @@ def update(
147147

148148
library.update_structure(
149149
self._mol,
150-
_cast("double*", _positions),
151-
_cast("double*", _lattice),
150+
_positions,
151+
_lattice,
152152
)
153153

154154

@@ -176,7 +176,7 @@ class DampingParam:
176176
of the object should use the second method.
177177
"""
178178

179-
_param = library.ffi.NULL
179+
_param = library.ParamHandle.null()
180180

181181
def __init__(self, **kwargs):
182182
"""Create new damping parameter from method name or explicit data"""
@@ -190,11 +190,11 @@ def __init__(self, **kwargs):
190190
self._param = self.new_param(**kwargs)
191191

192192
@staticmethod
193-
def load_param(method, **kwargs):
193+
def load_param(method, **kwargs) -> library.ParamHandle:
194194
raise NotImplementedError("Child class has to define parameter loading")
195195

196196
@staticmethod
197-
def new_param(**kwargs):
197+
def new_param(**kwargs) -> library.ParamHandle:
198198
raise NotImplementedError("Child class has to define parameter construction")
199199

200200

@@ -218,15 +218,22 @@ def __init__(self, **kwargs):
218218
DampingParam.__init__(self, **kwargs)
219219

220220
@staticmethod
221-
def load_param(method, atm=True):
222-
_method = library.ffi.new("char[]", method.encode())
221+
def load_param(method: str, atm: bool = True) -> library.ParamHandle:
223222
return library.load_rational_damping(
224-
_method,
223+
method,
225224
atm,
226225
)
227226

228227
@staticmethod
229-
def new_param(*, s6=1.0, s8, s9=1.0, a1, a2, alp=14.0):
228+
def new_param(
229+
*,
230+
s6: float = 1.0,
231+
s8: float,
232+
s9: float = 1.0,
233+
a1: float,
234+
a2: float,
235+
alp: float = 14.0,
236+
) -> library.ParamHandle:
230237
return library.new_rational_damping(
231238
s6,
232239
s8,
@@ -254,15 +261,22 @@ def __init__(self, **kwargs):
254261
DampingParam.__init__(self, **kwargs)
255262

256263
@staticmethod
257-
def load_param(method, atm=True):
258-
_method = library.ffi.new("char[]", method.encode())
264+
def load_param(method: str, atm: bool = True) -> library.ParamHandle:
259265
return library.load_zero_damping(
260-
_method,
266+
method,
261267
atm,
262268
)
263269

264270
@staticmethod
265-
def new_param(*, s6=1.0, s8, s9=1.0, rs6, rs8=1.0, alp=14.0):
271+
def new_param(
272+
*,
273+
s6: float = 1.0,
274+
s8: float,
275+
s9: float = 1.0,
276+
rs6: float,
277+
rs8: float = 1.0,
278+
alp: float = 14.0,
279+
) -> library.ParamHandle:
266280
return library.new_zero_damping(
267281
s6,
268282
s8,
@@ -289,15 +303,22 @@ def __init__(self, **kwargs):
289303
DampingParam.__init__(self, **kwargs)
290304

291305
@staticmethod
292-
def load_param(method, atm=True):
293-
_method = library.ffi.new("char[]", method.encode())
306+
def load_param(method: str, atm: bool = True) -> library.ParamHandle:
294307
return library.load_mrational_damping(
295-
_method,
308+
method,
296309
atm,
297310
)
298311

299312
@staticmethod
300-
def new_param(*, s6=1.0, s8, s9=1.0, a1, a2, alp=14.0):
313+
def new_param(
314+
*,
315+
s6: float = 1.0,
316+
s8: float,
317+
s9: float = 1.0,
318+
a1: float,
319+
a2: float,
320+
alp: float = 14.0,
321+
) -> library.ParamHandle:
301322
return library.new_mrational_damping(
302323
s6,
303324
s8,
@@ -327,15 +348,23 @@ def __init__(self, **kwargs):
327348
DampingParam.__init__(self, **kwargs)
328349

329350
@staticmethod
330-
def load_param(method, atm=True):
331-
_method = library.ffi.new("char[]", method.encode())
351+
def load_param(method: str, atm: bool = True) -> library.ParamHandle:
332352
return library.load_mzero_damping(
333-
_method,
353+
method,
334354
atm,
335355
)
336356

337357
@staticmethod
338-
def new_param(*, s6=1.0, s8, s9=1.0, rs6, rs8=1.0, alp=14.0, bet):
358+
def new_param(
359+
*,
360+
s6: float = 1.0,
361+
s8: float,
362+
s9: float = 1.0,
363+
rs6: float,
364+
rs8: float = 1.0,
365+
alp: float = 14.0,
366+
bet: float,
367+
) -> library.ParamHandle:
339368
return library.new_mzero_damping(
340369
s6,
341370
s8,
@@ -364,15 +393,23 @@ def __init__(self, **kwargs):
364393
DampingParam.__init__(self, **kwargs)
365394

366395
@staticmethod
367-
def load_param(method, atm=True):
368-
_method = library.ffi.new("char[]", method.encode())
396+
def load_param(method: str, atm: bool = True) -> library.ParamHandle:
369397
return library.load_optimizedpower_damping(
370-
_method,
398+
method,
371399
atm,
372400
)
373401

374402
@staticmethod
375-
def new_param(*, s6=1.0, s8, s9=1.0, a1, a2, alp=14.0, bet):
403+
def new_param(
404+
*,
405+
s6: float = 1.0,
406+
s8: float,
407+
s9: float = 1.0,
408+
a1: float,
409+
a2: float,
410+
alp: float = 14.0,
411+
bet,
412+
) -> library.ParamHandle:
376413
return library.new_optimizedpower_damping(
377414
s6,
378415
s8,
@@ -394,7 +431,7 @@ class DispersionModel(Structure):
394431
input.
395432
"""
396433

397-
_disp = library.ffi.NULL
434+
_disp = library.ModelHandle.null()
398435

399436
def __init__(
400437
self,
@@ -427,9 +464,9 @@ def get_dispersion(self, param: DampingParam, grad: bool) -> dict:
427464
self._mol,
428465
self._disp,
429466
param._param,
430-
_cast("double*", _energy),
431-
_cast("double*", _gradient),
432-
_cast("double*", _sigma),
467+
_energy,
468+
_gradient,
469+
_sigma,
433470
)
434471

435472
results = dict(energy=_energy)
@@ -449,8 +486,8 @@ def get_pairwise_dispersion(self, param: DampingParam) -> dict:
449486
self._mol,
450487
self._disp,
451488
param._param,
452-
_cast("double*", _pair_disp2),
453-
_cast("double*", _pair_disp3),
489+
_pair_disp2,
490+
_pair_disp3,
454491
)
455492

456493
return {
@@ -459,22 +496,55 @@ def get_pairwise_dispersion(self, param: DampingParam) -> dict:
459496
}
460497

461498

462-
def _cast(ctype, array):
463-
"""Cast a numpy array to a FFI pointer"""
464-
return (
465-
library.ffi.NULL
466-
if array is None
467-
else library.ffi.cast(ctype, array.ctypes.data)
468-
)
499+
class GeometricCounterpoise(Structure):
500+
"""
501+
.. Counterpoise correction parameters
502+
503+
Contains the required information to evaluate the counterpoise correction
504+
for a given geometry. The counterpoise correction is a method to correct
505+
the interaction energy in a supermolecular calculation for the basis set
506+
superposition error (BSSE).
507+
"""
508+
509+
_gcp = library.GCPHandle.null()
510+
511+
def __init__(
512+
self,
513+
numbers: np.ndarray,
514+
positions: np.ndarray,
515+
lattice: Optional[np.ndarray] = None,
516+
periodic: Optional[np.ndarray] = None,
517+
method: Optional[str] = None,
518+
basis: Optional[str] = None,
519+
):
520+
Structure.__init__(self, numbers, positions, lattice, periodic)
521+
522+
self._gcp = library.load_gcp_param(self._mol, method, basis)
523+
524+
def set_realspace_cutoff(self, bas: float, srb: float):
525+
"""Set realspace cutoff for evaluation of interactions"""
469526

527+
library.set_gcp_realspace_cutoff(self._gcp, bas, srb)
470528

471-
def _ref(ctype, value):
472-
"""Create a reference to a value"""
473-
if value is None:
474-
return library.ffi.NULL
475-
ref = library.ffi.new(ctype + "*")
476-
ref[0] = value
477-
return ref
529+
def get_counterpoise(self, grad: bool) -> dict:
530+
"""Evaluate the counterpoise corrected interaction energy"""
531+
532+
_energy = np.array(0.0)
533+
if grad:
534+
_gradient = np.zeros((len(self), 3))
535+
_sigma = np.zeros((3, 3))
536+
else:
537+
_gradient = None
538+
_sigma = None
539+
540+
library.get_counterpoise(self._mol, self._gcp, _energy, _gradient, _sigma)
541+
542+
results = dict(energy=_energy)
543+
if _gradient is not None:
544+
results.update(gradient=_gradient)
545+
if _sigma is not None:
546+
results.update(virial=_sigma)
547+
return results
478548

479549

480550
def _rename_kwargs(kwargs, old_name, new_name):

0 commit comments

Comments
 (0)