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/_mmc.py
ADDED
|
@@ -0,0 +1,757 @@
|
|
|
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
|
+
# For backwards compatibility we keep __all__ here in this internal file. In
|
|
23
|
+
# NCrystal 4.0.0 we actually renamed the public API file from _mmc.py to mmc.py.
|
|
24
|
+
|
|
25
|
+
__all__ = ['runsim_diffraction_pattern',
|
|
26
|
+
'quick_diffraction_pattern',
|
|
27
|
+
'quick_diffraction_pattern_autoparams']
|
|
28
|
+
|
|
29
|
+
def runsim_diffraction_pattern( cfgstr, *,
|
|
30
|
+
geomcfg,
|
|
31
|
+
srccfg,
|
|
32
|
+
nthreads = 'auto',
|
|
33
|
+
tally_detail_lvl = 2 ):
|
|
34
|
+
"""Run embedded "mini-MonteCarlo" to produce diffraction pattern, including
|
|
35
|
+
both effects of multiple scattering and absorption physics. This for now
|
|
36
|
+
results in exit angle (angle of emitted neutrons wrt. the Z-axis)
|
|
37
|
+
histograms, with a perfect 4pi detector.
|
|
38
|
+
|
|
39
|
+
This is highly experimental, and not yet fully documented.
|
|
40
|
+
|
|
41
|
+
Example geomcfg: 'sphere;r=0.1'. This describes a 0.1m=10cm radius sphere
|
|
42
|
+
centered at (0,0,0).
|
|
43
|
+
|
|
44
|
+
Example srccfg: 'constant;ekin=0.025;n=1e6;z=-0.1'. This starts 1e6 0.025eV
|
|
45
|
+
neutrons at (0,0,-10cm) with a direction (0,0,1).
|
|
46
|
+
|
|
47
|
+
tally_detail_lvl can be reduced to 1 or 0, if only the exit_angle histogram
|
|
48
|
+
is needed. tally_detail_lvl=2 provides more details, including histograms
|
|
49
|
+
for specific components (e.g. separating the contributions from single and
|
|
50
|
+
multiple scattering, and inelastic/elastic scatterings.
|
|
51
|
+
|
|
52
|
+
nthreads can be 'auto' or a specific integral value of threads to use.
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
assert tally_detail_lvl in (0,1,2)
|
|
56
|
+
from ._numpy import _np, _ensure_numpy, _np_linspace
|
|
57
|
+
_ensure_numpy()
|
|
58
|
+
|
|
59
|
+
nthreads = 9999 if nthreads=='auto' else min(9999,max(1,int(nthreads)))
|
|
60
|
+
|
|
61
|
+
setup_info = dict( nthreads = nthreads,
|
|
62
|
+
tally_detail_lvl = tally_detail_lvl,
|
|
63
|
+
material_cfgstr = cfgstr,
|
|
64
|
+
geomcfg = geomcfg,
|
|
65
|
+
srccfg = srccfg )
|
|
66
|
+
|
|
67
|
+
from ._chooks import _get_raw_cfcts
|
|
68
|
+
_rawfct = _get_raw_cfcts()
|
|
69
|
+
cfct = _rawfct['runmmcsim_stdengine']
|
|
70
|
+
|
|
71
|
+
ct,errsq,res_json = cfct( nthreads = nthreads,
|
|
72
|
+
tally_detail_lvl = int(tally_detail_lvl),
|
|
73
|
+
mat_cfgstr = str(cfgstr),
|
|
74
|
+
mmc_geomcfg = str(geomcfg),
|
|
75
|
+
mmc_srccfg = str(srccfg) )
|
|
76
|
+
|
|
77
|
+
class Hist1D:
|
|
78
|
+
|
|
79
|
+
def __init__( self, data ):
|
|
80
|
+
if data=='_no_init_':
|
|
81
|
+
return
|
|
82
|
+
self.__stats = data.get('stats')
|
|
83
|
+
self.__title = data['title']
|
|
84
|
+
hb = data['bindata']
|
|
85
|
+
self.__xmin = hb['xmin']
|
|
86
|
+
self.__xmax = hb['xmax']
|
|
87
|
+
self.__nbins = hb['nbins']
|
|
88
|
+
self.__y = _np.asarray(hb['content'],dtype=float)
|
|
89
|
+
esq = hb.get('errorsq')
|
|
90
|
+
self.__yerrsq = ( _np.asarray(esq,dtype=float)
|
|
91
|
+
if esq is not None
|
|
92
|
+
else None )
|
|
93
|
+
self.__yerr = None
|
|
94
|
+
assert self.__nbins == len(self.__y)
|
|
95
|
+
assert self.__yerrsq is None or self.__nbins == len(self.__yerrsq)
|
|
96
|
+
|
|
97
|
+
def clone( self, rebin_factor = 1 ):
|
|
98
|
+
c = Hist1D('_no_init_')
|
|
99
|
+
c.__stats = self.__stats
|
|
100
|
+
c.__title = self.__title
|
|
101
|
+
c.__xmin = self.__xmin
|
|
102
|
+
c.__xmax = self.__xmax
|
|
103
|
+
c.__nbins = self.__nbins
|
|
104
|
+
c.__y = self.__y.copy()
|
|
105
|
+
c.__yerrsq = self.__yerrsq.copy() if self.__yerrsq is not None else None
|
|
106
|
+
c.__yerr = self.__yerr.copy() if self.__yerr is not None else None
|
|
107
|
+
if rebin_factor > 1:
|
|
108
|
+
c.rebin( rebin_factor )
|
|
109
|
+
return c
|
|
110
|
+
|
|
111
|
+
def integrate( self, xlow, xhigh, tolerance = 1e-5 ):
|
|
112
|
+
"""
|
|
113
|
+
Returns integrated contents of the histogram over the area
|
|
114
|
+
[xlow,xhigh] along with the error of that value in a tuple
|
|
115
|
+
(content,error).
|
|
116
|
+
|
|
117
|
+
This is done translating xlow and xhigh to exact bin edges and then
|
|
118
|
+
calling integrate_bins. If that is not possible within the
|
|
119
|
+
tolerance, an exception is raised.
|
|
120
|
+
|
|
121
|
+
"""
|
|
122
|
+
if not ( xhigh >= xlow ):
|
|
123
|
+
from .exceptions import NCBadInput
|
|
124
|
+
raise NCBadInput('Invalid integration range requested.')
|
|
125
|
+
|
|
126
|
+
bw = self.binwidth
|
|
127
|
+
def _findedge(x):
|
|
128
|
+
if x <= self.__xmin:
|
|
129
|
+
return 0
|
|
130
|
+
if x >= self.__xmax:
|
|
131
|
+
return self.nbins
|
|
132
|
+
r = ( x - self.__xmin ) / bw
|
|
133
|
+
ir = int(r+0.5)
|
|
134
|
+
if abs(r-ir) > tolerance:
|
|
135
|
+
from .exceptions import NCBadInput
|
|
136
|
+
raise NCBadInput(f'Value {x} does not correspond exactly'
|
|
137
|
+
' to a bin edge within the tolerance.')
|
|
138
|
+
return ir
|
|
139
|
+
e_low = _findedge(xlow)
|
|
140
|
+
e_high = _findedge(xhigh)
|
|
141
|
+
if e_low == e_high:
|
|
142
|
+
return ( 0.0, 0.0 )
|
|
143
|
+
assert e_low >= 0
|
|
144
|
+
assert e_low < e_high < self.nbins
|
|
145
|
+
return self.integrate_bins( e_low, e_high - 1 )
|
|
146
|
+
|
|
147
|
+
def integrate_bins( self, bin_low = None, bin_up = None ):
|
|
148
|
+
"""
|
|
149
|
+
Returns integrated contents of the bins [bin_low,bin_up[ along with
|
|
150
|
+
the error of that value in a tuple (content,error).
|
|
151
|
+
|
|
152
|
+
If bin_low is None the integration will start at the first bin and
|
|
153
|
+
include the underflow bin.
|
|
154
|
+
|
|
155
|
+
If bin_up is None the integration will end at the last bin and
|
|
156
|
+
include the overflow bin.
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
add_overflow, add_underflow = False, False
|
|
160
|
+
if bin_low is None:
|
|
161
|
+
add_underflow = True
|
|
162
|
+
bin_low = 0
|
|
163
|
+
underflow_c = self.stats.get('underflow')
|
|
164
|
+
underflow_e2 = self.stats.get('underflow_errorsq')
|
|
165
|
+
if bool(underflow_c is None) != bool(underflow_e2 is None):
|
|
166
|
+
from .exceptions import NCBadInput
|
|
167
|
+
raise NCBadInput('Inconsistent underflow info')
|
|
168
|
+
if underflow_c is None:
|
|
169
|
+
add_underflow = False
|
|
170
|
+
|
|
171
|
+
if bin_up is None:
|
|
172
|
+
add_overflow = True
|
|
173
|
+
bin_up = self.__nbins
|
|
174
|
+
overflow_c = self.stats.get('overflow')
|
|
175
|
+
overflow_e2 = self.stats.get('overflow_errorsq')
|
|
176
|
+
if bool(overflow_c is None) != bool(overflow_e2 is None):
|
|
177
|
+
from .exceptions import NCBadInput
|
|
178
|
+
raise NCBadInput('Inconsistent overflow info')
|
|
179
|
+
if overflow_c is None:
|
|
180
|
+
add_overflow = False
|
|
181
|
+
|
|
182
|
+
bin_low, bin_up = int(bin_low), int(bin_up)
|
|
183
|
+
if bin_up < bin_low or bin_low<0 or bin_up > self.__nbins:
|
|
184
|
+
from .exceptions import NCBadInput
|
|
185
|
+
raise NCBadInput('Invalid bin range requested')
|
|
186
|
+
content_integral = self.__y[bin_low:bin_up].sum()
|
|
187
|
+
if add_underflow:
|
|
188
|
+
content_integral += underflow_c
|
|
189
|
+
if add_overflow:
|
|
190
|
+
content_integral += overflow_c
|
|
191
|
+
if self.__yerrsq is None:
|
|
192
|
+
#unweighted, just base erros on contents:
|
|
193
|
+
return ( content_integral, _np.sqrt(content_integral) )
|
|
194
|
+
errorsq_integral = self.__yerrsq[bin_low:bin_up].sum()
|
|
195
|
+
if add_underflow:
|
|
196
|
+
errorsq_integral += underflow_e2
|
|
197
|
+
if add_overflow:
|
|
198
|
+
errorsq_integral += overflow_e2
|
|
199
|
+
return ( content_integral, _np.sqrt(errorsq_integral) )
|
|
200
|
+
|
|
201
|
+
def add_contents( self, other_hist ):
|
|
202
|
+
o = other_hist
|
|
203
|
+
assert self.__xmin == o.__xmin
|
|
204
|
+
assert self.__xmax == o.__xmax
|
|
205
|
+
assert self.__nbins == o.__nbins
|
|
206
|
+
self.__stats = {}
|
|
207
|
+
self.__title = '<edited>'
|
|
208
|
+
self.__y += o.__y
|
|
209
|
+
self.__yerr = None
|
|
210
|
+
if self.__yerrsq is None:
|
|
211
|
+
if o.__yerrsq is None:
|
|
212
|
+
pass#done
|
|
213
|
+
else:
|
|
214
|
+
self.__yerrsq = self.__y + o.__yerrsq
|
|
215
|
+
else:
|
|
216
|
+
if o.__yerrsq is None:
|
|
217
|
+
self.__yerrsq += o.__y
|
|
218
|
+
else:
|
|
219
|
+
self.__yerrsq += o.__yerrsq
|
|
220
|
+
|
|
221
|
+
def rebin( self, rebin_factor ):
|
|
222
|
+
assert self.__nbins % rebin_factor == 0
|
|
223
|
+
def _dorebin(x):
|
|
224
|
+
return _np.sum( x.reshape( len(x)//rebin_factor, rebin_factor ),
|
|
225
|
+
axis=1)
|
|
226
|
+
self.__y = _dorebin(self.__y)
|
|
227
|
+
self.__yerr = None
|
|
228
|
+
if self.__yerrsq is not None:
|
|
229
|
+
self.__yerrsq = _dorebin(self.__yerrsq)
|
|
230
|
+
self.__nbins = self.__nbins // rebin_factor
|
|
231
|
+
assert self.__yerrsq is None or len(self.__yerrsq)==len(self.__y)
|
|
232
|
+
assert self.__nbins==len(self.__y)
|
|
233
|
+
|
|
234
|
+
@property
|
|
235
|
+
def stats( self ):
|
|
236
|
+
return self.__stats or {}
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def errors( self ):
|
|
240
|
+
if self.__yerr is None:
|
|
241
|
+
self.__yerr = _np.sqrt( self.errors_squared )
|
|
242
|
+
return self.__yerr
|
|
243
|
+
|
|
244
|
+
@property
|
|
245
|
+
def errors_squared( self ):
|
|
246
|
+
return ( self.__yerrsq
|
|
247
|
+
if self.__yerrsq is not None
|
|
248
|
+
else self.__y )
|
|
249
|
+
|
|
250
|
+
@property
|
|
251
|
+
def content( self ):
|
|
252
|
+
return self.__y
|
|
253
|
+
|
|
254
|
+
@property
|
|
255
|
+
def title( self ):
|
|
256
|
+
return self.__title
|
|
257
|
+
|
|
258
|
+
@property
|
|
259
|
+
def xmin( self ):
|
|
260
|
+
return self.__xmin
|
|
261
|
+
|
|
262
|
+
@property
|
|
263
|
+
def xmax( self ):
|
|
264
|
+
return self.__xmax
|
|
265
|
+
|
|
266
|
+
@property
|
|
267
|
+
def binwidth( self ):
|
|
268
|
+
return (self.__xmax-self.__xmin)/self.__nbins
|
|
269
|
+
|
|
270
|
+
@property
|
|
271
|
+
def nbins( self ):
|
|
272
|
+
return self.__nbins
|
|
273
|
+
|
|
274
|
+
@property
|
|
275
|
+
def bincenters( self ):
|
|
276
|
+
halfbw = 0.5*self.binwidth
|
|
277
|
+
return _np_linspace(self.__xmin+halfbw,
|
|
278
|
+
self.__xmax-halfbw,
|
|
279
|
+
self.__nbins)
|
|
280
|
+
|
|
281
|
+
@property
|
|
282
|
+
def binedges( self ):
|
|
283
|
+
return _np_linspace(self.__xmin,
|
|
284
|
+
self.__xmax,
|
|
285
|
+
self.__nbins+1)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _hist_curve( self, error_offset = 0.0 ):
|
|
289
|
+
be = self.binedges
|
|
290
|
+
y = self.content
|
|
291
|
+
if error_offset:
|
|
292
|
+
y += self.errors * error_offset
|
|
293
|
+
cx = _np.empty(self.__nbins*2)
|
|
294
|
+
cy = _np.empty(self.__nbins*2)
|
|
295
|
+
i = 0
|
|
296
|
+
for ibin in range(self.__nbins):
|
|
297
|
+
cx[i] = be[ibin]
|
|
298
|
+
cy[i] = y[ibin]
|
|
299
|
+
i+=1
|
|
300
|
+
cx[i] = be[ibin+1]
|
|
301
|
+
cy[i] = y[ibin]
|
|
302
|
+
i+=1
|
|
303
|
+
return cx,cy
|
|
304
|
+
|
|
305
|
+
def errorbar_args( self, style = True, **kwargs ):
|
|
306
|
+
d = {'x':self.bincenters,
|
|
307
|
+
'y':self.content,'xerr':0.5*self.binwidth,
|
|
308
|
+
'yerr':self.errors }
|
|
309
|
+
if style:
|
|
310
|
+
d.update({'fmt':'.',#dont connect with line
|
|
311
|
+
'mec':'black','mfc':'black',
|
|
312
|
+
#'ms':4,'mew':1,
|
|
313
|
+
'ecolor':'black','elinewidth':1.0 })
|
|
314
|
+
d.update(kwargs)
|
|
315
|
+
return d
|
|
316
|
+
|
|
317
|
+
def bar_args( self, style = True, **kwargs ):
|
|
318
|
+
d = {'x' : self.binedges[:-1],
|
|
319
|
+
'height': self.content,
|
|
320
|
+
'width': self.binwidth,
|
|
321
|
+
'align':'edge'
|
|
322
|
+
}
|
|
323
|
+
d.update(kwargs)
|
|
324
|
+
return d
|
|
325
|
+
|
|
326
|
+
def plot_hist( self, plt=None, axis=None, style=True, label=None,
|
|
327
|
+
show_errors=True, do_show = True, set_xlim = True ):
|
|
328
|
+
if not plt and not axis:
|
|
329
|
+
from .plot import _import_matplotlib_plt
|
|
330
|
+
plt = _import_matplotlib_plt()
|
|
331
|
+
if not axis:
|
|
332
|
+
axis = plt.gca()
|
|
333
|
+
axis.bar(**self.bar_args(label=label))
|
|
334
|
+
if show_errors:
|
|
335
|
+
( error_markers,
|
|
336
|
+
ecaplines,
|
|
337
|
+
ebarlinecols ) = axis.errorbar(**self.errorbar_args())
|
|
338
|
+
|
|
339
|
+
xmin,xmax,binwidth = self.xmin, self.xmax, self.binwidth
|
|
340
|
+
if set_xlim:
|
|
341
|
+
axis.set_xlim(xmin-1e-6*binwidth,xmax+1e-6*binwidth)
|
|
342
|
+
if do_show and plt:
|
|
343
|
+
plt.show()
|
|
344
|
+
|
|
345
|
+
class Results:
|
|
346
|
+
|
|
347
|
+
def __init__( self,
|
|
348
|
+
main_hist_content,
|
|
349
|
+
main_hist_errsq,
|
|
350
|
+
json_details,
|
|
351
|
+
cfgstr,
|
|
352
|
+
setup_info ):
|
|
353
|
+
|
|
354
|
+
self.__setup_info = setup_info
|
|
355
|
+
|
|
356
|
+
mainhist_dict = dict( title = 'MAIN',
|
|
357
|
+
bindata = dict( content = main_hist_content,
|
|
358
|
+
errorsq = main_hist_errsq,
|
|
359
|
+
nbins = len(main_hist_content),
|
|
360
|
+
xmin = 0.0,
|
|
361
|
+
xmax = 180.0 ),
|
|
362
|
+
stats = None )
|
|
363
|
+
|
|
364
|
+
datadict = {}
|
|
365
|
+
if json_details is not None:
|
|
366
|
+
import json
|
|
367
|
+
datadict = json.loads(json_details)
|
|
368
|
+
mainhist_dict['stats'] = datadict.get('main_stats')
|
|
369
|
+
|
|
370
|
+
hists = [ Hist1D( mainhist_dict ) ]
|
|
371
|
+
hists += [ Hist1D( h )
|
|
372
|
+
for h in (datadict.get('breakdown_hists',[])) ]
|
|
373
|
+
self.__hists = hists
|
|
374
|
+
self.__cfgstr = cfgstr
|
|
375
|
+
|
|
376
|
+
@property
|
|
377
|
+
def histograms( self ):
|
|
378
|
+
return self.__hists
|
|
379
|
+
|
|
380
|
+
@property
|
|
381
|
+
def histogram_main( self ):
|
|
382
|
+
return self.__hists[0]
|
|
383
|
+
|
|
384
|
+
@property
|
|
385
|
+
def histogram_breakdown( self ):
|
|
386
|
+
return dict( (h.title,h) for h in self.__hists[1:] )
|
|
387
|
+
|
|
388
|
+
def histogram_sum( self, *, select=None, exclude=None ):
|
|
389
|
+
if isinstance(exclude,str):
|
|
390
|
+
exclude=[exclude]
|
|
391
|
+
if isinstance(select,str):
|
|
392
|
+
select=[select]
|
|
393
|
+
hl = self.__hists[1:]
|
|
394
|
+
if not exclude and not select:
|
|
395
|
+
return self.histogram_main
|
|
396
|
+
if select:
|
|
397
|
+
hl = [ h for h in hl if h.title in select ]
|
|
398
|
+
if exclude:
|
|
399
|
+
hl = [ h for h in hl if h.title not in exclude ]
|
|
400
|
+
if len(hl) <= 1:
|
|
401
|
+
return hl[0] if hl else None
|
|
402
|
+
h = hl[0].clone()
|
|
403
|
+
for o in hl[1:]:
|
|
404
|
+
h.add_contents( o )
|
|
405
|
+
return h
|
|
406
|
+
|
|
407
|
+
@property
|
|
408
|
+
def cfgstr( self ):
|
|
409
|
+
return self.__cfgstr
|
|
410
|
+
|
|
411
|
+
@property
|
|
412
|
+
def setup_info( self ):
|
|
413
|
+
return self.__setup_info
|
|
414
|
+
|
|
415
|
+
def plot_main( self, **kwargs ):
|
|
416
|
+
self.plot(hist=0,**kwargs)
|
|
417
|
+
|
|
418
|
+
def plot_breakdown( self, **kwargs ):
|
|
419
|
+
self.plot(hist='breakdown',**kwargs)
|
|
420
|
+
|
|
421
|
+
def plot( self, *,
|
|
422
|
+
do_show = True,
|
|
423
|
+
do_newfig = True,
|
|
424
|
+
do_grid = False,
|
|
425
|
+
logy = False,
|
|
426
|
+
rebin_factor = 1,
|
|
427
|
+
hist = None,
|
|
428
|
+
title = None,
|
|
429
|
+
plt = None):
|
|
430
|
+
|
|
431
|
+
breakdown_mode = hist=='breakdown'
|
|
432
|
+
|
|
433
|
+
if breakdown_mode:
|
|
434
|
+
hist = None
|
|
435
|
+
else:
|
|
436
|
+
if hist is None:
|
|
437
|
+
hist = self.__hists[0]
|
|
438
|
+
assert isinstance(hist,Hist1D)
|
|
439
|
+
elif not isinstance(hist,Hist1D):
|
|
440
|
+
#must be index:
|
|
441
|
+
hist = self.__hists[hist]
|
|
442
|
+
assert isinstance(hist,Hist1D)
|
|
443
|
+
|
|
444
|
+
from .plot import _import_matplotlib_plt, _plt_final
|
|
445
|
+
plt = plt or _import_matplotlib_plt()
|
|
446
|
+
if do_newfig:
|
|
447
|
+
plt.figure()
|
|
448
|
+
|
|
449
|
+
do_legend = False
|
|
450
|
+
tot_integral_with_absorption = None#unknown
|
|
451
|
+
|
|
452
|
+
if breakdown_mode:
|
|
453
|
+
|
|
454
|
+
colors = {
|
|
455
|
+
'SINGLESCAT_ELAS' : 'blue',
|
|
456
|
+
'SINGLESCAT_INELAS' : 'orangered',
|
|
457
|
+
'MULTISCAT_PUREELAS' : 'cornflowerblue',
|
|
458
|
+
'MULTISCAT_OTHER' : 'orange',
|
|
459
|
+
'NOSCAT' : 'green',
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
do_legend = True
|
|
463
|
+
hists = [h for h in self.histograms if h.title!='MAIN']
|
|
464
|
+
hist_main = [h for h in self.histograms if h.title=='MAIN'][0]
|
|
465
|
+
if not hists:
|
|
466
|
+
from .exceptions import NCCalcError
|
|
467
|
+
raise NCCalcError('Can not produce breakdown plots'
|
|
468
|
+
' without more detailed tally.')
|
|
469
|
+
_hists = [h for h in hists if h.stats.get('integral',1.0) > 0.0]
|
|
470
|
+
if _hists:
|
|
471
|
+
hists=_hists
|
|
472
|
+
#Sort histograms for (possibly) more meaningful plot order:
|
|
473
|
+
hists.sort( key = lambda h: ['MULTISCAT_OTHER',
|
|
474
|
+
'SINGLESCAT_INELAS',
|
|
475
|
+
'MULTISCAT_PUREELAS',
|
|
476
|
+
'SINGLESCAT_ELAS',
|
|
477
|
+
'NOSCAT'].index(h.title))
|
|
478
|
+
|
|
479
|
+
integrals = [ h.stats.get('integral',-1.0) for h in hists ]
|
|
480
|
+
tot_integral = ( sum(integrals)
|
|
481
|
+
if all( i>=0.0 for i in integrals )
|
|
482
|
+
else None )
|
|
483
|
+
if 'nstat' in self.setup_info:#TODO: we should always have this!
|
|
484
|
+
#A bit hackish way to get the initial flux... works only
|
|
485
|
+
#with the current unweighted generators and quick mode:
|
|
486
|
+
tot_integral_with_absorption = self.setup_info['nstat']
|
|
487
|
+
|
|
488
|
+
if rebin_factor != 1:
|
|
489
|
+
_ = []
|
|
490
|
+
for h in hists:
|
|
491
|
+
h = h.clone( rebin_factor = rebin_factor )
|
|
492
|
+
_.append( h )
|
|
493
|
+
hists = _
|
|
494
|
+
hist_main = hist_main.clone( rebin_factor = rebin_factor)
|
|
495
|
+
|
|
496
|
+
h=hists[0].clone()
|
|
497
|
+
curve = h._hist_curve()
|
|
498
|
+
axis = plt.gca()
|
|
499
|
+
ymax_non_NOSCAT = [0.0]
|
|
500
|
+
def _fractionval_fmt(x):
|
|
501
|
+
return f'({x*100.0:.3g}%)'
|
|
502
|
+
def _addplot( title, curve, refcurve = None, fraction = None):
|
|
503
|
+
maxval = 0.0
|
|
504
|
+
if refcurve is None:
|
|
505
|
+
y2 = _np.zeros(len(curve[1]))
|
|
506
|
+
else:
|
|
507
|
+
y2=refcurve[1]
|
|
508
|
+
maxval = y2.max()
|
|
509
|
+
if title!='NOSCAT':
|
|
510
|
+
ymax_non_NOSCAT[0] = max(ymax_non_NOSCAT[0],
|
|
511
|
+
maxval,
|
|
512
|
+
curve[1].max())
|
|
513
|
+
lbl = title if title!='NOSCAT' else 'Transmitted'
|
|
514
|
+
if fraction is not None:
|
|
515
|
+
lbl = f'{lbl} {_fractionval_fmt(fraction)}'
|
|
516
|
+
axis.fill_between(*curve,y2,
|
|
517
|
+
label=lbl,
|
|
518
|
+
edgecolor="none",
|
|
519
|
+
facecolor=colors[title])
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def calcfrac(h):
|
|
524
|
+
ti = tot_integral
|
|
525
|
+
if tot_integral_with_absorption is not None:
|
|
526
|
+
ti = tot_integral_with_absorption
|
|
527
|
+
if ti is not None and ti > 0.0:
|
|
528
|
+
return h.stats['integral'] / ti
|
|
529
|
+
|
|
530
|
+
_addplot( h.title, curve, fraction = calcfrac(h) )
|
|
531
|
+
|
|
532
|
+
for i in range(1,len(hists)):
|
|
533
|
+
hi = hists[i]
|
|
534
|
+
h.add_contents(hi)
|
|
535
|
+
newcurve = h._hist_curve()
|
|
536
|
+
assert _np.array_equal(curve[0],newcurve[0])
|
|
537
|
+
_addplot(hi.title, newcurve, curve,
|
|
538
|
+
fraction = calcfrac(hi))
|
|
539
|
+
curve = newcurve
|
|
540
|
+
|
|
541
|
+
plt.gca().errorbar(**hist_main.errorbar_args())
|
|
542
|
+
if not logy:
|
|
543
|
+
plt.ylim(0.0,ymax_non_NOSCAT[0]*1.3 or None)
|
|
544
|
+
plt.xlim(0.0,180.0)
|
|
545
|
+
else:
|
|
546
|
+
if rebin_factor != 1:
|
|
547
|
+
hist = hist.clone( rebin_factor = rebin_factor )
|
|
548
|
+
hist.plot_hist(plt=plt,do_show = False)
|
|
549
|
+
if not title:
|
|
550
|
+
plt.title(hist.title or '<untitled histogram>')
|
|
551
|
+
if not logy:
|
|
552
|
+
plt.ylim(0.0)
|
|
553
|
+
|
|
554
|
+
plt.xticks(_np_linspace(0.0,180.0,180//30+1))
|
|
555
|
+
plt.xticks(_np_linspace(0.0,180.0,180//15+1),minor=True)
|
|
556
|
+
plt.xlabel('Exit Angle (degrees)')
|
|
557
|
+
plt.ylabel('Intensity (arbitrary units)')
|
|
558
|
+
if title:
|
|
559
|
+
plt.title(title)
|
|
560
|
+
|
|
561
|
+
suptitle_fs = 'medium'
|
|
562
|
+
if len(self.cfgstr)>40:
|
|
563
|
+
suptitle_fs = 'small'
|
|
564
|
+
if len(self.cfgstr)>80:
|
|
565
|
+
suptitle_fs = 'x-small'
|
|
566
|
+
if len(self.cfgstr)>101:
|
|
567
|
+
suptitle_fs = 'xx-small'
|
|
568
|
+
plt.suptitle(self.cfgstr,fontsize=suptitle_fs)
|
|
569
|
+
|
|
570
|
+
absfracstr = 'unknown fraction'
|
|
571
|
+
if tot_integral_with_absorption and tot_integral:
|
|
572
|
+
_absfrac = 1.0 - tot_integral/tot_integral_with_absorption
|
|
573
|
+
absfracstr = _fractionval_fmt(_absfrac)
|
|
574
|
+
plt.plot([], [], ' ', label="Absorbed %s"%absfracstr)
|
|
575
|
+
|
|
576
|
+
legargs = {}
|
|
577
|
+
|
|
578
|
+
_plt_final(do_grid = do_grid,
|
|
579
|
+
do_legend = do_legend,
|
|
580
|
+
do_show = do_show,
|
|
581
|
+
logx = False,
|
|
582
|
+
logy = logy,
|
|
583
|
+
plt = plt,
|
|
584
|
+
extra_legend_kwargs = legargs )
|
|
585
|
+
|
|
586
|
+
return Results( main_hist_content = ct,
|
|
587
|
+
main_hist_errsq = errsq,
|
|
588
|
+
json_details = res_json,
|
|
589
|
+
cfgstr = cfgstr,
|
|
590
|
+
setup_info = setup_info)
|
|
591
|
+
|
|
592
|
+
_length_units = {'km':1000.0,
|
|
593
|
+
'm':1.0,
|
|
594
|
+
'meter':1.0,
|
|
595
|
+
'cm':0.01,
|
|
596
|
+
'mm':0.001,
|
|
597
|
+
'mfp': None,#special
|
|
598
|
+
'nm':1e-9,
|
|
599
|
+
'aa':1e-10,
|
|
600
|
+
'Aa':1e-10,
|
|
601
|
+
'AA':1e-10,
|
|
602
|
+
'angstrom':1e-10}
|
|
603
|
+
|
|
604
|
+
_energy_units = {'eV':1.0,
|
|
605
|
+
'keV':1e3,
|
|
606
|
+
'MeV':1e6,
|
|
607
|
+
'GeV':1e9,
|
|
608
|
+
'meV':0.001,
|
|
609
|
+
'neV':1e-9,
|
|
610
|
+
'aa':None,
|
|
611
|
+
'Aa':None,
|
|
612
|
+
'AA':None,
|
|
613
|
+
'angstrom':None}
|
|
614
|
+
|
|
615
|
+
def _tofloat(s):
|
|
616
|
+
try:
|
|
617
|
+
return float(s)
|
|
618
|
+
except ValueError:
|
|
619
|
+
return None
|
|
620
|
+
|
|
621
|
+
def _parse_unit(valstr,unitmap):
|
|
622
|
+
v = _tofloat(valstr)
|
|
623
|
+
if v is not None:
|
|
624
|
+
return v, None, None
|
|
625
|
+
valstr=valstr.strip()
|
|
626
|
+
for unit,unitvalue in sorted(unitmap.items(),key=lambda x : (-len(x),x)):
|
|
627
|
+
if valstr.endswith(unit):
|
|
628
|
+
v = _tofloat(valstr[:-len(unit)])
|
|
629
|
+
if v is not None:
|
|
630
|
+
return v, unit, unitvalue
|
|
631
|
+
return None,None,None
|
|
632
|
+
|
|
633
|
+
def _parse_energy( valstr ):
|
|
634
|
+
v,u,uv = _parse_unit( valstr, _energy_units )
|
|
635
|
+
if v is not None and u is None and uv is None:
|
|
636
|
+
from .exceptions import NCBadInput
|
|
637
|
+
raise NCBadInput('Invalid energy specification (missing unit'
|
|
638
|
+
f' like Aa or eV): "{valstr}"')
|
|
639
|
+
if v is None:
|
|
640
|
+
from .exceptions import NCBadInput
|
|
641
|
+
raise NCBadInput(f'Invalid energy specification: "{valstr}"')
|
|
642
|
+
if u is not None and u.lower() in ('aa','angstrom'):
|
|
643
|
+
from .constants import wl2ekin
|
|
644
|
+
return wl2ekin(v)
|
|
645
|
+
v *= uv
|
|
646
|
+
return v
|
|
647
|
+
|
|
648
|
+
def _parse_length( valstr, mfp = None ):
|
|
649
|
+
v,u,uv = _parse_unit( valstr, _length_units )
|
|
650
|
+
if v is not None and u is None and uv is None:
|
|
651
|
+
from .exceptions import NCBadInput
|
|
652
|
+
_ex0="mfp" if mfp is not None else "mm"
|
|
653
|
+
raise NCBadInput('Invalid length specification (missing unit like '
|
|
654
|
+
f'{_ex0} or cm): "{valstr}"')
|
|
655
|
+
if v is None:
|
|
656
|
+
from .exceptions import NCBadInput
|
|
657
|
+
raise NCBadInput(f'Invalid length specification: "{valstr}"')
|
|
658
|
+
if u=='mfp':
|
|
659
|
+
if mfp is None:
|
|
660
|
+
raise ValueError('Invalid length specification ("mfp" '
|
|
661
|
+
f'not supported for this parameter): "{valstr}"')
|
|
662
|
+
v *= mfp
|
|
663
|
+
else:
|
|
664
|
+
v *= uv
|
|
665
|
+
return v
|
|
666
|
+
|
|
667
|
+
def _macroxs_if_isotropic( mat, **xsect_kwargs ):
|
|
668
|
+
#macroxs_scatter in units of [1/m]
|
|
669
|
+
return ( None if mat.scatter.isOriented()
|
|
670
|
+
else ( mat.info.factor_macroscopic_xs
|
|
671
|
+
* mat.scatter.xsect( **xsect_kwargs )
|
|
672
|
+
/ _parse_length('1cm') ) )
|
|
673
|
+
|
|
674
|
+
def quick_diffraction_pattern_autoparams( cfgstr ):
|
|
675
|
+
from .core import load as ncload
|
|
676
|
+
mat = ncload( cfgstr )
|
|
677
|
+
|
|
678
|
+
neutron_wl = 1.8 # todo: depend on e.g. Bragg threshold?
|
|
679
|
+
|
|
680
|
+
unit_mm = _parse_length('1mm')
|
|
681
|
+
unit_cm = _parse_length('1cm')
|
|
682
|
+
unit_m = _parse_length('1m')
|
|
683
|
+
assert unit_m == 1.0
|
|
684
|
+
macroxs_scatter = _macroxs_if_isotropic( mat, wl=neutron_wl )
|
|
685
|
+
if not macroxs_scatter:
|
|
686
|
+
material_thickness = '1cm'
|
|
687
|
+
else:
|
|
688
|
+
mfp_scatter = 1.0 / macroxs_scatter
|
|
689
|
+
def _round2digits(x):
|
|
690
|
+
return int(x*100+0.5) * 0.01
|
|
691
|
+
if mfp_scatter <= unit_cm:
|
|
692
|
+
material_thickness = f'{_round2digits(mfp_scatter/unit_mm):g}mm'
|
|
693
|
+
elif mfp_scatter <= unit_m:
|
|
694
|
+
material_thickness = f'{_round2digits(mfp_scatter/unit_cm):g}cm'
|
|
695
|
+
else:
|
|
696
|
+
material_thickness = f'{_round2digits(mfp_scatter/unit_m):g}m'
|
|
697
|
+
return dict( neutron_energy_str = f'{neutron_wl}Aa',
|
|
698
|
+
material_thickness_str = material_thickness )
|
|
699
|
+
|
|
700
|
+
def quick_diffraction_pattern( cfgstr, *,
|
|
701
|
+
neutron_energy,
|
|
702
|
+
material_thickness,
|
|
703
|
+
nstat = 'auto',
|
|
704
|
+
nthreads = 'auto' ):
|
|
705
|
+
|
|
706
|
+
### TODO: change c-api and use:
|
|
707
|
+
### from .misc import MaterialSource
|
|
708
|
+
### matsrc = MaterialSource(material)
|
|
709
|
+
### if matsrc.is_preloaded():
|
|
710
|
+
### #from .exceptions import NCBadInput
|
|
711
|
+
### raise NCBadInput( 'Diffraction patterns can not be produced for'
|
|
712
|
+
### ' preloaded materials (for instance simply passing'
|
|
713
|
+
### ' a cfgstring will work).' )
|
|
714
|
+
###
|
|
715
|
+
|
|
716
|
+
neutron_energy_eV = _parse_energy( neutron_energy )
|
|
717
|
+
|
|
718
|
+
from .core import load as ncload
|
|
719
|
+
mat = ncload( cfgstr )
|
|
720
|
+
#unit_cm = _parse_length('1cm')
|
|
721
|
+
unit_m = _parse_length('1m')
|
|
722
|
+
|
|
723
|
+
macroxs_scatter = _macroxs_if_isotropic( mat, ekin=neutron_energy_eV )
|
|
724
|
+
mfp_scatter = 1/macroxs_scatter if macroxs_scatter else float('inf')
|
|
725
|
+
|
|
726
|
+
sphere_diameter = _parse_length(material_thickness, mfp=mfp_scatter)
|
|
727
|
+
|
|
728
|
+
def simfct( n, cfgstr ):
|
|
729
|
+
import time
|
|
730
|
+
t0 = time.time()
|
|
731
|
+
r_meter = 0.5*sphere_diameter / unit_m
|
|
732
|
+
res = runsim_diffraction_pattern( cfgstr,
|
|
733
|
+
geomcfg = f'sphere;r={r_meter}',
|
|
734
|
+
srccfg = (f'constant;ekin={neutron_energy_eV}'
|
|
735
|
+
f';n={n};z={-r_meter*(1-1e-13)}'),
|
|
736
|
+
tally_detail_lvl = 2,
|
|
737
|
+
nthreads = nthreads
|
|
738
|
+
)
|
|
739
|
+
t1 = time.time()
|
|
740
|
+
return t1-t0, res
|
|
741
|
+
|
|
742
|
+
if nstat is None or nstat=='auto':
|
|
743
|
+
#simfct(1,cfgstr)#build mat cache
|
|
744
|
+
for nstat in [1e4,1e5,1e6,1e7]:
|
|
745
|
+
t,res = simfct(nstat,cfgstr)
|
|
746
|
+
#Usually, end within a second in total, but in worst cases, up to
|
|
747
|
+
#10seconds:
|
|
748
|
+
if t>0.1 and nstat >= 1e6:
|
|
749
|
+
break
|
|
750
|
+
if t>1.0:
|
|
751
|
+
break
|
|
752
|
+
else:
|
|
753
|
+
t,res=simfct(nstat,cfgstr)
|
|
754
|
+
|
|
755
|
+
res.setup_info['nstat'] = nstat
|
|
756
|
+
|
|
757
|
+
return res
|