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
NCrystal/datasrc.py ADDED
@@ -0,0 +1,226 @@
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
+ """
23
+
24
+ Utilities for customising NCrystal's data sources, for instance by adding
25
+ directories with data files to the search path, or adding in-memory files.
26
+
27
+ """
28
+
29
+ from ._chooks import _get_raw_cfcts,_str2cstr
30
+ import pathlib as _pathlib
31
+ _rawfct = _get_raw_cfcts()
32
+
33
+ def registerInMemoryFileData(virtual_filename,data):
34
+ """Register in-memory file data. This needs a "filename" and the content of this
35
+ virtual file. After registering such in-memory "files", they can be used
36
+ as file names in cfg strings or MatCfg objects. Registering the same
37
+ filename more than once, will simply override the content.
38
+
39
+ As a special case data can specified as "ondisk://<path>",
40
+ which will instead create a virtual alias for an on-disk file.
41
+
42
+ Technically, the registered file data will be delivered to users from a
43
+ factory named "virtual", so prefixing the filename with "virtual::" in
44
+ future requests, will guarantee that only data registered in this manner
45
+ is returned.
46
+ """
47
+ if ( isinstance(data,str) and data.startswith('ondisk://')):
48
+ data = 'ondisk://'+str(_pathlib.Path(data[9:]).resolve())
49
+ _rawfct['ncrystal_register_in_mem_file_data'](virtual_filename,data)
50
+
51
+
52
+ def addCustomSearchDirectory(dirpath):
53
+ """Register custom directories to be monitored for data files."""
54
+ _rawfct['ncrystal_add_custom_search_dir'](_str2cstr(str(_pathlib.Path(dirpath).resolve())))
55
+
56
+ def removeCustomSearchDirectories():
57
+ """Remove all search directories added with addCustomSearchDirectory."""
58
+ _rawfct['ncrystal_remove_custom_search_dirs']()
59
+
60
+ def removeAllDataSources():
61
+ """Disable all standard data sources, remove all TextData factories as well,
62
+ clear all registered virtual files and custom search directories. Finish
63
+ by calling global clearCaches function ("Ripley: I say we take off and
64
+ nuke the entire site from orbit. It's the only way to be sure.").
65
+ """
66
+ _rawfct['ncrystal_remove_all_data_sources']()
67
+
68
+ def enableAbsolutePaths( enable = True ):
69
+ """Whether or not absolute file paths are allowed."""
70
+ _rawfct['ncrystal_enable_abspaths'](1 if enable else 0)
71
+
72
+ def enableRelativePaths( enable = True ):
73
+ """Whether or not paths relative to current working directory are allowed."""
74
+ _rawfct['ncrystal_enable_relpaths'](1 if enable else 0)
75
+
76
+ def enableStandardSearchPath( enable = True ):
77
+ """Whether or not the standard search path should be searched. This standard
78
+ search path is is by default searched *after* the standard data library,
79
+ and is built by concatenating entries in the NCRYSTAL_DATA_PATH
80
+ environment variables with entries in the compile time definition of the
81
+ same name (in that order). Note that by default the standard search path
82
+ is searched *after* the standard data library.
83
+ """
84
+ _rawfct['ncrystal_enable_stdsearchpath'](1 if enable else 0)
85
+
86
+ def enableStandardDataLibrary( enable = True, dirpath_override = None ):
87
+ """Whether or not the standard data library shipped with NCrystal should be
88
+ searched.
89
+
90
+ Unless NCrystal is configured to have the standard data library embedded
91
+ into the binary at compilation time, the location (directory path) of the
92
+ standard data library is taken from the NCRYSTAL_DATADIR environment
93
+ variable. If the environment variable is not set, the location is taken
94
+ from the compile time definition of the same name. If neither is set, and
95
+ data was not embedded at compilation time, the standard data library will
96
+ be disabled by default and the location must be provided before it can be
97
+ enabled. In all cases, the location can be overridden if explicitly
98
+ provided by the user as the second parameter to this function.
99
+ """
100
+ import ctypes
101
+ d = _str2cstr(str(_pathlib.Path(dirpath_override).resolve())) if dirpath_override else ctypes.cast(None, ctypes.c_char_p)
102
+ _rawfct['ncrystal_enable_stddatalib'](1 if enable else 0, d)
103
+
104
+ class FileListEntry:
105
+ """Entry in list returned by browseFiles."""
106
+ def __init__(self,*,name,source,factName,priority):
107
+ self.__n = name or None
108
+ self.__f = factName or None
109
+ self.__p = int(priority) if priority.isdigit() else priority
110
+ self.__s = source or None
111
+
112
+ @property
113
+ def name(self):
114
+ """The (possibly virtual) filename needed to select this entry"""
115
+ return self.__n
116
+
117
+ @property
118
+ def source(self):
119
+ """Description (such as the parent directory in case of on-disk files)"""
120
+ return self.__s
121
+
122
+ @property
123
+ def factName(self):
124
+ """Name of the factory delivering entry."""
125
+ return self.__f
126
+
127
+ @property
128
+ def priority(self):
129
+ """The priority value of the entry (important in case multiple factories
130
+ delivers content with the the same name). Can be 'Unable',
131
+ 'OnlyOnExplicitRequest' or an integer priority value (entries with
132
+ higher values will be preferred).
133
+ """
134
+ return self.__p
135
+
136
+ @property
137
+ def fullKey(self):
138
+ """The string '%s::%s'%(self.factName,self.name), which can be used to
139
+ explicitly request this entry without interference from similarly
140
+ named entries in other factories.
141
+ """
142
+ return '%s::%s'%(self.__f,self.__n)
143
+
144
+ def __str__(self):
145
+ ll=[]
146
+ if self.__n:
147
+ ll+=['name=%s'%self.__n]
148
+ if self.__s:
149
+ ll+=['source=%s'%self.__s]
150
+ if self.__f:
151
+ ll+=['factory=%s'%self.__f]
152
+ ll+=['priority=%s'%self.__p]
153
+ return 'FileListEntry(%s)'%(', '.join(ll))
154
+
155
+ def __lt__(self, other):
156
+ if not isinstance(other, FileListEntry):
157
+ return False
158
+ return ( (self.__f,self.__n,self.__s,self.__p)
159
+ < (other.__f,other.__n,other.__s,other.__p) )
160
+
161
+ def browseFiles(dump=False,factory=None):
162
+ """Browse list of available input files (virtual or on-disk). The list is not
163
+ guaranteed to be exhaustive, but will usually include all files in
164
+ supported files in the most obvious locations (the NCrystal data
165
+ directory and other directories of the standard search path, the current
166
+ working directory, virtual files embedded in the NCrystal library or
167
+ registered dynamically.
168
+
169
+ Returns a list of FileListEntry objects. If the dump flag is set to True,
170
+ the list will also be printed to stdout in a human readable form.
171
+
172
+ Setting factory parameter will only return / print entries from the
173
+ factory of that name.
174
+
175
+ """
176
+ res=[]
177
+ def sortkey(e):
178
+ praw = e.priority
179
+ if praw=='Unable':
180
+ p=-2
181
+ elif isinstance(praw,int):
182
+ p=praw
183
+ else:
184
+ assert praw=='OnlyOnExplicitRequest'
185
+ p=-1
186
+ return (-p, e.factName,e.source,e.name)
187
+ for n,s,f,p in _rawfct['ncrystal_get_filelist']():
188
+ res.append( FileListEntry(name=n,source=s,factName=f,priority=p) )
189
+ res.sort(key=sortkey)
190
+ if dump:
191
+ seen_names=set()
192
+ def groupfct( e ):
193
+ return (e.factName,e.source,e.priority)
194
+ lastgroup = None
195
+ pending=[]
196
+ def print_pending():
197
+ if not pending:
198
+ return
199
+ if factory is not None and lastgroup[0]!=factory:
200
+ pending.clear()
201
+ return
202
+ n=len(pending) - 1
203
+ pending[0] = pending[0]%('%s files'%n if n!=1 else '%s file'%n )
204
+ for line in pending:
205
+ print (line)
206
+ pending.clear()
207
+ for e in res:
208
+ group = groupfct(e)
209
+ if lastgroup != group:
210
+ print_pending()
211
+ lastgroup = group
212
+ pending.append('==> %%s from "%s" (%s, priority=%s):'%group)
213
+ hidden = e.name in seen_names
214
+ seen_names.add(e.name)
215
+ extra=''
216
+ prname=e.name
217
+ if e.priority=='OnlyOnExplicitRequest':
218
+ prname='%s::%s'%(e.factName,e.name)
219
+ elif hidden:
220
+ extra=' <--- Hidden by higher priority entries (select as "%s::%s")'%(e.factName,e.name)
221
+ pending.append( ' %s%s'%(prname,extra))
222
+ print_pending()
223
+ return #return None in this case, to avoid spurious printouts in an interactive session
224
+ if factory is None:
225
+ return res
226
+ return [e for e in res if e.factName==factory]
NCrystal/exceptions.py ADDED
@@ -0,0 +1,66 @@
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
+ """Module defining all the exception and warning types emitted by NCrystal"""
23
+
24
+ #NB: We also put all of these in __all__ in the main __init__.py:
25
+
26
+ __all__ = [ 'NCrystalUserWarning',
27
+ 'NCException',
28
+ 'NCFileNotFound',
29
+ 'NCDataLoadError',
30
+ 'NCMissingInfo',
31
+ 'NCCalcError',
32
+ 'NCLogicError',
33
+ 'NCBadInput',
34
+ 'nc_assert' ]
35
+
36
+ class NCrystalUserWarning( UserWarning ):
37
+ """UserWarning's emitted from NCrystal code"""
38
+ def __init__(self,*args,**kwargs):
39
+ super(NCrystalUserWarning, self).__init__(*args,**kwargs)
40
+
41
+ class NCException(RuntimeError):
42
+ """Base class for all exceptions raised by NCrystal code"""
43
+ pass
44
+
45
+ class NCFileNotFound(NCException):
46
+ pass
47
+
48
+ class NCDataLoadError(NCException):
49
+ pass
50
+
51
+ class NCMissingInfo(NCException):
52
+ pass
53
+
54
+ class NCCalcError(NCException):
55
+ pass
56
+
57
+ class NCLogicError(NCException):
58
+ pass
59
+
60
+ class NCBadInput(NCException):
61
+ pass
62
+
63
+ def nc_assert(b,msg=""):
64
+ """Assertion which throws NCLogicError on failure"""
65
+ if not bool(b):
66
+ raise NCLogicError(msg if msg else 'assertion failed')
NCrystal/hfg2ncmat.py ADDED
@@ -0,0 +1,270 @@
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
+ """
23
+
24
+ Utilities for generating NCMAT files for hydrogen rich amorphous materials
25
+ (based on doi:10.1088/1361-648X/abfc13).
26
+
27
+ """
28
+
29
+ __all__ = [ 'hfg2ncmat']
30
+
31
+ def _default_debye_temp():
32
+ #Note: keep in synch with value mentioned in docstring of hfg2ncmat
33
+ #function, as well as the NCMATComposer.from_hfg method.
34
+ return 400.0
35
+
36
+ def _parseChemicalFormula(chemform):
37
+ import re
38
+ def err():
39
+ from .exceptions import NCBadInput
40
+ raise NCBadInput(f'Invalid chemical formula: "{chemform}"')
41
+ def is_ascii(s):
42
+ #str.isascii requires python 3.7
43
+ return all(ord(c) < 128 for c in s)
44
+ if not is_ascii(chemform) or not chemform.isalnum():
45
+ err()
46
+ d={}
47
+ prev=None
48
+ def add(element,count):
49
+ d[element] = d.get(element,0) + count
50
+
51
+ chemform_unused = chemform.strip()
52
+ for e in re.findall('[A-Z][a-z]?|[0-9]+', chemform.strip()):
53
+ if chemform_unused.startswith(e):
54
+ chemform_unused = chemform_unused[len(e):].strip()
55
+ else:
56
+ err()
57
+ if e.isdigit():
58
+ if prev is None or prev.isdigit():
59
+ err()
60
+ v=int(e)
61
+ if v<1 or v>1000000000:
62
+ err()
63
+ add(prev,v)
64
+ prev=None
65
+ continue
66
+ if prev is not None:
67
+ add(prev,1)
68
+ prev = None
69
+ prev = e
70
+ if prev is not None:
71
+ add(prev,1)
72
+ if chemform_unused:
73
+ err()
74
+ return d
75
+
76
+ def _parseHydrogenBindingSpecification(spec):
77
+ from .exceptions import NCBadInput
78
+ def err():
79
+ raise NCBadInput(f'Invalid hydrogen binding specification: "{spec}"')
80
+ def is_ascii(s):
81
+ #str.isascii requires python 3.7
82
+ return all(ord(c) < 128 for c in s)
83
+ if not is_ascii(spec) or not spec.replace('+','').isalnum():
84
+ err()
85
+
86
+ fg2nH=dict(CHali=1,CHaro=1,CH2=2,CH3=3,NH=1,NH2=2,NH3=3,OH=1,SH=1)
87
+ d={}
88
+ for part in spec.split('+'):
89
+ p=part.strip().split('x')
90
+ if len(p)!=2:
91
+ err()
92
+ count,fg=p
93
+ if fg not in fg2nH:
94
+ raise NCBadInput(f'Unknown functional group in "{spec}": "{fg}"')
95
+ if not count.isdigit() or not 1 <= int(count) <= 10000:
96
+ raise NCBadInput(f'Invalid count in "{spec}": "{count}"')
97
+ d[fg] = d.get(fg,0)+int(count)*fg2nH[fg]
98
+ return d
99
+
100
+ def hfg2ncmat( spec,
101
+ formula, *,
102
+ density,
103
+ title,
104
+ debyetemp = _default_debye_temp(),
105
+ verbose = True,
106
+ notrim = False ):
107
+ """Function which can be used to generate NCMAT data for hydrogen-rich
108
+ amorphous materials, in which the hydrogen atoms are bound to certain standard
109
+ functional groups (e.g. carbohydrates, polyimides, polymers, ...). Based on the
110
+ material's density, (empirical) chemical formula, and the specification of
111
+ hydrogen bindings in terms of standard functional groups, NCMAT data is
112
+ generated. In this NCMAT data, non-hydrogen atoms are treated with a simplistic
113
+ model (idealised Debye model of phonon vibrations, assuming a Debye temperature
114
+ of 400K for all atoms unless the debyetemp parameter is provided. The hydrogen
115
+ atoms are treated with a proper phonon density of state (VDOS) curve, which is
116
+ constructed based on the provided binding specifications. This is done using an
117
+ idea (and VDOS curves from) the following publication:
118
+
119
+ "Thermal neutron cross sections of amino acids from average contributions
120
+ of functional groups", G. Romanelli, et. al., J. Phys.: Condens. Matter,
121
+ (2021). doi:10.1088/1361-648X/abfc13
122
+
123
+ Example of valid spec strings for the spec parameter. Note only the hydrogen
124
+ bindings are important here:
125
+
126
+ "1xCHali+2xCH2+8xCHaro+2xCH3+1xOH" (20 hydrogen atoms)
127
+ "1xNH+3xSH+2xCH2" (8 hydrogen atoms).
128
+
129
+ List of valid bindings which can be used in the spec string:
130
+
131
+ CHali (1 hydrogen atom, aliphatic binding)
132
+ CHaro (1 hydrogen atom, aromatic binding, e.g. on phenyl group).
133
+ CH2 (2 hydrogen atoms)
134
+ CH3 (3 hydrogen atoms)
135
+ NH (1 hydrogen atom)
136
+ NH2 (2 hydrogen atoms)
137
+ NH3 (3 hydrogen atoms)
138
+ OH (1 hydrogen atom)
139
+ SH (1 hydrogen atom)
140
+
141
+ Note that as only hydrogen atoms will have a realistic VDOS curve, and as the
142
+ incoherent approximation is employed, the realism of the resulting material
143
+ modelling will be higher for materials with more hydrogen atoms. As a metric of
144
+ this, the function prints out how much of the total scattering cross section
145
+ (sum of sigma_coh+sigma_incoh for all atoms), is due to the sigma_incoh
146
+ contribution from hydrogen atoms (unless verbose=False).
147
+
148
+ """
149
+ #NOTE: Keep the description above synchronised with help text of of the
150
+ #ncrystal_hfg2ncmat command-line app!
151
+
152
+ from ._hfgdata import get_data as hfg_get_data
153
+
154
+ if verbose:
155
+ from ._common import print
156
+ else:
157
+ def print(*args,**kwargs):
158
+ pass
159
+
160
+ spec_parsed = _parseHydrogenBindingSpecification(spec)
161
+ chemform = _parseChemicalFormula(formula)
162
+
163
+ nH = chemform.get('H',-999)
164
+ if sum(v for k,v in spec_parsed.items()) != nH:
165
+ from .exceptions import NCBadInput
166
+ raise NCBadInput('number of hydrogen items in --formula'
167
+ ' and --spec does not match up.')
168
+
169
+ egrid,vdos = hfg_get_data()
170
+
171
+ #Calculate hydrogen VDOS:
172
+
173
+ h_vdos = sum( nh * vdos[fgname] for fgname,nh in sorted(spec_parsed.items()))
174
+ h_vdos /= h_vdos.max()
175
+
176
+ debye_cut_bin = 4#shave off first few points, to force parabolic behaviour near E=0.
177
+ if not notrim:
178
+ for i in range(len(h_vdos)):
179
+ if egrid[i]<0.05:
180
+ continue#don't touch first bins
181
+ if h_vdos[i]<1e-7:
182
+ h_vdos[i]=0.0
183
+ ntrim=2
184
+ assert debye_cut_bin%ntrim==0
185
+ debye_cut_bin //= ntrim
186
+ h_vdos = h_vdos[::ntrim]
187
+ egrid = egrid[::ntrim]
188
+
189
+ while len(h_vdos)>10 and h_vdos[-2]==0.0 and h_vdos[-1]==0.0:
190
+ h_vdos = h_vdos[0:-1]
191
+ egrid = egrid[0:-1]
192
+
193
+ appname='ncrystal_hfg2ncmat'
194
+
195
+ initial_comments='#\n'
196
+ for ll in title.replace('\\n','\n').splitlines():
197
+ initial_comments+= f'# {ll}\n' if ll.strip() else '#\n'
198
+ initial_comments+='#'
199
+
200
+ recordargs=[ f'--spec={spec}',
201
+ f'--formula={formula}',
202
+ f'--density={density}' ]
203
+ if _default_debye_temp() != debyetemp:
204
+ recordargs.append('--debyetemp=%.14g'%debyetemp)
205
+ if notrim:
206
+ recordargs.append('--notrim')
207
+ recordargs='\n# '.join(recordargs)
208
+
209
+ out = f"""NCMAT v5
210
+ {initial_comments}
211
+ # ----------------------------------------------------------------------------
212
+ #
213
+ # File generated by {appname} with:
214
+ #
215
+ # {recordargs}
216
+ #
217
+ # The hydrogen VDOS curve in this file is constructed using data from:
218
+ #
219
+ # "Thermal neutron cross sections of amino acids from average contributions
220
+ # of functional groups", G. Romanelli, et. al., J. Phys.: Condens. Matter,
221
+ # (2021). doi:10.1088/1361-648X/abfc13
222
+ #
223
+ # Considering only scat. lengths and composition, incoherent scattering on
224
+ # hydrogen accounts for <<<HYDROGENPERCENTAGE>>> of scattering in this material (a higher value
225
+ # implies more accurate modelling).
226
+ #
227
+ @STATEOFMATTER
228
+ solid
229
+ @DENSITY
230
+ {density} g_per_cm3
231
+ """
232
+
233
+ ntot = sum(n for elem,n in chemform.items())
234
+ for elem,n in chemform.items():
235
+ if elem=='H':
236
+ continue
237
+ out+=f"""@DYNINFO
238
+ element {elem}
239
+ fraction {n}/{ntot}
240
+ type vdosdebye
241
+ debye_temp {debyetemp}
242
+ """
243
+ out+=f"""@DYNINFO
244
+ element H
245
+ fraction {nH}/{ntot}
246
+ type vdos
247
+ vdos_egrid {0.001*egrid[debye_cut_bin]:.8g} {0.001*egrid[-1]:.8g}
248
+ """
249
+ from .ncmat import formatVectorForNCMAT
250
+ out += formatVectorForNCMAT('vdos_density',h_vdos[debye_cut_bin:])
251
+ print ("Generated NCMAT data...")
252
+ print ("Verifying that it can be loaded with NCrystal...")
253
+ from .core import load as nc_load
254
+ loaded_mat = nc_load(out)
255
+ print ("Succesfully loaded...")
256
+
257
+ breakdown = []
258
+ for di in loaded_mat.info.dyninfos:
259
+ for cohtype in 'incoherent','coherent':
260
+ key='%s (%s)'%(di.atomData.displayLabel(),cohtype)
261
+ sigma = di.atomData.coherentXS() if cohtype=='coherent' else di.atomData.incoherentXS()
262
+ breakdown+=[(di.fraction*sigma,key)]
263
+ sigma_tot = sum(s for s,k in breakdown)
264
+ print("Contribution breakdown based on composition:")
265
+ for s,key in reversed(sorted(breakdown)):
266
+ print (" Contribution to bound scattering XS from %s is %5.2f %%"%(key.ljust(14),s*100.0/sigma_tot))
267
+ if key=='H (incoherent)':
268
+ contrib_incH = s/sigma_tot
269
+
270
+ return out.replace('<<<HYDROGENPERCENTAGE>>>','%.3g%%'%(100.0*contrib_incH))