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
@@ -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')) )