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/ncmat.py ADDED
@@ -0,0 +1,778 @@
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 NCMAT creation, most notably the NCMATComposer helper class which
25
+ provides a pythonic and flexible object oriented interface for creating NCMAT
26
+ data representing a given material, and for verifying, writing, registering, or
27
+ loading said data.
28
+
29
+ """
30
+
31
+ class NCMATComposer:
32
+ """Helper class for composing an NCrystal material.
33
+
34
+ One can either start with an empty instance, or bootstrap the process by
35
+ starting the material from an existing NCMAT file, an NCrystal cfg-string,
36
+ an NCrystal.Info object, a CIF file, or from a variety of files (anything
37
+ that can be loaded with ASE and contains a suitable crystal
38
+ structure). Depending on the specifics of the problem, one will then use
39
+ various methods on the object to add or modify material information as
40
+ desired, before ultimately the create_ncmat() method can be used to create
41
+ the resulting NCMAT data. As a convenience the .write() and .register_as()
42
+ methods can be used to write this data as a physical or virtual file
43
+ respectively, or the .load() method can be used to directly load the
44
+ data. Finally, the .plot_xsect() and .inspect() methods can also be used to
45
+ quickly load and investigate the material. It might also be useful to print
46
+ the output of .create_ncmat(), to understand what material has been created.
47
+
48
+ For a more in-depth discussion and usage examples, please refer to the
49
+ example Jupyter-Lab notebooks in the
50
+ https://github.com/mctools/ncrystal-notebooks repository.
51
+
52
+ Note that the "labels" used in the various methods, to identify different
53
+ atoms in the materials are custom user labels. They are simply a way to
54
+ provide a handle associated with a particular atom role or species in the
55
+ material. For many simple materials, all labels will simply have the name of
56
+ the element or isotope ("Al", "Li6", "D", ...), and each type of element and
57
+ isotope will only appear once in the material. That is certainly OK, for
58
+ such simple materials, and in these cases there is no need to concern
59
+ one-self with the atomic composition of each label, since it can be inferred
60
+ from the label itself. However, sometimes different atoms of the same type
61
+ will appear in more than one role in the material (i.e. there might be one
62
+ group of loosely bound aluminium atoms, while a different group of aluminium
63
+ atoms are more tightly bound). Or, it might be that a given label is
64
+ actually associated with an actual mix of elements of isotopes (i.e. a label
65
+ might be associated with a particular isotopic enrichment, or a
66
+ multi-element mixture). In these cases, each label should have it's precise
67
+ composition defined with a call to the .set_composition(..) method.
68
+
69
+ In other words: Unless the label happens to coincide with the name of an
70
+ element or isotope ("Al", "Li6", "D", ...), its atomic composition MUST be
71
+ defined by a call to the .set_composition(..) method. And if a given element
72
+ has more than one role in the material, it must of course also have more
73
+ than one label - one for each role (e.g. "tightAl" and "looseAl").
74
+
75
+ To ensure high data quality and reliability, all crystalline materials will
76
+ by default have their crystal structure verified for consistency with the
77
+ indicated spacegroup, before it is converted to NCMAT data. For that reason,
78
+ the third-party module "spglib" must be installed in order for this
79
+ verification to take place (note that the .refine_crystal_structure() method
80
+ can also be invoked before the material is used, to detect the spacegroup in
81
+ if it was not provided). Fortunately, "spglib" is available both on PyPI and
82
+ in the conda-forge conda channel.
83
+
84
+ """
85
+
86
+ def __init__(self, data = None, fmt = None, quiet = False, plotlabel = None ):
87
+ """Initialise. Either an empty instance, or if data (and possibly fmt)
88
+ is provided, from a variety of data. Typically, the fmt parameter can be
89
+ left out, but occasionally it might be needed to specify it
90
+ explicitly. Supported fmt's are "cif", "cfgstr", "ncmat", (for NCMAT
91
+ data), "info" (for NCrystal.Info or LoadedMaterial objects), ase (for
92
+ ASE structures). Finally, the format "via_ase" is special, since it will
93
+ always process the input with ASE, even if another more obvious format
94
+ exists (i.e. CIF files will be processed via ASE first, not passed along
95
+ directly to the NCrystal.CIFLoader). As an alternative to the "fmt"
96
+ parameter, one can also invoke the various .from_xxx(..) static
97
+ methods. This has the potential advantage of allowing the specification
98
+ of parameters dedicated only to that input data type.
99
+
100
+ If quiet=True, no informative messages will be emitted, and the plotlabel
101
+ will be passed along to .set_plotlabel().
102
+
103
+ """
104
+ from ._ncmatimpl import NCMATComposerImpl as Impl
105
+ self.__impl = ( data if isinstance( data, Impl )
106
+ else ( data.__impl if isinstance( data, NCMATComposer )
107
+ else Impl( data = data, fmt = fmt, quiet = quiet ) ) )
108
+ if plotlabel:
109
+ self.set_plotlabel(plotlabel)
110
+
111
+ @staticmethod
112
+ def from_cif( cifsrc, quiet=False, mp_apikey = None,
113
+ uiso_temperature = None,
114
+ override_spacegroup = None, **kwargs ):
115
+ """Initialise from CIF data (anything suitable for the
116
+ NCrystal.cifutils.CIFSource class). Internally, the loading is carried
117
+ out by instantiating a CIFLoader instance (from the NCrystal.cifutils
118
+ module), and calling it's .create_ncmat_composer() method. Any provided
119
+ arguments will be passsed along to the call to .create_ncmat_composer(),
120
+ except for the override_spacegroup argument which is used when the
121
+ CIFLoader is instantiated.
122
+
123
+ For advanced work, it might be better to use the CIFLoader class
124
+ directly.
125
+ """
126
+ from ._ncmatimpl import NCMATComposerImpl as Impl
127
+ return NCMATComposer( Impl.from_cif( cifsrc=cifsrc, quiet=quiet,
128
+ uiso_temperature = uiso_temperature,
129
+ override_spacegroup = override_spacegroup,
130
+ mp_apikey = mp_apikey, **kwargs ) )
131
+
132
+ @staticmethod
133
+ def from_cfgstr( cfgstr ):
134
+ """Loads an Info object from the given cfg-string, and pass it on to
135
+ .from_info(..)."""
136
+ from ._ncmatimpl import NCMATComposerImpl as Impl
137
+ return NCMATComposer( Impl.from_cfgstr( cfgstr = cfgstr ) )
138
+
139
+ @staticmethod
140
+ def from_ase( ase_obj, ase_format = None, *, quiet = False, **kwargs ):
141
+ """Initialise from an ASE object. This is actually done internally by
142
+ first converting the ASE object to CIF data, and then subsequently
143
+ passing it on to the .from_cif(..) method (any **kwargs will be
144
+ passed along to the .from_cif call). If the provided ase_obj is not
145
+ actually already an ASE object, an attempt is done to load it into one
146
+ via the ase.io.read module (mimicking the fmt="via_ase" behaviour of the
147
+ __init__ method). In that case, any value in the ase_format parameter
148
+ will be passed along to ase.io.read's format parameter.
149
+ """
150
+ from ._ncmatimpl import NCMATComposerImpl as Impl
151
+ return NCMATComposer( Impl.from_ase( ase_obj = ase_obj, quiet = quiet,
152
+ ase_format = ase_format, **kwargs ) )
153
+
154
+ @staticmethod
155
+ def from_info( info_obj ):
156
+ """Initialise from provided Info (or LoadedMaterial) object.
157
+
158
+ In case the material is a gas, the temperature of the resulting
159
+ NCMATComposer will for safety be locked (due to the strong temperature
160
+ dependency of densities for gasses).
161
+ """
162
+ from ._ncmatimpl import NCMATComposerImpl as Impl
163
+ return NCMATComposer( Impl.from_info( info_obj = info_obj ) )
164
+
165
+ @staticmethod
166
+ def from_ncmat( data ):
167
+ """Loads an Info object from the given NCMAT data, and passes it on to
168
+ .from_info(..). If the NCMAT data has no newlines and doesn't start with
169
+ 'NCMAT', it will actually be passed directly on to .from_cfgstr(..)
170
+ instead.
171
+ """
172
+ from ._ncmatimpl import NCMATComposerImpl as Impl
173
+ return NCMATComposer( Impl.from_ncmat( data ) )
174
+
175
+ @staticmethod
176
+ def from_hfg( spec,
177
+ formula, *,
178
+ density,
179
+ title,
180
+ debyetemp = 400.0,
181
+ verbose = True,
182
+ notrim = False ):
183
+ """Constructs an amorphous hydrogen-rich material via the hfg2ncmat
184
+ function from the NCrystal.hfg2ncmat module. Refer to that function for
185
+ usage instructions.
186
+ """
187
+ from .hfg2ncmat import _default_debye_temp, hfg2ncmat
188
+ from ._ncmatimpl import NCMATComposerImpl as Impl
189
+ assert _default_debye_temp()==400.0, "from_hfg default must be updated"
190
+ ncmat = hfg2ncmat( spec = spec,
191
+ formula = formula,
192
+ density = density,
193
+ title = title,
194
+ debyetemp = debyetemp,
195
+ verbose = verbose,
196
+ notrim = notrim )
197
+ c = NCMATComposer( Impl.from_ncmat( ncmat,
198
+ keep_header=True ) )
199
+ c.set_plotlabel(title)
200
+ return c
201
+
202
+ def __call__( self, cfg_params = None, **kwargs ):
203
+ """Convenience short-cut for the .create_ncmat() method"""
204
+ return self.create_ncmat( cfg_params = cfg_params, **kwargs )
205
+
206
+ def update_atomdb( self, element_or_isotope, data = None, *, mass = None, coh_scat_len=None, incoh_xs=None, abs_xs=None ):
207
+ """Specify atom data for particular element or isotope. Either specify
208
+ the data parameter as a string (in @ATOMDB format like "28.97649466525u
209
+ 4.7fm 0.001b 0.101b") or AtomData object, or put all four physics values
210
+ (mass, coh_scat_len, incoh_xs, and abs_xs) directly as floating point
211
+ numbers. More specifically these numbers are mass value (amu), coherent
212
+ scattering length (fm), incoherent cross section (barn), and absorption
213
+ cross section @v_n=2200m/s (barn).
214
+ """
215
+ return self.__impl.update_atomdb( element_or_isotope,data=data,mass=mass,
216
+ coh_scat_len=coh_scat_len,incoh_xs=incoh_xs,abs_xs=abs_xs )
217
+
218
+ def find_label( self, element, allow_multi=False ):
219
+ """Attempt to locate label which has a composition containing provided
220
+ element (which can be a string name or Z value like Al, 13, B10,
221
+ ...). If exactly one such label exists, it will be returned, otherwise
222
+ None is returned. If allow_multi==True, the result(s) will instead be
223
+ returned as a list, and all matching labels will be returned.
224
+ """
225
+ return self.__impl.find_label( element = element, allow_multi = allow_multi )
226
+
227
+ def set_composition( self, label, *composition ):
228
+ """Use like .set_composition('H','D') or .set_composition('H is D'). Mixtures
229
+ can also be defined, for instance like .set_composition('H','0.2 H1 0.8
230
+ H2'). For a more in-depth discussion of the syntax, consult the @ATOMDB
231
+ section on https://github.com/mctools/ncrystal/wiki/NCMAT-format
232
+
233
+ Set atomic composition associated with a given label. Usage examples:
234
+
235
+ .set_composition('mylbl','Al') #mylbl now has a composition of only (natural) Al atoms.
236
+ .set_composition('Al','Al') #Al atoms (notice this call is not needed
237
+ #since the label is already an element name).
238
+ .set_composition('Al','Cr') #Well if you want to use confusing labels you can
239
+ .set_composition('mylbl','Li6') #specific isotope
240
+ .set_composition('mylbl','D') #deuterium is supported by either 'D' or 'H2'
241
+
242
+ More complicated mixtures are supported as well:
243
+
244
+ .set_composition('mylbl',0.95,'B10',0.05,'B11')#enriched boron
245
+ .set_composition('mylbl',[0.95,'B10',0.05,'B11'])#same but defined via sequence
246
+ .set_composition('mylbl',[(0.95,'B10'),(0.05,'B11')])#same but defined via sequence of pairs
247
+ .set_composition('mylbl','0.95 B10 0.05 B11')#same but via a string in @ATOMDB-like form.
248
+ .set_composition('mylbl is 0.95 B10 0.05 B11')#same but even more @ATOMDB like.
249
+ .set_composition('Al is 0.99 Al 0.005 Cr 0.005 B10')#mix and match elements and isotopes
250
+ """
251
+ return self.__impl.set_composition( label, *composition )
252
+
253
+ def remap_atom( self, element_or_isotope, *composition ):
254
+ """Calling this method updates all usage of the denoted
255
+ element_or_isotope marker ('C', 'Al', 'D', 'B10', ...) and replaces it
256
+ with the particular composition. For instance, .remap_atom('H','D') can
257
+ be used to fully deuterate a material. For a description of the allowed
258
+ compositon syntax, refer to the .set_composition method.
259
+
260
+ The remapping will only affect labels and compositions that have already
261
+ been added to the NCMATComposer object.
262
+ """
263
+ return self.__impl.remap_atom( element_or_isotope, *composition )
264
+
265
+ def clear_comments( self ):
266
+ """Clear comments added with the add_comments method."""
267
+ return self.__impl.clear_comments()
268
+
269
+ def add_comments( self, comments, add_empty_line_divider=False ):
270
+ """Add comments (string or list of strings) to the top of NCMAT data."""
271
+ return self.__impl.add_comments( comments = comments,
272
+ add_empty_line_divider = add_empty_line_divider )
273
+
274
+ def clone(self):
275
+ """Return a new independent NCMATComposer object which is a clone of the
276
+ current one."""
277
+ return NCMATComposer( self.__impl.clone() )
278
+
279
+ def get_cache_key( self ):
280
+ """
281
+ Returns tuple of integers which will be unchanged if generated NCMAT
282
+ content is unchanged. Note that this is partially based on memory
283
+ addresses of python objects, so the keys are NOT reproducible outside
284
+ the current process.
285
+ """
286
+ return self.__impl.get_cache_key()
287
+
288
+ def to_dict(self):
289
+ return self.__impl.to_dict()
290
+
291
+ def set_cellsg( self, *, a,b,c, alpha,beta,gamma, spacegroup=None ):
292
+ """Assumes alpha,beta,gamma in degrees, a,b,c in Aangstrom, spacegroup
293
+ an int in range 1..230."""
294
+ return self.__impl.set_cellsg( a=a,b=b,c=c,
295
+ alpha=alpha,beta=beta,gamma=gamma,
296
+ spacegroup=spacegroup )
297
+
298
+ def set_cellsg_cubic( self, a, *, spacegroup=None ):
299
+ """Like .set_cellsg but will set b=c=a and alpha=beta=gamma=90."""
300
+ return self.__impl.set_cellsg_cubic( a = a, spacegroup = spacegroup )
301
+
302
+ def set_atompos( self, atompos ):
303
+ """The atompos parameter must be list of entries
304
+ (label,x,y,z) or (label,x,y,z,site_occupancy).
305
+
306
+ If provided, the site_occupancy value (a number in (0,1] must be the
307
+ same for all identical labels.
308
+
309
+ The site_occupancy parameter is for now highly experimental and will
310
+ result in generated NCMAT files with fake sterile atoms inserted for
311
+ technical reasons (resulting in a higher density but hopefully correct
312
+ physics as long as the user does not subsequently try to override the
313
+ density directly via the cfg-string parameters). This is perfectly fine
314
+ for usage in a MC simulation context where NCrystal provides the entire
315
+ physics of the material (e.g. McStas), but in a context where a base
316
+ material must be created using the composition from NCrystal
317
+ (e.g. Geant4 or OpenMC), it most likely will result in an error.
318
+ """
319
+ return self.__impl.set_atompos( atompos = atompos )
320
+
321
+ def refine_crystal_structure( self, symprec = 0.01, quiet = False ):
322
+ """Attempt to refine the crystal structure (does nothing if not a
323
+ crystalline material). This ignores the spacegroup number (if any), and
324
+ uses spglib to refine and standardise the crystal structure based on the
325
+ provided unit cell parameters and atomic positions. The unit cell
326
+ parameters, spacegroup number, and atomic positions, are all updated
327
+ accordingly.
328
+ """
329
+ return self.__impl.refine_crystal_structure( symprec = symprec,
330
+ quiet = quiet )
331
+
332
+ def verify_crystal_structure( self, symprec = 0.01, quiet = False ):
333
+ """Attempt to verify the crystal structure (does nothing if not a
334
+ crystalline material). This requires a spacegroup number to be
335
+ available, and uses spglib to verify that the crystal structure (unit
336
+ cell and atom positions) is consistent with that spacegroup
337
+ number. Raises an exception in case the structure could not be verified,
338
+ otherwise does nothing.
339
+ """
340
+ return self.__impl.verify_crystal_structure( symprec = symprec,
341
+ quiet = quiet )
342
+
343
+ def set_density( self, value, unit = 'g/cm3' ):
344
+ """
345
+ Specify material density for non-crystalline materials. If provided,
346
+ the unit must be a string which is one of "g/cm3" (default) , "kg/m3",
347
+ or "atoms/Aa3". Note that any material can have its density modified
348
+ with the cfg-string parameter "density", while the present method is
349
+ instead used to provide the basic density embedded in NCMAT format for
350
+ non-crystalline materials.
351
+ """
352
+ return self.__impl.set_density( value = value, unit = unit )
353
+
354
+ def set_fraction( self, label, value ):
355
+ """
356
+ Specify fraction of component associated with a given label. This is
357
+ not needed for crystalline materials, where the fractions can be
358
+ inferred from the unit cell contents. Note that the various
359
+ set_dyninfo_xxx methods have an optional fraction parameter, which can
360
+ also be used to set the fraction directly.
361
+
362
+ """
363
+ return self.__impl.set_fraction( label = label, value = value )
364
+
365
+ def allow_fallback_dyninfo( self, debye_temp = 300.0 ):
366
+ """For crystalline materials only, allow a fall-back modelling of any
367
+ components with absent dynamic information via a VDOS-Debye model using
368
+ the specified Debye temperature.
369
+ """
370
+ return self.__impl.allow_fallback_dyninfo( debye_temp = debye_temp )
371
+
372
+ def set_dyninfo_vdos( self, label, vdos_egrid, vdos, *, fraction = None, comment = None ):
373
+ """Set dynamics of component to be modelled by a 1D phonon density of
374
+ state (DOS) curve (VDOS="Vibrational DOS"). This is the preferred way to
375
+ provide dynamics for components of solid materials, as it not only
376
+ gives enough information to estimate the Debye-Waller factors needed for
377
+ elastic scattering in all such materials, but also allows realistic
378
+ inelastic scattering via expansion into full 2D scattering kernels.
379
+ This model is not appropriate for gaseous or liquid materials.
380
+ """
381
+ return self.__impl.set_dyninfo_vdos( label = label,
382
+ vdos_egrid = vdos_egrid,
383
+ vdos = vdos,
384
+ comment = comment,
385
+ fraction = fraction )
386
+
387
+ def set_dyninfo_vdosdebye( self, label, debye_temp, *, fraction = None, comment = None ):
388
+ """Set dynamics of component to be modelled by a Debye temperature. This
389
+ allows temperature-dependent atomic displacements and Debye-Waller
390
+ factors to be estimated, and inelastic scattering will be modelled based
391
+ on an idealised VDOS curve (a parabola up to a cutoff frequency
392
+ corresponding to E=k*T_debye). This model is only appropriate for solid
393
+ materials.
394
+ """
395
+ return self.__impl.set_dyninfo_vdosdebye( label = label,
396
+ debye_temp = debye_temp,
397
+ comment = comment,
398
+ fraction = fraction )
399
+
400
+ def set_dyninfo_debyetemp( self, label, debye_temp, *, fraction = None, comment = None ):
401
+ """Alias for set_dyninfo_vdosdebye."""
402
+ return self.set_dyninfo_vdosdebye( label=label, debye_temp=debye_temp,
403
+ fraction=fraction, comment=comment )
404
+
405
+ def set_dyninfo_msd( self, label, msd, *, temperature, fraction = None, comment = None, ):
406
+ """Calculate and set Debye temperature based on
407
+ mean-squared-displacement (msd) value (in Aa^2). This also needs the
408
+ temperature value for which the msd value is associated (in
409
+ kelvin). Note that this temperature value is NOT necessarily the
410
+ temperature at which the material will later be used. See
411
+ .set_dyninfo_vdosdebye(..) for further information about the resulting
412
+ modelling.
413
+ """
414
+ return self.__impl.set_dyninfo_msd( label = label,
415
+ msd = msd,
416
+ temperature = temperature,
417
+ comment = comment,
418
+ fraction = fraction )
419
+
420
+ def set_dyninfo_uiso( self, label, uiso, temperature, *, fraction = None, comment = None ):
421
+ """Alias for set_dyninfo_msd. The uiso value is just another name for
422
+ "msd" and both have a unit of Aa^2."""
423
+ return self.set_dyninfo_msd( label=label, msd=uiso, temperature=temperature,
424
+ fraction=fraction, comment=comment )
425
+
426
+ def set_dyninfo_scatknl( self, label, *, alphagrid, betagrid, temperature,
427
+ sab = None, sab_scaled = None, egrid = None,
428
+ fraction = None, comment = None ):
429
+ """Set dynamics of component to be modelled by the provided 2D
430
+ S(alpha,beta) ("sab") scattering kernel. Such kernels are
431
+ temperature-dependent, so one must also specify the temperature for
432
+ which the kernel is valid, which of necessity will lock the temperature
433
+ of the entire material at that temperature. As NCrystal currently is not
434
+ able to estimate atomic displacements and Debye-Waller factors from such
435
+ kernels, they can not be used with crystalline materials at all, and are
436
+ in principle not suitable for amorphous solids either, as even
437
+ incoherent-elastic contributions will be absent. Thus, this modelling is
438
+ mainly intended for liquids or gasses.
439
+ """
440
+ return self.__impl.set_dyninfo_scatknl( label = label,
441
+ alphagrid = alphagrid,
442
+ betagrid = betagrid,
443
+ temperature = temperature,
444
+ sab = sab,
445
+ sab_scaled = sab_scaled,
446
+ egrid = egrid,
447
+ comment = comment,
448
+ fraction = fraction )
449
+
450
+ def set_dyninfo_freegas( self, label, *, fraction = None, comment = None ):
451
+ """Set dynamics of component to be modelled by a free gas description (a gas
452
+ of non-interacting atoms). This model is not suitable for solid
453
+ materials, except as a crude approximation. It is specifically
454
+ disallowed for crystalline materials, since it does not provide atomic
455
+ displacements (Debye-Waller factors). See .set_dyninfo_debyetemp(..),
456
+ .set_dyninfo_msd(..) and .allow_fallback_dyninfo() for easy
457
+ (low-realism) alternatives for crystalline materials.
458
+ """
459
+ return self.__impl.set_dyninfo_freegas( label = label,
460
+ comment = comment,
461
+ fraction = fraction )
462
+
463
+ def set_dyninfo_sterile( self, label, *, fraction = None, comment = None ):
464
+ """Set dynamics of component to be modelled as being without any scattering
465
+ cross section. This model is obviously unrealistic and exists for
466
+ debugging purposes only. It can not be used for crystalline materials
467
+ (for these one could use .update_atomdb instead to introduce atoms with
468
+ no scattering cross section into the crystal).
469
+ """
470
+ return self.__impl.set_dyninfo_sterile( label = label,
471
+ comment = comment,
472
+ fraction = fraction )
473
+
474
+ def set_dyninfo_from_object( self, label, source_dyninfo, comment = None, fraction=None ):
475
+ """Set dyninfo of given label based on existing NCrystal.DynamicInfo
476
+ object.
477
+
478
+ Note that in principle the flow NCMAT-data -> Info -> NCMATComposer ->
479
+ NCMAT-data is not guaranteed to be 100% lossless, but in practice the
480
+ resulting physics is unlikely to be unaffected by this. For a guaranteed
481
+ loss-less operation, one must manually edit the NCMAT data and copy the
482
+ exact lines of the relevant @DYNINFO section into the new NCMAT data
483
+ (this is beyond the scope of the NCMATComposer).
484
+ """
485
+ return self.__impl.set_dyninfo_from_object( label = label,
486
+ source_dyninfo = source_dyninfo,
487
+ comment = comment,
488
+ fraction = fraction )
489
+
490
+ def transfer_dyninfo_objects( self, source, mapping = None, allow_none = False ):
491
+ """Set dyninfo from source. Source can be a list of
492
+ NCrystal.DynamicInfo, a single NCrystal.DynamicInfo info object, or any
493
+ sort of material source like an Info object, a cfg-str, NCMAT data, etc.
494
+
495
+ If a mapping dict is provided, it must be a mapping between labels used
496
+ on the NCMATComposer object and display labels in the source DynamicInfo
497
+ objects (example: {"H":"H","mylbl":"Al","O16":"O"}). If a mapping is NOT
498
+ provided, an automatic mapping will be attempted based on element Z
499
+ values between source entries and labels already registered in the
500
+ composer (in this case it must be possible to determine the composition
501
+ of all labels). Any excess entries in the source will be ignored.
502
+
503
+ An NCBadInput error will be raised if no dynamic information is actually
504
+ transferred, unless allow_none=True is provided.
505
+
506
+ """
507
+ return self.__impl.transfer_dyninfo_objects( source = source,
508
+ mapping = mapping,
509
+ allow_none = allow_none )
510
+
511
+ def set_state_of_matter( self, state_of_matter ):
512
+ """Explicitly specify the state of matter, which must be a string with
513
+ value of "solid", "liquid", or "gas". Call it with an empty string or
514
+ None to clear the information.
515
+
516
+ Note that NCrystal will automatically classify materials as "solid" if
517
+ they are crystalline materials and/or have dynamic information of the
518
+ types vdos, vdosdebye, or msd. Trying to change the state of matter
519
+ value for such materials will result in an error at a later stage.
520
+ """
521
+ return self.__impl.set_state_of_matter( state_of_matter )
522
+
523
+
524
+ def add_secondary_phase(self, fraction, cfgstr, normalise = True ):
525
+ """Add a secondary phase to the material, by specifying the phase volume
526
+ fraction and a cfg-strings defining the content of the secondary
527
+ phase. For instance a material with air bubbles inside, might be
528
+ emulated by calling .add_secondary_phase(0.05,"gasmix::air"), which
529
+ would add a secondary phase to the material, occupying 5% of the volume
530
+ and containing air. For details, please see the discussion under the
531
+ heading "The @OTHERPHASES section" in the document at
532
+ https://github.com/mctools/ncrystal/wiki/NCMAT-format.
533
+
534
+ Unless normalise=False, the cfg-string will be normalised with a call to
535
+ normaliseCfg(..) from the NCrystal.cfgstr module.
536
+ """
537
+ return self.__impl.add_secondary_phase( fraction = fraction,
538
+ cfgstr = cfgstr,
539
+ normalise = normalise )
540
+
541
+ def add_hard_sphere_sans_model( self, sphere_radius ):
542
+ """As a technology preview of future more complete SANS support, one can
543
+ for now use this method to add hard-sphere SANS scattering between the
544
+ primary phase, and the first phase added with .add_secondary_phase(..)
545
+ Note that as this results in a @CUSTOM_HARDSPHERESANS section in the
546
+ resulting NCMAT data (because it is a tech-preview), warnings will be
547
+ emitted when the data is subsequently loaded by NCrystal.
548
+
549
+ The implemented hard-sphere model only has a single free parameter, the
550
+ sphere_radius in angstrom.
551
+ """
552
+ return self.__impl.add_hard_sphere_sans_model(sphere_radius=sphere_radius)
553
+
554
+ def add_raw_content( self, content ):
555
+ """This is an experts-only method for adding raw text data to be
556
+ appended to the generated NCMAT data. Note that multiple calls to this
557
+ method will simply append more content, if you wish to remove content
558
+ again you must call clear_raw_content.
559
+
560
+ Note that this method does not necessarily add a newline to your content
561
+ if it is missing.
562
+
563
+ """
564
+ return self.__impl.add_raw_content( content )
565
+
566
+ def clear_raw_content( self ):
567
+ """Remove any content added by calls to .add_raw_content(..)"""
568
+ return self.__impl.clear_raw_content()
569
+
570
+ def get_raw_content( self ):
571
+ """Return any content added by calls to .add_raw_content(..). Returns an
572
+ empty string if no such content was added."""
573
+ return self.__impl.get_raw_content()
574
+
575
+ def set_custom_section_data( self, section_name, content ):
576
+ """Add a @CUSTOM_<sectionname> section with the provided content to the
577
+ generated NCMAT data. Note that multiple calls to this method with the
578
+ same section_name will simply override the content of that custom
579
+ section. To completely remove a previously added custom section, use the
580
+ .clear_custom_section_data(..) method.
581
+ """
582
+ return self.__impl.set_custom_section_data( section_name, content )
583
+
584
+ def get_custom_section_data( self, section_name = None ):
585
+ """Access any @CUSTOM_<sectionname> contents which was previously added
586
+ with the .set_custom_section_data(..) method. Returns None if data for
587
+ that section was not added. If called without parameters, a dictionary
588
+ of all such data in the form { section_name : content, ... } is
589
+ returned instead.
590
+ """
591
+ return self.__impl.get_custom_section_data( section_name )
592
+
593
+ def clear_custom_section_data( self, section_name = None ):
594
+ """Remove any @CUSTOM_<sectionname> section previously added by calling
595
+ set_custom_section_data. Does nothing if no such section was previously
596
+ added. Calling with no arguments clears all custom section data.
597
+ """
598
+ return self.__impl.clear_custom_section_data( section_name )
599
+
600
+ def lock_temperature( self, value ):
601
+ """
602
+ Lock the temperature of the material to the given value. This not
603
+ only changes the default temperature of the material, but also "locks"
604
+ it in the sense that any attempts at using the cfg-level variable "temp"
605
+ to modify the temperature will result in an error. See also the
606
+ .set_default_temperature() method. Call with value=None to clear any
607
+ previous effects of .lock_temperature() or .set_default_temperature().
608
+ """
609
+ return self.__impl.lock_temperature( value )
610
+
611
+ def set_default_temperature( self, value ):
612
+ """
613
+ Modify the default temperature of the material from the usual
614
+ 293.15K. The temperature value can still be modified subsequently using
615
+ the cfg parameter "temp" as usual. To prevent this, use the
616
+ .lock_temperature() method instead. Call with value=None to clear any
617
+ previous effects of .lock_temperature() or .set_default_temperature().
618
+ """
619
+ return self.__impl.set_default_temperature( value )
620
+
621
+ def get_temperature_setting( self ):
622
+ """Returns the temperature setting resulting from calls to
623
+ .lock_temperature() or .set_default_temperature(). Returns a tuple of
624
+ two values: the temperature value in kelvin, and a boolean indicating
625
+ whether the value has been locked or is merely a changed
626
+ default. Returns (None,None) in the absence of such a setting.
627
+ """
628
+ return self.__impl.get_temperature_setting()
629
+
630
+ @property
631
+ def state_of_matter( self ):
632
+ """
633
+ Access the state of matter which will be one of "solid", "liquid",
634
+ or "gas". However, this is only available if explicitly set, otherwise
635
+ None is returned.
636
+ """
637
+ return self.__impl.get_state_of_matter()
638
+
639
+ def get_labels( self ):
640
+ """List of all labels registered so far."""
641
+ return self.__impl.get_labels()
642
+
643
+ def write( self, path, cfg_params = None ):
644
+ """Produce NCMAT data and write it to the provided file path. If any
645
+ cfg_params are provided, they will be embedded in the NCMAT data using
646
+ the NCRYSTALMATCFG[..] syntax.
647
+ """
648
+ return self.__impl.write( path = path,
649
+ cfg_params = cfg_params )
650
+
651
+ def register_as( self, virtual_filename, cfg_params = None ):
652
+ """Produce NCMAT data and register it in memory with the provided
653
+ virtual file-name, using the registerInMemoryFileData(..) function from
654
+ the NCrystal.datasrc module. If any cfg_params are provided, they will
655
+ be embedded in the NCMAT data using the NCRYSTALMATCFG[..] syntax.
656
+ """
657
+ return self.__impl.register_as( virtual_filename = virtual_filename,
658
+ cfg_params = cfg_params )
659
+
660
+ def load(self, cfg_params = None, *, force = False ):
661
+ """Will create NCMAT and load it with the directLoad(..) function of the
662
+ NCrystal.core module. The cfg_params parameter can be used to apply cfg
663
+ parameters (e.g. cfg_params="temp=200K;dcutoff=0.1")., while force=True
664
+ will prevent the NCMATComposer from simply returning a previously loaded
665
+ material (usually one should just leave the default force=False value
666
+ untouched).
667
+ """
668
+ return self.__impl.load( cfg_params = cfg_params, force = force )
669
+
670
+ def plot_xsect( self, cfg_params = None, **kwargs_plot_xsect ):
671
+ """
672
+ Quick plot (with matplotlib) showing the cross sections produced by
673
+ current material, possibly after appending certain cfg_params. This is
674
+ using the plot_xsect function from the NCrystal.plot module, so refer to
675
+ that function for available arguments.
676
+ """
677
+ return self.__impl.plot_xsect( self, cfg_params, kwargs_plot_xsect )
678
+
679
+ def inspect( self, cfg_params = None, **kwargs_plot_xsect ):
680
+ """Similar to the plot_xsect(..) function, but will also print out
681
+ information (e.g. .dump()) about the Info, Scatter, and Absorption
682
+ objects.
683
+ """
684
+ return self.__impl.inspect( self, cfg_params, kwargs_plot_xsect )
685
+
686
+ def get_chemical_composition( self, as_str = False ):
687
+ """Chemical composition as list of [(elemiso,count),...] where elemiso
688
+ is a marker like "Al", "D", "B10", .... For crystals, the counts will
689
+ represent the number of elements/isotopes per unit cell. Returns None in
690
+ case of incomplete information.
691
+
692
+ If as_str = True, the result will instead be returned encoded in a
693
+ string (e.g. "H2O").
694
+ """
695
+ return self.__impl.get_chemical_composition( as_str=as_str )
696
+
697
+ def create_ncmat( self, cfg_params = None, *,
698
+ meta_data = False,
699
+ verify_crystal_structure = True ):
700
+ """Creates and returns NCMAT data based on the material settings
701
+ provided so far. Any cfg_params provided here will be embedded into the
702
+ NCMAT data itself, using the NCRYSTALMATCFG[..] syntax.
703
+
704
+ If meta_data=True, the return value will be a tuple whose first element
705
+ is the created NCMAT data, and whose second element is a dictionary with
706
+ a high-level meta-data concerning the material (like chemical
707
+ composition, spacegroup, etc.).
708
+
709
+ Unless verify_crystal_structure=False (not recommended!) any crystalline
710
+ material will have its structure verified by calling
711
+ .verify_crystal_structure(), possibly raising an exception in case of
712
+ issues found. Depending on the scenario, it might be possible to prevent
713
+ this by calling .refine_crystal_structure(), which will hopefully fix it
714
+ up before verification (but be vigilant and double-check that the
715
+ refinement did not simply hide some fundamental flaw of the input data).
716
+ """
717
+ return self.__impl.create_ncmat( cfg_params = cfg_params,
718
+ meta_data = meta_data,
719
+ verify_crystal_structure = verify_crystal_structure )
720
+
721
+
722
+ def set_plotlabel( self, lbl ):
723
+ """Sets the plotlabel (cf. .plotlabel)."""
724
+ return self.__impl.set_plotlabel(lbl)
725
+
726
+ @property
727
+ def plotlabel( self ):
728
+ """An optional label for the material, which can be used for plotting
729
+ purposes in legends, etc.
730
+ """
731
+ return self.__impl.plotlabel
732
+
733
+ def as_spglib_cell( self ):
734
+ """For a crystalline material, return the unit cell definition in a
735
+ format suitable for usage in spglib calls."""
736
+ return self.__impl.as_spglib_cell()
737
+
738
+ def _unofficial_vdos2sab_ignore( self, *, order_low, order_high = None, mode = None ):
739
+ """This expert-only method can be used to exclude certain phonon orders
740
+ from the scattering kernel during vdos2sab expansion. It is primarily
741
+ intended as a debugging aid for visualisation purposes, or for
742
+ developers intending to develop coherent single-phonon physics.
743
+
744
+ Be aware that, for now, trying to use the Info.DI_VDOS[Debye].loadKernel
745
+ or .plot_knl(), will NOT reflect this hack!
746
+ """
747
+ return self.__impl._unofficial_vdos2sab_ignore( order_low = order_low,
748
+ order_high = order_high,
749
+ mode = mode )
750
+
751
+ def _get_impl_obj( self ):
752
+ return self.__impl
753
+
754
+ def formatVectorForNCMAT(name,values,indent=' '):
755
+ """Utility function for help in python scripts composing .ncmat files,
756
+ transforming an array of of values into a properly formatted text string,
757
+ with word-wrapping, usage of <val>r<n> syntax, etc.
758
+ """
759
+ from ._ncmatimpl import formatVectorForNCMAT as _
760
+ return _( name = name,
761
+ values = values,
762
+ indent = indent )
763
+
764
+ def _rawParseNCMAT(text_data_name,*,asJSONStr=False):
765
+ """Parses NCMAT content and returns as Python data structure (a dictionary). The
766
+ format of this data structure should be mostly self-evident by
767
+ inspection, and is not guaranteed to stay the same across NCrystal
768
+ versions. If asJSONStr=true, the data structure will be returned as a
769
+ JSON-encoded string, instead of a Python dictionary.
770
+
771
+ WARNING: This function is considered experimental and is currently NOT
772
+ feature complete. It only returns data from a few select NCMAT sections."""
773
+ from ._chooks import _get_raw_cfcts
774
+ _js = _get_raw_cfcts()['nc_ncmat2json'](text_data_name if not hasattr(text_data_name,'rawData') else text_data_name.rawData )
775
+ if asJSONStr:
776
+ return _js
777
+ import json
778
+ return json.loads(_js)