ncrystal-python 4.1.8__tar.gz → 4.2.2__tar.gz

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 (62) hide show
  1. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/PKG-INFO +1 -1
  2. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/pyproject.toml +1 -0
  3. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/__init__.py +3 -3
  4. ncrystal_python-4.2.2/src/NCrystal/_cli_ncmat2endf.py +337 -0
  5. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/_common.py +35 -1
  6. ncrystal_python-4.2.2/src/NCrystal/_ncmat2endf_impl.py +1580 -0
  7. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/_ncmatimpl.py +28 -11
  8. ncrystal_python-4.2.2/src/NCrystal/_sabutils.py +100 -0
  9. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/cifutils.py +1 -1
  10. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/ncmat.py +8 -3
  11. ncrystal_python-4.2.2/src/NCrystal/ncmat2endf.py +313 -0
  12. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/plot.py +6 -4
  13. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/ncrystal_python.egg-info/PKG-INFO +1 -1
  14. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/ncrystal_python.egg-info/SOURCES.txt +4 -0
  15. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/ncrystal_python.egg-info/entry_points.txt +1 -0
  16. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/LICENSE +0 -0
  17. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/README.md +0 -0
  18. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/setup.cfg +0 -0
  19. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/__main__.py +0 -0
  20. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/_chooks.py +0 -0
  21. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/_cli_cif2ncmat.py +0 -0
  22. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/_cli_endf2ncmat.py +0 -0
  23. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/_cli_hfg2ncmat.py +0 -0
  24. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/_cli_mcstasunion.py +0 -0
  25. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/_cli_ncmat2cpp.py +0 -0
  26. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/_cli_ncmat2hkl.py +0 -0
  27. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/_cli_nctool.py +0 -0
  28. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/_cli_vdos2ncmat.py +0 -0
  29. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/_cli_verifyatompos.py +0 -0
  30. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/_cliimpl.py +0 -0
  31. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/_cliwrap_config.py +0 -0
  32. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/_coreimpl.py +0 -0
  33. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/_hfgdata.py +0 -0
  34. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/_hklobjects.py +0 -0
  35. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/_is_std.py +0 -0
  36. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/_locatelib.py +0 -0
  37. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/_miscimpl.py +0 -0
  38. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/_mmc.py +0 -0
  39. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/_msg.py +0 -0
  40. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/_ncmat2cpp_impl.py +0 -0
  41. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/_numpy.py +0 -0
  42. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/_testimpl.py +0 -0
  43. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/api.py +0 -0
  44. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/atomdata.py +0 -0
  45. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/cfgstr.py +0 -0
  46. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/cli.py +0 -0
  47. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/constants.py +0 -0
  48. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/core.py +0 -0
  49. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/datasrc.py +0 -0
  50. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/exceptions.py +0 -0
  51. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/hfg2ncmat.py +0 -0
  52. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/mcstasutils.py +0 -0
  53. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/misc.py +0 -0
  54. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/mmc.py +0 -0
  55. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/ncmat2cpp.py +0 -0
  56. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/obsolete.py +0 -0
  57. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/plugins.py +0 -0
  58. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/test.py +0 -0
  59. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/NCrystal/vdos.py +0 -0
  60. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/ncrystal_python.egg-info/dependency_links.txt +0 -0
  61. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/ncrystal_python.egg-info/requires.txt +0 -0
  62. {ncrystal_python-4.1.8 → ncrystal_python-4.2.2}/src/ncrystal_python.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ncrystal-python
3
- Version: 4.1.8
3
+ Version: 4.2.2
4
4
  Summary: Library for thermal neutron transport in crystals and other materials.
5
5
  Author: NCrystal developers (Thomas Kittelmann, Xiao Xiao Cai)
6
6
  License:
@@ -52,6 +52,7 @@ ncrystal_cif2ncmat = "NCrystal._cli_cif2ncmat:main"
52
52
  ncrystal_endf2ncmat = "NCrystal._cli_endf2ncmat:main"
53
53
  ncrystal_hfg2ncmat = "NCrystal._cli_hfg2ncmat:main"
54
54
  ncrystal_mcstasunion = "NCrystal._cli_mcstasunion:main"
55
+ ncrystal_ncmat2endf = "NCrystal._cli_ncmat2endf:main"
55
56
  ncrystal_ncmat2cpp = "NCrystal._cli_ncmat2cpp:main"
56
57
  ncrystal_ncmat2hkl = "NCrystal._cli_ncmat2hkl:main"
57
58
  ncrystal_vdos2ncmat = "NCrystal._cli_vdos2ncmat:main"
