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/_cli_nctool.py
ADDED
|
@@ -0,0 +1,1018 @@
|
|
|
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
|
+
import os
|
|
23
|
+
import math
|
|
24
|
+
from ._cliimpl import ( create_ArgumentParser,
|
|
25
|
+
cli_entry_point,
|
|
26
|
+
print, warn )
|
|
27
|
+
from . import _common as nccommon
|
|
28
|
+
|
|
29
|
+
@cli_entry_point
|
|
30
|
+
def main( progname, arglist ):
|
|
31
|
+
if '--bench' in arglist:
|
|
32
|
+
benchmark_mode(progname,arglist)
|
|
33
|
+
elif '--pytrace' in arglist:
|
|
34
|
+
arglist.remove('--pytrace')
|
|
35
|
+
import trace
|
|
36
|
+
trace.Trace().runfunc(std_main,progname,arglist)
|
|
37
|
+
else:
|
|
38
|
+
std_main( progname, arglist )
|
|
39
|
+
|
|
40
|
+
#Sphinx doc function. Signature always the following:
|
|
41
|
+
def create_argparser_for_sphinx( progname ):
|
|
42
|
+
return parseArgs(progname,[],return_parser=True)
|
|
43
|
+
|
|
44
|
+
#in unit tests we dont display interactive images and we reduce cpu consumption
|
|
45
|
+
#by watching for special env var:
|
|
46
|
+
def _is_unittest():
|
|
47
|
+
return nccommon.ncgetenv_bool('TOOL_UNITTESTS')
|
|
48
|
+
|
|
49
|
+
#Function for importing required python modules which may be missing, to provide
|
|
50
|
+
#a somewhat more helpful error to the user:
|
|
51
|
+
def import_optpymod(name):
|
|
52
|
+
import importlib
|
|
53
|
+
errmsg = None
|
|
54
|
+
try:
|
|
55
|
+
themod = importlib.import_module(name)
|
|
56
|
+
except ImportError as exc:
|
|
57
|
+
errmsg = 'ERROR: Could not import a required python module: %s'%name
|
|
58
|
+
if maybeThisIsConda() and name in ('matplotlib','numpy'):
|
|
59
|
+
errmsg += (' (looks like you are using conda so you might solve it'
|
|
60
|
+
' by running "conda install [-c conda-forge] %s")'%name)
|
|
61
|
+
raise SystemExit(errmsg) from exc
|
|
62
|
+
return themod
|
|
63
|
+
|
|
64
|
+
def parseArgs( progname, arglist, *, return_parser = False ):
|
|
65
|
+
import argparse
|
|
66
|
+
from . import core as nccore
|
|
67
|
+
|
|
68
|
+
if '--mc' in arglist and ( '--help' in arglist or '-h' in arglist ):
|
|
69
|
+
|
|
70
|
+
from ._common import print
|
|
71
|
+
print(f"""
|
|
72
|
+
usage: {progname} --mc SRCCFG GEOMCFG MATCFG
|
|
73
|
+
|
|
74
|
+
Invoke the embedded stepping Monte Carlo to display a 4pi diffraction pattern of
|
|
75
|
+
neutrons going through a simple sample. SRCCFG describes the source of neutrons,
|
|
76
|
+
GEOMCFG the shape of the sample volume, and MATCFG is a normal NCrystal
|
|
77
|
+
cfg-string describing the sample material.
|
|
78
|
+
|
|
79
|
+
In the simplest case, simply supply a neutron energy value (e.g. "20meV",
|
|
80
|
+
"1.8Aa", ...) as SRCCFG and a geometry thickness as GEOMCFG (e.g. "1mm",
|
|
81
|
+
"2.5cm", "1mfp", ...). This will result in a pencil beam of neutrons of that
|
|
82
|
+
energy impinging centrally on a sphere with that thickness (diameter). The
|
|
83
|
+
thickness unit "mfp" means mean-free-path between scattering interactions. The
|
|
84
|
+
number of neutrons simulated will be automatically determined, so the simulation
|
|
85
|
+
can finish in less than a second.
|
|
86
|
+
|
|
87
|
+
For advanced users, it is also possibly to provide more options in both SRCCFG
|
|
88
|
+
and GEOMCFG, but that is currently considered experimental and not documented
|
|
89
|
+
further here.
|
|
90
|
+
|
|
91
|
+
As an example, to get the diffraction patterm of a a 2Aa neutron through a
|
|
92
|
+
sphere of T=250K aluminium with diameter 2cm:
|
|
93
|
+
|
|
94
|
+
$> {progname} --mc "2Aa" "2cm" "Al_sg225.ncmat;temp=250K"
|
|
95
|
+
|
|
96
|
+
You can use --logy/--liny to override the y-axis logarithmic setting of the
|
|
97
|
+
plot, and if you supply --pdf, a pdf file will produced instead of an
|
|
98
|
+
interactive plot being launched.
|
|
99
|
+
""")
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
descr="""
|
|
104
|
+
|
|
105
|
+
The most common usage of this tool is to load input data (usually .ncmat files)
|
|
106
|
+
with NCrystal (v%s) and plot resulting isotropic cross sections for thermal
|
|
107
|
+
neutrons. This is done by specifying one or more configurations ("cfg-strings"),
|
|
108
|
+
which indicates data names (e.g. file names) and optionally cfg parameters
|
|
109
|
+
(e.g. temperatures). Specifying more than one configuration, results in a single
|
|
110
|
+
comparison plot of the total scattering cross section based on the different
|
|
111
|
+
materials. Specifying just a single file, results in a more detailed cross
|
|
112
|
+
section plot as well as a 2D plot of generated scatter angles. Other behaviours
|
|
113
|
+
can be obtained by specifying flags as indicated below.
|
|
114
|
+
|
|
115
|
+
"""%nccore.get_version()
|
|
116
|
+
|
|
117
|
+
descr=descr.strip()
|
|
118
|
+
|
|
119
|
+
epilog="""
|
|
120
|
+
examples:
|
|
121
|
+
$ %(prog)s Al_sg225.ncmat
|
|
122
|
+
plot aluminium cross sections and scatter-angles versus neutron wavelength.
|
|
123
|
+
$ %(prog)s Al_sg225.ncmat Ge_sg227.ncmat --common temp=200
|
|
124
|
+
cross sections for aluminium and germanium at T=200K
|
|
125
|
+
$ %(prog)s "Al_sg225.ncmat;dcutoff=0.1" "Al_sg225.ncmat;dcutoff=0.4" "Al_sg225.ncmat;dcutoff=0.8"
|
|
126
|
+
effect of d-spacing cut-off on aluminium cross sections
|
|
127
|
+
$ %(prog)s "Al_sg225.ncmat;temp=20" "Al_sg225.ncmat;temp=293.15" "Al_sg225.ncmat;temp=600"
|
|
128
|
+
effect of temperature on aluminium cross sections
|
|
129
|
+
$ %(prog)s "phases<0.65*Al_sg225.ncmat&0.35*MgO_sg225_Periclase.ncmat>;temp=100K"
|
|
130
|
+
investigate multiphase material at 100K"""
|
|
131
|
+
|
|
132
|
+
parser = create_ArgumentParser(prog=progname,
|
|
133
|
+
description=descr,
|
|
134
|
+
epilog=epilog,
|
|
135
|
+
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
136
|
+
parser.add_argument('input_cfgs', metavar='CFGSTR', type=str, nargs='*',
|
|
137
|
+
help="""Input data (cfg-strings) to investigate. This
|
|
138
|
+
can just be simple file-names or full-blown cfg-strings
|
|
139
|
+
in the usual NCrystal syntax (see also examples
|
|
140
|
+
below).""")
|
|
141
|
+
parser.add_argument('--version', action='version', version=str(nccore.get_version()))
|
|
142
|
+
parser.add_argument('-d','--dump', action='count',default=0,
|
|
143
|
+
help=('Dump derived information rather than displaying'
|
|
144
|
+
' plots. Specify multiple times to increase'
|
|
145
|
+
' verbosity.'))
|
|
146
|
+
parser.add_argument('--mc', nargs=2,metavar=('SRCCFG','GEOMCFG'),
|
|
147
|
+
help=('Run embedded Monte Carlo app to produce a'
|
|
148
|
+
' diffraction pattern of material. Run'
|
|
149
|
+
' --mc --help for detailed instructions.'))
|
|
150
|
+
parser.add_argument('--common','-c', metavar='CFG', type=str, default=[],
|
|
151
|
+
help='Common configuration items that will be applied to all input cfg strings',action='append')
|
|
152
|
+
parser.add_argument('--coh_elas','--bragg', action='store_true',
|
|
153
|
+
help="""Only generate coherent-elastic (Bragg diffraction) component""")
|
|
154
|
+
parser.add_argument('--incoh_elas', action='store_true',
|
|
155
|
+
help="""Only generate incoherent-elastic component""")
|
|
156
|
+
parser.add_argument('--sans', action='store_true',
|
|
157
|
+
help="""Only generate SANS component""")
|
|
158
|
+
parser.add_argument('--elastic', action='store_true',
|
|
159
|
+
help="""Only generate elastic components (including SANS)""")
|
|
160
|
+
parser.add_argument('--inelastic', action='store_true',
|
|
161
|
+
help="""Only generate inelastic components""")
|
|
162
|
+
parser.add_argument('-a','--absorption', action='store_true',
|
|
163
|
+
help="""Include absorption in cross section plots""")
|
|
164
|
+
parser.add_argument('--phases', action='store_true',
|
|
165
|
+
help="""Show cross section breakdown of a single multiphase material by phase rather than physics process""")
|
|
166
|
+
parser.add_argument('-x','--xrange', type=str,nargs='?',
|
|
167
|
+
help='Override plot range, e.g. "1e-5:1e2" or "0:10"')
|
|
168
|
+
parser.add_argument('--logy', action='store_true',
|
|
169
|
+
help='Force y-axis to use logarithmic scale.')
|
|
170
|
+
parser.add_argument('--liny', action='store_true',
|
|
171
|
+
help='Force y-axis to use linear scale.')
|
|
172
|
+
parser.add_argument('-e','--energy', action='store_true',
|
|
173
|
+
help="""Show plots versus neutron energy rather than wavelength""")
|
|
174
|
+
parser.add_argument('-p','--pdf', action='store_true',
|
|
175
|
+
help="""Generate PDF file rather than launching an interactive plot viewer.""")
|
|
176
|
+
parser.add_argument('--test', action='store_true',
|
|
177
|
+
help="""Perform quick validation of NCrystal installation.""")
|
|
178
|
+
parser.add_argument('--doc', action='count',default=0,
|
|
179
|
+
help="""Print documentation about the available cfg-str variables. Specify twice for more detailed help.""")
|
|
180
|
+
dpi_default=200
|
|
181
|
+
parser.add_argument('--dpi', default=-1,type=int,
|
|
182
|
+
help="""Change plot resolution. Set to 0 to leave matplotlib defaults alone.
|
|
183
|
+
(default value is %i, or whatever the NCRYSTAL_DPI env var is set to)."""%dpi_default)
|
|
184
|
+
parser.add_argument('--cfg',action='store_true',
|
|
185
|
+
help='Print normalised cfg-string and dump meta-data about loaded physics processes.')
|
|
186
|
+
parser.add_argument('--plugins', action='store_true',
|
|
187
|
+
help='List currently enabled loaded plugins.')
|
|
188
|
+
parser.add_argument('-b','--browse', action='store_true',
|
|
189
|
+
help='List data available in standard locations (e.g. the files in the current directory or search path)')
|
|
190
|
+
parser.add_argument('--extract', type=str, default=None, metavar="DATANAME",
|
|
191
|
+
help='''Extract contents of DATANAME (e.g. a file name) using the same lookup mechanism as used for data
|
|
192
|
+
specified in NCrystal cfg strings. This can therefore also be used to inspect
|
|
193
|
+
in-memory (or on-demand created) data.''')
|
|
194
|
+
|
|
195
|
+
if return_parser:
|
|
196
|
+
return parser
|
|
197
|
+
|
|
198
|
+
args = parser.parse_args(arglist)
|
|
199
|
+
del arglist
|
|
200
|
+
|
|
201
|
+
if args.logy and args.liny:
|
|
202
|
+
parser.error('Do not specify both --liny and --logy')
|
|
203
|
+
if not args.logy and not args.liny:
|
|
204
|
+
args.logy = 'auto'
|
|
205
|
+
else:
|
|
206
|
+
args.logy = bool(args.logy)
|
|
207
|
+
assert args.logy in (True,False,'auto')
|
|
208
|
+
delattr(args,'liny')
|
|
209
|
+
|
|
210
|
+
if args.xrange:
|
|
211
|
+
try:
|
|
212
|
+
_=args.xrange.split(':')
|
|
213
|
+
if not len(_)==2:
|
|
214
|
+
raise ValueError
|
|
215
|
+
_ = ( float(_[0]), float(_[1]) )
|
|
216
|
+
if not ( _[0]>=0.0 and _[1]>_[0]):
|
|
217
|
+
raise ValueError
|
|
218
|
+
except ValueError:
|
|
219
|
+
parser.error(f'Invalid --xrange argument: "{args.xrange}"')
|
|
220
|
+
args.xrange = _
|
|
221
|
+
|
|
222
|
+
has_single_cfgstr = args.input_cfgs and len(args.input_cfgs)==1
|
|
223
|
+
if args.cfg and not has_single_cfgstr:
|
|
224
|
+
parser.error('Option --cfg requires exactly one cfg-string to be specified.')
|
|
225
|
+
|
|
226
|
+
if args.phases and not has_single_cfgstr:
|
|
227
|
+
parser.error('Option --phase requires exactly one cfg-string to be specified.')
|
|
228
|
+
|
|
229
|
+
if args.dump and not has_single_cfgstr:
|
|
230
|
+
parser.error('Option --dump requires exactly one cfg-string to be specified.')
|
|
231
|
+
|
|
232
|
+
if args.mc and not has_single_cfgstr:
|
|
233
|
+
parser.error('Option --mc requires exactly one cfg-string to be specified.')
|
|
234
|
+
|
|
235
|
+
if args.extract or args.plugins or args.doc or args.browse:
|
|
236
|
+
return args
|
|
237
|
+
|
|
238
|
+
if args.dpi==-1:
|
|
239
|
+
args.dpi = nccommon.ncgetenv_int_nonneg('DPI',dpi_default)
|
|
240
|
+
|
|
241
|
+
if args.dpi>3000:
|
|
242
|
+
parser.error('Too high DPI value requested.')
|
|
243
|
+
|
|
244
|
+
if args.test:
|
|
245
|
+
if any((args.cfg,
|
|
246
|
+
args.input_cfgs,
|
|
247
|
+
args.dump,
|
|
248
|
+
args.mc,
|
|
249
|
+
args.coh_elas,
|
|
250
|
+
args.incoh_elas,
|
|
251
|
+
args.sans,
|
|
252
|
+
args.elastic,
|
|
253
|
+
args.inelastic,
|
|
254
|
+
args.absorption,
|
|
255
|
+
args.pdf,
|
|
256
|
+
args.phases)):
|
|
257
|
+
parser.error('Do not specify other arguments with --test.')
|
|
258
|
+
|
|
259
|
+
ncomp_select = sum((1 if _ else 0) for _ in (args.coh_elas,args.incoh_elas,args.sans,args.elastic,args.inelastic))
|
|
260
|
+
if ncomp_select > 1:
|
|
261
|
+
parser.error('Do not specify more than one of: --coh_elas/--bragg, --incoh_elas, --sans, --elastic or --inelastic.')
|
|
262
|
+
|
|
263
|
+
if args.coh_elas:
|
|
264
|
+
args.comp = 'coh_elas'
|
|
265
|
+
elif args.incoh_elas:
|
|
266
|
+
args.comp = 'incoh_elas'
|
|
267
|
+
elif args.sans:
|
|
268
|
+
args.comp = 'sans'
|
|
269
|
+
elif args.elastic:
|
|
270
|
+
args.comp = 'elastic'
|
|
271
|
+
elif args.inelastic:
|
|
272
|
+
args.comp = 'inelastic'
|
|
273
|
+
else:
|
|
274
|
+
args.comp = 'all'
|
|
275
|
+
|
|
276
|
+
if args.absorption and ncomp_select>0:
|
|
277
|
+
parser.error('Do not specify --absorption with either of: --coh_elas/--bragg, --incoh_elas, --sans, --elastic or --inelastic.')
|
|
278
|
+
|
|
279
|
+
if args.dump and (ncomp_select>0 or args.absorption or args.mc):
|
|
280
|
+
parser.error('Do not specify --dump with either of: --mc, --coh_elas/--bragg, --incoh_elas, --sans, --elastic, --inelastic or --absorption.')
|
|
281
|
+
|
|
282
|
+
if args.dump and len(args.input_cfgs)>1:
|
|
283
|
+
parser.error('Do not specify more than one input cfg string with --dump [-d].')
|
|
284
|
+
|
|
285
|
+
if args.mc and (ncomp_select>0 or args.absorption):
|
|
286
|
+
parser.error('Do not specify --mc with either of: --coh_elas/--bragg, --incoh_elas, --sans, --elastic, --inelastic or --absorption.')
|
|
287
|
+
|
|
288
|
+
if args.mc and len(args.input_cfgs)>1:
|
|
289
|
+
parser.error('Do not specify more than one input cfg string with --mc [-d].')
|
|
290
|
+
|
|
291
|
+
args.common=';'.join(args.common)
|
|
292
|
+
return args
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def std_main( progname, arglist ):
|
|
296
|
+
from . import core as nccore
|
|
297
|
+
args = parseArgs( progname, arglist )
|
|
298
|
+
if args is None:
|
|
299
|
+
return#ended (e.g. after --help)
|
|
300
|
+
|
|
301
|
+
if args.doc:
|
|
302
|
+
full = (args.doc >= 2)
|
|
303
|
+
from .cfgstr import generateCfgStrDoc
|
|
304
|
+
print(generateCfgStrDoc( 'txt_full' if full else 'txt_short' ),end='')
|
|
305
|
+
if not full:
|
|
306
|
+
print("NOTE: Condensed output generated. Specify --doc twice for more details.")
|
|
307
|
+
return
|
|
308
|
+
|
|
309
|
+
if args.extract:
|
|
310
|
+
s = nccore.createTextData(args.extract).rawData
|
|
311
|
+
if s is None:
|
|
312
|
+
raise SystemExit('Error: unknown file "%s"'%args.extract)
|
|
313
|
+
print(s,end='')
|
|
314
|
+
return
|
|
315
|
+
|
|
316
|
+
if args.plugins:
|
|
317
|
+
from .plugins import browsePlugins
|
|
318
|
+
browsePlugins(dump=True)
|
|
319
|
+
return
|
|
320
|
+
|
|
321
|
+
if args.browse:
|
|
322
|
+
from .datasrc import browseFiles
|
|
323
|
+
browseFiles(dump=True)
|
|
324
|
+
return
|
|
325
|
+
|
|
326
|
+
if args.test:
|
|
327
|
+
from ._testimpl import test
|
|
328
|
+
test()
|
|
329
|
+
return
|
|
330
|
+
|
|
331
|
+
if args.cfg or ( len(args.input_cfgs)==1 and not (args.dump or args.mc) ):
|
|
332
|
+
#Dump cfg debug info if requested or running with just 1 file.
|
|
333
|
+
assert len(args.input_cfgs)==1
|
|
334
|
+
origcfg=args.input_cfgs[0]
|
|
335
|
+
if args.common:
|
|
336
|
+
origcfg += f';{args.common}'
|
|
337
|
+
print(f'==> Debugging cfg-string: "{origcfg}"')
|
|
338
|
+
_ = comp2cfgpars(args.comp)
|
|
339
|
+
if _:
|
|
340
|
+
assert args.comp != 'all'
|
|
341
|
+
print(f'==> Adding due to --{args.comp} flag specified: "{_}"')
|
|
342
|
+
origcfg += ';' + _
|
|
343
|
+
from .cfgstr import normaliseCfg
|
|
344
|
+
normcfg = normaliseCfg(origcfg)
|
|
345
|
+
print(f'==> Normalised cfg-string : "{normcfg}"')
|
|
346
|
+
abs_obj=nccore.createAbsorption(normcfg)
|
|
347
|
+
sc_obj = nccore.createScatter(normcfg)
|
|
348
|
+
print( '==> Absorption process (code level objects):')
|
|
349
|
+
proc_a = abs_obj
|
|
350
|
+
proc_a.dump(' '*27)
|
|
351
|
+
print( '==> Scattering process (code level objects):')
|
|
352
|
+
proc_s = sc_obj
|
|
353
|
+
proc_s.dump(' '*27)
|
|
354
|
+
if args.cfg:
|
|
355
|
+
return
|
|
356
|
+
|
|
357
|
+
_mpldpi[0] = args.dpi
|
|
358
|
+
|
|
359
|
+
cfgs=[Cfg(e,args.common) for e in args.input_cfgs]
|
|
360
|
+
cfgs_normalisedstrings = [c.cfgstr for c in cfgs]
|
|
361
|
+
for cstr in set(cfgs_normalisedstrings):
|
|
362
|
+
if cfgs_normalisedstrings.count(cstr)!=1:
|
|
363
|
+
warn("Configuration specified more than once: \"%s\""%cstr)
|
|
364
|
+
|
|
365
|
+
if not cfgs:
|
|
366
|
+
raise SystemExit('Error: nothing selected.'
|
|
367
|
+
' Please run with --help for usage instructions.')
|
|
368
|
+
|
|
369
|
+
if args.dump:
|
|
370
|
+
assert len(cfgs)==1
|
|
371
|
+
cfgs[0].get_info().dump(verbose = int(args.dump)-1 )
|
|
372
|
+
return
|
|
373
|
+
|
|
374
|
+
if args.mc:
|
|
375
|
+
plot_mmc(cfgs[0].cfgstr,
|
|
376
|
+
srccfg = args.mc[0],
|
|
377
|
+
geomcfg = args.mc[1],
|
|
378
|
+
logy = args.logy,
|
|
379
|
+
do_pdf = args.pdf )
|
|
380
|
+
else:
|
|
381
|
+
plot_xsect( cfgs, comp = args.comp, absorption = args.absorption, pdf=args.pdf,
|
|
382
|
+
versus_energy=args.energy, xrange = args.xrange, logy = args.logy,
|
|
383
|
+
breakdown_by_phases = args.phases )
|
|
384
|
+
|
|
385
|
+
if len(cfgs)==1 and not nccommon.ncgetenv_bool('TOOL_NO2DSCATTER'):
|
|
386
|
+
plot_2d_scatangle( cfgs[0], comp = args.comp, pdf=args.pdf, versus_energy=args.energy, xrange = args.xrange )
|
|
387
|
+
|
|
388
|
+
if len(cfgs)==1 and not nccommon.ncgetenv_bool('TOOL_NOAUTOMCPLOT'):
|
|
389
|
+
from . import _mmc as nc_mmc
|
|
390
|
+
auto_params = nc_mmc.quick_diffraction_pattern_autoparams( cfgs[0].cfgstr )
|
|
391
|
+
plot_mmc( cfgs[0].cfgstr,
|
|
392
|
+
auto_params['neutron_energy_str'],
|
|
393
|
+
auto_params['material_thickness_str'],
|
|
394
|
+
logy=args.logy,
|
|
395
|
+
do_pdf=args.pdf )
|
|
396
|
+
if args.pdf:
|
|
397
|
+
_,_,pdf = import_npplt(True)
|
|
398
|
+
import datetime
|
|
399
|
+
try:
|
|
400
|
+
d = pdf.infodict()
|
|
401
|
+
except AttributeError:
|
|
402
|
+
d={}
|
|
403
|
+
d['Title'] = 'Plots made with NCrystal nctool from file%s %s'%('' if len(args.input_cfgs)==1 else 's',
|
|
404
|
+
','.join(os.path.basename(f) for f in args.input_cfgs))
|
|
405
|
+
d['Author'] = 'NCrystal %s (via nctool)'%nccore.get_version()
|
|
406
|
+
d['Subject'] = 'NCrystal plots'
|
|
407
|
+
d['Keywords'] = 'NCrystal'
|
|
408
|
+
d['CreationDate'] = datetime.datetime.today()
|
|
409
|
+
d['ModDate'] = datetime.datetime.today()
|
|
410
|
+
pdf.close()
|
|
411
|
+
print("created %s"%_pdffilename)
|
|
412
|
+
|
|
413
|
+
def create_ekins(npoints,range_override):
|
|
414
|
+
from ._numpy import _np_geomspace
|
|
415
|
+
if range_override:
|
|
416
|
+
if range_override[0]<=0.0:
|
|
417
|
+
range_override = ( range_override[1]*1e-10, range_override[1] )
|
|
418
|
+
return _np_geomspace(*range_override,npoints)
|
|
419
|
+
else:
|
|
420
|
+
return _np_geomspace(1e-5,1e2,npoints)
|
|
421
|
+
|
|
422
|
+
def create_wavelengths(np,cfgs,npoints,range_override):
|
|
423
|
+
if range_override:
|
|
424
|
+
wls_min,wls_max = range_override
|
|
425
|
+
else:
|
|
426
|
+
bragg_thresholds = [c.braggthreshold() or 0.0 for c in cfgs]
|
|
427
|
+
fallback = 10.0#materials with no bragg threshold
|
|
428
|
+
max_bragg_threshold = ( max(bragg_thresholds) if bragg_thresholds else None ) or fallback
|
|
429
|
+
wls_max = 1.2 * ( float(int(max_bragg_threshold*1.01+1.0)) if not math.isinf(max_bragg_threshold) else fallback )
|
|
430
|
+
wls_min = 1e-4
|
|
431
|
+
from ._numpy import _np_linspace
|
|
432
|
+
return _np_linspace( wls_min, wls_max, npoints )
|
|
433
|
+
|
|
434
|
+
_mpldpi=[None]
|
|
435
|
+
_pdffilename='ncrystal.pdf'
|
|
436
|
+
_npplt = None
|
|
437
|
+
def import_npplt(pdf=False):
|
|
438
|
+
#TODO: For interactive usage, we must have consistent global matplotlib
|
|
439
|
+
#manager for all of NCrystal, and we must use a context manager to switch to
|
|
440
|
+
#the agg backend when we need pdf plots. We should also only every change
|
|
441
|
+
#rcParams with the matplotlib.pyplot.rc_context context manager. See also
|
|
442
|
+
#the discussion at: https://github.com/matplotlib/matplotlib/issues/26362
|
|
443
|
+
|
|
444
|
+
#Maybe we should also ensure that all of our matplotlib plots simply return
|
|
445
|
+
#standalone Figure() objects?
|
|
446
|
+
#https://matplotlib.org/devdocs/gallery/user_interfaces/web_application_server_sgskip.html
|
|
447
|
+
global _npplt
|
|
448
|
+
if _npplt:
|
|
449
|
+
#pdf par must be same as last call:
|
|
450
|
+
if bool(pdf) != bool(_npplt[2] is not None):
|
|
451
|
+
raise SystemExit('Currently we do not support switching back and '
|
|
452
|
+
'forth between pdf plotting in the same process '
|
|
453
|
+
'(see also '
|
|
454
|
+
'https://github.com/mctools/ncrystal/issues/202)')
|
|
455
|
+
return _npplt
|
|
456
|
+
np = import_optpymod('numpy')
|
|
457
|
+
mpl = import_optpymod('matplotlib')
|
|
458
|
+
|
|
459
|
+
if _mpldpi[0]:
|
|
460
|
+
mpl.rcParams['figure.dpi']=_mpldpi[0]
|
|
461
|
+
|
|
462
|
+
#ability to quit plot windows with Q:
|
|
463
|
+
if 'keymap.quit' in mpl.rcParams and 'q' not in mpl.rcParams['keymap.quit']:
|
|
464
|
+
mpl.rcParams['keymap.quit'] = tuple(list(mpl.rcParams['keymap.quit'])+['q','Q'])
|
|
465
|
+
|
|
466
|
+
if _is_unittest() or pdf:
|
|
467
|
+
mpl.use('agg')
|
|
468
|
+
if pdf:
|
|
469
|
+
try:
|
|
470
|
+
from matplotlib.backends.backend_pdf import PdfPages
|
|
471
|
+
except ImportError:
|
|
472
|
+
PdfPages = None
|
|
473
|
+
if not PdfPages:
|
|
474
|
+
raise SystemExit("ERROR: Your installation of matplotlib does"
|
|
475
|
+
" not have the required support for PDF output.")
|
|
476
|
+
plt = import_optpymod('matplotlib.pyplot')
|
|
477
|
+
|
|
478
|
+
_npplt = (np,plt,PdfPages(_pdffilename) if pdf else None)
|
|
479
|
+
|
|
480
|
+
return _npplt
|
|
481
|
+
|
|
482
|
+
#functions for creating labels and title:
|
|
483
|
+
def _remove_common_keyvals(dicts):
|
|
484
|
+
"""remove any key from the passed dicts which exists with identical value in all
|
|
485
|
+
the dicts. Returns a single dictionary with entries thus removed."""
|
|
486
|
+
sets=[set((k,v) for k,v in list(d.items())) for d in dicts]
|
|
487
|
+
common = dict(set.intersection(*sets)) if sets else {}
|
|
488
|
+
for k in list(common.keys()):
|
|
489
|
+
for d in dicts:
|
|
490
|
+
d.pop(k,None)
|
|
491
|
+
return common
|
|
492
|
+
|
|
493
|
+
def _serialise_cfg(part):
|
|
494
|
+
#transform to list of tuples [(key,value),..] where entries can be
|
|
495
|
+
#(parname,value) or compname/filename.
|
|
496
|
+
|
|
497
|
+
mpstart = 'phases<'
|
|
498
|
+
if part._cfg.cfgstr.startswith(mpstart):
|
|
499
|
+
assert part._cfg.cfgstr.count('>')==1
|
|
500
|
+
_=part._cfg.cfgstr[7:].split('>')
|
|
501
|
+
main,trailing_common_cfg = _ if len(_)>1 else (_[0],'')
|
|
502
|
+
else:
|
|
503
|
+
_=part._cfg.cfgstr.split(';',1)
|
|
504
|
+
main,trailing_common_cfg = _ if len(_)>1 else (_[0],'')
|
|
505
|
+
ll = [ ('[FILENAME]', main.strip() ), #using '[]' in special keys avoids clashes
|
|
506
|
+
('[COMPNAME]', part._compname ) ] #(cfg strs can't contain such chars)
|
|
507
|
+
for e in trailing_common_cfg.split(';'):
|
|
508
|
+
e=e.strip()
|
|
509
|
+
if e == 'ignorefilecfg':
|
|
510
|
+
raise SystemExit('ERROR: The ignorefilecfg keyword is no longer supported')
|
|
511
|
+
elif e:
|
|
512
|
+
k,v=e.split('=')
|
|
513
|
+
ll.append( (k.strip(),v.strip()) )
|
|
514
|
+
return ll
|
|
515
|
+
|
|
516
|
+
def _classify_differences(parts):
|
|
517
|
+
ll=[]
|
|
518
|
+
for p in parts:
|
|
519
|
+
ll.append( dict(_serialise_cfg(p)) )
|
|
520
|
+
common = _remove_common_keyvals(ll)
|
|
521
|
+
return common,ll
|
|
522
|
+
|
|
523
|
+
def _cfgdict_to_str(cfgdict):
|
|
524
|
+
fn = cfgdict.pop('[FILENAME]','')
|
|
525
|
+
if '*' in fn:
|
|
526
|
+
#multiphase, fix up a bit
|
|
527
|
+
_=''
|
|
528
|
+
for phase in fn.split('&'):
|
|
529
|
+
fraction,phcfg = phase.split('*')
|
|
530
|
+
if _:
|
|
531
|
+
_ += ' + '
|
|
532
|
+
multsymb = '\u00D7'
|
|
533
|
+
_ += '%s%s(%s)'%(fraction,multsymb,phcfg)
|
|
534
|
+
fn = '{%s}'%_
|
|
535
|
+
o = [fn] if fn else []
|
|
536
|
+
cn = cfgdict.pop('[COMPNAME]','')
|
|
537
|
+
if cfgdict:
|
|
538
|
+
o += [', '.join('%s=%s'%(k,v) for k,v in sorted(cfgdict.items()))]
|
|
539
|
+
if cn:
|
|
540
|
+
o += [ { 'coh_elas':'Coherent elastic',
|
|
541
|
+
'incoh_elas':'Incoherent elastic',
|
|
542
|
+
'elastic':'Elastic',
|
|
543
|
+
'inelastic':'Inelastic',
|
|
544
|
+
'sans':'SANS',
|
|
545
|
+
'absorption':'Absorption',
|
|
546
|
+
'all':'Total scattering',
|
|
547
|
+
'scattering+absorption':'Total scattering+Absorption'}[cn] ]
|
|
548
|
+
return ' '.join(b%a for a,b in zip(o,['%s','[%s]','(%s)']))
|
|
549
|
+
|
|
550
|
+
def create_title_and_labels(parts):
|
|
551
|
+
partscfg_common,partscfg_unique = _classify_differences(parts)
|
|
552
|
+
return _cfgdict_to_str(partscfg_common),[(_cfgdict_to_str(uc) or 'default') for uc in partscfg_unique]
|
|
553
|
+
|
|
554
|
+
def _end_plot(plt,pdf):
|
|
555
|
+
if _is_unittest():
|
|
556
|
+
with open(os.devnull,'wb') as fh:
|
|
557
|
+
plt.savefig(fh,format='raw')
|
|
558
|
+
plt.close()
|
|
559
|
+
elif pdf:
|
|
560
|
+
pdf.savefig()
|
|
561
|
+
plt.close()
|
|
562
|
+
else:
|
|
563
|
+
plt.show()
|
|
564
|
+
|
|
565
|
+
def comp2cfgpars(comp):
|
|
566
|
+
assert comp in ('coh_elas','incoh_elas','elastic','inelastic','sans','all')
|
|
567
|
+
return { 'coh_elas' : 'incoh_elas=0;inelas=0;sans=0',
|
|
568
|
+
'incoh_elas' : 'coh_elas=0;inelas=0;sans=0',
|
|
569
|
+
'elastic' : 'inelas=0',
|
|
570
|
+
'inelastic' : 'elas=0',
|
|
571
|
+
'sans' : 'coh_elas=0;incoh_elas=0;inelas=0',
|
|
572
|
+
'all' : '' }[comp]
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def _frexp10(x):
|
|
576
|
+
exp = int(math.floor(math.log10(abs(x))))
|
|
577
|
+
return x / 10**exp, exp
|
|
578
|
+
|
|
579
|
+
def _latex_format(x):
|
|
580
|
+
if len('%f'%x)<6:
|
|
581
|
+
return '%f'%x
|
|
582
|
+
b,e = _frexp10(x)
|
|
583
|
+
e=f'10^{e}'
|
|
584
|
+
if b==1:
|
|
585
|
+
return e
|
|
586
|
+
return f'{b:g}'+r'\cdot'+e
|
|
587
|
+
|
|
588
|
+
def plot_mmc(cfgstr,srccfg,geomcfg,logy,do_pdf):
|
|
589
|
+
assert logy in (True,False,'auto')
|
|
590
|
+
if logy=='auto':
|
|
591
|
+
logy=True
|
|
592
|
+
np,plt,pdf = import_npplt(do_pdf)
|
|
593
|
+
from . import _mmc as nc_mmc
|
|
594
|
+
quick_mode = False
|
|
595
|
+
if ';' not in srccfg and ';' not in geomcfg:
|
|
596
|
+
#NB: this logic is in principle not quite robust in all cases
|
|
597
|
+
#(e.g. srccfg='sphere' would register as quick_mode, and then fail):
|
|
598
|
+
quick_mode = True
|
|
599
|
+
|
|
600
|
+
if quick_mode:
|
|
601
|
+
res = nc_mmc.quick_diffraction_pattern(cfgstr,
|
|
602
|
+
neutron_energy = srccfg,
|
|
603
|
+
material_thickness = geomcfg,
|
|
604
|
+
nstat = 'auto' )
|
|
605
|
+
nstat = res.setup_info['nstat']
|
|
606
|
+
title = (f'${_latex_format(nstat)}$ {srccfg} neutrons'
|
|
607
|
+
f' through {geomcfg} diameter sphere')
|
|
608
|
+
else:
|
|
609
|
+
res = nc_mmc.runsim_diffraction_pattern( cfgstr,
|
|
610
|
+
geomcfg = geomcfg,
|
|
611
|
+
srccfg = srccfg,
|
|
612
|
+
tally_detail_lvl = 2 )
|
|
613
|
+
title = (f'src="{srccfg}", geom="{geomcfg}"')
|
|
614
|
+
|
|
615
|
+
res.plot_breakdown( rebin_factor=10,#todo: hardcoded for now
|
|
616
|
+
logy=logy,
|
|
617
|
+
title = title,
|
|
618
|
+
plt = plt,
|
|
619
|
+
do_show = False
|
|
620
|
+
)
|
|
621
|
+
_end_plot(plt,pdf)
|
|
622
|
+
|
|
623
|
+
def plot_xsect(cfgs,comp,absorption,pdf,versus_energy,xrange,logy,breakdown_by_phases):
|
|
624
|
+
assert logy in (True,False,'auto')
|
|
625
|
+
if logy=='auto':
|
|
626
|
+
logy=versus_energy
|
|
627
|
+
assert comp in ('coh_elas','incoh_elas','elastic','inelastic','sans','all')
|
|
628
|
+
assert not absorption or comp=='all'
|
|
629
|
+
scalefactors = None
|
|
630
|
+
|
|
631
|
+
if breakdown_by_phases:
|
|
632
|
+
assert len(cfgs)==1
|
|
633
|
+
if cfgs[0].nPhases()==0:
|
|
634
|
+
warn("--phases ignored for a single phase material")
|
|
635
|
+
breakdown_by_phases = False
|
|
636
|
+
else:
|
|
637
|
+
mothercfg = cfgs[0]
|
|
638
|
+
scalefactors = list(mothercfg.getChildPhaseNumberFraction(i) for i in range(mothercfg.nPhases()))
|
|
639
|
+
assert abs(sum(scalefactors)-1.0)<1e-10
|
|
640
|
+
cfgs = list(mothercfg.getChildPhaseCfg(i) for i in range(mothercfg.nPhases()))
|
|
641
|
+
|
|
642
|
+
np,plt,pdf = import_npplt(pdf)
|
|
643
|
+
if versus_energy:
|
|
644
|
+
plt.xlabel('Neutron energy [eV]')
|
|
645
|
+
else:
|
|
646
|
+
plt.xlabel('Neutron wavelength [angstrom]')
|
|
647
|
+
plt.ylabel('Cross section [barn/atom]')
|
|
648
|
+
if len(cfgs)==1 and comp in ('all','elastic'):
|
|
649
|
+
if comp=='all':
|
|
650
|
+
parts=[cfgs[0].get_scatter('coh_elas'),cfgs[0].get_scatter('incoh_elas'),
|
|
651
|
+
cfgs[0].get_scatter('inelastic'),cfgs[0].get_scatter('sans')]
|
|
652
|
+
else:
|
|
653
|
+
assert comp=='elastic'
|
|
654
|
+
parts=[cfgs[0].get_scatter('coh_elas'),cfgs[0].get_scatter('incoh_elas'),cfgs[0].get_scatter('sans')]
|
|
655
|
+
if absorption:
|
|
656
|
+
assert comp=='all'
|
|
657
|
+
parts += [cfgs[0].get_absorption()]
|
|
658
|
+
else:
|
|
659
|
+
if absorption:
|
|
660
|
+
assert comp=='all'
|
|
661
|
+
parts=[c.get_totalxsect() for c in cfgs]
|
|
662
|
+
else:
|
|
663
|
+
parts=[c.get_scatter(comp) for c in cfgs]
|
|
664
|
+
|
|
665
|
+
if breakdown_by_phases:
|
|
666
|
+
sc_sans = mothercfg.get_scatter('sans')
|
|
667
|
+
if not sc_sans._nullprocess:
|
|
668
|
+
scalefactors += [1.0]
|
|
669
|
+
parts += [sc_sans]
|
|
670
|
+
|
|
671
|
+
if not breakdown_by_phases:
|
|
672
|
+
#trim unused process types (but always show all in case of breakdown_by_phases):
|
|
673
|
+
parts = [p for p in parts if not p._nullprocess]
|
|
674
|
+
|
|
675
|
+
if not breakdown_by_phases:
|
|
676
|
+
title,labels = create_title_and_labels(parts)
|
|
677
|
+
if len(set(labels))!=len(labels):
|
|
678
|
+
warn("Comparing identical setups?")
|
|
679
|
+
else:
|
|
680
|
+
title,labels = create_title_and_labels(parts)
|
|
681
|
+
|
|
682
|
+
npts = 3000
|
|
683
|
+
if versus_energy:
|
|
684
|
+
ekins = create_ekins(npts,xrange)
|
|
685
|
+
else:
|
|
686
|
+
wavelengths = create_wavelengths(np,cfgs,npts,xrange)
|
|
687
|
+
from .constants import wl2ekin as nc_wl2ekin
|
|
688
|
+
ekins = nc_wl2ekin(wavelengths)
|
|
689
|
+
need_tot = (len(cfgs)==1 and len(parts)>1) or breakdown_by_phases
|
|
690
|
+
xsects_tot = None
|
|
691
|
+
max_len_label = 0
|
|
692
|
+
|
|
693
|
+
#colors inspired by http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
|
|
694
|
+
col_red = "#F15854"
|
|
695
|
+
partcols = [
|
|
696
|
+
"#5DA5DA", # (blue)
|
|
697
|
+
"#FAA43A", # (orange)
|
|
698
|
+
"#60BD68", # (green)
|
|
699
|
+
#"#B2912F", # (brown)
|
|
700
|
+
"#B276B2", # (purple)
|
|
701
|
+
#"#DECF3F", # (yellow)
|
|
702
|
+
#"#F17CB0", # (pink)
|
|
703
|
+
"#4D4D4D", # (gray)
|
|
704
|
+
]
|
|
705
|
+
if not need_tot:
|
|
706
|
+
partcols = [col_red]+partcols
|
|
707
|
+
|
|
708
|
+
linewidth = 2.0
|
|
709
|
+
|
|
710
|
+
xvar = ekins if versus_energy else wavelengths
|
|
711
|
+
|
|
712
|
+
ydatarange = {}
|
|
713
|
+
def update_ydatarange(xsects):
|
|
714
|
+
ynz = xsects[np.nonzero(xsects)]
|
|
715
|
+
y0nonzero,y0,y1 = ( ynz.min() if len(ynz) else None), xsects.min(), xsects.max()
|
|
716
|
+
if y0 < ydatarange.get('ymin',float('inf')):
|
|
717
|
+
ydatarange['ymin'] = y0
|
|
718
|
+
if y0nonzero is not None and y0nonzero < ydatarange.get('ymin_nonzero',float('inf')):
|
|
719
|
+
ydatarange['ymin_nonzero'] = y0nonzero
|
|
720
|
+
if y1 > ydatarange.get('ymax',float('-inf')):
|
|
721
|
+
ydatarange['ymax'] = y1
|
|
722
|
+
|
|
723
|
+
for ipart,part in enumerate(parts):
|
|
724
|
+
#cfg = part._cfg
|
|
725
|
+
#compname = part._compname
|
|
726
|
+
if part.isOriented():
|
|
727
|
+
raise SystemExit("ERROR: This utility can not produce quick"
|
|
728
|
+
" cross-section plots for oriented processes"
|
|
729
|
+
" (but you can still inspect the material with"
|
|
730
|
+
" --dump)")
|
|
731
|
+
xsects = part.crossSectionIsotropic(ekins)
|
|
732
|
+
if scalefactors is not None:
|
|
733
|
+
xsects *= scalefactors[ipart]
|
|
734
|
+
if need_tot:
|
|
735
|
+
if xsects_tot is None:
|
|
736
|
+
xsects_tot = np.zeros(len(xsects))
|
|
737
|
+
xsects_tot += xsects
|
|
738
|
+
label=labels[ipart]
|
|
739
|
+
max_len_label = max(max_len_label,len(label))
|
|
740
|
+
ls={0:'-',1:'--',2:':'}.get(ipart//len(partcols),'-.')
|
|
741
|
+
plt.plot(xvar,xsects,label=label,color=partcols[ipart%len(partcols)],lw=linewidth,ls=ls)
|
|
742
|
+
update_ydatarange(xsects)
|
|
743
|
+
if need_tot:
|
|
744
|
+
if comp=='all' and len(cfgs)==1 and not breakdown_by_phases:
|
|
745
|
+
#sanity check
|
|
746
|
+
if absorption:
|
|
747
|
+
xsects_tot_direct = cfgs[0].get_totalxsect().crossSectionIsotropic(ekins)
|
|
748
|
+
else:
|
|
749
|
+
xsects_tot_direct = cfgs[0].get_scatter('all').crossSectionIsotropic(ekins)
|
|
750
|
+
xsects_discrepancy = xsects_tot_direct-xsects_tot
|
|
751
|
+
discr_lvl = max(abs(xsects_discrepancy))
|
|
752
|
+
if discr_lvl > 1e-10:
|
|
753
|
+
warn("WARNING: Discrepancy in breakdown into components detected (at the"
|
|
754
|
+
+f" {discr_lvl} level)!! Some plugins might be incorrectly programmed.")
|
|
755
|
+
plt.plot(xvar,xsects_discrepancy,
|
|
756
|
+
label='WARNING: Discrepancy!',
|
|
757
|
+
color="cyan",lw=linewidth)
|
|
758
|
+
|
|
759
|
+
update_ydatarange(xsects_tot)
|
|
760
|
+
plt.plot(xvar,xsects_tot,
|
|
761
|
+
label={'all':'Total','elastic':'Total elastic'}[comp],
|
|
762
|
+
color="#F15854",lw=linewidth)#red-ish colour (see above)
|
|
763
|
+
leg_fsize = 'large'
|
|
764
|
+
if max_len_label > 40:
|
|
765
|
+
leg_fsize = 'medium'
|
|
766
|
+
if max_len_label > 60:
|
|
767
|
+
leg_fsize = 'small'
|
|
768
|
+
if max_len_label > 80:
|
|
769
|
+
leg_fsize = 'smaller'
|
|
770
|
+
try:
|
|
771
|
+
if len(parts)>1:
|
|
772
|
+
leg=plt.legend(loc='best',fontsize=leg_fsize)
|
|
773
|
+
if hasattr(leg,'set_draggable'):
|
|
774
|
+
leg.set_draggable(True)
|
|
775
|
+
else:
|
|
776
|
+
leg.draggable(True)
|
|
777
|
+
except TypeError:
|
|
778
|
+
plt.legend(loc='best')
|
|
779
|
+
plt.grid()
|
|
780
|
+
single_yval = bool(ydatarange.get('ymin','n/a')==ydatarange.get('ymax','n/a'))
|
|
781
|
+
if single_yval and ydatarange.get('ymin','n/a')==0.0:
|
|
782
|
+
if logy:
|
|
783
|
+
warn('Could not set log scale since curves are 0.0 everywhere')
|
|
784
|
+
plt.gca().set_ylim(0.0,1.0)
|
|
785
|
+
elif logy:
|
|
786
|
+
if ydatarange.get('ymin',1.0) <= 0.0:
|
|
787
|
+
_=ydatarange.get('ymin_nonzero',ydatarange.get('ymax',1.0)*1e-10)
|
|
788
|
+
if _:
|
|
789
|
+
plt.gca().set_ylim(_,None)
|
|
790
|
+
plt.gca().set_yscale('log')
|
|
791
|
+
else:
|
|
792
|
+
plt.gca().set_ylim(0,None)
|
|
793
|
+
|
|
794
|
+
if versus_energy:
|
|
795
|
+
plt.gca().set_xlim(ekins[0],ekins[-1])
|
|
796
|
+
plt.gca().set_xscale('log')
|
|
797
|
+
else:
|
|
798
|
+
if wavelengths[0]*100<wavelengths[-1]:
|
|
799
|
+
plt.gca().set_xlim(0.0,wavelengths[-1])
|
|
800
|
+
else:
|
|
801
|
+
plt.gca().set_xlim(wavelengths[0],wavelengths[-1])
|
|
802
|
+
if title:
|
|
803
|
+
if len(title)>30:
|
|
804
|
+
plt.title(title,fontsize='smaller')
|
|
805
|
+
else:
|
|
806
|
+
plt.title(title)
|
|
807
|
+
_end_plot(plt,pdf)
|
|
808
|
+
|
|
809
|
+
def plot_2d_scatangle(cfg,comp,pdf,versus_energy,xrange):
|
|
810
|
+
assert comp in ('coh_elas','incoh_elas','elastic','inelastic','sans','all')
|
|
811
|
+
part=cfg.get_scatter(comp)
|
|
812
|
+
|
|
813
|
+
np,plt,pdf = import_npplt(pdf)
|
|
814
|
+
|
|
815
|
+
#reproducible plots:
|
|
816
|
+
import random
|
|
817
|
+
random.seed(123456)
|
|
818
|
+
|
|
819
|
+
#higher granularity wavelengths than for 1D plot to avoid artifacts:
|
|
820
|
+
npts = 100 if _is_unittest() else 30000
|
|
821
|
+
if versus_energy:
|
|
822
|
+
ekins = create_ekins(npts,xrange)
|
|
823
|
+
else:
|
|
824
|
+
wavelengths = create_wavelengths(np,[cfg],npts,xrange)
|
|
825
|
+
from .constants import wl2ekin as nc_wl2ekin
|
|
826
|
+
ekins = nc_wl2ekin(wavelengths)
|
|
827
|
+
|
|
828
|
+
#get title (label should be uninteresting for a single part):
|
|
829
|
+
title,labels = create_title_and_labels([part])
|
|
830
|
+
|
|
831
|
+
#First figure out how many points to put at each wavelength (or energy)
|
|
832
|
+
if not part._nullprocess:
|
|
833
|
+
xsects = part.crossSectionIsotropic(ekins)
|
|
834
|
+
n2d = 100 if _is_unittest() else 25000
|
|
835
|
+
sumxs = np.sum(xsects)
|
|
836
|
+
if sumxs:
|
|
837
|
+
n_at_xvar = np.random.poisson(xsects*n2d/np.sum(xsects))
|
|
838
|
+
else:
|
|
839
|
+
n_at_xvar = np.zeros(len(xsects))
|
|
840
|
+
else:
|
|
841
|
+
n_at_xvar = np.zeros(len(wavelengths))
|
|
842
|
+
|
|
843
|
+
xvar = ekins if versus_energy else wavelengths
|
|
844
|
+
|
|
845
|
+
n2d=int(np.sum(n_at_xvar))#correction for random fluctuations
|
|
846
|
+
if n2d>0:
|
|
847
|
+
from .constants import wl2ekin as nc_wl2ekin
|
|
848
|
+
plot_angles = np.zeros(n2d)
|
|
849
|
+
plot_xvar = np.zeros(n2d)
|
|
850
|
+
j = 0
|
|
851
|
+
for i,n in enumerate(n_at_xvar):
|
|
852
|
+
i,n = int(i),int(n)
|
|
853
|
+
evalue = xvar[i] if versus_energy else nc_wl2ekin(xvar[i])
|
|
854
|
+
ekinfinal,mu = part.sampleScatterIsotropic(evalue,repeat=int(n))
|
|
855
|
+
plot_angles[j:j+n] = np.arccos(mu)
|
|
856
|
+
plot_xvar[j:j+n].fill(xvar[i])
|
|
857
|
+
j+=n
|
|
858
|
+
plot_angles *= (180./np.pi)
|
|
859
|
+
else:
|
|
860
|
+
plot_angles = None
|
|
861
|
+
plot_xvar = None
|
|
862
|
+
|
|
863
|
+
if plot_xvar is not None:
|
|
864
|
+
try:
|
|
865
|
+
plt.scatter(plot_xvar,plot_angles,alpha=0.2,marker='.',edgecolor=None,color='black',s=2,zorder=1)
|
|
866
|
+
except ValueError:
|
|
867
|
+
plt.scatter(plot_xvar,plot_angles,alpha=0.2,edgecolor=None,color='black',s=2,zorder=1)
|
|
868
|
+
|
|
869
|
+
if versus_energy:
|
|
870
|
+
plt.gca().set_xlim(ekins[0],ekins[-1])
|
|
871
|
+
plt.gca().set_xscale('log')
|
|
872
|
+
else:
|
|
873
|
+
if wavelengths[0]*100<wavelengths[-1]:
|
|
874
|
+
plt.gca().set_xlim(0.0,wavelengths[-1])
|
|
875
|
+
else:
|
|
876
|
+
plt.gca().set_xlim(wavelengths[0],wavelengths[-1])
|
|
877
|
+
plt.gca().set_ylim(0,180)
|
|
878
|
+
if versus_energy:
|
|
879
|
+
plt.xlabel('Neutron energy [eV]')
|
|
880
|
+
else:
|
|
881
|
+
plt.xlabel('Neutron wavelength [angstrom]')
|
|
882
|
+
plt.ylabel('Scattering angle [degrees]')
|
|
883
|
+
if title:
|
|
884
|
+
plt.title(title)
|
|
885
|
+
plt.grid()
|
|
886
|
+
_end_plot(plt,pdf)
|
|
887
|
+
|
|
888
|
+
class XSSum:
|
|
889
|
+
#Combine scatter+absorption processes (hence no sampleScatterIsotropic method).
|
|
890
|
+
def __init__(self,*processes):
|
|
891
|
+
self._p = processes[:]
|
|
892
|
+
def crossSectionIsotropic(self,ekin):
|
|
893
|
+
return sum(p.crossSectionIsotropic(ekin) for p in self._p)
|
|
894
|
+
def isOriented(self):
|
|
895
|
+
return any(p.isOriented() for p in self._p)
|
|
896
|
+
|
|
897
|
+
class Cfg:
|
|
898
|
+
def __init__(self,cfgstr, commoncfgstr):
|
|
899
|
+
from .cfgstr import normaliseCfg
|
|
900
|
+
self._cfgstr = normaliseCfg('%s;%s'%(cfgstr,commoncfgstr))
|
|
901
|
+
self._sc = {}
|
|
902
|
+
self._abs = None
|
|
903
|
+
self._totxs = None
|
|
904
|
+
self._info = None
|
|
905
|
+
self._bt = 'not_init'
|
|
906
|
+
self._iphase = None
|
|
907
|
+
|
|
908
|
+
def nPhases(self):
|
|
909
|
+
return len(self.get_info().phases)
|
|
910
|
+
|
|
911
|
+
def getChildPhaseCfg(self,iphase):
|
|
912
|
+
assert( iphase < self.nPhases() )
|
|
913
|
+
childcfg = Cfg( self._cfgstr,'phasechoice=%i'%iphase )
|
|
914
|
+
childcfg._iphase = iphase
|
|
915
|
+
return childcfg
|
|
916
|
+
|
|
917
|
+
def getChildPhaseNumberFraction(self,iphase):
|
|
918
|
+
#fraction of atoms in phase
|
|
919
|
+
i = self.get_info()
|
|
920
|
+
volfrac,iph = i.phases[iphase]
|
|
921
|
+
return volfrac*iph.numberdensity / i.numberdensity
|
|
922
|
+
|
|
923
|
+
def braggthreshold(self):
|
|
924
|
+
"""in Aa (or None). Largest BT value for any crystalline phase."""
|
|
925
|
+
if self._bt != 'not_init':
|
|
926
|
+
return self._bt
|
|
927
|
+
def largestbtrecursive(info):
|
|
928
|
+
if info.isSinglePhase():
|
|
929
|
+
return info.braggthreshold
|
|
930
|
+
bts = [ largestbtrecursive(ph[1]) for ph in info.phases ]
|
|
931
|
+
bts = [ e for e in bts if e ]
|
|
932
|
+
return max(bts) if bts else None
|
|
933
|
+
self._bt = largestbtrecursive( self.get_info() )
|
|
934
|
+
return self._bt
|
|
935
|
+
|
|
936
|
+
def get_scatter(self,comp = 'all', allowfail = False):
|
|
937
|
+
if comp not in self._sc:
|
|
938
|
+
from . import core as nccore
|
|
939
|
+
extra_cfg = comp2cfgpars(comp)
|
|
940
|
+
cstr = ';'.join([self._cfgstr,extra_cfg])
|
|
941
|
+
try:
|
|
942
|
+
sc = nccore.createScatter(cstr)
|
|
943
|
+
except nccore.NCException:
|
|
944
|
+
if allowfail:
|
|
945
|
+
return None
|
|
946
|
+
else:
|
|
947
|
+
raise
|
|
948
|
+
sc._nullprocess = sc.isNull()
|
|
949
|
+
sc._cfg = self
|
|
950
|
+
sc._compname = comp
|
|
951
|
+
self._sc[comp] = sc
|
|
952
|
+
return self._sc[comp]
|
|
953
|
+
|
|
954
|
+
def get_absorption(self):
|
|
955
|
+
if not self._abs:
|
|
956
|
+
from . import core as nccore
|
|
957
|
+
a = nccore.createAbsorption(self._cfgstr)
|
|
958
|
+
a._nullprocess = a.isNull()
|
|
959
|
+
a._cfg = self
|
|
960
|
+
a._compname = 'absorption'
|
|
961
|
+
self._abs = a
|
|
962
|
+
return self._abs
|
|
963
|
+
|
|
964
|
+
def get_totalxsect(self):
|
|
965
|
+
if not self._totxs:
|
|
966
|
+
a,s = self.get_absorption(),self.get_scatter('all')
|
|
967
|
+
t = XSSum(a,s)
|
|
968
|
+
t._nullprocess = a._nullprocess and s._nullprocess
|
|
969
|
+
t._cfg = self
|
|
970
|
+
t._compname = 'scattering+absorption'
|
|
971
|
+
self._totxs = t
|
|
972
|
+
return self._totxs
|
|
973
|
+
|
|
974
|
+
def get_info(self):
|
|
975
|
+
if not self._info:
|
|
976
|
+
from . import core as nccore
|
|
977
|
+
self._info = nccore.createInfo(self._cfgstr)
|
|
978
|
+
return self._info
|
|
979
|
+
|
|
980
|
+
@property
|
|
981
|
+
def cfgstr(self):
|
|
982
|
+
return self._cfgstr
|
|
983
|
+
|
|
984
|
+
def benchmark_mode( progname, arglist):
|
|
985
|
+
|
|
986
|
+
parser = create_ArgumentParser(prog=progname,
|
|
987
|
+
description='nctool benchmark mode'
|
|
988
|
+
' (triggered by presence of --bench). '
|
|
989
|
+
'Measured timing is printed (in '
|
|
990
|
+
'milliseconds).')
|
|
991
|
+
parser.add_argument('--bench', action='store_true',
|
|
992
|
+
help='Flag triggering the benchmark mode.')
|
|
993
|
+
parser.add_argument('cfgstr', metavar='CFGSTR', type=str,nargs=1,
|
|
994
|
+
help="NCrystal material cfg-string to investigate.")
|
|
995
|
+
parser.add_argument('--onlyinfo', action='store_true',
|
|
996
|
+
help='Unless this flag is specified, both createInfo()'
|
|
997
|
+
' and createScatter() are benchmarked.')
|
|
998
|
+
parser.add_argument('-n','--nrepeat', default=1,type=int,
|
|
999
|
+
help="""Number of times to measure.""")
|
|
1000
|
+
parser.add_argument('--threads', default=1,type=int,
|
|
1001
|
+
help="Number of threads in NCrystal's"
|
|
1002
|
+
" factory thread pool.")
|
|
1003
|
+
args=parser.parse_args( arglist )
|
|
1004
|
+
nccommon.ncsetenv('FACTORY_THREADS',args.threads)
|
|
1005
|
+
from . import misc as nc_misc
|
|
1006
|
+
assert len(args.cfgstr)==1
|
|
1007
|
+
dt = nc_misc._benchloadcfg( args.cfgstr[0],
|
|
1008
|
+
do_scatter = not args.onlyinfo,
|
|
1009
|
+
nrepeat=args.nrepeat )
|
|
1010
|
+
if _is_unittest():
|
|
1011
|
+
dt = 0.0
|
|
1012
|
+
print(f'{dt*1000.0:.2f}ms')
|
|
1013
|
+
|
|
1014
|
+
def maybeThisIsConda():
|
|
1015
|
+
import os
|
|
1016
|
+
import sys
|
|
1017
|
+
return ( os.environ.get('CONDA_PREFIX',None) or
|
|
1018
|
+
os.path.exists(os.path.join(sys.base_prefix, 'conda-meta')) )
|