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/ncmat2cpp.py ADDED
@@ -0,0 +1,80 @@
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 providing utilities for embedding text data in C++ code"""
23
+
24
+ def ncmat2cpp( *input_files_or_text_data,
25
+ quiet = False,
26
+ cppfunctionname='registerData',
27
+ compact=False,
28
+ width=140,
29
+ validate=False,
30
+ extra_includes=None,
31
+ outfile = None,
32
+ regfctname=None ):
33
+ """Function which can be used to embed the content of .ncmat files (or
34
+ actually any ASCII/UTF8 excoded text files) directly into a C++ library. It
35
+ does so by reading the files and creating C++ code which keeps the contents
36
+ of the files in static UTF8-encoded strings, and registers those strings
37
+ with the NCrystal C++ API, using the original filename as key. Naturally,
38
+ for this to work the resulting C++ code should be stored in a file, and that
39
+ file must be compiled along with the rest of the users C++ code, and the
40
+ enclosing function must be invoked.
41
+
42
+ Note that despite the name of this function, it can actually be used to
43
+ process any text string.
44
+
45
+ If outfile is not None, the contents will be stored in that file. In any
46
+ case, the C++ code will be returned as a string.
47
+
48
+ if regfctname is None, the C++ function used for registering the in-memory
49
+ file data will be assumed to be
50
+ "NCrystal::registerInMemoryStaticFileData(const std::string&,const char*)".
51
+
52
+ For a meaning of the the other parameters, see 'ncrystal_ncmat2cpp --help'.
53
+
54
+ """
55
+ #NOTE: The above doc-string should be kept in sync with argparse help text
56
+ #in _ncmat2cpp_impl.py.
57
+
58
+ #NOTE: ncrystal_ncmat2cpp is a special command-line script, since the
59
+ #NCrystal CMake code needs to be able to invoke _cli_ncmat2cpp.py directly
60
+ #as a standalone script, in a mode where no other of the NCrystal python
61
+ #modules are imported. Therefore, all of the actual code implementing the
62
+ #ncmat2cpp functionality needs to reside in _cli_ncmat2cpp.py, rather than
63
+ #here in ncmat2cpp.py. This is the opposite of how most other command-line
64
+ #scripts are implemented, with the bulk of the implementation residing in
65
+ #the Python API, and the command-line script being a wrapper around it.
66
+
67
+ if not input_files_or_text_data:
68
+ from .exceptions import NCBadInput
69
+ raise NCBadInput('No files or text data provided')
70
+
71
+ from ._ncmat2cpp_impl import files2cppcode
72
+ return files2cppcode( infiles = input_files_or_text_data,
73
+ quiet = quiet,
74
+ outfile = outfile,
75
+ cppfunctionname = cppfunctionname,
76
+ compact = compact,
77
+ width = width,
78
+ validate = validate,
79
+ extra_includes = extra_includes,
80
+ regfctname = regfctname )
NCrystal/obsolete.py ADDED
@@ -0,0 +1,67 @@
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
+ Obsolete functions
25
+
26
+ """
27
+
28
+ from ._common import ncgetenv_bool as _ncgetenv_bool
29
+
30
+ def decodecfg_packfact(cfgstr):
31
+ """OBSOLETE FUNCTION (always returns 1.0 now)."""
32
+ from . import _common as nc_common
33
+ nc_common.warn('The decodecfg_packfact function is obsolete and now always'
34
+ ' returns 1.0. It will be removed in a future release.')
35
+ return 1.0
36
+
37
+ def getFileContents(name):
38
+ """OBSOLETE FUNCTION: Use createTextData(..).rawData instead."""
39
+ from . import _common as nc_common
40
+ nc_common.warn('The getFileContents(name) function is obsolete. Please use the'
41
+ ' createTextData function instead (specifically calling '
42
+ 'createTextData(name).rawData will produce the same results getFileContents(name).')
43
+ from .core import createTextData
44
+ return createTextData(name).rawData
45
+
46
+ def clearInfoCaches():
47
+ """Deprecated. Does the same as clearCaches()"""
48
+ from . import _common as nc_common
49
+ nc_common.warn('The clearInfoCaches function is deprecated. Please'
50
+ ' call the clearCaches() function instead.')
51
+ from .core import clearCaches
52
+ clearCaches()
53
+
54
+ def disableCaching():
55
+ """Obsolete function. Instead call clearCaches() as needed."""
56
+ raise RuntimeError('The disableCaching() function has been obsoleted and no longer works. Users can'
57
+ +' instead call the clearCaches() function if really needed to clear the caches.')
58
+
59
+ def enableCaching():
60
+ """Obsolete function. Instead call clearCaches() as needed."""
61
+ raise RuntimeError('The enableCaching function has been removed. Users can'
62
+ +' instead call the clearCaches function if really needed to clear the caches.')
63
+
64
+ if not _ncgetenv_bool('NOPYOBSOLETE'):
65
+ _ = globals()
66
+ for _k in [_k for _k in _.keys() if ( hasattr(_k,'startswith') and not _k.startswith('_') )]:
67
+ del _[_k]
NCrystal/plot.py ADDED
@@ -0,0 +1,484 @@
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
+ Utility functions related to plotting.
25
+
26
+ """
27
+
28
+ def plot_xsect( material, *, mode='wl', do_show = True, do_newfig = True,
29
+ npts=5000, xmin = None, xmax = None, ymin=None, ymax=None,
30
+ logx = None, logy=None,
31
+ scatter_breakdown = True, xsmode='peratom',
32
+ show_absorption = True, show_scattering = True,
33
+ do_legend = True, do_grid = True, color = None,
34
+ labelfct = None, only_total = False, extra_cfg=None,
35
+ title = None ):
36
+ """Quick plot (with matplotlib) showing the cross sections produced by
37
+ current material. The mode parameter can be either 'wl' or 'ekin', and
38
+ logx/logy can be set to None or a boolean to control whether the
39
+ corresponding axis should be logarithmic (None indicates False when
40
+ mode=='wl' and True when mode=='ekin'). Set scatter_breakdown to False
41
+ to show the total scattering contribution rather than a breakdown into
42
+ components (only affects materials that are not preloaded).
43
+
44
+ The extra_cfg parameter can be used to append cfg parameters
45
+ (e.g. "comp=bragg,incoh_elas;temp=50K").
46
+
47
+ Use the plot_xsects (note the trailing "s") function instead to compare
48
+ cross sections from multiple materials.
49
+
50
+ """
51
+ from . import misc as nc_misc
52
+ matsrc = nc_misc.MaterialSource( material, cfg_params = extra_cfg )
53
+ assert mode in ('wl','ekin')
54
+ assert logx is None or logx in (True,False)
55
+ assert xsmode in ('macroscopic','peratom')
56
+ loadedmat = matsrc.load()
57
+ if not loadedmat.scatter and not loadedmat.absorption:
58
+ from .exceptions import NCBadInput
59
+ raise NCBadInput('Can not produce plots for material source which contains no physics processes')
60
+ do_absn = show_absorption and bool(loadedmat.absorption)
61
+ do_scat = show_scattering and bool(loadedmat.scatter)
62
+ labelfct = labelfct or ( lambda x : x )
63
+ plt = _import_matplotlib_plt()
64
+ if do_newfig:
65
+ plt.figure()
66
+ from ._numpy import _np_linspace, _np_geomspace, _ensure_numpy
67
+ _ensure_numpy()
68
+ from .constants import wl2ekin
69
+ from ._common import _palette_Few as _palette
70
+ wlmax = _estimate_longest_interesting_wavelength( loadedmat.info )
71
+ if mode=='wl':
72
+ xmin = 0.0 if xmin is None else xmin
73
+ xmax = wlmax if xmax is None else xmax
74
+ logxydefault = False
75
+ else:
76
+ xmin = ( wl2ekin(wlmax)*1e-3 ) if ( xmin is None ) else ( xmin or 1e-7 )
77
+ xmax = xmax or xmin*1e5
78
+ logxydefault = True
79
+ assert xmax > xmin
80
+ logx = logxydefault if logx is None else bool(logx)
81
+ logy = logxydefault if logy is None else bool(logy)
82
+ x = (_np_geomspace if logx else _np_linspace)( xmin, xmax, npts )
83
+ xsectargs = dict(wl=x) if mode=='wl' else dict(ekin=x)
84
+ if mode=='macroscopic' and not loadedmat.info:
85
+ from .exceptions import NCBadInput
86
+ raise NCBadInput('Can not produce macroscopic cross section'
87
+ ' plots for material source which contains'
88
+ ' no NCrystal.Info object (and therefore no material density).')
89
+ xsectfactor = ( loadedmat.info.factor_macroscopic_xs ) if ( xsmode=='macroscopic' and loadedmat.info ) else 1.0
90
+ do_scatter_breakdown = do_scat and scatter_breakdown and not matsrc.is_preloaded and not loadedmat.scatter.isNull()
91
+ xs_s = xsectfactor * loadedmat.scatter.xsect(**xsectargs) if do_scat else None
92
+ xs_a = xsectfactor * loadedmat.absorption.xsect(**xsectargs) if do_absn else None
93
+ xs_tot = xs_s + xs_a if ( do_scat and do_absn ) else ( xs_s if do_scat else xs_a )
94
+ nullxs = not ( xs_tot.max() > 0.0 )
95
+ if nullxs and logy:
96
+ from ._common import warn
97
+ warn('Could not set log scale since curves are 0.0 everywhere')
98
+ logy=False
99
+
100
+ plotcalls = []
101
+ def add_plot(x,y,*a,**kw):
102
+ if y is not None:
103
+ #if logy:
104
+ # nonzeroy = ( y > 0.0 )
105
+ # x,y = x[nonzeroy],y[nonzeroy]
106
+ plotcalls.append( (x,y,a,kw) )
107
+ if do_scatter_breakdown:
108
+ lblmap = { 'coh_elas':'Coherent elastic',
109
+ 'incoh_elas':'Incoherent elastic',
110
+ 'inelas':'Inelastic',
111
+ 'sans':'SANS'}
112
+ colmap = { 'coh_elas':'blue',
113
+ 'incoh_elas':'yellow',
114
+ 'inelas':'green',
115
+ 'sans':'pink'}#nb unused: brown, gray (but brown looks similar to yellow)
116
+
117
+ assert set(lblmap.keys())==set(nc_misc.standard_comp_types),"update lblmap"
118
+ assert set(colmap.keys())==set(nc_misc.standard_comp_types),"update colmap"
119
+ for comp in nc_misc.detect_scattering_components( matsrc ):
120
+ _xs_comp = xsectfactor * matsrc.load(f'comp={comp}',doInfo=False,doAbsorption=False).scatter.xsect(**xsectargs)
121
+ add_plot(x,_xs_comp,label=labelfct(lblmap[comp]),
122
+ color = color or _palette[colmap[comp]] )
123
+ else:
124
+ add_plot(x,xs_s,label=labelfct('Scattering'),
125
+ color = color or _palette['blue'])
126
+ add_plot(x,xs_a,label=labelfct('Absorption'),color = color or _palette['purple'])
127
+
128
+ if only_total:
129
+ plotcalls=[]
130
+ if only_total or len(plotcalls) > 1:
131
+ totlabel = 'Total' if ( do_absn and do_scat ) else ( 'Total scattering' if do_scat else 'Total absorption' )
132
+ add_plot(x,xs_tot,label=labelfct(totlabel),
133
+ color=color or _palette['red'])
134
+ for x,y,a,kw in plotcalls:
135
+ plt.plot(x,y,*a,**kw)
136
+
137
+ plt.xlabel('Neutron wavelength (angstrom)' if mode=='wl' else 'Neutron energy (eV)')
138
+ plt.ylabel('Macroscopic cross section (1/cm)' if xsmode=='macroscopic' else 'Cross section per atom (barn)')
139
+
140
+ auto_ymin = ( None if logy else 0.0)
141
+ auto_ymax = ( 1.0 if nullxs else None )
142
+ ymin = ( auto_ymin if ymin is None else ymin )
143
+ ymax = ( auto_ymax if ymax is None else ymax )
144
+ if ymin is not None or ymax is not None:
145
+ if ymin is not None and ymax is not None and not ymax>ymin:
146
+ from ._common import warn
147
+ def _fmt(x):
148
+ return ( 'auto' if x is None else x )
149
+ warn('ymin/ymax parameters would lead to a plot range of'
150
+ f' [{ymin},{ymax}]. Reverting to [{_fmt(auto_ymin)},{_fmt(auto_ymax)}].')
151
+ ymin,ymax = auto_ymin,auto_ymax
152
+ plt.ylim(ymin,ymax)
153
+ plt.xlim(xmin,xmax)
154
+ if title is not False:
155
+ plt.title(title or matsrc.plotlabel)
156
+ _plt_final(do_grid,do_legend,do_show,logy=logy,logx=logx,plt=plt)
157
+
158
+ def plot_xsects( *materials, **plotkwargs ):
159
+ """Compares cross sections of multiple materials. Accepts similar keywords
160
+ as the plot_xsect function."""
161
+ from .misc import MaterialSource
162
+ mats = [ MaterialSource(m) for m in materials ]
163
+ do={}
164
+ for e in ['do_newfig','do_legend','do_grid','do_show','logy','logx']:
165
+ do[e] = plotkwargs.get(e,None if e in ('logy','logx') else True)
166
+ plotkwargs[e] = False
167
+ title = plotkwargs.get('title')
168
+ plotkwargs['title'] = False
169
+ plotkwargs['scatter_breakdown']=False
170
+ plotkwargs['only_total'] = True
171
+ plt = _import_matplotlib_plt()
172
+ if do['do_newfig']:
173
+ plt.figure()
174
+ col_ordered = _get_col_ordered()
175
+ for i,m in enumerate(mats):
176
+ plotkwargs['color'] = col_ordered[i%len(col_ordered)]
177
+ plotkwargs['labelfct'] = lambda _ : m.plotlabel
178
+ if i+1 == len(mats):
179
+ #the last:
180
+ plotkwargs['logy'] = do['logy']
181
+ plotkwargs['logx'] = do['logx']
182
+ plot_xsect(m,**plotkwargs)
183
+ if title:
184
+ plt.title(title)
185
+ _plt_final(do['do_grid'],do['do_legend'],do['do_show'],plt=plt)
186
+
187
+ def plot_vdos( *vdos, unit='meV',
188
+ show_orig_data = False,
189
+ show_normalised = True,
190
+ do_show = True, do_newfig = True,
191
+ npts_parabola=5000, logy=False,
192
+ do_legend = True, do_grid = True, color = None,
193
+ labelfct = None ):
194
+ """
195
+ Quick plot (with matplotlib) showing the requested VDOS curve(s).
196
+ """
197
+ from . import misc as nc_misc
198
+ from . import vdos as nc_vdos
199
+ #from ._common import _palette_Few as _palette
200
+ from ._numpy import _np_linspace,_ensure_numpy, _np
201
+ unit_name, unit_value = nc_vdos._parsevdosunit( unit )
202
+
203
+ #from ._numpy import _np_linspace, _np_geomspace, _np, _ensure_numpy
204
+ #_ensure_numpy()
205
+ #from .constants import wl2ekin
206
+
207
+ vdoslist = [ ( nc_misc.AnyVDOS(v),False) for v in vdos ]
208
+ if show_orig_data:
209
+ ll=[]
210
+ for v,_ in vdoslist:
211
+ ll.append( (v, False ) )
212
+ if v.has_orig:
213
+ ll.append( (v, True ) )
214
+ vdoslist = ll
215
+
216
+ plt = _import_matplotlib_plt()
217
+ if do_newfig:
218
+ plt.figure()
219
+
220
+ col_ordered = _get_col_ordered()
221
+
222
+ for ivdos,(vdos,use_orig) in enumerate(vdoslist):
223
+ lbl = vdos.label or 'VDOS#%i'%(ivdos+1)
224
+ if use_orig:
225
+ lbl += ' (original)'
226
+ if labelfct:
227
+ lbl = labelfct( lbl )
228
+ x = vdos.egrid( orig = use_orig ) / unit_value
229
+ y = vdos.dos( orig = use_orig, norm = show_normalised ) * unit_value
230
+ color = col_ordered[ivdos%len(col_ordered)]
231
+ if x[0] > 0.0 and npts_parabola:
232
+ _ensure_numpy()
233
+ xp = _np_linspace( 0.0, x[0], npts_parabola )
234
+ plt.plot(xp, y[0] *(xp/x[0])**2, color = color, ls = ':' )
235
+ if y[-1] > 0.0:
236
+ _ensure_numpy()
237
+ x = _np.append( x, [ x[-1] ] )
238
+ y = _np.append( y, [ 0.0 ] )
239
+ plt.plot(x,y,color = color, label = lbl, marker='.' )
240
+
241
+ plt.xlabel(f'Frequency ({unit_name})')
242
+ plt.ylabel('VDOS (arbitrary units)')
243
+ _plt_final(do_grid,do_legend,do_show,logy=logy,plt=plt)
244
+
245
+ def plot_knl( kernel, do_newfig = True, do_show = True, do_grid = True, logz=False, phasespace_curves = None,
246
+ clim=None, xlim = None, ylim = None):
247
+ """Quick plot (with matplotlib) showing the requested scattering kernel in
248
+ in S(alpha,beta) format). The kernel is assume to be a dictionary with keys
249
+ 'alpha', 'beta', and 'sab' associated with values being numpy arrays
250
+ containing the relevant info. Optionally, keys 'egrid' and 'suggestedEmax'
251
+ can be present, and will be used to estimate Emax - the highest intended
252
+ neutron energy for which the table is to be used. Emax is only used to plot
253
+ the kinematic bound of Emax.
254
+ """
255
+ #estimate emax:
256
+ _egrid = kernel.get('egrid')
257
+ _suggestedEmax = kernel.get('suggestedEmax')
258
+ emax = None
259
+ if _suggestedEmax and _suggestedEmax>0.0:
260
+ emax = _suggestedEmax
261
+ if emax is None and _egrid is not None:
262
+ import numbers
263
+ if isinstance(_egrid,numbers.Real):
264
+ emax = float(_egrid)
265
+ elif len(_egrid)==1:
266
+ emax = _egrid[0]
267
+ elif len(_egrid)==3:
268
+ emax = _egrid[1]
269
+ elif len(_egrid)>3:
270
+ emax = _egrid[-1]
271
+
272
+ alpha = kernel['alpha']
273
+ beta = kernel['beta']
274
+ sab = kernel['sab']
275
+ assert len(alpha)>=5
276
+ assert len(beta)>=5
277
+ assert len(alpha)*len(beta) == len(sab)
278
+
279
+ from ._numpy import _ensure_numpy, _np, _np_linspace
280
+ _ensure_numpy()
281
+ plt = _import_matplotlib_plt()
282
+ if do_newfig:
283
+ plt.figure()
284
+
285
+
286
+ def plotedges(x):
287
+ """given array [a,b,c,...,x,y,z] returns [a,(a+b)2,(b+c)/2,...,(y+z)/2,z]"""
288
+ return _np.concatenate([[x[0]],0.5*(x[:-1]+x[1::1]),[x[-1]]])
289
+ na,nb = len(alpha),len(beta)
290
+ pa = plotedges(alpha)
291
+ pb = plotedges(beta)
292
+ x,y=_np.meshgrid(pa,pb)
293
+ if logz:
294
+ sab = _np.log(sab)
295
+ sab=sab.reshape((nb,na))
296
+
297
+ cmap = 'jet'#fallback for some special options
298
+ #sab_max = sab.max()
299
+ quadmesh = plt.pcolormesh( x,y,sab, clim=clim,cmap=cmap )
300
+
301
+ def _real_mpl_object( o ):
302
+ #in case we are running under NCRYSTAL_FAKEPYPLOT=log, we are dealing
303
+ #with a wrapped object - which we can not pass directly to matplotlib
304
+ #classes.
305
+ return o.the_real_inspected_object if hasattr(o,'the_real_inspected_object') else o
306
+
307
+ plt.colorbar(_real_mpl_object(quadmesh))
308
+ if clim:
309
+ quadmesh.set_clim(clim)
310
+
311
+ plt.xlim(*(xlim or (0.0,alpha[-1])))
312
+ plt.ylim(*(ylim or (beta[0],beta[-1])))
313
+
314
+ def alpha_limits(E_div_kT,beta):
315
+ c = E_div_kT
316
+ cb = c + beta
317
+ assert cb>=0.0,f'c+beta={cb}, beta={beta}, c={c}'
318
+ a = cb + c
319
+ b = 2*_np.sqrt( c * cb )
320
+ assert not _np.isinf(a).any()
321
+ assert not _np.isinf(b).any()
322
+ return max(0.0,a - b), a + b
323
+ alpha_minus = _np.vectorize(lambda *a : alpha_limits(*a)[0])
324
+ alpha_plus = _np.vectorize(lambda *a : alpha_limits(*a)[1])
325
+ def kin_curve( E_div_kT ):
326
+ b0, b1 = b0,b1=max(beta.min(),-E_div_kT),beta.max()
327
+ #Smoother curve by putting more points around beta=0, less points
328
+ #further out (actually, we could improve this alg when
329
+ #ekin_div_kT>>1):
330
+ if b0<1.0 and b1>1.0:
331
+ bvals=_np.append(_np_linspace(b0,1.0,2500), _np_linspace(1.0+1e-14,b1,2500))
332
+ else:
333
+ bvals=_np_linspace(b0,b1,5000)
334
+ color='yellow'
335
+ plt.plot(alpha_minus(E_div_kT,bvals),bvals,label=f'E/kT={E_div_kT}',color=color,lw=2)
336
+ plt.plot(alpha_plus(E_div_kT,bvals).clip(0,alpha.max()),bvals,color=color,lw=2)
337
+
338
+ from .constants import constant_boltzmann
339
+ kT = kernel['temperature']*constant_boltzmann
340
+
341
+ for ekin in (phasespace_curves or []):
342
+ kin_curve( ekin / kT )
343
+
344
+ plt.xlabel('alpha')
345
+ plt.ylabel('beta')
346
+ _plt_final(do_grid=do_grid,do_legend=False,do_show=do_show,plt=plt)
347
+
348
+ def plot_vdos_Gn( Gn, unit = 'meV', logy = False,
349
+ do_newfig = True, do_legend = True,
350
+ do_grid = True, do_show = True ):
351
+ """Plot Sjolander Gn functions. Each Gn must be specified as a sequence like
352
+ (egrid,Gnvals,n[,label]), where either len(egrid)==len(Gnvals) or
353
+ egrid=(emin,emax). The Gn argument can either be a single Gn function, or a
354
+ sequence of them.
355
+ """
356
+ def decode_Gn( x ):
357
+ if len(x) not in (3,4):
358
+ return None
359
+ if len(x[1]) <= 4:
360
+ return None
361
+ if len(x) == 3:
362
+ egrid, gnvals, n = x
363
+ label = None
364
+ elif len(x) == 4:
365
+ egrid, gnvals, n, label = x
366
+ else:
367
+ return None
368
+ from ._numpy import _ensure_numpy,_np,_np_linspace
369
+ _ensure_numpy()
370
+ if len(egrid)==2 and len(gnvals)>2:
371
+ egrid = _np_linspace(egrid[0],egrid[1],len(gnvals))
372
+ return dict( egrid = _np.asarray(egrid,dtype=float),
373
+ gnvals = _np.asarray(gnvals,dtype=float),
374
+ n = n,
375
+ label = f'{label} (G{n})' if label else f'G{n}' )
376
+ _ = decode_Gn( Gn )
377
+ if _ is not None:
378
+ gnlist = [ _ ]
379
+ else:
380
+ gnlist = [ decode_Gn(e) for e in Gn ]
381
+ if not gnlist or any( e is None for e in gnlist ):
382
+ from .exceptions import NCBadInput
383
+ raise NCBadInput('Invalid specification of Gn functions.')
384
+
385
+ from . import vdos as nc_vdos
386
+ #from ._common import _palette_Few as _palette
387
+ unit_name, unit_value = nc_vdos._parsevdosunit( unit )
388
+
389
+ plt = _import_matplotlib_plt()
390
+ if do_newfig:
391
+ plt.figure()
392
+
393
+ for gn in gnlist:
394
+ plt.plot( gn['egrid']/unit_value, gn['gnvals'], 'o', label = gn['label'] )
395
+
396
+ plt.xlabel(f'Energy ({unit_name})')
397
+ _plt_final(do_grid,do_legend,do_show,logy=logy,plt=plt)
398
+
399
+ def _get_col_ordered():
400
+ from ._common import _palette_Few
401
+ return [_palette_Few.get(k,k) for k in
402
+ ('blue','orange','green','red','brown',
403
+ 'purple','yellow','pink','gray','black')]
404
+
405
+ def _plt_final(do_grid,
406
+ do_legend,
407
+ do_show, *,
408
+ logx=False,
409
+ logy=False,
410
+ plt=None,
411
+ extra_legend_kwargs = None ):
412
+ plt = plt or _import_matplotlib_plt()
413
+ if logx:
414
+ plt.semilogx()
415
+ if logy:
416
+ plt.semilogy()
417
+ if do_legend:
418
+ leg=plt.legend(**(extra_legend_kwargs or {}))
419
+ if do_legend=='draggable':
420
+ leg.set_draggable(True)
421
+ if do_grid:
422
+ plt.grid()
423
+ if do_show:
424
+ plt.show()
425
+
426
+ _fakepyplot_mode_cache = [None]
427
+ def _fakepyplot_mode():
428
+ if _fakepyplot_mode_cache[0] is None:
429
+ from ._common import ncgetenv
430
+ _ = ncgetenv('FAKEPYPLOT','')
431
+ if not _ or _=='0':
432
+ res = False
433
+ else:
434
+ res = 'log_and_plot' if ( _ == 'log' ) else 'log_and_block'
435
+ _fakepyplot_mode_cache[0] = res
436
+ return _fakepyplot_mode_cache[0]
437
+
438
+ _theplt=[None]
439
+ def _import_matplotlib_plt():
440
+ if not _theplt[0]:
441
+ mode = _fakepyplot_mode()
442
+ if mode:
443
+ from ._testimpl import _create_pyplot_inspector
444
+ _theplt[0] = _create_pyplot_inspector( pass_calls_to_real_plt = ( mode == 'log_and_plot' ) )
445
+ else:
446
+ #first matplotlib alone, for a perhaps more pedagogical ImportError.
447
+ import matplotlib # noqa F401
448
+ import matplotlib.pyplot as plt
449
+ _theplt[0]=plt
450
+ return _theplt[0]
451
+
452
+ _thepdfpages=[None]
453
+ def _import_matplotlib_pdfpages():
454
+ if not _thepdfpages[0]:
455
+ def _raw_import_pdf_pages():
456
+ try:
457
+ from matplotlib.backends.backend_pdf import PdfPages
458
+ except ImportError:
459
+ raise ImportError("ERROR: Your installation of matplotlib does not have the required support for PDF output.")
460
+ return PdfPages
461
+ mode = _fakepyplot_mode()
462
+ if mode!='log_and_block':
463
+ import matplotlib
464
+ matplotlib.use('agg')
465
+ if mode:
466
+ from ._testimpl import _create_pdfpages_inspector
467
+ _thepdfpages[0] = _create_pdfpages_inspector( _raw_import_pdf_pages() if mode != 'log_and_block' else None )
468
+ else:
469
+ _thepdfpages[0] = _raw_import_pdf_pages()
470
+ return _thepdfpages[0]
471
+
472
+ def _find_highest_bragg_edge( info ):
473
+ if info.isSinglePhase():
474
+ return info.braggthreshold
475
+ ll = [_find_highest_bragg_edge(p) for frac,p in info.phases]
476
+ ll = [e for e in ll if e]
477
+ return max(ll) if ll else None
478
+
479
+ def _estimate_longest_interesting_wavelength( info ):
480
+ #longest wavelength of interest in material, for the purpose of plotting. In
481
+ #case of bragg edges, this will be the longest bragg edge found in the
482
+ #material.
483
+ _ = _find_highest_bragg_edge( info ) if info else None
484
+ return _*1.2 if _ else 15.0
NCrystal/plugins.py ADDED
@@ -0,0 +1,49 @@
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
+ Utilities related to plugins and dynamic factories.
25
+
26
+ """
27
+
28
+ def hasFactory(name):
29
+ """Check if a factory of a given name exists"""
30
+ from ._chooks import _get_raw_cfcts,_str2cstr
31
+ return bool(_get_raw_cfcts()['ncrystal_has_factory'](_str2cstr(name)))
32
+
33
+ def browsePlugins(dump=False):
34
+
35
+ """Return list of plugins [(pluginname,filename,plugintype),...].
36
+
37
+ If the dump flag is set to True, the list will not be returned. Instead it
38
+ will be printed to stdout.
39
+ """
40
+ from ._chooks import _get_raw_cfcts
41
+ ll=_get_raw_cfcts()['ncrystal_get_pluginlist']()
42
+ if not dump:
43
+ return ll
44
+ from ._common import print
45
+ print('NCrystal has %i plugins loaded.'%len(ll))
46
+ for i in range(len(ll)):
47
+ pluginname, filename, plugintype = ll[i]
48
+ print('==> %s (%s%s)'%(pluginname,plugintype,
49
+ ' from %s'%filename if filename else ''))