@@ -48,14 +48,14 @@ additionally also use the following reference in your work:
48
48
 
49
49
  For detailed usage conditions and licensing of this open source project, see:
50
50
 
51
- https://github.com/mctools/ncrystal/blob/master/NOTICE
52
- https://github.com/mctools/ncrystal/blob/master/LICENSE
51
+ https://github.com/mctools/ncrystal/blob/HEAD/NOTICE
52
+ https://github.com/mctools/ncrystal/blob/HEAD/LICENSE
53
53
 
54
54
  """
55
55
 
56
56
  #NB: Synchronize meta-data below with fields in setup.py+template_setup.py.in meta data:
57
57
  __license__ = "Apache 2.0, http://www.apache.org/licenses/LICENSE-2.0"
58
- __version__ = '4.1.8'
58
+ __version__ = '4.2.2'
59
59
  __status__ = "Production"
60
60
  __author__ = "NCrystal developers (Thomas Kittelmann, Xiao Xiao Cai)"
61
61
  __copyright__ = "Copyright 2015-2024 %s"%__author__
@@ -0,0 +1,337 @@
1
+
2
+ ################################################################################
3
+ ## ##
4
+ ## This file is part of NCrystal (see https://mctools.github.io/ncrystal/) ##
5
+ ## ##
6
+ ## Copyright 2015-2025 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
+ from ._cliimpl import ( create_ArgumentParser,
23
+ cli_entry_point )
24
+
25
+ longopt_metadata = '--mdata'
26
+ metavar_elastic = 'MODE'
27
+ metavar_matname = 'NAME'
28
+ metavar_metadata = 'DATA'
29
+ longopt_elastic = '--elas'
30
+ longopt_matname = '--name'
31
+ longopt_datenow = '--now'
32
+ longopt_othertemps = '--othertemps'
33
+
34
+ #Examples here so they can be unit tested:
35
+ examples = [
36
+ ['Al_sg225.ncmat;temp=350K'],
37
+ ['Si_sg227.ncmat;temp=293.6K','-m','MATNUM:Si:99',longopt_datenow],
38
+ ['ZnO_sg186_ZincOxide.ncmat;temp=293.15K','-n','ZnO',
39
+ '-e','scaled','-m','MATNUM:Zn:101,O:102'],
40
+ ['Bi_sg166.ncmat;comp=inelas;temp=77K','-m','AUTH:J. Doe']
41
+ ]
42
+
43
+ def _parseArgs( progname, arglist, return_parser=False ):
44
+ from .ncmat2endf import ( available_elastic_modes,
45
+ default_smin_value,
46
+ default_emax_value )
47
+ from ._common import print
48
+ from argparse import RawTextHelpFormatter
49
+ import textwrap
50
+ import json
51
+ import shlex
52
+
53
+ helpw = 60
54
+ descrw = helpw + 22
55
+ descr_sections = [
56
+ """Script for creating a set of ENDF-6 thermal scattering files for the
57
+ material described by a particular NCrystal cfg-string.
58
+ """,
59
+ """
60
+ The script uses the endf-parserpy package from IAEA to format and check
61
+ the syntax of the ENDF-6 file:
62
+ """,
63
+ """
64
+ G. Schnabel, D. L. Aldama, R. Capote,
65
+ https://doi.org/10.48550/arXiv.2312.08249
66
+ """,
67
+ f"""
68
+ Note that while the handling of multiple temperatures in one ENDF-6
69
+ file is supported via the {longopt_othertemps} keyword, it is not
70
+ recommended. This is because NCrystal computes an optimal (alpha, beta)
71
+ grid for each material and temperature, while the ENDF format imposes
72
+ the same grid on all temperatures.
73
+ """,
74
+ ]
75
+
76
+ # NOTICE ^^^^^^^^^^^
77
+ #
78
+ # When updating ncmat2endf there are 2 main doc texts that have to be
79
+ # checked for updates:
80
+ #
81
+ # 1) The ncmat2endf function doc-string in ncmat2endf.py
82
+ # 2) The ncmat2endf CLI --help text in _cli_ncmat2endf.py
83
+ #
84
+
85
+ descr = '\n\n'.join(textwrap.fill(' '.join(e.strip().split()),descrw)
86
+ for e in descr_sections)
87
+
88
+ def exquote(e):
89
+ #prefer " for quoting (for Windows compatibility)
90
+ e = shlex.quote(e)
91
+ if '"' not in e and "'" in e:
92
+ e = e.replace("'",'"')
93
+ if '"' not in e and ',' in e:
94
+ #Add some quotes that shlex did not think necessary:
95
+ e = '"%s"'%e
96
+ return e
97
+
98
+ descr += "\n\nExample invocations:\n\n"
99
+ exw = descrw - len(progname) - 7
100
+ expre=f' $> {progname} '
101
+ for example in examples:
102
+ s=['']
103
+ for e in example:
104
+ e = exquote(e)
105
+ if len(s[-1]+e) > exw:
106
+ s[-1] += ' \\'
107
+ s.append('')
108
+ s[-1] += ' %s'%e
109
+ descr += '%s%s\n'%(expre,s[0])
110
+ for e in s[1:]:
111
+ descr += '%s%s\n'%(' '*len(expre),e)
112
+ descr += '\n'
113
+
114
+ usagestr = (
115
+ f'{progname} CFGSTR [{longopt_elastic} {metavar_elastic}]'
116
+ + f' [{longopt_matname} {metavar_matname}]'
117
+ + f' [{longopt_metadata} {metavar_metadata}]\n'
118
+ + (' '*(len(progname)+8))
119
+ + '[<<additional options described below>>]'
120
+ )
121
+
122
+ parser = create_ArgumentParser( prog = progname,
123
+ description=descr.strip()+'\n',
124
+ usage=usagestr,
125
+ formatter_class=RawTextHelpFormatter )
126
+ def wrap(t):
127
+ return textwrap.fill(t,width=helpw)
128
+
129
+ required_args = parser.add_argument_group('required arguments')
130
+ required_args.add_argument('CFGSTR',
131
+ help=wrap('NCrystal cfg-string defining the'
132
+ ' material.'))
133
+
134
+ ba = parser.add_argument_group('Commonly used arguments')
135
+ ba.add_argument( '-n', longopt_matname, metavar=metavar_matname,
136
+ help=wrap('Name of the material to be processed.'
137
+ 'If set ENDF files will be named '
138
+ f'tsl_element_in_<{metavar_matname}>.endf.'))
139
+ elasmode_default = 'scaled'
140
+ assert elasmode_default in available_elastic_modes
141
+ elasmode_other = list(e for e in available_elastic_modes
142
+ if e != elasmode_default )
143
+ assert len(elasmode_other)==2
144
+ ba.add_argument('-e', longopt_elastic,metavar=metavar_elastic,
145
+ help=wrap('Approximation used for the elastic component'
146
+ f' (default "{elasmode_default}, other options'
147
+ f' are "{elasmode_other[0]}" and'
148
+ f' "{elasmode_other[1]}").'
149
+ ' See DOI:10.1016/j.nima.2021.166227 for'
150
+ ' meaning of modes.'),
151
+ type=str, choices=available_elastic_modes,
152
+ default=elasmode_default)
153
+ ba.add_argument(longopt_metadata,default={},
154
+ help=wrap('JSON dictionary containing ENDF-6'
155
+ f' metadata. Run with {longopt_metadata}=help '
156
+ 'for more information.'))
157
+ ba.add_argument('-m',metavar='KEY:VAL', dest='mdata_kvlist',
158
+ action='append', nargs='+',
159
+ help=wrap('Add metadata entries. Run with '
160
+ f'{longopt_metadata}=help for more info.'))
161
+ ba.add_argument(longopt_datenow,action='store_true',
162
+ help=wrap('Set metadata fields EDATE, DDATE and RDATE'
163
+ ' to current date.'))
164
+
165
+ parser.add_argument('-v','--verbose', action='count',default=0,
166
+ help=wrap('Increase verbosity. Specify twice'
167
+ ' for additional verbosity.'))
168
+ parser.add_argument('--quiet','-q',default=False,action='store_true',
169
+ help=wrap('Silence non-error output.'))
170
+ ba.add_argument('-d', '--dir', default = '.', metavar='PATH', dest='outdir',
171
+ help=wrap('Directory for output files (default: current).'))
172
+ ba.add_argument('-i','--index', default = '',
173
+ metavar='FILE', dest='jsonindex',
174
+ help=wrap('Story summary of output in FILE (JSON format).'))
175
+ ba.add_argument('-f', '--force',action='store_true',
176
+ help=wrap('Overwrite output files if'
177
+ ' they already exist (danger!)'))
178
+
179
+ expert_args = parser.add_argument_group('Advanced expert-only arguments')
180
+ expert_args.add_argument(longopt_othertemps,metavar='TVALS',
181
+ nargs='+',
182
+ type=float,
183
+ help=wrap('Additional temperatures to process. As'
184
+ ' noted above this is not normally'
185
+ ' recommended, and it is preferred'
186
+ ' to invoke the script for each'
187
+ ' temperature independently using the'
188
+ ' "temp" keyword in the cfg-string.') )
189
+ expert_args.add_argument('--smin',metavar='VALUE',
190
+ type=float, default=default_smin_value,
191
+ help=wrap('Minimum value of S(alpha, beta) stored'
192
+ f' (default: {default_smin_value})'))
193
+ expert_args.add_argument('--emax',
194
+ type=float, default=default_emax_value,
195
+ help=wrap('Maximum neutron energy covered by'
196
+ ' the kernels'
197
+ f' (default: {default_emax_value:g}eV)')
198
+ )
199
+ expert_args.add_argument('--asymsab',action='store_true',
200
+ help=wrap('Store S(a,b) in asymmetric form.'))
201
+ expert_args.add_argument('--totsab',action='store_true',
202
+ help=wrap('Store S(a,b) branches for positive'
203
+ ' and negative beta'))
204
+
205
+ if return_parser:
206
+ return parser
207
+
208
+ #Avoid annoying CFGSTR-missing error when ppl use --mdata=help:
209
+ is_mdata_help = False
210
+ if f'{longopt_metadata}=help' in arglist:
211
+ is_mdata_help = True
212
+ elif longopt_metadata in arglist and 'help' in arglist:
213
+ if arglist.index(longopt_metadata)+1==arglist.index('help'):
214
+ is_mdata_help=True
215
+ if is_mdata_help:
216
+ arglist = [f'{longopt_metadata}=help','dummy']
217
+
218
+ args=parser.parse_args(arglist)
219
+ if args.mdata:
220
+ if args.mdata == 'help':
221
+ print(gen_metadata_doc())
222
+ raise SystemExit(0)
223
+ try:
224
+ args.mdata = json.loads(args.mdata)
225
+ except json.JSONDecodeError:
226
+ parser.error(f'Argument to {longopt_metadata} must be a JSON'
227
+ ' dictionary of key, value pairs')
228
+ else:
229
+ if not isinstance(args.mdata,dict):
230
+ parser.error(f'Argument to {longopt_metadata} must be a JSON'
231
+ ' dictionary of key, value pairs')
232
+ assert isinstance(args.mdata,dict)
233
+ for ee in args.mdata_kvlist or []:
234
+ for e in ee:
235
+ kv = list(_.strip() for _ in e.split(':',1))
236
+ if not len(kv)==2 or not kv[0]:
237
+ parser.error(f'Invalid parameter for -m: {repr(e)}')
238
+ args.mdata[kv[0]] = kv[1]
239
+ args.m = None
240
+
241
+ #map verbosity to 0...3 needed for Python API:
242
+ if args.quiet:
243
+ if args.verbose:
244
+ parser.error('Inconsistent usage of --quiet and --verbose flags')
245
+ else:
246
+ args.verbose = min( 3, args.verbose+1 )
247
+
248
+ if args.jsonindex:
249
+ import pathlib
250
+ args.jsonindex = pathlib.Path(args.jsonindex)
251
+ if args.jsonindex.is_file():
252
+ if not args.force:
253
+ parser.error('File already exists (run with --force to'
254
+ f' overwrite): {args.jsonindex}')
255
+ args.jsonindex.unlink()
256
+ assert not args.jsonindex.is_file()
257
+ if not args.jsonindex.parent.is_dir():
258
+ parser.error('Directory does not exist: {args.jsonindex.parent}')
259
+ args.jsonindex = args.jsonindex.absolute()
260
+
261
+ return args
262
+
263
+ def create_argparser_for_sphinx( progname ):
264
+ return _parseArgs(progname,[],return_parser=True)
265
+
266
+ @cli_entry_point
267
+ def main( progname, arglist ):
268
+ args = _parseArgs( progname, arglist )
269
+ if args.quiet:
270
+ from ._common import ( modify_ncrystal_print_fct_ctxmgr,
271
+ WarningSpy )
272
+ with modify_ncrystal_print_fct_ctxmgr('block'):
273
+ with WarningSpy( block = True ):
274
+ _main_impl(args)
275
+ else:
276
+ _main_impl(args)
277
+
278
+ def _main_impl( args ):
279
+ from .ncmat2endf import EndfMetaData, ncmat2endf
280
+ metadata = EndfMetaData()
281
+ if args.mdata:
282
+ metadata.update_from_dict(args.mdata)
283
+ if args.now:
284
+ metadata.set_all_dates_as_now()
285
+ lasym = 0
286
+ if args.totsab:
287
+ lasym = 1
288
+ if args.asymsab:
289
+ lasym += 2
290
+ r = ncmat2endf( args.CFGSTR,
291
+ material_name = args.name,
292
+ endf_metadata = metadata,
293
+ othertemps = args.othertemps,
294
+ elastic_mode = args.elas,
295
+ force = args.force,
296
+ smin = args.smin,
297
+ emax = args.emax,
298
+ lasym = lasym,
299
+ verbosity = args.verbose,
300
+ outdir = args.outdir )
301
+ if args.jsonindex:
302
+ import json
303
+ r_json = json.dumps( r, indent = 4 ).rstrip() + '\n'
304
+ print(f'Writing index file: {args.jsonindex.name}')
305
+ args.jsonindex.write_text( r_json )
306
+
307
+ def gen_metadata_doc():
308
+ from ._ncmat2endf_impl import _impl_get_metadata_params_and_docs
309
+ import textwrap
310
+
311
+ d = _impl_get_metadata_params_and_docs()
312
+ assert 'LIBNAME' in d
313
+ assert 'ALAB' in d
314
+ txt = ''
315
+ w = 80
316
+ def section( x ):
317
+ return textwrap.fill(' '.join(x.strip().split()),w)
318
+
319
+ txt += section(
320
+ f"""Meta-data for ENDF can be provided by the {longopt_metadata}
321
+ option, by specifying a JSON dictionary like:"""
322
+ )
323
+ txt+=('''\n\n %s='{ "LIBNAME" : "MySuperLib"'''%longopt_metadata
324
+ +''', "ALAB" : "MySuperLab" }'\n\n''')
325
+ txt += section(
326
+ """Or by adding individual items with the -m option like:"""
327
+ )
328
+ txt+=('''\n\n -m LIBNAME:MySuperLib -m AUTH:"J. Chadwick"\n\n''')
329
+ txt += section('The list of supported meta-data'
330
+ ' keys and their meaning is:')
331
+ txt += '\n\n'
332
+ kmax = max(len(k) for k in d)
333
+ for k, v in d.items():
334
+ s = f' {k.rjust(kmax)} : '
335
+ for i,e in enumerate(textwrap.fill( v, width=w-len(s) ).splitlines()):
336
+ txt += (' '*len(s) if i else s) + e + '\n'
337
+ return txt
@@ -130,7 +130,15 @@ def warn(msg):
130
130
  """Emit NCrystalUserWarning via standard warnings.warn function"""
131
131
  from .exceptions import NCrystalUserWarning
132
132
  import warnings
133
- warnings.warn( NCrystalUserWarning(str(msg)), stacklevel = 2 )
133
+ m = str(msg)
134
+ if _add_warn_counts_to_msgs[0]:
135
+ _add_warn_counts_to_msgs[1] += 1
136
+ m = '%s [warn#%i]'%(m,_add_warn_counts_to_msgs[1])
137
+ warnings.warn( NCrystalUserWarning(m), stacklevel = 2 )
138
+
139
+ #Hook to avoid repeated warnings to be silenced during unit tests, by appending
140
+ #a warning number to them:
141
+ _add_warn_counts_to_msgs = [False,0]
134
142
 
135
143
  class WarningSpy:
136
144
  """Context manager which spies on any warnings emitted via warnings
