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 +7 -0
- feffdb/apps.py +77 -0
- feffdb/feffdat.db +0 -0
- feffdb/feffdb.py +318 -0
- feffdb/feffpath.py +559 -0
- feffdb/schema.py +80 -0
- feffdb/simpledb.py +373 -0
- feffdb/utils.py +359 -0
- feffdb/version.py +24 -0
- feffdb-0.1.dist-info/METADATA +62 -0
- feffdb-0.1.dist-info/RECORD +15 -0
- feffdb-0.1.dist-info/WHEEL +5 -0
- feffdb-0.1.dist-info/entry_points.txt +2 -0
- feffdb-0.1.dist-info/licenses/LICENSE +28 -0
- feffdb-0.1.dist-info/top_level.txt +1 -0
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)
|