forked from deepmodeling/dpdata
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathformat.py
More file actions
360 lines (287 loc) · 10.2 KB
/
format.py
File metadata and controls
360 lines (287 loc) · 10.2 KB
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
"""Implement the format plugin system."""
from __future__ import annotations
import os
from abc import ABC
from .plugin import Plugin
class Format(ABC):
"""The abstract base class for all formats.
To add a new format, one should create a new class inherited from this class, and then
- implement several methods, such as :meth:`from_system`;
- register the format with a key;
- add documentation in the class docstring;
The new format can be either insider or outside the package.
"""
__FormatPlugin = Plugin()
__FromPlugin = Plugin()
__ToPlugin = Plugin()
@staticmethod
def register(key):
"""Register a format plugin.
By default, after a format plugin is registered, the following methods
will be registered as well for :meth:`System`, :meth:`LabeledSystem`, :meth:`MultiSystems`, and
:meth:`BondOrderSystem`:
- from_{key.replace('/', '_')}
- to_{key.replace('/', '_')}
- from({key}, ...)
- to({key}, ...)
The decorator should be explicitly executed before :mod:`dpdata.system`
is imported. A module will be imported automatically if it
- is a submodule of :mod:`dpdata.plugins`;
- is registered at the `dpdata.plugins` entry point
Parameters
----------
key : str
The key to register the plugin.
Returns
-------
function
The decorator function.
Examples
--------
Register a format plugin:
>>> @Format.register('test')
... @Format.register('test2')
... class TestFormat(Format):
... pass
"""
return Format.__FormatPlugin.register(key)
@staticmethod
def register_from(key):
"""Register a from method if the target method name is not default.
Parameters
----------
key : str
The key to register the plugin.
Returns
-------
function
The decorator function.
Examples
--------
Register a from method:
>>> @Format.register_from('from_test_haha')
... @Format.register('test)
... class TestFormat(Format):
... pass
This will register a from method named from_test_haha, although the
format name is test.
"""
return Format.__FromPlugin.register(key)
@staticmethod
def register_to(key):
"""Register a to method if the target method name is not default.
Parameters
----------
key : str
The key to register the plugin.
Returns
-------
function
The decorator function.
Examples
--------
Register a to method:
>>> @Format.register_to('to_test_haha')
... @Format.register('test')
... class TestFormat(Format):
... pass
This will register a to method named to_test_haha, although the
format name is test.
"""
return Format.__ToPlugin.register(key)
@staticmethod
def get_formats():
"""Get all registered formats."""
return Format.__FormatPlugin.plugins
@staticmethod
def get_from_methods():
"""Get all registered from methods."""
return Format.__FromPlugin.plugins
@staticmethod
def get_to_methods():
"""Get all registered to methods."""
return Format.__ToPlugin.plugins
@staticmethod
def post(func_name):
"""Register a post function for from method.
Such function will be called after the "from" method is called.
Parameters
----------
func_name : str or list of str
The name of the post function.
Returns
-------
function
The decorator function.
Examples
--------
Register a post function:
>>> @Format.post('remove_pbc')
... @Format.register('test')
... class TestFormat(Format):
... pass
"""
def decorator(object):
if not isinstance(func_name, (list, tuple, set)):
object.post_func = (func_name,)
else:
object.post_func = tuple(func_name)
return object
return decorator
def from_system(self, file_name, **kwargs):
"""Implement System.from that converts from this format to System.
Parameters
----------
file_name : str
file name, i.e. the first argument
**kwargs : dict
keyword arguments that will be passed from the method
Returns
-------
data : dict
system data, whose keys are defined in System.DTYPES
"""
raise NotImplementedError(
f"{self.__class__.__name__} doesn't support System.from"
)
def to_system(self, data, *args, **kwargs):
"""Implement System.to that converts from System to this format.
Parameters
----------
data : dict
system data, whose keys are defined in System.DTYPES
*args : list
arguments that will be passed from the method
**kwargs : dict
keyword arguments that will be passed from the method
"""
raise NotImplementedError(
f"{self.__class__.__name__} doesn't support System.to"
)
def from_labeled_system(self, file_name, **kwargs):
"""Implement LabeledSystem.from that converts from this format to LabeledSystem.
Parameters
----------
file_name : str
file name, i.e. the first argument
**kwargs : dict
keyword arguments that will be passed from the method
Returns
-------
data : dict
system data, whose keys are defined in LabeledSystem.DTYPES
"""
raise NotImplementedError(
f"{self.__class__.__name__} doesn't support LabeledSystem.from"
)
def to_labeled_system(self, data, *args, **kwargs):
"""Implement LabeledSystem.to that converts from LabeledSystem to this format.
By default, LabeledSystem.to will fallback to System.to.
Parameters
----------
data : dict
system data, whose keys are defined in LabeledSystem.DTYPES
*args : list
arguments that will be passed from the method
**kwargs : dict
keyword arguments that will be passed from the method
"""
return self.to_system(data, *args, **kwargs)
def from_bond_order_system(self, file_name, **kwargs):
"""Implement BondOrderSystem.from that converts from this format to BondOrderSystem.
Parameters
----------
file_name : str
file name, i.e. the first argument
**kwargs : dict
keyword arguments that will be passed from the method
Returns
-------
data : dict
system data
"""
raise NotImplementedError(
f"{self.__class__.__name__} doesn't support BondOrderSystem.from"
)
def to_bond_order_system(self, data, rdkit_mol, *args, **kwargs):
"""Implement BondOrderSystem.to that converts from BondOrderSystem to this format.
By default, BondOrderSystem.to will fallback to LabeledSystem.to.
Parameters
----------
data : dict
system data
rdkit_mol : rdkit.Chem.rdchem.Mol
rdkit mol object
*args : list
arguments that will be passed from the method
**kwargs : dict
keyword arguments that will be passed from the method
"""
return self.to_system(data, *args, **kwargs)
class MultiModes:
"""File mode for MultiSystems.
The current implemented modes are:
- 0 (default): not implemented
- 1: every directory under the top-level directory is a system.
"""
NotImplemented = 0
Directory = 1
MultiMode = MultiModes.NotImplemented
def from_multi_systems(self, directory, **kwargs):
"""Implement MultiSystems.from that converts from this format to MultiSystems.
By default, this method follows MultiMode to implement the conversion.
Parameters
----------
directory : str
directory of system
**kwargs : dict
keyword arguments that will be passed from the method
Returns
-------
filenames: list[str]
list of filenames
"""
if self.MultiMode == self.MultiModes.Directory:
return [
os.path.join(directory, name)
for name in os.listdir(directory)
if os.path.isdir(os.path.join(directory, name))
]
raise NotImplementedError(
f"{self.__class__.__name__} doesn't support MultiSystems.from"
)
def to_multi_systems(self, formulas, directory, **kwargs):
"""Implement MultiSystems.to that converts from MultiSystems to this format.
By default, this method follows MultiMode to implement the conversion.
Parameters
----------
formulas : list[str]
list of formulas
directory : str
directory of system
**kwargs : dict
keyword arguments that will be passed from the method
"""
if self.MultiMode == self.MultiModes.Directory:
return [os.path.join(directory, ff) for ff in formulas]
raise NotImplementedError(
f"{self.__class__.__name__} doesn't support MultiSystems.to"
)
def mix_system(self, *system, type_map, **kwargs):
"""Mix the systems into mixed_type ones according to the unified given type_map.
Parameters
----------
*system : System
The systems to mix
type_map : list of str
Maps atom type to name
**kwargs : dict
keyword arguments that will be passed from the method
Returns
-------
mixed_systems: dict
dict of mixed system with key 'atom_numbs'
"""
raise NotImplementedError(
f"{self.__class__.__name__} doesn't support System.from"
)