@@ -506,3 +514,29 @@ def _lookup_existing_file( path ):
506
514
  p = pathlib.Path(dd).joinpath(path)
507
515
  if p.exists():
508
516
  return p.absolute()
517
+
518
+ _override_datetime_now = [ None ]
519
+ def _datetime_now():
520
+ """Returns datetime.datetime.now() but possibly intercepted for unit tests"""
521
+ import datetime
522
+ n = datetime.datetime.now()
523
+ return _override_datetime_now[0] or n
524
+
525
+ class FixedFakeDatetimeNow():
526
+ # Context manager to be used in unit tests to modify the returned value from
527
+ # the datetime_now function below, to a fixed value.
528
+ def __enter__(self):
529
+ import datetime
530
+ self.__orig = _override_datetime_now[0]
531
+ _override_datetime_now[0] = datetime.datetime( 2017, 8, 29, 13, 40,
532
+ tzinfo
533
+ = datetime.timezone.utc )
534
+ def __exit__(self,*args,**kwargs):
535
+ _override_datetime_now[0] = self.__orig
536
+
537
+ def fixed_fake_datetime_now(f):
538
+ #To be used as a decorator
539
+ def fw(*a, **kw):
540
+ with FixedFakeDatetimeNow():
541
+ return f(*a, **kw)
542
+ return fw