1313#
1414# You should have received a copy of the Lesser GNU General Public License
1515# along with dftd4. If not, see <https://www.gnu.org/licenses/>.
16- """Wrapper around the C-API of the dftd4 shared library."""
16+ """
17+ Wrapper around the C-API of the dftd4 shared library.
18+ It provides the definition the basic interface to the library for most further integration
19+ in other Python frameworks.
20+
21+ The classes defined here allow a more Pythonic usage of the API object provided by the
22+ library in actual workflows than the low-level access provided in the CFFI generated wrappers.
23+ """
1724
1825from typing import Optional
1926import numpy as np
2027
2128
22- from .libdftd4 import (
23- ffi as _ffi ,
24- lib as _lib ,
25- new_error ,
26- new_structure ,
27- new_d4_model ,
28- custom_d4_model ,
29- new_rational_damping ,
30- load_rational_damping ,
31- handle_error ,
32- )
29+ from . import library
3330
3431
3532class Structure :
@@ -41,7 +38,7 @@ class Structure:
4138 and immutable atomic identifiers
4239 """
4340
44- _mol = _ffi .NULL
41+ _mol = library . ffi .NULL
4542
4643 def __init__ (
4744 self ,
@@ -78,7 +75,7 @@ def __init__(
7875 else :
7976 _periodic = None
8077
81- self ._mol = new_structure (
78+ self ._mol = library . new_structure (
8279 self ._natoms ,
8380 _cast ("int*" , _numbers ),
8481 _cast ("double*" , _positions ),
@@ -116,43 +113,57 @@ def update(
116113 else :
117114 _lattice = None
118115
119- _error = new_error ()
120- _lib .dftd4_update_structure (
121- _error ,
116+ library .update_structure (
122117 self ._mol ,
123118 _cast ("double*" , _positions ),
124119 _cast ("double*" , _lattice ),
125120 )
126121
127- handle_error (_error )
128-
129122
130123class DampingParam :
131- """Damping parameters for the dispersion correction"""
124+ """
125+ Rational damping function for DFT-D4.
126+
127+ The damping parameters contained in the object are immutable. To change the
128+ parametrization, a new object must be created. Furthermore, the object is
129+ opaque to the user and the contained data cannot be accessed directly.
130+
131+ There are two main ways provided to generate a new damping parameter object:
132+
133+ 1. a method name is passed to the constructor, the library will load the
134+ required data from the *s-dftd3* shared library.
135+
136+ 2. all required parameters are passed to the constructor and the library will
137+ generate an object from the given parameters.
138+
139+ .. note::
140+
141+ Mixing of the two methods is not allowed to avoid partial initialization
142+ of any created objects. Users who need full control over the creation
143+ of the object should use the second method.
144+ """
132145
133- _param = _ffi .NULL
146+ _param = library . ffi .NULL
134147
135148 def __init__ (self , * , method = None , ** kwargs ):
136149 """Create new damping parameter from method name or explicit data"""
137150
138- if method is not None :
139- _method = _ffi .new ("char[]" , method .encode ())
140- self ._param = load_rational_damping (
141- _method ,
142- kwargs .get ("s9" , 1.0 ) > 0.0 ,
143- )
151+ if "method" in kwargs and kwargs ["method" ] is None :
152+ del kwargs ["method" ]
153+
154+ if "method" in kwargs :
155+ self ._param = self .load_param (** kwargs )
144156 else :
145- try :
146- self ._param = new_rational_damping (
147- kwargs .get ("s6" , 1.0 ),
148- kwargs ["s8" ],
149- kwargs .get ("s9" , 1.0 ),
150- kwargs ["a1" ],
151- kwargs ["a2" ],
152- kwargs .get ("alp" , 16.0 ),
153- )
154- except KeyError as e :
155- raise RuntimeError ("Constructor requires argument for " + str (e ))
157+ self ._param = self .new_param (** kwargs )
158+
159+ @staticmethod
160+ def load_param (method , atm = True ):
161+ _method = library .ffi .new ("char[]" , method .encode ())
162+ return library .load_rational_damping (_method , atm )
163+
164+ @staticmethod
165+ def new_param (* , s6 = 1.0 , s8 , s9 = 1.0 , a1 , a2 , alp = 16.0 ):
166+ return library .new_rational_damping (s6 , s8 , s9 , a1 , a2 , alp )
156167
157168
158169class DispersionModel (Structure ):
@@ -165,7 +176,7 @@ class DispersionModel(Structure):
165176 recreating it.
166177 """
167178
168- _disp = _ffi .NULL
179+ _disp = library . ffi .NULL
169180
170181 def __init__ (
171182 self ,
@@ -181,29 +192,27 @@ def __init__(
181192 Structure .__init__ (self , numbers , positions , charge , lattice , periodic )
182193
183194 if "ga" in kwargs or "gc" in kwargs or "wf" in kwargs :
184- self ._disp = custom_d4_model (
195+ self ._disp = library . custom_d4_model (
185196 self ._mol ,
186197 kwargs .get ("ga" , 3.0 ),
187198 kwargs .get ("gc" , 2.0 ),
188199 kwargs .get ("wf" , 6.0 ),
189200 )
190201 else :
191- self ._disp = new_d4_model (self ._mol )
202+ self ._disp = library . new_d4_model (self ._mol )
192203
193204 def get_dispersion (self , param : DampingParam , grad : bool ) -> dict :
194205 """Perform actual evaluation of the dispersion correction"""
195206
196- _error = new_error ()
197- _energy = _ffi .new ("double *" )
207+ _energy = library .ffi .new ("double *" )
198208 if grad :
199209 _gradient = np .zeros ((len (self ), 3 ))
200210 _sigma = np .zeros ((3 , 3 ))
201211 else :
202212 _gradient = None
203213 _sigma = None
204214
205- _lib .dftd4_get_dispersion (
206- _error ,
215+ library .get_dispersion (
207216 self ._mol ,
208217 self ._disp ,
209218 param ._param ,
@@ -212,8 +221,6 @@ def get_dispersion(self, param: DampingParam, grad: bool) -> dict:
212221 _cast ("double*" , _sigma ),
213222 )
214223
215- handle_error (_error )
216-
217224 results = dict (energy = _energy [0 ])
218225 if _gradient is not None :
219226 results .update (gradient = _gradient )
@@ -224,13 +231,12 @@ def get_dispersion(self, param: DampingParam, grad: bool) -> dict:
224231 def get_properties (self ) -> dict :
225232 """Evaluate dispersion related properties"""
226233
227- _error = new_error ()
228234 _c6 = np .zeros ((len (self ), len (self )))
229235 _cn = np .zeros ((len (self )))
230236 _charges = np .zeros ((len (self )))
231237 _alpha = np .zeros ((len (self )))
232238
233- _lib . dftd4_get_properties (
239+ library . get_properties (
234240 _error ,
235241 self ._mol ,
236242 self ._disp ,
@@ -240,8 +246,6 @@ def get_properties(self) -> dict:
240246 _cast ("double*" , _alpha ),
241247 )
242248
243- handle_error (_error )
244-
245249 return {
246250 "coordination numbers" : _cn ,
247251 "partial charges" : _charges ,
@@ -252,21 +256,17 @@ def get_properties(self) -> dict:
252256 def get_pairwise_dispersion (self , param : DampingParam ) -> dict :
253257 """Evaluate pairwise representation of the dispersion energy"""
254258
255- _error = new_error ()
256259 _pair_disp2 = np .zeros ((len (self ), len (self )))
257260 _pair_disp3 = np .zeros ((len (self ), len (self )))
258261
259- _lib .dftd4_get_pairwise_dispersion (
260- _error ,
262+ library .get_pairwise_dispersion (
261263 self ._mol ,
262264 self ._disp ,
263265 param ._param ,
264266 _cast ("double*" , _pair_disp2 ),
265267 _cast ("double*" , _pair_disp3 ),
266268 )
267269
268- handle_error (_error )
269-
270270 return {
271271 "additive pairwise energy" : _pair_disp2 ,
272272 "non-additive pairwise energy" : _pair_disp3 ,
@@ -275,13 +275,17 @@ def get_pairwise_dispersion(self, param: DampingParam) -> dict:
275275
276276def _cast (ctype , array ):
277277 """Cast a numpy array to a FFI pointer"""
278- return _ffi .NULL if array is None else _ffi .cast (ctype , array .ctypes .data )
278+ return (
279+ library .ffi .NULL
280+ if array is None
281+ else library .ffi .cast (ctype , array .ctypes .data )
282+ )
279283
280284
281285def _ref (ctype , value ):
282286 """Create a reference to a value"""
283287 if value is None :
284- return _ffi .NULL
285- ref = _ffi .new (ctype + "*" )
288+ return library . ffi .NULL
289+ ref = library . ffi .new (ctype + "*" )
286290 ref [0 ] = value
287291 return ref
0 commit comments