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.
Files changed (53) hide show
  1. NCrystal/__init__.py +85 -0
  2. NCrystal/__main__.py +98 -0
  3. NCrystal/_chooks.py +854 -0
  4. NCrystal/_cli_cif2ncmat.py +269 -0
  5. NCrystal/_cli_endf2ncmat.py +503 -0
  6. NCrystal/_cli_hfg2ncmat.py +144 -0
  7. NCrystal/_cli_mcstasunion.py +74 -0
  8. NCrystal/_cli_ncmat2cpp.py +31 -0
  9. NCrystal/_cli_ncmat2hkl.py +180 -0
  10. NCrystal/_cli_nctool.py +1018 -0
  11. NCrystal/_cli_vdos2ncmat.py +463 -0
  12. NCrystal/_cli_verifyatompos.py +257 -0
  13. NCrystal/_cliimpl.py +307 -0
  14. NCrystal/_cliwrap_config.py +36 -0
  15. NCrystal/_common.py +499 -0
  16. NCrystal/_coreimpl.py +114 -0
  17. NCrystal/_hfgdata.py +546 -0
  18. NCrystal/_hklobjects.py +136 -0
  19. NCrystal/_is_std.py +0 -0
  20. NCrystal/_locatelib.py +210 -0
  21. NCrystal/_miscimpl.py +354 -0
  22. NCrystal/_mmc.py +757 -0
  23. NCrystal/_msg.py +60 -0
  24. NCrystal/_ncmat2cpp_impl.py +445 -0
  25. NCrystal/_ncmatimpl.py +2131 -0
  26. NCrystal/_numpy.py +76 -0
  27. NCrystal/_testimpl.py +579 -0
  28. NCrystal/api.py +56 -0
  29. NCrystal/atomdata.py +177 -0
  30. NCrystal/cfgstr.py +77 -0
  31. NCrystal/cifutils.py +1795 -0
  32. NCrystal/cli.py +96 -0
  33. NCrystal/constants.py +134 -0
  34. NCrystal/core.py +1910 -0
  35. NCrystal/datasrc.py +226 -0
  36. NCrystal/exceptions.py +66 -0
  37. NCrystal/hfg2ncmat.py +270 -0
  38. NCrystal/mcstasutils.py +438 -0
  39. NCrystal/misc.py +317 -0
  40. NCrystal/mmc.py +35 -0
  41. NCrystal/ncmat.py +778 -0
  42. NCrystal/ncmat2cpp.py +80 -0
  43. NCrystal/obsolete.py +67 -0
  44. NCrystal/plot.py +484 -0
  45. NCrystal/plugins.py +49 -0
  46. NCrystal/test.py +76 -0
  47. NCrystal/vdos.py +1034 -0
  48. ncrystal_python-3.9.81.dist-info/LICENSE +206 -0
  49. ncrystal_python-3.9.81.dist-info/METADATA +515 -0
  50. ncrystal_python-3.9.81.dist-info/RECORD +53 -0
  51. ncrystal_python-3.9.81.dist-info/WHEEL +5 -0
  52. ncrystal_python-3.9.81.dist-info/entry_points.txt +10 -0
  53. 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