feffdb 0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
feffdb/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ from .utils import get_feffdb, is_feffdb, create_feffdb
2
+ from .feffdb import FeffDatabase
3
+ from .feffpath import feffpath, FeffDatFile
4
+ from .version import __version__, version_tuple
5
+
6
+ __all__ = ['get_feffdb', 'is_feffdb', 'create_feffdb', 'feffpath',
7
+ 'FeffDatabase', 'FeffDatFile', '__version__', 'version_tuple']
feffdb/apps.py ADDED
@@ -0,0 +1,77 @@
1
+ from argparse import ArgumentParser
2
+
3
+ from pathlib import Path
4
+ from tabulate import tabulate
5
+ import xraydb
6
+ from .utils import get_feffdb, create_feffdb
7
+ from .feffdb import FeffDatabase
8
+
9
+
10
+ VALID_ELEMS = [el.element for el in xraydb.get_xraydb().get_cache('elements')]
11
+ VALID_ELEMS.extend(['None', 'none', 'All', 'all'])
12
+
13
+ def feffdb_cli():
14
+ """
15
+ feffdb command-line app
16
+ """
17
+ dbname_default = get_feffdb().absolute().as_posix()
18
+ parser = ArgumentParser(description='Feff Database')
19
+ parser.add_argument('-n', '--name', dest='dbname',
20
+ default=None, help=f"database to use [{dbname_default}]" )
21
+ parser.add_argument('-c', '--create', action='store_true', default=False,
22
+ help="create new database")
23
+ parser.add_argument('--rmin', default=0,
24
+ help='minimum distance (Ang)')
25
+ parser.add_argument('--rmax', default=10,
26
+ help='maximum distance (Ang)')
27
+ parser.add_argument('absorber', nargs='?',
28
+ help='symbol for absorbing element (use "all" for all absorbers)')
29
+ parser.add_argument('scatterer', nargs='?',
30
+ help='symbol for scattering element')
31
+
32
+ args = parser.parse_args()
33
+
34
+ dbname = args.dbname or dbname_default
35
+
36
+ if Path(dbname).exists() and args.create:
37
+ print(f"database {dbname} already exists!")
38
+
39
+ if not Path(dbname).exists():
40
+ if args.create:
41
+ print("will create db ", dbname)
42
+ create_feffdb(name=dbname)
43
+ return
44
+ else:
45
+ print(f"database {dbname} does not exist, use '-c' to create")
46
+ return
47
+
48
+ feffdb = FeffDatabase(dbname, create_ok=False)
49
+ npaths = feffdb.list_feffdat()
50
+ if npaths is None or len(npaths) < 1:
51
+ print(f"database {dbname} exists but is empty")
52
+ return
53
+
54
+
55
+
56
+ absorber = args.absorber
57
+ if absorber not in VALID_ELEMS:
58
+ print(f"Unknown absorber: {absorber}, Must be valid atomic symbol or 'All' or 'None'")
59
+
60
+ scatterer = args.scatterer
61
+ if scatterer is not None and scatterer not in VALID_ELEMS:
62
+ print(f"Unknown scatterer: {scatterer} Must be valid atomic symbol or empty or 'All' or 'None'")
63
+
64
+ out = []
65
+ if absorber is not None or scatterer is not None:
66
+ if scatterer in ('All', 'all', 'None', 'none'):
67
+ scatterer = None
68
+ if absorber in ('All', 'all', 'None', 'none'):
69
+ absorber = None
70
+ rows = feffdb.list_feffdat(absorber=absorber, scatterer=scatterer,
71
+ rmin=float(args.rmin), rmax=float(args.rmax).
72
+ with_geometry=False)
73
+ out.append(row)
74
+ if len(out) > 0:
75
+ print(tabulate(out, headers='keys', tablefmt='psql'))
76
+ else:
77
+ parser.print_usage()
feffdb/feffdat.db ADDED
Binary file
feffdb/feffdb.py ADDED
@@ -0,0 +1,318 @@
1
+ #!/bin/env python
2
+ '''
3
+ This script creates an SQLite3 database for Feff Data
4
+ '''
5
+ import json
6
+ import lzma
7
+ from pathlib import Path
8
+ from pyshortcuts import hash_password, check_password_rules
9
+ from xraydb import atomic_number, atomic_symbol
10
+
11
+
12
+ from .simpledb import SimpleDB
13
+ from .utils import (get_feffdb, create_feffdb,
14
+ parse_cif, parse_feffinp, parse_feffdat)
15
+
16
+ def compress(val):
17
+ return lzma.compress(val.encode('utf-8'))
18
+
19
+ def decompress(val):
20
+ return lzma.decompress(val).decode('utf-8')
21
+
22
+ class FeffDatabase(SimpleDB):
23
+ def __init__(self, dbname=None, create_ok=False, read_only=False):
24
+ if dbname is not None:
25
+ dbpath = Path(dbname)
26
+ else:
27
+ try:
28
+ dbpath = get_feffdb()
29
+ except ValueError:
30
+ dbpath = None
31
+
32
+ if (dbpath is None or not dbpath.exists()) and create_ok:
33
+ create_feffdb(dbname=dbname)
34
+
35
+ self.dbname = dbpath.absolute().as_posix()
36
+ SimpleDB.__init__(self, dbname=self.dbname, server='sqlite',
37
+ read_only=read_only)
38
+
39
+ def __repr__(self):
40
+ return f"FeffDatabase('{self.dbname}')"
41
+
42
+
43
+ def __addrow(self, table, data):
44
+ """add row to tablle, or return existing row with data"""
45
+ # first look if this already exists
46
+ row = self.get_rows(table, where=data,
47
+ limit_one=True, none_if_empty=True)
48
+
49
+ if row is None:
50
+ self.insert(table, **data)
51
+ row = self.get_rows(table, where=data,
52
+ limit_one=True, none_if_empty=True)
53
+ if row is None:
54
+ return None
55
+ return row.id
56
+
57
+
58
+ def add_cif_file(self, cif_file):
59
+ """add a CIF file to the database"""
60
+ try:
61
+ out = parse_cif(cif_file)
62
+ except ValueError:
63
+ print(f"error parsing {cif_file}")
64
+ return None
65
+
66
+ text = compress(out.pop('text'))
67
+ fname = out.pop('filename')
68
+ dbargs = {k:v for k, v in out.items()}
69
+ dbargs['ciftext'] = text
70
+ if dbargs['source_db'] is None and fname is not None:
71
+ dbargs['source_db'] = fname
72
+
73
+ this_id = self.__addrow('cif', dbargs)
74
+ if this_id is None:
75
+ raise ValueError(f'could not add CIF: {cif_file}')
76
+ return this_id
77
+
78
+
79
+ def add_feffinp(self, feff_inp, cif_file=None):
80
+ """add a Feff input file to the database"""
81
+ cif_id = 0
82
+ if cif_file is not None:
83
+ cif_id = self.add_cif_file(cif_file)
84
+
85
+ try:
86
+ finp = parse_feffinp(feff_inp)
87
+ except ValueError:
88
+ raise ValueError(f'error parsing feffinp : {feff_inp}')
89
+
90
+ text = compress(finp.pop('text'))
91
+ finp.pop('filename')
92
+ dbargs = {k:v for k, v in finp.items()}
93
+ dbargs['inptext'] = text
94
+ dbargs['cif_id'] = cif_id
95
+
96
+ this_id = self.__addrow('feffinp', dbargs)
97
+ if this_id is None:
98
+ raise ValueError(f'could not add feffinp : {feff_inp}')
99
+ return this_id
100
+
101
+ def add_feffdat(self, feffdat, feff_inp, cif_file=None,
102
+ label=None, description=None,
103
+ avoid_duplicates=True, rtoler=0.015):
104
+ """add a Feff.dat file to the database"""
105
+
106
+ finp_id = self.add_feffinp(feff_inp, cif_file=cif_file)
107
+
108
+ try:
109
+ dat = parse_feffdat(feffdat)
110
+ except ValueError:
111
+ raise ValueError(f'error parsing feff.dat file: {feffdat}')
112
+
113
+ text = compress(dat.pop('text'))
114
+ fname = dat.pop('filename')
115
+
116
+ if avoid_duplicates:
117
+ nearby = self.list_feffdat(absorber=dat['absorber'],
118
+ scatterer=dat['scatterer'],
119
+ edge=dat['edge'],
120
+ rmin=float(dat['reff'])-rtoler,
121
+ rmax=float(dat['reff'])+rtoler)
122
+ if len(nearby) > 0:
123
+ print("Avoiding Duplicate entries:")
124
+ s = nearby[0]
125
+ print('This: ', dat['absorber'], dat['scatterer'], dat['edge'], dat['reff'])
126
+ print(s['id'], s['label'], s['absorber'], s['scatterer'], s['edge'], s['reff'])
127
+ return
128
+
129
+ if description is None:
130
+ # default description is filenamae and a few parent folders
131
+ words = Path(fname).parts
132
+ n = min(len(words), 2)
133
+ description = Path(*words[-n:]).as_posix()
134
+ if label is None:
135
+ # default label combines absorber, scatterer, reff
136
+ words = [atomic_symbol(dat['absorber']),
137
+ atomic_symbol(dat['scatterer']),
138
+ str(round(100*dat['reff']))]
139
+ label = ''.join(words)
140
+
141
+
142
+ if label is not None:
143
+ # check fie repeated rows
144
+ rows = self.get_rows('feffdat', where={'label': label},
145
+ none_if_empty=True)
146
+ if rows is not None:
147
+ words = [atomic_symbol(dat['absorber']),
148
+ atomic_symbol(dat['scatterer']),
149
+ str(round(100*dat['reff'])), '_', dat['edge']]
150
+ label = ''.join(words)
151
+ rows = self.get_rows('feffdat', where={'label': label},
152
+ none_if_empty=True)
153
+
154
+ if rows is not None:
155
+ olabel, i = label, 0
156
+ while rows is not None:
157
+ i += 1
158
+ label = f'{olabel}_{i}'
159
+ rows = self.get_rows('feffdat', where={'label': label},
160
+ none_if_empty=True)
161
+
162
+
163
+ dbargs = {k:v for k, v in dat.items()}
164
+ dbargs['feffdat'] = text
165
+ dbargs['feffinp_id'] = finp_id
166
+ dbargs['label'] = label
167
+ dbargs['description'] = description
168
+
169
+ this_id = self.__addrow('feffdat', dbargs)
170
+ if this_id is None:
171
+ raise ValueError(f'could not add feff data: {feffdat}')
172
+ return this_id
173
+
174
+ def get_feffdat(self, feffid):
175
+ """get text of feff.dat, either by table 'id' or
176
+ or by 'label' in feffdat table
177
+ """
178
+ try:
179
+ where = {'id': int(feffid)}
180
+ except ValueError:
181
+ where = {'label': feffid}
182
+
183
+ row = self.get_rows('feffdat', where=where,
184
+ limit_one=True, none_if_empty=True)
185
+ if row is not None:
186
+ return decompress(row.feffdat)
187
+
188
+ def get_feffinp(self, feffinp_id):
189
+ "get text of feff.inp by id in feffinp table"
190
+ row = self.get_rows('feffinp', where={'id': feffinp_id},
191
+ limit_one=True, none_if_empty=True)
192
+ if row is not None:
193
+ return decompress(row.inptext)
194
+
195
+ def get_feffinp_for_feffdat(self, feffdat_id):
196
+ "get text of feff.inp for a Feff.dat file"
197
+ row = self.get_rows('feffdat', where={'id': feffdat_id},
198
+ limit_one=True, none_if_empty=True)
199
+ if row is not None:
200
+ trow = self.get_rows('feffinp', where={'id': row.feffinp_id},
201
+ limit_one=True, none_if_empty=True)
202
+ if trow is not None:
203
+ return decompress(trow.inptext)
204
+
205
+ def get_cifinp_for_feffinp(self, feffinp_id):
206
+ "get text of CIF for a feff.inp file"
207
+ row = self.get_rows('feffinp', where={'id': feffinp_id},
208
+ limit_one=True, none_if_empty=True)
209
+ if row is not None:
210
+ trow = self.get_rows('cif', where={'id': row.cif_id},
211
+ limit_one=True, none_if_empty=True)
212
+ if trow is not None:
213
+ return decompress(trow.ciftext)
214
+
215
+ def list_feffdat(self, absorber=None, scatterer=None, edge=None,
216
+ rmin=0., rmax=20.0, with_geometry=True):
217
+ """get a list of feff.dat files matching on one or more criteria:
218
+
219
+ Arguments
220
+ ------------
221
+ absorber (None or str) symbol for absorber [None for all]
222
+ scatterer (None or str) symbol for scatterer [None for all]
223
+ edge (None or str) string for absorption edge [None for all]
224
+ rmin float minimum R value [0.0]
225
+ rmax float maximum R value [20.0]
226
+ with_geometry bool whether to include 'geometry' [True]
227
+ Returns
228
+ --------
229
+ list of path dictionaries
230
+ """
231
+ wargs = {}
232
+ if absorber is not None:
233
+ if isinstance(absorber, str):
234
+ absorber = atomic_number(absorber.title())
235
+ wargs['absorber'] = absorber
236
+ if scatterer is not None:
237
+ if isinstance(scatterer, str):
238
+ scatterer = atomic_number(scatterer.title())
239
+ wargs['scatterer'] = scatterer
240
+ if edge is not None:
241
+ wargs['edge'] = edge.title()
242
+
243
+ rows = self.get_rows('feffdat', where=wargs, limit_one=False)
244
+ out = []
245
+ for row in rows:
246
+ if row.reff > rmin and row.reff < rmax:
247
+ formula = '?'
248
+ finp = self.get_rows('feffinp', where={'id': row.feffinp_id},
249
+ limit_one=True, none_if_empty=True)
250
+ if finp is not None and finp.cif_id > 0:
251
+ cif = self.get_rows('cif', where={'id': finp.cif_id},
252
+ limit_one=True, none_if_empty=True)
253
+ if cif is not None:
254
+ formula = cif.formula
255
+ compound = cif.compound.lower()
256
+
257
+ rowdat = {'id': row.id,
258
+ 'label': row.label,
259
+ 'absorber': atomic_symbol(row.absorber),
260
+ 'scatterer': atomic_symbol(row.scatterer),
261
+ 'edge': row.edge,
262
+ 'reff': row.reff,
263
+ 'degen': row.degen,
264
+ 'formula': formula,
265
+ 'compound': compound,
266
+ 'description': row.description}
267
+ if with_geometry:
268
+ rowdat['geometry'] = json.loads(row.geometry)
269
+ out.append(rowdat)
270
+ return out
271
+
272
+
273
+ def add_person(self, email, name=None, password=''):
274
+ """add a person (email, optional name) to feffdat table:
275
+ used for ratings and to identify added feff.dat files
276
+ """
277
+ dbargs = {'email': email}
278
+ row = self.get_rows('person', where=dbargs,
279
+ limit_one=True, none_if_empty=True)
280
+ if row is not None:
281
+ if name is not None and name != row.name:
282
+ self.update('person', where=dbargs, name=name)
283
+ else:
284
+ print(f"warning person {email} already exisis")
285
+ else:
286
+ if name is not None:
287
+ dbargs['name'] = name
288
+ if password is None:
289
+ password = ''
290
+ if len(password) > 1:
291
+ password_ok = check_password_rules(password)
292
+ if not password_ok:
293
+ raise ValueError('Password is invalid')
294
+ dbargs['password'] = hash_password(password)
295
+
296
+ self.__addrow('person', dbargs)
297
+
298
+ def get_person(self, email):
299
+ """get person by email"""
300
+ return self.get_rows('person', where={'email': email},
301
+ limit_one=True, none_if_empty=True)
302
+
303
+ def check_person_password(self, email, password):
304
+ """check validity of a person's password"""
305
+ # if len(person
306
+
307
+ def get_feffdat_ratings(self, feffdat_id=None, email=None):
308
+ """get all ratings for feff.dat file either by feffdat_id or by email
309
+ """
310
+ args = {}
311
+ if feffdat_id is not None:
312
+ args['feffdat_id'] = feffdat_id
313
+ if email is not None:
314
+ person_row = self.get_person(email)
315
+ if person_row is not None:
316
+ args['person_row'] = person_row.id
317
+
318
+ return self.get_rows('feffdat_ratings', where=args)