ncrystal-python 3.9.81__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- NCrystal/__init__.py +85 -0
- NCrystal/__main__.py +98 -0
- NCrystal/_chooks.py +854 -0
- NCrystal/_cli_cif2ncmat.py +269 -0
- NCrystal/_cli_endf2ncmat.py +503 -0
- NCrystal/_cli_hfg2ncmat.py +144 -0
- NCrystal/_cli_mcstasunion.py +74 -0
- NCrystal/_cli_ncmat2cpp.py +31 -0
- NCrystal/_cli_ncmat2hkl.py +180 -0
- NCrystal/_cli_nctool.py +1018 -0
- NCrystal/_cli_vdos2ncmat.py +463 -0
- NCrystal/_cli_verifyatompos.py +257 -0
- NCrystal/_cliimpl.py +307 -0
- NCrystal/_cliwrap_config.py +36 -0
- NCrystal/_common.py +499 -0
- NCrystal/_coreimpl.py +114 -0
- NCrystal/_hfgdata.py +546 -0
- NCrystal/_hklobjects.py +136 -0
- NCrystal/_is_std.py +0 -0
- NCrystal/_locatelib.py +210 -0
- NCrystal/_miscimpl.py +354 -0
- NCrystal/_mmc.py +757 -0
- NCrystal/_msg.py +60 -0
- NCrystal/_ncmat2cpp_impl.py +445 -0
- NCrystal/_ncmatimpl.py +2131 -0
- NCrystal/_numpy.py +76 -0
- NCrystal/_testimpl.py +579 -0
- NCrystal/api.py +56 -0
- NCrystal/atomdata.py +177 -0
- NCrystal/cfgstr.py +77 -0
- NCrystal/cifutils.py +1795 -0
- NCrystal/cli.py +96 -0
- NCrystal/constants.py +134 -0
- NCrystal/core.py +1910 -0
- NCrystal/datasrc.py +226 -0
- NCrystal/exceptions.py +66 -0
- NCrystal/hfg2ncmat.py +270 -0
- NCrystal/mcstasutils.py +438 -0
- NCrystal/misc.py +317 -0
- NCrystal/mmc.py +35 -0
- NCrystal/ncmat.py +778 -0
- NCrystal/ncmat2cpp.py +80 -0
- NCrystal/obsolete.py +67 -0
- NCrystal/plot.py +484 -0
- NCrystal/plugins.py +49 -0
- NCrystal/test.py +76 -0
- NCrystal/vdos.py +1034 -0
- ncrystal_python-3.9.81.dist-info/LICENSE +206 -0
- ncrystal_python-3.9.81.dist-info/METADATA +515 -0
- ncrystal_python-3.9.81.dist-info/RECORD +53 -0
- ncrystal_python-3.9.81.dist-info/WHEEL +5 -0
- ncrystal_python-3.9.81.dist-info/entry_points.txt +10 -0
- ncrystal_python-3.9.81.dist-info/top_level.txt +1 -0
NCrystal/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))
|