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,463 @@
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
+ from ._cliimpl import ( create_ArgumentParser,
23
+ cli_entry_point,
24
+ print )
25
+ from ._common import write_text
26
+ from .constants import ( constant_planck, constant_boltzmann )
27
+ from .vdos import vdos_units_2_eV
28
+ import pathlib
29
+ units_2_fact = vdos_units_2_eV
30
+ units_opts = ', '.join(sorted(units_2_fact.keys()))
31
+
32
+ _cache_np=[None]
33
+ def get_numpy():
34
+ if _cache_np[0] is None:
35
+ from ._numpy import _ensure_numpy, _np
36
+ _ensure_numpy()
37
+ _cache_np[0] = _np
38
+ return _cache_np[0]
39
+
40
+ def parseArgs( progname, arglist, return_parser=False ):
41
+ import textwrap
42
+ helpw = 60
43
+ descrw = helpw + 22
44
+
45
+ descr="""WARNING: This script is somewhat outdated. Many users will instead
46
+ wish to use the PhononDOSAnalyser helper class from the Python API in the
47
+ NCrystal.vdos python module. For more information, see the jupyter notebook
48
+ about "Adding phonon information" on:
49
+ https://github.com/mctools/ncrystal-notebooks
50
+
51
+ Script which can read vdos curves from either .ncmat files or simple two-column
52
+ ascii text files (the two columns being energy grid and density) and help users
53
+ prepare output suitable for inclusion in .ncmat files. When the input is not an
54
+ .ncmat file, the user must specify the energy grid units by adding a line in the file
55
+ like for instance "#unit:THz" or "#unit:meV".
56
+
57
+ In case of NCMAT files with more than one VDOS, just post-fix the filename with
58
+ the element to investigate, separated by '@@', e.g. "Al2O3.ncmat@@O". In case of
59
+ .txt files with more than 2 columns, select the column representing the VDOS
60
+ density in the same manner, e.g. "Al2O3.txt@2" (the first column is always the
61
+ energy or frequency).
62
+
63
+ Thus it is possible to plot the curve, compare against vdos curves from other
64
+ .ncmat files, apply low-E truncation (cutoff), regularise binning, and overlay
65
+ with an ideal Debye spectrum for a given Debye energy or Debye
66
+ temperature. Finally, when running without --plot, it will output the resulting
67
+ spectrum into format which is ready for inclusion in .ncmat files.
68
+
69
+ """
70
+ descr = '\n\n'.join(textwrap.fill(e,descrw) for e in descr.split('\n\n'))
71
+
72
+ from argparse import RawTextHelpFormatter
73
+ parser = create_ArgumentParser( prog = progname,
74
+ description=descr,
75
+ formatter_class=RawTextHelpFormatter )
76
+ def wrap(t):
77
+ return textwrap.fill(' '.join(t.split()),width=helpw)
78
+
79
+ parser.add_argument("FILE",help=wrap("Either .ncmat file with VDOS or a"
80
+ " text file with two colums of data:"
81
+ " egrid and density."))
82
+ parser.add_argument('--plot','-p', action='store_true',help='Plot extracted spectrum')
83
+ parser.add_argument("--cutoff",'-c',nargs='+',default=None,type=float,
84
+ help=wrap("""Emin cutoff points in eV (more than one can
85
+ be provided for simultaneous inspection with --plot)."""))
86
+ parser.add_argument("--unit",'-u',default='meV',type=str,
87
+ help=wrap('Visualisation unit (ignored unless'
88
+ ' --plot is supplied). Defaults to meV. '
89
+ f'Possible options are {units_opts}.'))
90
+ parser.add_argument("--ref",'-r',nargs='+',
91
+ action='append',type=str,
92
+ help=wrap("""Optionally provide list of .ncmat files
93
+ with vdos data, to superimpose on plots."""))
94
+ parser.add_argument("--forceregular",'-f',type=int,nargs='?',default=0,
95
+ help=wrap("""Optionally provide this argument to
96
+ reparameterise with that amount of linearly spaced
97
+ points in [emin,emax+epsilon], where epsilon is chosen
98
+ so the grid can be extended to 0 with a whole number of
99
+ bins. This format will be directly used by NCrystal
100
+ without on-the-fly reparameterisation upon loading."""))
101
+ parser.add_argument("--debye",'-d',nargs='?',default='',type=str,
102
+ help=wrap("""Set to debye temperature (unit K) or egrid
103
+ point (units like meV, eV, THz, ...) in order to plot
104
+ Debye spectrum with that parameter on top."""))
105
+ parser.add_argument('--g1',default=0.0,type=float,metavar='TEMP',
106
+ help=wrap("""Use with --plot to show Sjolander's G1
107
+ function at the indicated temperature value (in kelvin)
108
+ instead of the DOS directly. This is the Symmetric G1
109
+ fct without a detailed balance factor, and it will be
110
+ plotted assuming gamma0=1.0."""))
111
+ parser.add_argument('--stdout',action='store_true',help=wrap("""Produce no
112
+ output file but print vdos_egrid and vdos_density lines
113
+ to stdout (for scripting)"""))
114
+
115
+ #We could expand the env var name to account for any namespace:
116
+ # from ._common import expand_envname
117
+ # dpienvvar = expand_envname('DPI')
118
+ #But that will break the unit tests, so we just use:
119
+ dpienvvar = 'NCRYSTAL_DPI'
120
+
121
+ dpi_default=200
122
+ parser.add_argument('--dpi', default=-1,type=int,
123
+ help=wrap(f"""Change plot resolution. Set to 0 to leave
124
+ matplotlib defaults alone. (default value is
125
+ {dpi_default}, or whatever the {dpienvvar} env var is
126
+ set to)."""))
127
+
128
+ if return_parser:
129
+ return parser
130
+ args = parser.parse_args(arglist)
131
+
132
+ if args.forceregular is None:
133
+ parser.error('Missing argument (number of points) to --forceregular.')
134
+
135
+ if args.dpi==-1:
136
+ from ._common import ncgetenv_int_nonneg
137
+ args.dpi = ncgetenv_int_nonneg('DPI',dpi_default)
138
+ if args.dpi > 3000:
139
+ parser.error('Too high DPI value requested.')
140
+
141
+ if args.ref:
142
+ args.ref = [item for sublist in args.ref for item in sublist]
143
+ if args.ref and not args.plot:
144
+ parser.error('Option --ref requires --plot')
145
+ if args.unit and args.unit not in units_2_fact:
146
+ parser.error(f'Unknown unit {args.unit}. Valid options are {units_opts}')
147
+ if args.debye and not args.plot:
148
+ parser.error('Option --debye requires --plot')
149
+ if args.stdout and args.plot:
150
+ parser.error('Option --stdout can not be used with --plot')
151
+ if args.cutoff and len(args.cutoff)>1 and not args.plot:
152
+ parser.error('Option --cutoff can only have one argument when not using --plot')
153
+ if args.cutoff and len(args.cutoff)>1 and args.forceregular:
154
+ parser.error('Option --cutoff can only have one argument when using --forceregular')
155
+
156
+ if args.debye:
157
+ if args.debye.endswith('K'):
158
+ args.debye = float(args.debye[0:-1])*constant_boltzmann
159
+ else:
160
+ #find (longest, so "meV" does not trigger "eV") fitting unit:
161
+ ll=[ (len(u),u) for u in units_2_fact.keys() if args.debye.endswith(u) ]
162
+ ll.sort()
163
+ if not ll:
164
+ parser.error("Option --debye requires unit (see --help)")
165
+ unit = ll[-1][1]
166
+ args.debye = units_2_fact[unit] * float(args.debye[0:-len(unit)])
167
+
168
+
169
+ return args
170
+
171
+ def create_argparser_for_sphinx( progname ):
172
+ return parseArgs(progname,[],return_parser=True)
173
+
174
+ def decodeFileName(filename):
175
+ if '@@' in filename:
176
+ path,select = filename.split('@@',1)
177
+ else:
178
+ path,select = filename,None
179
+ import os
180
+ bn=os.path.basename(path)
181
+ return dict(path=path,select=select,basename=bn,
182
+ title=bn if not select else '%s in %s'%((select if not select.isdigit(
183
+ ) else f'column #{select}'),bn))
184
+
185
+ def getVDOSFromFile(fn):
186
+ fnd = decodeFileName(fn)
187
+ if fnd['basename'].endswith('.ncmat'):
188
+ return getVDOSFromNCMAT(fn)
189
+ return getVDOSFromTXT(fn)
190
+
191
+ def getVDOSFromTXT(fn):
192
+ fn = decodeFileName(fn)
193
+ select=None
194
+ if fn['select']:
195
+ assert fn['select'].isdigit(),"selection must be column number"
196
+ select = int(fn['select'])
197
+ #figure out unit:
198
+ unit=None
199
+ with open(fn['path']) as fh:
200
+ for ll in fh:
201
+ if ll.startswith('#') and 'unit' in ll:
202
+ _=ll.split('#',1)[1].split(':',1)
203
+ if not len(_)==2:
204
+ continue
205
+ unit=_[1].strip()
206
+ if unit not in units_2_fact.keys():
207
+ raise RuntimeError(f'Unknown unit "{unit}" specified in {fn["path"]}. Valid choices are: {units_opts}')
208
+ break
209
+ if not unit:
210
+ raise RuntimeError(f'Missing energy/frequency unit in {fn["path"]}. Please put initial line with content like "#unit:THz". Valid units are: {units_opts}')
211
+ usecols=None
212
+ if select is not None:
213
+ assert select>0
214
+ usecols=(0,select)
215
+ _ = get_numpy().genfromtxt(fn['path'],dtype=[('egrid','f8'),('density','f8')],usecols=usecols)
216
+ egrid=_['egrid'].copy()
217
+ density=_['density'].copy()
218
+ density /= density.max()
219
+ return egrid.copy()*units_2_fact[unit],density.copy()
220
+
221
+ def getVDOSFromNCMAT(fn):
222
+ fnd = decodeFileName(fn)
223
+ from . import core as NC
224
+ info = NC.createInfo(fnd['path'])
225
+ select = fnd['select']
226
+ di_vdos = [di for di in info.dyninfos if isinstance(di,NC.Info.DI_VDOS)]
227
+ if select is not None:
228
+ ll=[]
229
+ for di in di_vdos:
230
+ dl=di.atomData.displayLabel()
231
+ if dl!=select:
232
+ print(f"NB: Ignoring (due to selection) VDOS for element {dl} in file {fn}.")
233
+ else:
234
+ ll+=[di]
235
+ if not ll:
236
+ raise RuntimeError(f'ERROR: Could not find VDOS in file {fn} for selected element: {select}')
237
+ if not len(ll)==1:
238
+ raise RuntimeError(f'ERROR: Multiple VDOS entries in file {fn} for selected element: {select} (which is rather odd!)')
239
+ di_vdos = ll
240
+ if len(di_vdos)>1:
241
+ s=' '.join(di.atomData.displayLabel() for di in di_vdos)
242
+ raise RuntimeError(f"ERROR: Multiple VDOS entries found in file {fn}. Please select one of them (by putting \"@@<element>\" after the file-name): {s}")
243
+ elif not di_vdos:
244
+ raise RuntimeError(f"ERROR: No vdos found in file {fn}")
245
+ eg,ds = di_vdos[0].vdosOrigEgrid(),di_vdos[0].vdosOrigDensity()
246
+ ds /= ds.max()
247
+ if len(eg)==2:
248
+ get_numpy()
249
+ from ._numpy import _np_linspace
250
+ eg = _np_linspace(eg[0],eg[1],len(ds))
251
+ return eg.copy(),ds.copy()
252
+
253
+
254
+
255
+
256
+ @cli_entry_point
257
+ def main( progname, arglist ):
258
+ args = parseArgs( progname, arglist )
259
+ from ._numpy import _ensure_numpy, _np
260
+ _ensure_numpy()
261
+
262
+ file_decoded = decodeFileName(args.FILE)
263
+ args_file_basename = file_decoded['basename']
264
+ egrid,density = getVDOSFromFile(args.FILE)
265
+
266
+ np = _np
267
+
268
+ assert len(egrid) == len(density)
269
+ print (f"Loaded VDOS with {len(density)} grid points from {args_file_basename}")
270
+
271
+ np = get_numpy()
272
+ def numpy_is_sorted(a):
273
+ return np.all(a[:-1] <= a[1:])
274
+ def numpy_is_strongly_sorted(a):
275
+ return np.all(a[:-1] < a[1:])
276
+
277
+ if not numpy_is_strongly_sorted(egrid):
278
+ for i in range(len(egrid)-1):
279
+ if not egrid[i] < egrid[i+1]:
280
+ print("Problems detected in egrid points with values ",egrid[i],"and",egrid[i+1])
281
+ raise RuntimeError('ERROR: egrid values (first column) of input file are not in sorted'
282
+ +' (ascending) order, or there are identical elements.')
283
+
284
+ cutoffs=[]
285
+ if args.cutoff:
286
+ for c in args.cutoff:
287
+ if c >= egrid[-1]:
288
+ raise RuntimeError(f'ERROR: Cutoff value {c} is higher than highest point in egrid')
289
+ i=np.searchsorted(egrid,c)
290
+ assert i==0 or egrid[i-1]<c
291
+ assert egrid[i]>=c
292
+ cutoffs+=[ (i, egrid[i] ) ]
293
+ print(f" => Mapping cutoff value of {c} to grid point at {cutoffs[-1][1]}")
294
+
295
+
296
+ if args.forceregular or (not args.plot):
297
+ if applyCutoff(egrid,density,cutoffs)[0][0]<=1e-5:
298
+ raise RuntimeError(f"""
299
+ ERROR: The first value in the loaded egrid is {egrid[0]} which is less than 1e-5eV.
300
+ This is not allowed when using --forceregular or when not using --plot.
301
+ Please use the --cutoff parameter to remove lowest part of input spectrum (perhaps
302
+ after investigating the cutoff value with --plot).
303
+ """)
304
+
305
+
306
+ if args.forceregular:
307
+ regularised_egrid,regularised_density = regularise(*applyCutoff(egrid,density,cutoffs),args.forceregular)
308
+
309
+ if args.plot:
310
+ vis_unit=args.unit
311
+ vis_unit_fact = 1.0 / units_2_fact[vis_unit]
312
+
313
+ def plt_plot(egrid,dos,*aargs,**kwargs):
314
+ if args.g1 and args.g1 > 0.0:
315
+ #G1 = f(E)/(E*2*gamma0*sinh(E/2kT)) [symmetric G1 that is]
316
+ #u = E/2kT
317
+ #asymmetric means another factor of exp(-u).
318
+ #And exp(-u)/2sinh(u) = exp(-u) / (exp(u)-exp(-u) ) = 1 / ( exp(2u)-1)
319
+ #And exp(+u)/2sinh(u) = exp(+u) / (exp(u)-exp(-u) ) = 1 / ( 1-exp(-2u) )
320
+ #
321
+ #So with gamma0=0 we get:
322
+ egrid_eV = egrid.copy() / vis_unit_fact
323
+ T = args.g1
324
+ #gamma0 = 1.0
325
+ two_u = egrid_eV / ( constant_boltzmann * T )
326
+ #g1asym_neg = dos / ( egrid_eV * -np.expm1(-two_u) )
327
+ #g1asym_pos = dos / ( egrid_eV * np.expm1(two_u) )
328
+ g1sym = dos / (egrid_eV*np.sinh(0.5*two_u))
329
+ plt.plot(egrid,g1sym,*aargs,**kwargs)
330
+ else:
331
+ plt.plot(egrid,dos,*aargs,**kwargs)
332
+
333
+ import matplotlib as mpl
334
+ mpl.rcParams['figure.dpi']=args.dpi
335
+ #ability to quit plot windows with Q:
336
+ if 'keymap.quit' in mpl.rcParams and 'q' not in mpl.rcParams['keymap.quit']:
337
+ mpl.rcParams['keymap.quit'] = tuple(list(mpl.rcParams['keymap.quit'])+['q','Q'])
338
+ import matplotlib.pyplot as plt
339
+ plt.xlabel(vis_unit)
340
+ plt_plot(egrid*vis_unit_fact,density,'o-',label=file_decoded['title'])
341
+ if args.forceregular:
342
+ plt_plot(regularised_egrid*vis_unit_fact,regularised_density,'x-',label='regularised')
343
+ from ._numpy import _np_linspace
344
+ for c_idx, c_val in cutoffs:
345
+ d=density[c_idx]
346
+ # f(x)=k*x^2, f(c_val)=d<=> k*c_val^2 = d <=> k = d/c_val^2
347
+ x=_np_linspace(0.0,c_val,3000)
348
+ plt_plot(x*vis_unit_fact,(d/c_val**2)*(x**2),label=f'with cutoff {c_val} eV')
349
+ if args.debye:
350
+ x=_np_linspace(0.0,max(egrid.max(),args.debye),1000)
351
+ y = np.where( x<=args.debye, x**2 * ( density.max() / args.debye**2 ), 0.0 )
352
+ plt_plot(x*vis_unit_fact,y,
353
+ label=f'Debye spectrum (E_Debye={1000*args.debye:.5}meV, T_Debye={args.debye/constant_boltzmann:.5}K)')
354
+ for r in (args.ref or []):
355
+ eg,ds = getVDOSFromFile(r)
356
+ plt_plot(eg*vis_unit_fact,ds,label=decodeFileName(r)['title'])
357
+ plt.legend()
358
+ plt.title(file_decoded['title'])
359
+ plt.grid(ls=':')
360
+ plt.show()
361
+ return
362
+
363
+ if args.forceregular:
364
+ egrid, density = regularised_egrid,regularised_density
365
+ else:
366
+ egrid, density = applyCutoff(egrid,density,cutoffs)
367
+
368
+ #Check if egrid is linspace:
369
+ binwidth = (egrid[-1]-egrid[0])/(len(egrid)-1)
370
+ is_linspace=True
371
+ if not args.forceregular:
372
+ for i in range(len(egrid)-1):
373
+ bw=egrid[i+1]-egrid[i]
374
+ if abs(binwidth-bw)>0.01*binwidth:
375
+ is_linspace=False
376
+ break
377
+ if is_linspace:
378
+ print('NB: Detected linearly spaced input egrid')
379
+
380
+ #normalise so unity at highest point (gives smaller file sizes):
381
+ density /= density.max()
382
+
383
+ #remove excess trailing zeros
384
+ while len(density)>10 and density[-2]==0.0 and density[-1]==0.0:
385
+ density = density[0:-1]
386
+ egrid = egrid[0:-1]
387
+
388
+ from .ncmat import formatVectorForNCMAT
389
+
390
+ egrid_cnt =''
391
+ if is_linspace:
392
+ egrid_cnt += f' vdos_egrid {egrid[0]:.14} {egrid[-1]:.14}'
393
+ else:
394
+ egrid_cnt += formatVectorForNCMAT('vdos_egrid',egrid)
395
+ egrid_cnt += '\n'
396
+ egrid_cnt += formatVectorForNCMAT('vdos_density',density)
397
+
398
+ if args.stdout:
399
+ print("<<<GENERATED-CONTENT-BEGIN>>>")
400
+ print(egrid_cnt,end='')
401
+ else:
402
+ outfn=pathlib.Path('converted_output.ncmat')
403
+ content = f"""NCMAT v5
404
+ #Autogenerated file from {args_file_basename}.
405
+ @DENSITY
406
+ 1.0 g_per_cm3 #FIX{'ME'}!! Please replace with proper value, or remove and optionally provide crystal structure!
407
+ @DYNINFO
408
+ element <UNKNOWN-PLEASE-EDIT>
409
+ fraction 1
410
+ type vdos\n"""
411
+ content += egrid_cnt
412
+ content += '\n'
413
+ write_text( outfn, content )
414
+ print(f"Wrote {outfn}")
415
+
416
+ def applyCutoff(egrid,density,cutoffs):
417
+ if cutoffs:
418
+ assert len(cutoffs)==1
419
+ c_idx,c_val = cutoffs[0]
420
+ return egrid[c_idx:], density[c_idx:]
421
+ return egrid,density
422
+
423
+ def regularise(egrid,density,n):
424
+
425
+ #first step back from any zeroes at the upper end:
426
+ i=1
427
+ while i < len(density) and density[-i]==0.0:
428
+ i += 1
429
+ safepeel = i-2
430
+ if safepeel>=1:
431
+ print (f"Ignoring {safepeel} last points while regularising since last {safepeel+1} points are 0.")
432
+ egrid,density = egrid[0:-(safepeel)],density[0:-(safepeel)]
433
+ emin,emax=egrid[0],egrid[-1]
434
+ print('old range',emin,emax)
435
+ THZ = constant_planck*1e12
436
+ print('old range [THZ]',emin/THZ,emax/THZ)
437
+
438
+ np = get_numpy()
439
+ for k in range(1,1000000000):
440
+ #k is number of bins below emin, an integral number by definition in a regularised grid.
441
+ binwidth = emin/k
442
+ nbins=int(np.floor((emax-emin)/binwidth))+1
443
+ eps = (emin+nbins*binwidth)-emax
444
+ assert eps>=0.0
445
+ if nbins+1 >= n:
446
+ break
447
+ n=nbins+1
448
+ binwidth = emin/k
449
+ new_emax = emin + (n-1) * binwidth
450
+ if abs( (new_emax-binwidth) - emax ) < 1e-3*binwidth:
451
+ nbins -= 1
452
+ n -= 1
453
+ new_emax -= binwidth
454
+ print (f" ==> Choosing regular grid with n={n} pts from emin={emin} to emax={new_emax} ({new_emax-emax} beyond old emax)")
455
+ assert new_emax >= emax-binwidth*1.001e-3
456
+ from ._numpy import _np_linspace
457
+ new_egrid = _np_linspace(emin,new_emax,n)
458
+ test=new_egrid[0] / ( (new_egrid[-1]-new_egrid[0])/(len(new_egrid)-1) )
459
+ assert abs(round(test)-test)<1e-6,f'{test}'
460
+ np = get_numpy()
461
+ new_density = np.interp(new_egrid,egrid,density, left=0.0, right=0.0)
462
+ print('last density values in new grid:',new_density[-5:])
463
+ return new_egrid,new_density