ncrystal-python 3.9.81__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.
- NCrystal/__init__.py +85 -0
- NCrystal/__main__.py +98 -0
- NCrystal/_chooks.py +854 -0
- NCrystal/_cli_cif2ncmat.py +269 -0
- NCrystal/_cli_endf2ncmat.py +503 -0
- NCrystal/_cli_hfg2ncmat.py +144 -0
- NCrystal/_cli_mcstasunion.py +74 -0
- NCrystal/_cli_ncmat2cpp.py +31 -0
- NCrystal/_cli_ncmat2hkl.py +180 -0
- NCrystal/_cli_nctool.py +1018 -0
- NCrystal/_cli_vdos2ncmat.py +463 -0
- NCrystal/_cli_verifyatompos.py +257 -0
- NCrystal/_cliimpl.py +307 -0
- NCrystal/_cliwrap_config.py +36 -0
- NCrystal/_common.py +499 -0
- NCrystal/_coreimpl.py +114 -0
- NCrystal/_hfgdata.py +546 -0
- NCrystal/_hklobjects.py +136 -0
- NCrystal/_is_std.py +0 -0
- NCrystal/_locatelib.py +210 -0
- NCrystal/_miscimpl.py +354 -0
- NCrystal/_mmc.py +757 -0
- NCrystal/_msg.py +60 -0
- NCrystal/_ncmat2cpp_impl.py +445 -0
- NCrystal/_ncmatimpl.py +2131 -0
- NCrystal/_numpy.py +76 -0
- NCrystal/_testimpl.py +579 -0
- NCrystal/api.py +56 -0
- NCrystal/atomdata.py +177 -0
- NCrystal/cfgstr.py +77 -0
- NCrystal/cifutils.py +1795 -0
- NCrystal/cli.py +96 -0
- NCrystal/constants.py +134 -0
- NCrystal/core.py +1910 -0
- NCrystal/datasrc.py +226 -0
- NCrystal/exceptions.py +66 -0
- NCrystal/hfg2ncmat.py +270 -0
- NCrystal/mcstasutils.py +438 -0
- NCrystal/misc.py +317 -0
- NCrystal/mmc.py +35 -0
- NCrystal/ncmat.py +778 -0
- NCrystal/ncmat2cpp.py +80 -0
- NCrystal/obsolete.py +67 -0
- NCrystal/plot.py +484 -0
- NCrystal/plugins.py +49 -0
- NCrystal/test.py +76 -0
- NCrystal/vdos.py +1034 -0
- ncrystal_python-3.9.81.dist-info/LICENSE +206 -0
- ncrystal_python-3.9.81.dist-info/METADATA +515 -0
- ncrystal_python-3.9.81.dist-info/RECORD +53 -0
- ncrystal_python-3.9.81.dist-info/WHEEL +5 -0
- ncrystal_python-3.9.81.dist-info/entry_points.txt +10 -0
- ncrystal_python-3.9.81.dist-info/top_level.txt +1 -0
NCrystal/_common.py
ADDED
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
|
|
2
|
+
################################################################################
|
|
3
|
+
## ##
|
|
4
|
+
## This file is part of NCrystal (see https://mctools.github.io/ncrystal/) ##
|
|
5
|
+
## ##
|
|
6
|
+
## Copyright 2015-2024 NCrystal developers ##
|
|
7
|
+
## ##
|
|
8
|
+
## Licensed under the Apache License, Version 2.0 (the "License"); ##
|
|
9
|
+
## you may not use this file except in compliance with the License. ##
|
|
10
|
+
## You may obtain a copy of the License at ##
|
|
11
|
+
## ##
|
|
12
|
+
## http://www.apache.org/licenses/LICENSE-2.0 ##
|
|
13
|
+
## ##
|
|
14
|
+
## Unless required by applicable law or agreed to in writing, software ##
|
|
15
|
+
## distributed under the License is distributed on an "AS IS" BASIS, ##
|
|
16
|
+
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ##
|
|
17
|
+
## See the License for the specific language governing permissions and ##
|
|
18
|
+
## limitations under the License. ##
|
|
19
|
+
## ##
|
|
20
|
+
################################################################################
|
|
21
|
+
|
|
22
|
+
"""Module with internal utilities used by several NCrystal modules"""
|
|
23
|
+
|
|
24
|
+
#Common print function for NCrystal modules (allowing one to capture and
|
|
25
|
+
#redirect the output of NCrystal):
|
|
26
|
+
|
|
27
|
+
def _builtin_print():
|
|
28
|
+
import builtins
|
|
29
|
+
return builtins.print
|
|
30
|
+
|
|
31
|
+
_stdprint = [_builtin_print()]
|
|
32
|
+
|
|
33
|
+
def print(*args,**kwargs):
|
|
34
|
+
_stdprint[0](*args,**kwargs)
|
|
35
|
+
|
|
36
|
+
#TODO: Overlaps somewhat with the (more complete) _setMsgHandler from _msg.py:
|
|
37
|
+
def set_ncrystal_print_fct( fct ):
|
|
38
|
+
oldfct = _stdprint[0]
|
|
39
|
+
_stdprint[0] = fct or _builtin_print()
|
|
40
|
+
return oldfct
|
|
41
|
+
|
|
42
|
+
def get_ncrystal_print_fct():
|
|
43
|
+
return _stdprint[0]
|
|
44
|
+
|
|
45
|
+
_cache_ns = [None]
|
|
46
|
+
def get_namespace():
|
|
47
|
+
if _cache_ns[0] is None:
|
|
48
|
+
from ._locatelib import get_libpath_and_namespace as f
|
|
49
|
+
_p,_ns = f()
|
|
50
|
+
_cache_ns[0] = _ns
|
|
51
|
+
return _cache_ns[0]
|
|
52
|
+
|
|
53
|
+
_cache_nsev = [None]
|
|
54
|
+
def has_namespaced_envvars():
|
|
55
|
+
if _cache_nsev[0] is None:
|
|
56
|
+
import pathlib
|
|
57
|
+
_cache_nsev[0] = ( pathlib.Path(__file__).parent
|
|
58
|
+
.joinpath('_do_namespace_envvars.py').is_file() )
|
|
59
|
+
return _cache_nsev[0]
|
|
60
|
+
|
|
61
|
+
def expand_envname( name ):
|
|
62
|
+
assert not name.startswith('NCRYSTAL_')#common mistake
|
|
63
|
+
if has_namespaced_envvars():
|
|
64
|
+
return 'NCRYSTAL' + get_namespace().upper()+'_' + name
|
|
65
|
+
else:
|
|
66
|
+
return 'NCRYSTAL_' + name
|
|
67
|
+
|
|
68
|
+
def ncgetenv( name, defval = None ):
|
|
69
|
+
import os
|
|
70
|
+
return os.environ.get( expand_envname(name), defval )
|
|
71
|
+
|
|
72
|
+
def ncgetenv_bool( name ):
|
|
73
|
+
v = ncgetenv( name )
|
|
74
|
+
if v is None or v=='0':
|
|
75
|
+
return False
|
|
76
|
+
return True
|
|
77
|
+
|
|
78
|
+
def ncgetenv_int( name, defval ):
|
|
79
|
+
assert isinstance( defval, int)
|
|
80
|
+
v = ncgetenv( name )
|
|
81
|
+
if v is None:
|
|
82
|
+
return defval
|
|
83
|
+
try:
|
|
84
|
+
v=int(v)
|
|
85
|
+
except ValueError:
|
|
86
|
+
n = expand_envname( name )
|
|
87
|
+
raise ValueError(f"ERROR: {n} environment variable must"
|
|
88
|
+
" be integral value if set")
|
|
89
|
+
return v
|
|
90
|
+
|
|
91
|
+
def ncgetenv_int_nonneg( name, defval ):
|
|
92
|
+
assert isinstance( defval, int) and defval >= 0
|
|
93
|
+
v = ncgetenv_int( name, defval )
|
|
94
|
+
if v < 0:
|
|
95
|
+
n = expand_envname( name )
|
|
96
|
+
raise ValueError(f"ERROR: {n} environment variable must"
|
|
97
|
+
" be non-negative value if set")
|
|
98
|
+
return v
|
|
99
|
+
|
|
100
|
+
def ncsetenv( name, val ):
|
|
101
|
+
import os
|
|
102
|
+
assert not name.startswith('NCRYSTAL_')#common mistake
|
|
103
|
+
varname = 'NCRYSTAL' + get_namespace().upper()+'_' + name
|
|
104
|
+
if val is None:
|
|
105
|
+
if varname in os.environ:
|
|
106
|
+
del os.environ[varname]
|
|
107
|
+
else:
|
|
108
|
+
os.environ[varname] = str(val)
|
|
109
|
+
|
|
110
|
+
class modify_ncrystal_print_fct_ctxmgr:
|
|
111
|
+
"""Context manager for modifying the print function (for instance to block
|
|
112
|
+
output temporarily)"""
|
|
113
|
+
def __init__(self, fct ):
|
|
114
|
+
if fct == 'block':
|
|
115
|
+
def fct( *a, **kw ):
|
|
116
|
+
pass
|
|
117
|
+
self.__fct = fct
|
|
118
|
+
|
|
119
|
+
def __enter__(self):
|
|
120
|
+
self.__orig = get_ncrystal_print_fct()
|
|
121
|
+
set_ncrystal_print_fct( self.__fct )
|
|
122
|
+
|
|
123
|
+
def __exit__(self,*args,**kwargs):
|
|
124
|
+
set_ncrystal_print_fct( self.__orig )
|
|
125
|
+
self.__orig = None
|
|
126
|
+
self.__fct = None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def warn(msg):
|
|
130
|
+
"""Emit NCrystalUserWarning via standard warnings.warn function"""
|
|
131
|
+
from .exceptions import NCrystalUserWarning
|
|
132
|
+
import warnings
|
|
133
|
+
warnings.warn( NCrystalUserWarning(str(msg)), stacklevel = 2 )
|
|
134
|
+
|
|
135
|
+
class WarningSpy:
|
|
136
|
+
"""Context manager which spies on any warnings emitted via warnings
|
|
137
|
+
module and returns list of messages and categories.
|
|
138
|
+
|
|
139
|
+
It does so by temporarily intercepting the warnings.showwarning
|
|
140
|
+
function.
|
|
141
|
+
|
|
142
|
+
Usage example:
|
|
143
|
+
with WarningSpy() as warnlist:
|
|
144
|
+
fct()
|
|
145
|
+
print( "Warnings emitted:", warnlist )
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
def __init__(self,block=False,blockfct = None):
|
|
149
|
+
assert not ( bool(block) and blockfct )
|
|
150
|
+
self.__block = bool(block)
|
|
151
|
+
self.__blockfct = blockfct
|
|
152
|
+
self.__state = None
|
|
153
|
+
|
|
154
|
+
def __enter__(self):
|
|
155
|
+
#Make sure we create our own warnings.catch_warnings context
|
|
156
|
+
#manager. This not only has the advantage of automatically restoring the
|
|
157
|
+
#global warnings states (including the showwarning and simplefilter
|
|
158
|
+
#function) upon __exit__, but also means that global warning
|
|
159
|
+
#suppressions etc. will be temporarily disabled.
|
|
160
|
+
self.__state = {}
|
|
161
|
+
s = self.__state
|
|
162
|
+
import warnings
|
|
163
|
+
s['warncntxmgr'] = warnings.catch_warnings()
|
|
164
|
+
s['warncntxmgr'].__enter__()
|
|
165
|
+
s['orig'] = warnings.showwarning
|
|
166
|
+
warnings.showwarning = self.__spy
|
|
167
|
+
warnings.simplefilter("always")
|
|
168
|
+
s['l'] = []
|
|
169
|
+
return s['l']
|
|
170
|
+
|
|
171
|
+
def __exit__(self,*args,**kwargs):
|
|
172
|
+
assert self.__state
|
|
173
|
+
import warnings
|
|
174
|
+
warnings.showwarning = self.__state['orig']#not strictly needed since next line will also restore it
|
|
175
|
+
self.__state['warncntxmgr'].__exit__()
|
|
176
|
+
self.__state = None
|
|
177
|
+
|
|
178
|
+
@staticmethod
|
|
179
|
+
def _fmtwarning( message, category ):
|
|
180
|
+
catname = None
|
|
181
|
+
if category is not None:
|
|
182
|
+
catname = str(getattr(category,'__name__',category)).strip()
|
|
183
|
+
elif isinstance(message,Warning):
|
|
184
|
+
catname = str(message.__class__.__name__).strip()
|
|
185
|
+
if not catname:
|
|
186
|
+
catname='Warning'
|
|
187
|
+
return ( ( str(message).strip() or '<no message>'), catname )
|
|
188
|
+
|
|
189
|
+
def __spy( self, message, category, *args, **kwargs ):
|
|
190
|
+
#only store string objects, to decouple lifetimes of more complex objects:
|
|
191
|
+
msg_str, cat_str = WarningSpy._fmtwarning( message, category )
|
|
192
|
+
self.__state['l'].append( ( msg_str, cat_str ) )
|
|
193
|
+
do_block = self.__block or ( self.__blockfct and self.__blockfct(msg_str, cat_str) )
|
|
194
|
+
if not do_block:
|
|
195
|
+
return self.__state['orig']( message, category, *args, **kwargs )
|
|
196
|
+
|
|
197
|
+
def find_fraction( x, tol = 1e-15, max_denom = 1000000 ):
|
|
198
|
+
import fractions
|
|
199
|
+
f = fractions.Fraction.from_float( x )
|
|
200
|
+
f = f.limit_denominator(max_denom)
|
|
201
|
+
if hasattr(f,'as_integer_ratio'):
|
|
202
|
+
#py 3.8+
|
|
203
|
+
a,b = f.as_integer_ratio()
|
|
204
|
+
else:
|
|
205
|
+
a,b = f.numerator,f.denominator
|
|
206
|
+
if abs( a / b - x ) < tol:
|
|
207
|
+
return a,b
|
|
208
|
+
else:
|
|
209
|
+
return None
|
|
210
|
+
|
|
211
|
+
def prettyFmtValue(x):
|
|
212
|
+
"""Recognises common fractions in truncated values like '0.33333333' or
|
|
213
|
+
'0.4166667' and formats them as '1/3' and '5/12'. This not only saves space,
|
|
214
|
+
but can also reinject accuracy in atom positions (ncrystal_verifyatompos can
|
|
215
|
+
be used to check this, but it seems to be a clear improvement in all cases
|
|
216
|
+
tested so far). Additionally also performs optimisations for storage
|
|
217
|
+
efficiency, in particular omitting leading 0s with no information value
|
|
218
|
+
(e.g. "0.2341" becomes ".2341").
|
|
219
|
+
|
|
220
|
+
"""
|
|
221
|
+
assert 0.0 <= x <= 1.0
|
|
222
|
+
if x==0.0:
|
|
223
|
+
return '0'
|
|
224
|
+
if x==1.0:
|
|
225
|
+
return '1'
|
|
226
|
+
if x==0.5:
|
|
227
|
+
return '1/2'
|
|
228
|
+
def stripleading0(s):
|
|
229
|
+
return ( s[1:] if (len(s) > 2 and s.startswith('0.')) else s )
|
|
230
|
+
xfmt = stripleading0('%.13g'%x)#.14g leads to irreproducibility issues in
|
|
231
|
+
#our tests but there are sooo many numbers ending with 3333.... or
|
|
232
|
+
#666667.... that we can safely "snap" these to their correct values:
|
|
233
|
+
if xfmt[0]=='.' and xfmt.endswith('33333') and len(xfmt)<18:
|
|
234
|
+
xfmt += '3'*(18-len(xfmt))
|
|
235
|
+
if xfmt[0]=='.' and xfmt.endswith('66667') and len(xfmt)<18:
|
|
236
|
+
xfmt = xfmt[:-1]+'6'*(18-len(xfmt))+'7'
|
|
237
|
+
ff = find_fraction( x, tol = 1e-14, max_denom = 40 )
|
|
238
|
+
if ff is not None and ff[1]!=1:
|
|
239
|
+
assert ff[1] > 0
|
|
240
|
+
v = ff[0] / ff[1]
|
|
241
|
+
v_dbfmt = f'{ff[0]}/{ff[1]}'
|
|
242
|
+
#check if credible that the numbers are identical (must be close AND the str
|
|
243
|
+
#representations must be consistent with truncation):
|
|
244
|
+
expected_at_prec = stripleading0((f'%.{len(xfmt)-1}g')%v)
|
|
245
|
+
if abs(v-x)<1e-7 and expected_at_prec == xfmt:
|
|
246
|
+
return v_dbfmt
|
|
247
|
+
if abs(v-x)<1e-14:
|
|
248
|
+
return v_dbfmt
|
|
249
|
+
#No fraction found:
|
|
250
|
+
if xfmt.isdigit() and float( xfmt ) != x:
|
|
251
|
+
#abort, we don't want 0.99999999999 to print as '1' (which would then be
|
|
252
|
+
#mapped to 0 in a unit cell):
|
|
253
|
+
return stripleading0('%.19g'%x)
|
|
254
|
+
return xfmt
|
|
255
|
+
|
|
256
|
+
def _split_trailing_digit( s ):
|
|
257
|
+
if not s or not s[-1].isdigit():
|
|
258
|
+
return s,None
|
|
259
|
+
n=0
|
|
260
|
+
while (n+1)<len(s) and s[-(n+1)].isdigit():
|
|
261
|
+
n += 1
|
|
262
|
+
if n:
|
|
263
|
+
return s[0:-n], int( s[-n:] )
|
|
264
|
+
else:
|
|
265
|
+
return s, None
|
|
266
|
+
|
|
267
|
+
def check_elem_or_isotope_marker( s ):
|
|
268
|
+
"""If input is of form "Al", "O16", ..., return it, otherwise return
|
|
269
|
+
None. Ignores excess whitespace in input. Will also map "H2"->"D" and
|
|
270
|
+
"H3"->"T".
|
|
271
|
+
"""
|
|
272
|
+
s=' '.join(str(s).split())
|
|
273
|
+
if not s:
|
|
274
|
+
return None
|
|
275
|
+
elem_name, isotope_val = _split_trailing_digit( s )
|
|
276
|
+
|
|
277
|
+
#special support for D,T:
|
|
278
|
+
if not isotope_val and elem_name in ('D','T'):
|
|
279
|
+
return elem_name
|
|
280
|
+
if elem_name == 'H' and isotope_val in (2,3):
|
|
281
|
+
return 'D' if isotope_val == 2 else 'T'
|
|
282
|
+
|
|
283
|
+
from .atomdata import isElementName
|
|
284
|
+
|
|
285
|
+
if isElementName( elem_name) and ( isotope_val is None or ( 0 <= isotope_val < 999 ) ):
|
|
286
|
+
return elem_name if not isotope_val else '%s%i'%(elem_name,isotope_val)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _hill_sort( chemform ):
|
|
290
|
+
#takes chemform like [('Al',2'),('O',3)] and sorts in order of Hill system notation
|
|
291
|
+
|
|
292
|
+
#Remap H2/H3 and remove duplicates:
|
|
293
|
+
remap = {'H2':'D','H3':'T'}
|
|
294
|
+
if any(k in remap for k,v in chemform) or len(set(k for k,v in chemform))!=len(chemform):
|
|
295
|
+
d={}
|
|
296
|
+
for k,v in chemform:
|
|
297
|
+
k = remap.get(k,k)
|
|
298
|
+
d[k] = d.setdefault(k,0) + v
|
|
299
|
+
return _hill_sort( list(d.items()) )
|
|
300
|
+
|
|
301
|
+
has_carbon = any( en=='C' for en,c in chemform )
|
|
302
|
+
if not has_carbon:
|
|
303
|
+
return list( sorted( chemform ) )
|
|
304
|
+
def hillsortkey( e ):
|
|
305
|
+
#if not has carbon, then all in alphabetical order
|
|
306
|
+
#first carbon, then H/D/T, then in alphabetical order
|
|
307
|
+
if e[0]=='C':
|
|
308
|
+
return ( -999999, e )
|
|
309
|
+
is_hydrogen = ( e[0] in ('H','D','T') or ( e[0].startswith('H') and e[0][1:].isdigit() ) )
|
|
310
|
+
return ( -1, e ) if is_hydrogen else (0, e )
|
|
311
|
+
return list(sorted(chemform,key=hillsortkey))
|
|
312
|
+
|
|
313
|
+
def _gcd( *vals ):
|
|
314
|
+
import math
|
|
315
|
+
if len(vals)<2:
|
|
316
|
+
return vals[0] if vals else None
|
|
317
|
+
gcd = math.gcd(vals[0],vals[1])
|
|
318
|
+
for e in vals[2:]:
|
|
319
|
+
gcd = math.gcd( gcd, e )
|
|
320
|
+
return gcd
|
|
321
|
+
|
|
322
|
+
def format_chemform( chemform, *, allow_rescaling = True ):
|
|
323
|
+
#takes chemform like [('Al',2),('O',3)] and returns nicely formatted string
|
|
324
|
+
#"Al2O3", with no duplicated element names and sorted according to the Hill
|
|
325
|
+
#system of notation. Additionally, integral counts are divided by greatest
|
|
326
|
+
#common divisor, and an attempt is made to scale up non-integral counts
|
|
327
|
+
#where it makes sense (e.g. Al0.5O1.5 becomes AlO3).
|
|
328
|
+
if len(chemform)==1:
|
|
329
|
+
return str(chemform[0][0])
|
|
330
|
+
cf = _hill_sort(chemform)
|
|
331
|
+
def is_near_int(v):
|
|
332
|
+
return abs(int(v)-v)<1e-15
|
|
333
|
+
def get_non_ints(_l):
|
|
334
|
+
return [ v for k,v in _l if not is_near_int(v) ]
|
|
335
|
+
|
|
336
|
+
non_ints = get_non_ints( cf )
|
|
337
|
+
def _find_frac(x):
|
|
338
|
+
return find_fraction( x, max_denom = 100 )
|
|
339
|
+
non_int_ffractions = [ _find_frac(x) for x in non_ints ]
|
|
340
|
+
denoms = [ ff[1] for ff in non_int_ffractions if ff is not None ]
|
|
341
|
+
cf_alt = None
|
|
342
|
+
if allow_rescaling and denoms:
|
|
343
|
+
import math
|
|
344
|
+
factor = 1
|
|
345
|
+
while denoms:
|
|
346
|
+
_d = denoms.pop(0)
|
|
347
|
+
_g = math.gcd( factor, _d )
|
|
348
|
+
factor *= ( _d // _g )
|
|
349
|
+
_cf = []
|
|
350
|
+
for k,v in cf:
|
|
351
|
+
if is_near_int(v):
|
|
352
|
+
_cf.append( (k,v*factor ) )
|
|
353
|
+
else:
|
|
354
|
+
ff = _find_frac( v )
|
|
355
|
+
if ff is None:
|
|
356
|
+
_cf.append( (k,v*factor ) )
|
|
357
|
+
else:
|
|
358
|
+
assert factor % ff[1] == 0
|
|
359
|
+
_cf.append( (k,ff[0] * ( factor // ff[1] ) ) )
|
|
360
|
+
if max( v for k,v in _cf ) < 100:
|
|
361
|
+
cf_alt = _cf
|
|
362
|
+
|
|
363
|
+
#find and apply gcd of all integers:
|
|
364
|
+
def final_format( the_cf ):
|
|
365
|
+
ll=[ int(v) for k,v in the_cf if is_near_int(v) ]
|
|
366
|
+
gcd = _gcd( *ll ) if ( ll and allow_rescaling ) else 1
|
|
367
|
+
def wrapiso( x ):
|
|
368
|
+
#nb: these curly braces are not great for filenames...:
|
|
369
|
+
return x if not x[-1].isdigit() else '{%s}'%x
|
|
370
|
+
the_cf = [ (wrapiso(en),(count//gcd if is_near_int(count) else count/gcd)) for en,count in the_cf ]
|
|
371
|
+
return ''.join( (en if count==1 else '%s%g'%(en,int(count) if count==int(count) else count)) for en,count in the_cf )
|
|
372
|
+
|
|
373
|
+
f1 = final_format(cf)
|
|
374
|
+
f2 = final_format(cf_alt) if cf_alt else f1
|
|
375
|
+
return f1 if len(f1) < len(f2) else f2
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def _classifySG(sgno):
|
|
379
|
+
assert 1<=sgno<=230
|
|
380
|
+
ll=[(195,'cubic'),(168,'hexagonal'),(143,'trigonal'),
|
|
381
|
+
(75,'tetragonal'),(16,'orthorombic'),(3,'monoclinic'),(1,'triclinic')]
|
|
382
|
+
for thr,nme in ll:
|
|
383
|
+
if sgno>=thr:
|
|
384
|
+
return nme
|
|
385
|
+
assert False
|
|
386
|
+
|
|
387
|
+
#colors inspired by http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
|
|
388
|
+
#supposedly from Stephen Few's book, "Show Me the Numbers":
|
|
389
|
+
_palette_Few = dict(red = "#F15854",
|
|
390
|
+
blue="#5DA5DA",
|
|
391
|
+
orange="#FAA43A",
|
|
392
|
+
green="#60BD68",
|
|
393
|
+
brown="#B2912F",
|
|
394
|
+
purple="#B276B2",
|
|
395
|
+
yellow="#DECF3F",
|
|
396
|
+
pink="#F17CB0",
|
|
397
|
+
gray="#4D4D4D")
|
|
398
|
+
|
|
399
|
+
def _grid_is_linspace( grid, tol = 1e-6 ):
|
|
400
|
+
if len(grid)<=2:
|
|
401
|
+
return len(grid)==2
|
|
402
|
+
from ._numpy import _ensure_numpy, _np
|
|
403
|
+
_ensure_numpy()
|
|
404
|
+
g = _np.asarray(grid,dtype=float)
|
|
405
|
+
bws = g[1:] - g[:-1]
|
|
406
|
+
bmin, bmax = bws.min(), bws.max()
|
|
407
|
+
if ( not ( bmax >= bmin > 0.0 ) ) or _np.isinf(bmin) or _np.isinf(bmax):
|
|
408
|
+
return False
|
|
409
|
+
return ( bmax - bmin ) < tol * ( bmax + bmin )
|
|
410
|
+
|
|
411
|
+
def extract_path( s ):
|
|
412
|
+
"""Returns pathlib.Path path object from argument if it is a path, otherwise
|
|
413
|
+
None. Strings with no newlines will be assumed to be paths."""
|
|
414
|
+
import pathlib
|
|
415
|
+
if hasattr( s, '__fspath__' ):
|
|
416
|
+
return pathlib.Path(s)
|
|
417
|
+
if isinstance( s, str ) and '\n' not in s:
|
|
418
|
+
res_try = _lookup_existing_file(s)
|
|
419
|
+
if res_try:
|
|
420
|
+
return res_try
|
|
421
|
+
return pathlib.Path(s)
|
|
422
|
+
|
|
423
|
+
def download_url( url, decode_as_utf8_str = True, wrap_exception = True ):
|
|
424
|
+
import urllib.request
|
|
425
|
+
import urllib.error
|
|
426
|
+
try:
|
|
427
|
+
req = urllib.request.Request(url)
|
|
428
|
+
with urllib.request.urlopen(req) as response:
|
|
429
|
+
data = response.read()
|
|
430
|
+
except urllib.error.URLError as e:
|
|
431
|
+
if wrap_exception:
|
|
432
|
+
from .exceptions import NCException
|
|
433
|
+
raise NCException(f'Error downloading url "{url}": {e}')
|
|
434
|
+
else:
|
|
435
|
+
raise e
|
|
436
|
+
if decode_as_utf8_str:
|
|
437
|
+
try:
|
|
438
|
+
data = data.decode('utf8')
|
|
439
|
+
except UnicodeDecodeError as e:
|
|
440
|
+
if wrap_exception:
|
|
441
|
+
from .exceptions import NCException
|
|
442
|
+
raise NCException(f'Error decoding url to utf8 data "{url}": {e}')
|
|
443
|
+
else:
|
|
444
|
+
raise e
|
|
445
|
+
return data
|
|
446
|
+
|
|
447
|
+
def _decodeflt(s):
|
|
448
|
+
import numbers
|
|
449
|
+
if s is None:
|
|
450
|
+
return None
|
|
451
|
+
if isinstance(s,numbers.Real):
|
|
452
|
+
return float(s)
|
|
453
|
+
try:
|
|
454
|
+
x = float( s )
|
|
455
|
+
except (TypeError,ValueError):
|
|
456
|
+
return None
|
|
457
|
+
return x
|
|
458
|
+
|
|
459
|
+
def _calc_md5hexdigest( str_or_bytes, / ):
|
|
460
|
+
import hashlib
|
|
461
|
+
if hasattr(str_or_bytes,'encode'):
|
|
462
|
+
data = str_or_bytes.encode('utf8',errors='backslashreplace')
|
|
463
|
+
else:
|
|
464
|
+
data = str_or_bytes
|
|
465
|
+
return hashlib.md5( data ).hexdigest()
|
|
466
|
+
|
|
467
|
+
def write_text( path, content ):
|
|
468
|
+
"""Like path.write_text(content) but forcing some global settings
|
|
469
|
+
to ensure consistent NCrystal behaviour on any platform. Specifically
|
|
470
|
+
encoding='utf8' and newline='\n'"""
|
|
471
|
+
#The newline parameter was only added for pathlib.Path.write_text in python
|
|
472
|
+
#3.10, so we do instead:
|
|
473
|
+
import pathlib
|
|
474
|
+
with pathlib.Path(path).open( 'wt',
|
|
475
|
+
encoding = 'utf8',
|
|
476
|
+
newline='\n' ) as fh:
|
|
477
|
+
fh.write(content)
|
|
478
|
+
|
|
479
|
+
def _lookup_existing_file( path ):
|
|
480
|
+
"""Will wrap in pathlib.Path, except in simplebuild development mode where
|
|
481
|
+
it will try $SBLD_DATA_DIR/+path when the path is a string with a single
|
|
482
|
+
path separator. Returns None if unable to find an existing file at the
|
|
483
|
+
location, otherwise an absolute path to the file.
|
|
484
|
+
|
|
485
|
+
"""
|
|
486
|
+
import pathlib
|
|
487
|
+
p = pathlib.Path( path ).expanduser()
|
|
488
|
+
if p.exists():
|
|
489
|
+
return p.absolute()
|
|
490
|
+
if ( isinstance( path, str )
|
|
491
|
+
and path.count('/')==1
|
|
492
|
+
and ( pathlib.Path(__file__).parent
|
|
493
|
+
.joinpath('_is_sblddevel.py').is_file() ) ):
|
|
494
|
+
import os
|
|
495
|
+
dd = os.environ.get('SBLD_DATA_DIR')
|
|
496
|
+
if dd:
|
|
497
|
+
p = pathlib.Path(dd).joinpath(path)
|
|
498
|
+
if p.exists():
|
|
499
|
+
return p.absolute()
|
NCrystal/_coreimpl.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
|
|
2
|
+
################################################################################
|
|
3
|
+
## ##
|
|
4
|
+
## This file is part of NCrystal (see https://mctools.github.io/ncrystal/) ##
|
|
5
|
+
## ##
|
|
6
|
+
## Copyright 2015-2024 NCrystal developers ##
|
|
7
|
+
## ##
|
|
8
|
+
## Licensed under the Apache License, Version 2.0 (the "License"); ##
|
|
9
|
+
## you may not use this file except in compliance with the License. ##
|
|
10
|
+
## You may obtain a copy of the License at ##
|
|
11
|
+
## ##
|
|
12
|
+
## http://www.apache.org/licenses/LICENSE-2.0 ##
|
|
13
|
+
## ##
|
|
14
|
+
## Unless required by applicable law or agreed to in writing, software ##
|
|
15
|
+
## distributed under the License is distributed on an "AS IS" BASIS, ##
|
|
16
|
+
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ##
|
|
17
|
+
## See the License for the specific language governing permissions and ##
|
|
18
|
+
## limitations under the License. ##
|
|
19
|
+
## ##
|
|
20
|
+
################################################################################
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
Internal implementation details for functionality in core.py
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
__all__ = []
|
|
29
|
+
|
|
30
|
+
class divdos_methods:
|
|
31
|
+
|
|
32
|
+
"""Common methods shared between DI_VDOS and DI_VDOSDebye"""
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def _plot_Gn():
|
|
36
|
+
def plot_Gn( _self, n, nmax=None, without_xsect = False, **plotkwargs ):
|
|
37
|
+
"""Calculate and plot Sjolander's Gn function of n'th order. If
|
|
38
|
+
without_xsect is True, the curves do not include a factor of the
|
|
39
|
+
bound scattering cross section. Internally, the plots are produced
|
|
40
|
+
with NCrystal.plot.plot_vdos_Gn, and any plotkwargs are passed along
|
|
41
|
+
to that function
|
|
42
|
+
"""
|
|
43
|
+
assert 1 <= n <= 99999 and ( nmax is None or n<=nmax<=99999 )
|
|
44
|
+
n2 = int( nmax if nmax is not None else n )
|
|
45
|
+
from .vdos import extractGn
|
|
46
|
+
from .plot import plot_vdos_Gn
|
|
47
|
+
def f(_n):
|
|
48
|
+
return extractGn( n=_n, vdos = _self, mass_amu = _self.atomData.averageMassAMU(),
|
|
49
|
+
temperature = _self.temperature, expand_egrid = True,
|
|
50
|
+
scatxs = 1.0 if without_xsect else _self.atomData.scatteringXS() )
|
|
51
|
+
plot_vdos_Gn( [ (list(f(i))+[i]) for i in range(int(n),n2+1) ], **plotkwargs )
|
|
52
|
+
return plot_Gn
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def _extract_Gn():
|
|
56
|
+
def extract_Gn( _self, n, *, expand_egrid = True, without_xsect = False ):
|
|
57
|
+
"""Calculate Sjolander's Gn function of n'th order. If expand_egrid
|
|
58
|
+
returns (egrid,Gn_values), otherwise ((emin,emax),Gn_values). If
|
|
59
|
+
without_xsect is True, the result will not be multiplied by the
|
|
60
|
+
bound scattering cross section.
|
|
61
|
+
"""
|
|
62
|
+
assert 1 <= n <= 99999
|
|
63
|
+
from .vdos import extractGn as _extgn
|
|
64
|
+
return _extgn( _self, n=int(n),
|
|
65
|
+
mass_amu=_self.atomData.averageMassAMU(),
|
|
66
|
+
temperature=_self.temperature,
|
|
67
|
+
scatxs = 1.0 if without_xsect else _self.atomData.scatteringXS(),
|
|
68
|
+
expand_egrid = expand_egrid )
|
|
69
|
+
return extract_Gn
|
|
70
|
+
|
|
71
|
+
@staticmethod
|
|
72
|
+
def _extract_custom_knl():
|
|
73
|
+
def extract_custom_knl( _self,
|
|
74
|
+
vdoslux = 3,
|
|
75
|
+
order_weight_fct = None,
|
|
76
|
+
without_xsect = False,
|
|
77
|
+
plot = False,
|
|
78
|
+
**plotkwargs ):
|
|
79
|
+
"""Extract (and optionally plot) a scattering kernel based on the
|
|
80
|
+
VDOS curve. See the NCrystal.vdos.extractKnl function for a
|
|
81
|
+
description of the arguments, the only difference being that the
|
|
82
|
+
mass_amu, temperature, and scatxs parameters do not need to be
|
|
83
|
+
provided here, and the without_xsects parameter can be used to avoid
|
|
84
|
+
having the kernel include a factor of the bound scattering cross
|
|
85
|
+
section.
|
|
86
|
+
"""
|
|
87
|
+
from .vdos import extractKnl
|
|
88
|
+
return extractKnl( _self,
|
|
89
|
+
vdoslux = vdoslux,
|
|
90
|
+
mass_amu = _self.atomData.averageMassAMU(),
|
|
91
|
+
temperature = _self.temperature,
|
|
92
|
+
scatxs = 1.0 if without_xsect else _self.atomData.scatteringXS(),
|
|
93
|
+
order_weight_fct = order_weight_fct,
|
|
94
|
+
plot = plot, **plotkwargs )
|
|
95
|
+
|
|
96
|
+
return extract_custom_knl
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def _plot_vdos():
|
|
100
|
+
def plot_vdos( _self, **kwargs ):
|
|
101
|
+
"""Plot VDOS using the NCrystal.plot.plot_vdos function. Any
|
|
102
|
+
kwargs are simply passed along."""
|
|
103
|
+
from .plot import plot_vdos
|
|
104
|
+
plot_vdos( _self, **kwargs )
|
|
105
|
+
return plot_vdos
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
def _plot_knl():
|
|
109
|
+
def plot_knl( self, vdoslux = 3, **kwargs ):
|
|
110
|
+
"""Plot the scattering kernel using the NCrystal.plot.plot_knl
|
|
111
|
+
function. Any kwargs are simply passed along."""
|
|
112
|
+
from .plot import plot_knl
|
|
113
|
+
plot_knl( self._loadKernel( vdoslux=vdoslux ), **( kwargs or {}) )
|
|
114
|
+
return plot_knl
|