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/_numpy.py ADDED
@@ -0,0 +1,76 @@
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
+ """Internal module providing ctypes-based hooks into the compiled NCrystal
24
+ shared library"""
25
+
26
+ __all__ = ['_np',
27
+ '_ensure_numpy',
28
+ '_np_linspace',
29
+ '_np_geomspace',
30
+ '_np_logspace',
31
+ '_np_trapezoid']
32
+
33
+ try:
34
+ import numpy as _np
35
+ except ImportError:
36
+ _np = None
37
+
38
+ def _ensure_numpy():
39
+ if not _np:
40
+ from .exceptions import NCException
41
+ raise NCException("Numpy not available - array "
42
+ "based functionality is unavailable")
43
+ return _np
44
+
45
+ def _np_linspace(start,stop,num=50):
46
+ """linspace with reproducible endpoint value"""
47
+ _ensure_numpy()
48
+ assert num >= 2
49
+ ll = _np.linspace(start,stop,num)
50
+ ll[0] = start
51
+ ll[-1] = stop
52
+ return ll
53
+
54
+ def _np_geomspace(start,stop,num=50):
55
+ """geomspace with reproducible endpoint value"""
56
+ _ensure_numpy()
57
+ assert num >= 2
58
+ ll = _np.geomspace(start,stop,num)
59
+ ll[0] = start
60
+ ll[-1] = stop
61
+ return ll
62
+
63
+ def _np_logspace(start,stop,num=50):
64
+ """logspace with reproducible endpoint value"""
65
+ _ensure_numpy()
66
+ assert num >= 2
67
+ ll = _np.logspace(start,stop,num)
68
+ ll[0] = 10.0**start
69
+ ll[-1] = 10.0**stop
70
+ return ll
71
+
72
+ def _np_trapezoid( *args, **kwargs ):
73
+ _ensure_numpy()
74
+ if hasattr(_np,'trapezoid'):
75
+ return _np.trapezoid( *args, **kwargs )
76
+ return _np.trapz( *args, **kwargs )
NCrystal/_testimpl.py ADDED
@@ -0,0 +1,579 @@
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
+ """Implementation of the built-in unit tests."""
23
+
24
+ __all__ = ['test','test_cmdline','test_cmake','test_extra','test_all']
25
+ from ._common import print as _nc_print
26
+ import contextlib as _contextlib
27
+
28
+ def test( verbose = False ):
29
+ """Quick test that NCrystal works as expected in the current installation."""
30
+ _actualtest( verbose = verbose )
31
+ if verbose!='quiet':
32
+ _nc_print("Tests completed succesfully")
33
+
34
+ def test_cmdline( verbose = False ):
35
+ """Quick test that NCrystal command-line scripts are present and working in
36
+ the current installation."""
37
+ _actual_test_cmdline( verbose = verbose )
38
+ if verbose!='quiet':
39
+ _nc_print("Tests completed succesfully")
40
+
41
+ def test_cmake( verbose = False ):
42
+ """Quick test that the NCrystal installation supports down-stream CMake/C++
43
+ projects (possibly after using ncrystal-config to get CMAKE_PREFIX_PATH and
44
+ (DY)LD_LIBRARY_PATHS setup).
45
+ """
46
+ _actual_test_cmake( verbose = verbose, ignore_if_absent = False )
47
+ if verbose!='quiet':
48
+ _nc_print("Tests completed succesfully")
49
+
50
+ def test_extra( verbose = False ):
51
+ """Run both test() and test_cmdline(). If CMake is present, also run test_cmake()."""
52
+ _actualtest( verbose = verbose )
53
+ _actual_test_cmdline( verbose = verbose )
54
+ _actual_test_cmake( verbose = verbose, ignore_if_absent = True )
55
+ if verbose!='quiet':
56
+ _nc_print("Tests completed succesfully")
57
+
58
+ def test_all( verbose = False ):
59
+ """Run both test(), test_cmdline(), and test_cmake()."""
60
+ _actualtest( verbose = verbose )
61
+ _actual_test_cmdline( verbose = verbose )
62
+ _actual_test_cmake( verbose = verbose, ignore_if_absent = False )
63
+ if verbose!='quiet':
64
+ _nc_print("Tests completed succesfully")
65
+
66
+ def _get_prfct( verbose ):
67
+ if verbose and verbose != 'quiet':
68
+ return lambda *a, **kw : _nc_print('::NCrystalTest::',*a,**kw)
69
+ else:
70
+ return lambda *a, **kw : None
71
+
72
+ def _actualtest( verbose ):
73
+ prfct = _get_prfct( verbose )
74
+ prfct('starting standard Python-API testing')
75
+ try:
76
+ import numpy as _np
77
+ except ImportError:
78
+ _np = None
79
+ from . import core as NC
80
+ from .constants import wl2ekin
81
+ from .plugins import hasFactory
82
+
83
+ def require(b):
84
+ if not b:
85
+ raise RuntimeError('check failed')
86
+ def flteq(a,b,rtol=1.0e-6,atol=1.0e-6):
87
+ return abs(a-b) <= 0.5 * rtol * (abs(a) + abs(b)) + atol
88
+ def require_flteq(a,b):
89
+ if not flteq(a,b):
90
+ raise RuntimeError('check failed (%.16g != %.16g, diff %g)'%(a,b,a-b))
91
+ return True
92
+ require(hasFactory('stdncmat'))
93
+ from . import _common as nc_common
94
+ require( nc_common.prettyFmtValue(0.25) == '1/4' )
95
+ require( nc_common.prettyFmtValue(0.25 + 1e-12) != '1/4' )
96
+ require( nc_common.prettyFmtValue(0.25 + 1e-15) == '1/4' )
97
+
98
+ _cfgstr='stdlib::Al_sg225.ncmat;dcutoff=1.4'
99
+ prfct(f'Trying to createInfo("{_cfgstr}")')
100
+ al = NC.createInfo(_cfgstr)
101
+ prfct('Verifying loaded Info object')
102
+ require(al.hasTemperature() and require_flteq(al.getTemperature(),293.15))
103
+ require_flteq(al.getXSectFree(),1.39667)
104
+ require_flteq(al.getXSectAbsorption(),0.231)
105
+ require_flteq(al.getDensity(),2.69864547673)
106
+ require_flteq(al.getNumberDensity(),0.06023238256131625)
107
+ require(al.hasDebyeTemperature())
108
+
109
+ require(al.hasStructureInfo())
110
+ si=al.getStructureInfo()
111
+ require( si['spacegroup'] == 225 )
112
+ require_flteq(si['a'],4.04958)
113
+ require_flteq(si['b'],4.04958)
114
+ require_flteq(si['c'],4.04958)
115
+ require( si['alpha'] == 90.0 )
116
+ require( si['beta'] == 90.0 )
117
+ require( si['gamma'] == 90.0 )
118
+ require( si['n_atoms'] == 4 )
119
+ require_flteq(si['volume'],66.4094599932)
120
+ require( al.hasHKLInfo() )
121
+ require( al.nHKL() == 3 )
122
+ require_flteq(al.hklDLower(),1.4)
123
+ require( al.hklDUpper() > 1e36 )
124
+ expected_hkl = { 0 : (1, 1, 1, 8, 2.3380261031049243, 1.7731590373262052),
125
+ 1 : (2, 0, 0, 6, 2.02479, 1.7317882793764163),
126
+ 2 : (2, 2, 0, 12, 1.4317427394787092, 1.5757351418107877) }
127
+ for idx,hkl in enumerate(al.hklList()):
128
+ h,k,l_,mult,dsp,fsq = hkl
129
+ require(idx<len(expected_hkl))
130
+ e = expected_hkl[idx]
131
+ require( list(e)[0:4] == [h,k,l_,mult] )
132
+ require_flteq(dsp, e[4])
133
+ require_flteq(fsq, e[5])
134
+
135
+ _cfgstr2='stdlib::Al_sg225.ncmat;dcutoff=1.4;incoh_elas=0;inelas=0'
136
+ prfct(f'Trying to createScatter("{_cfgstr2}")')
137
+ #We do all createScatter... here with independent RNG, for reproducibility
138
+ #and to avoid consuming random numbers from other streams.
139
+ alpc = NC.createScatterIndependentRNG(_cfgstr2)
140
+ prfct('Verifying loaded Scatter object')
141
+ require( alpc.name == 'PCBragg' )
142
+ require( isinstance(alpc.name,str) )
143
+ require( alpc.refCount() in (1,2) )
144
+ require( type(alpc.refCount()) == int ) # noqa E721
145
+ require( alpc.isNonOriented() )
146
+ #_nc_print(alpc.xsect(wl=4.0))
147
+ require_flteq(1.632435821586171,alpc.crossSectionIsotropic(wl2ekin(4.0)) )
148
+ require_flteq(1.632435821586171,alpc.crossSection(wl2ekin(4.0),(1,0,0)))
149
+ require( alpc.crossSectionIsotropic(wl2ekin(5.0)) == 0.0 )
150
+
151
+ require( alpc.rngSupportsStateManipulation() )
152
+ require(alpc.getRNGState()=='a79fd777407ba03b3d9d242b2b2a2e58b067bd44')
153
+
154
+ alpc.setRNGState('deadbeefdeadbeefdeadbeefdeadbeefb067bd44')
155
+ require(alpc.getRNGState()=='deadbeefdeadbeefdeadbeefdeadbeefb067bd44')
156
+ alpc_clone = alpc.clone()
157
+ require(alpc.getRNGState()=='deadbeefdeadbeefdeadbeefdeadbeefb067bd44')
158
+ require(alpc_clone.getRNGState()=='e0fd16d42a2aced7706cffa08536d869b067bd44')
159
+ alpc_clone2 = alpc_clone.clone(for_current_thread=True)
160
+ require(alpc_clone2.getRNGState()=='cc762bb1160a0be514300da860f6d160b067bd44')
161
+ alpc_clone3 = alpc_clone.clone(rng_stream_index = 12345 )
162
+ require(alpc_clone3.getRNGState()=='3a20660a10fd581bd7cddef8fc3f32a2b067bd44')
163
+
164
+ #Pick Nickel at 1.2 angstrom, to also both vdos + incoherent-elastic + coherent-elastic:
165
+ _cfgstr3='stdlib::Ni_sg225.ncmat;dcutoff=0.6;vdoslux=2'
166
+ _seed=2543577
167
+ prfct(f'Trying to createScatterIndependentRNG("{_cfgstr3}",{_seed})')
168
+ nipc = NC.createScatterIndependentRNG(_cfgstr3,_seed)
169
+ prfct('Verifying loaded Scatter object')
170
+ nipc_testwl = 1.2
171
+ #_nc_print(nipc.xsect(wl=nipc_testwl),nipc.xsect(wl=5.0))
172
+ require_flteq(16.76474410391571,nipc.xsect(wl=nipc_testwl))
173
+ require_flteq(16.76474410391571,nipc.xsect(wl=nipc_testwl,direction=(1,0,0)))
174
+ require_flteq(5.958467463288343,nipc.xsect(wl=5.0))
175
+
176
+ require( nipc.name == 'ProcComposition' )
177
+
178
+ expected = [ ( 0.056808478892590906, 0.5361444826572666 ),
179
+ ( 0.056808478892590906, 0.5361444826572666 ),
180
+ ( 0.056808478892590906, 0.36219866365374176 ),
181
+ ( 0.056808478892590906, 0.8391056916029316 ),
182
+ ( 0.03200142524676351, -0.37261037212010517 ),
183
+ ( 0.056808478892590906, -0.10165685368899147 ),
184
+ ( 0.056808478892590906, -0.15963879335683306 ),
185
+ ( 0.056808478892590906, 0.8260541809964751 ),
186
+ ( 0.0779984939788784, -0.5293576625127443 ),
187
+ ( 0.05348552589207497, -0.09540771817962344 ),
188
+ ( 0.056808478892590906, 0.8260541809964751 ),
189
+ ( 0.041255667101120046, -0.21139471030502716 ),
190
+ ( 0.056808478892590906, -0.10165685368899147 ),
191
+ ( 0.056808478892590906, -0.10165685368899147 ),
192
+ ( 0.056808478892590906, 0.5361444826572666 ),
193
+ ( 0.056808478892590906, -0.3915665520281999 ),
194
+ ( 0.056808478892590906, 0.36219866365374176 ),
195
+ ( 0.05750889239879721, -0.5221309343148964 ),
196
+ ( 0.056808478892590906, 0.36219866365374176 ),
197
+ ( 0.08122761653652728, -0.9893394211150188 ),
198
+ ( 0.056808478892590906, -0.5655123710317247 ),
199
+ ( 0.05809677932650489, -0.9514020394895405 ),
200
+ ( 0.056808478892590906, 0.3042167239859003 ),
201
+ ( 0.056808478892590906, 0.7378808571510718 ),
202
+ ( 0.056808478892590906, -0.10165685368899147 ),
203
+ ( 0.08045215149882884, -0.8062011016624717 ),
204
+ ( 0.056808478892590906, -0.5655123710317247 ),
205
+ ( 0.06930589080120417, 0.079019907465779 ),
206
+ ( 0.04019429812207957, -0.9619814414415885 ),
207
+ ( 0.08983559328581395, -0.5822087429342399 ) ]
208
+
209
+ if _np is None:
210
+ ekin,mu=[],[]
211
+ for i in range(30):
212
+ _ekin,_mu=nipc.sampleScatterIsotropic(wl2ekin(nipc_testwl))
213
+ mu += [_mu]
214
+ ekin += [_ekin]
215
+ else:
216
+ ekin,mu = nipc.sampleScatterIsotropic(wl2ekin(nipc_testwl),repeat=30)
217
+
218
+ for i in range(len(ekin)):
219
+ #print ( f' ( {ekin[i]}, {mu[i]} ),');continue
220
+ require_flteq(ekin[i],expected[i][0])
221
+ require_flteq(mu[i],expected[i][1])
222
+
223
+ expected = [ ( 0.056808478892590906, (0.07228896531453344, -0.5190173207165885, 0.8517014302500192) ),
224
+ ( 0.056808478892590906, (-0.9249112255344181, -0.32220112076758217, -0.20180600252850442) ),
225
+ ( 0.056808478892590906, (-0.15963879335683306, -0.8486615569734178, 0.5042707778277745) ),
226
+ ( 0.04922198429225973, (-0.9779858857774598, 0.14099376149839138, 0.1538322672218415) ),
227
+ ( 0.056808478892590906, (0.07228896531453344, 0.7905105193171594, -0.6081672667471253) ),
228
+ ( 0.056808478892590906, (-0.10165685368899147, -0.8869759070713323, -0.4504882066969593) ),
229
+ ( 0.056808478892590906, (0.07228896531453344, -0.39741541395284924, -0.914787021249449) ),
230
+ ( 0.056808478892590906, (-0.10165685368899147, -0.9768880366798581, -0.1880309758785167) ),
231
+ ( 0.02561081364848724, (-0.8847741369311427, -0.465745536939693, 0.015994418980024606) ),
232
+ ( 0.056808478892590906, (0.8260541809964751, 0.539797243436807, 0.16202909009269678) ),
233
+ ( 0.07443151255169597, (-0.6036845347910699, -0.06282202590029042, -0.7947442201839992) ),
234
+ ( 0.056808478892590906, (0.8260541809964751, 0.10854661864786977, 0.5530389874487663) ),
235
+ ( 0.056808478892590906, (0.5361444826572666, 0.7795115518549294, 0.3238994199452849) ),
236
+ ( 0.056808478892590906, (0.07228896531453344, 0.746175597107444, 0.6618128767069312) ),
237
+ ( 0.056808478892590906, (-0.10165685368899147, -0.4247181868490453, 0.8996001033001911) ),
238
+ ( 0.056808478892590906, (0.5361444826572666, 0.5555760611065321, -0.6355189486093415) ),
239
+ ( 0.05736877062456004, (-0.17262993734116835, -0.6849866797325108, 0.7078079918470932) ),
240
+ ( 0.056808478892590906, (0.3042167239859003, -0.8706122815482211, -0.3866347631352975) ),
241
+ ( 0.056808478892590906, (-0.7384733804796917, 0.6322144258925643, -0.23443972789660028) ),
242
+ ( 0.056808478892590906, (-0.15963879335683306, 0.21525619037302965, -0.9634211063505222) ),
243
+ ( 0.056808478892590906, (0.41359447569500096, 0.4927058865194684, 0.7656242675514158) ),
244
+ ( 0.056808478892590906, (0.25796367721315083, 0.48520231047621615, 0.8354839670198411) ),
245
+ ( 0.056808478892590906, (0.5785005938702705, 0.8104481067271115, -0.09225469740985966) ),
246
+ ( 0.04320250494907263, (-0.03036895176557113, -0.49547892839001373, 0.8680889115120317) ),
247
+ ( 0.054287027970592844, (-0.360243154961136, -0.9063878964988544, 0.22064870356299168) ),
248
+ ( 0.056808478892590906, (0.36219866365374176, -0.8822186430862216, 0.3008361577978114) ),
249
+ ( 0.056808478892590906, (0.7680722413286334, 0.5975216576265994, -0.23028873347945303) ),
250
+ ( 0.056808478892590906, (0.32922859149927786, -0.9426419619170849, 0.0550878042084668) ),
251
+ ( 0.056808478892590906, (-0.10165685368899147, -0.2489220191768986, -0.9631737706493833) ),
252
+ ( 0.0670453578395921, (-0.8979763975977056, 0.34669277926553477, 0.2710027788835134) ) ]
253
+
254
+ for i in range(30):
255
+ out_ekin,outdir = nipc.sampleScatter(wl2ekin(nipc_testwl),(1.0,0.0,0.0))
256
+ #print ( f' ( {out_ekin}, {outdir} ),');continue
257
+ require_flteq(out_ekin,expected[i][0])
258
+ require_flteq(outdir[0],expected[i][1][0])
259
+ require_flteq(outdir[1],expected[i][1][1])
260
+ require_flteq(outdir[2],expected[i][1][2])
261
+
262
+ _cfgstr4="""stdlib::Ge_sg227.ncmat;dcutoff=0.5;mos=40.0arcsec
263
+ ;dir1=@crys_hkl:5,1,1@lab:0,0,1
264
+ ;dir2=@crys_hkl:0,-1,1@lab:0,1,0""".replace(' ','').replace('\n','')
265
+ _seed4 = 3453455
266
+ prfct(f'Trying to createScatterIndependentRNG("{_cfgstr4}",{_seed4})')
267
+ gesc = NC.createScatterIndependentRNG(_cfgstr4,_seed4)
268
+ prfct('Verifying loaded Scatter object')
269
+ require_flteq(591.0263476502018,gesc.crossSection(wl2ekin(1.540),( 0., 1., 1. )))
270
+ require_flteq(1.667600586136298,gesc.crossSection(wl2ekin(1.540),( 1., 1., 0. )))
271
+ prfct('standard Python-API testing done')
272
+
273
+
274
+ class CallInspector:
275
+
276
+ @property
277
+ def name( self ):
278
+ return self.__name
279
+
280
+ @property
281
+ def the_real_inspected_object( self ):
282
+ return self.__realobj
283
+
284
+ def __init__( self, name, *, realobj = None, subfcts = None, fmtcall = None ):
285
+ self.__name = name
286
+ self.__realobj = realobj
287
+ self.__subfcts = {}
288
+ self.__fmtcall = fmtcall or _fmtcall
289
+ for sf in (subfcts or []):
290
+ if isinstance(sf,str):
291
+ self.__subfcts[sf] = None
292
+ else:
293
+ assert len(sf)==2 and isinstance(sf,tuple)
294
+ self.__subfcts[sf[0]] = sf[1]
295
+
296
+ def __call__( self, *a, **kwargs ):
297
+ return self.__getattrimpl('__call__')(*a,**kwargs)
298
+
299
+ def __getattr__( self, attrname ):
300
+ return self.__getattrimpl(attrname)
301
+
302
+ def __getattrimpl( self, attrname ):
303
+ if attrname not in self.__subfcts:
304
+ raise RuntimeError(f'Not allowed to access attribute {attrname} of {self.__name} objects.')
305
+ sf_kwargs = self.__subfcts[attrname] or {}
306
+ thefmtcall = sf_kwargs.get('fmtcall') or self.__fmtcall
307
+ def wrapper( *args, **kwargs ):
308
+ _n = f'{self.__name}.{attrname}' if attrname!='__call__' else self.__name
309
+ _nc_print("CALLING %s"%thefmtcall(_n,args,kwargs))
310
+ res = getattr(self.__realobj,attrname)(*args,**kwargs) if self.__realobj else None
311
+ return CallInspector( name = 'ResultOf[%s(..)]'%_n,
312
+ realobj = res, **sf_kwargs ) if sf_kwargs else None
313
+ return wrapper
314
+
315
+ _fmtvalue_default_ndigits = 3
316
+ def _fmtvalue( x, *, ndigits = _fmtvalue_default_ndigits ):
317
+ import numbers
318
+ if isinstance(x,numbers.Real) and not isinstance(x,numbers.Integral):
319
+ fmtstring = f'%.{ndigits}g'
320
+ s=fmtstring%x#only 3 to avoid too many spurious test issues due to FPE irrep.
321
+ return s+'.0' if ( s.isdigit() or s[0]=='-' and s[1:].isdigit() ) else s
322
+ return repr(x)
323
+
324
+ def _fmtcall(fctname,args=tuple(),kwargs={}):
325
+ import numbers
326
+ def _fmt(a):
327
+ if isinstance(a,numbers.Real):
328
+ return _fmtvalue(a)
329
+ def pruneaddr(s):
330
+ while ' object at 0x' in s:
331
+ _ = s.split(' object at 0x',1)
332
+ while _[1] and _[1][0].isalnum():
333
+ _[1] = _[1][1:]
334
+ s = ' object at SNIPADDR'.join(_)
335
+ return s
336
+ return 'Object[%s]'%a.name if isinstance(a,CallInspector) else pruneaddr(repr(a))
337
+ ll = [ _fmt(a) for a in args ]
338
+ ll += [ '%s=%s'%(k,_fmt(v)) for k,v in sorted(kwargs.items()) ]
339
+ a=','.join(ll)
340
+ return f'{fctname}({a})'
341
+
342
+ def _create_pyplot_inspector( pass_calls_to_real_plt ):
343
+ import copy
344
+ if pass_calls_to_real_plt:
345
+ import matplotlib.pyplot as realplt
346
+ else:
347
+ realplt = None
348
+ def shorten( x ):
349
+ if hasattr(x,'shape') and len(x.shape)==2:
350
+ return 'Array(shape=%s,content=%s)'%(x.shape,shorten(x.flatten()))
351
+ if isinstance(x,str) or not hasattr(x,'__len__'):
352
+ return x
353
+ #Fewer digits to guard against annoying test errors:
354
+ maxxval = max(x)
355
+ def _fmtthislistval(val):
356
+ if val==0.0:
357
+ return '0.0'
358
+ if abs(val)<abs(maxxval)*1e-13:
359
+ return 'TINYVAL'
360
+ #return _fmtvalue( val, ndigits = max(1,(_fmtvalue_default_ndigits-2)) )
361
+ elif abs(val)<abs(maxxval)*1e-8:
362
+ return 'SMALLVAL'
363
+ #return _fmtvalue( val, ndigits = max(1,(_fmtvalue_default_ndigits-1)) )
364
+ else:
365
+ return _fmtvalue( val )
366
+ if len(x) <= 10:
367
+ return list(_fmtthislistval(e) for e in x)
368
+ else:
369
+ return list(_fmtthislistval(e) for e in x[0:3])+['...']+list(_fmtthislistval(e) for e in x[-3:])
370
+ def _create_shortening_fmtcall( nargs_to_shorten, kwargs_to_shorten = None ):
371
+ def fmtcall_pltplot( name, args, kwargs ):
372
+ plot_args, plot_kwargs = args, kwargs
373
+ if nargs_to_shorten>0:
374
+ plot_args = [ shorten(e) for e in args[0:nargs_to_shorten] ] + list(args[nargs_to_shorten:])
375
+ if any( e in kwargs for e in (kwargs_to_shorten or [])):
376
+ plot_kwargs = copy.deepcopy(kwargs)
377
+ for xy in ('x','y'):
378
+ if xy in plot_kwargs:
379
+ plot_kwargs[xy] = shorten(plot_kwargs[xy])
380
+ return _fmtcall(name, plot_args,plot_kwargs)
381
+ return fmtcall_pltplot
382
+
383
+ return CallInspector( name = 'plt',
384
+ realobj = realplt,
385
+ subfcts = [ 'title','xlabel','ylabel','semilogx','semilogy',
386
+ 'legend','grid','show','figure',
387
+ 'savefig','ylim','xlim','colorbar',
388
+ 'tight_layout','close',
389
+ ( 'plot', dict( fmtcall=_create_shortening_fmtcall(2,('x','y')) ) ),
390
+ ( 'pcolormesh',dict( subfcts=['set_clim'],
391
+ fmtcall=_create_shortening_fmtcall(3) ) ),
392
+ ] )
393
+
394
+ def _create_pdfpages_inspector( real_pdfpages ):
395
+ return CallInspector( name = 'PdfPages',
396
+ realobj = real_pdfpages,
397
+ subfcts = [ ('__call__',dict(subfcts=['savefig','close'])) ] )
398
+
399
+ def _run_cmd( cmd, env = None ):
400
+ import sys
401
+ import subprocess
402
+ sys.stdout.flush()
403
+ sys.stderr.flush()
404
+ if env and sys.platform == 'darwin':
405
+ #osx's system protection #$%#^ is so damn annoying:
406
+ for name in ['DYLD_LIBRARY_PATH','LD_LIBRARY_PATH']:
407
+ v = env.get(name)
408
+ if v is not None:
409
+ from shlex import quote
410
+ #NB: "export A=B" is posix, not bash, so hopefully OK on all OSX
411
+ #shells:
412
+ cmd = 'export %s=%s && %s'%(name,quote(v),cmd)
413
+ try:
414
+ p = subprocess.Popen( cmd, shell=True, env=env,
415
+ stdout=subprocess.PIPE,
416
+ stderr=subprocess.PIPE )
417
+ output = p.communicate()[0]
418
+ ok = p.returncode==0
419
+ except OSError:
420
+ ok = False
421
+ sys.stdout.flush()
422
+ sys.stderr.flush()
423
+ return ok, output
424
+
425
+ def _test_cmdline_script_availablity( prfct ):
426
+ import subprocess
427
+ import shutil
428
+ from .cli import cli_tool_list, cli_tool_lookup
429
+ for t in cli_tool_list():
430
+ c = cli_tool_lookup(t)['shellcmd']
431
+ prfct(f"Testing availability of command: {c}")
432
+ if not shutil.which(c):
433
+ raise RuntimeError(f'Command {c} not found!')
434
+ ev = subprocess.run( [c,'--help'], capture_output = True )
435
+ if ev.returncode != 0:
436
+ raise RuntimeError(f'Command "{c} --help" did not run succesfully!')
437
+
438
+ def _actual_test_cmdline( verbose ):
439
+ from .cli import cli_tool_lookup
440
+ import shlex
441
+ prfct = _get_prfct( verbose )
442
+ prfct('starting testing of cmd-line utilities')
443
+ _test_cmdline_script_availablity( prfct )
444
+ cmds = ['ncrystal-config --help',
445
+ 'ncrystal-config -s',
446
+ 'nctool --version',
447
+ 'nctool --help',
448
+ 'nctool --test',
449
+ 'ncrystal_endf2ncmat --help',
450
+ 'ncrystal_hfg2ncmat --help',
451
+ 'ncrystal_ncmat2cpp --help',
452
+ 'ncrystal_ncmat2hkl --help',
453
+ 'ncrystal_cif2ncmat --help',
454
+ 'ncrystal_vdos2ncmat --help',
455
+ 'ncrystal_verifyatompos --help',
456
+ 'ncrystal-config --show cmakedir']
457
+ for cmd in cmds:
458
+ cmd = shlex.split(cmd)
459
+ cmd[0] = cli_tool_lookup( cmd[0] )['shellcmd']
460
+ cmd = shlex.join(cmd)
461
+ prfct('Trying to run:',cmd)
462
+ ok, output = _run_cmd(cmd)
463
+ if not ok:
464
+ raise RuntimeError('Command failed: %s'%cmd)
465
+ prfct('testing of cmd-line utilities done')
466
+
467
+ @_contextlib.contextmanager
468
+ def _work_in_tmpdir():
469
+ """Context manager for working in a temporary directory (automatically created+cleaned) and then switching back"""
470
+ import os
471
+ import tempfile
472
+ the_cwd = os.getcwd()
473
+ with tempfile.TemporaryDirectory() as tmpdir:
474
+ try:
475
+ os.chdir(tmpdir)
476
+ yield
477
+ finally:
478
+ os.chdir(the_cwd)#Important to leave tmpdir *before* deletion, to
479
+ #avoid PermissionError on Windows.
480
+
481
+ def _actual_test_cmake( verbose = False, ignore_if_absent = False ):
482
+ prfct = _get_prfct( verbose )
483
+ prfct('starting testing of compiled downstream cmake-based projects')
484
+ import shutil
485
+ cmakecmd = shutil.which('cmake')
486
+ if not cmakecmd:
487
+ if ignore_if_absent:
488
+ prfct('Skipping CMake test since "cmake" command not found.')
489
+ return
490
+ raise RuntimeError('cmake command not found')
491
+
492
+ prfct('Using cmake command: %s'%cmakecmd)
493
+
494
+
495
+ def _singleline2str( x ):
496
+ return ((x.decode() if hasattr(x,'decode') else x) or '').strip()
497
+
498
+ _cmd = 'ncrystal-config --show cmakedir'
499
+ ok, ncrystal_cmakedir = _run_cmd(_cmd)
500
+ if not ok:
501
+ raise RuntimeError('Command failed: %s'%_cmd)
502
+ ncrystal_cmakedir = _singleline2str(ncrystal_cmakedir)
503
+ prfct('ncrystal-config gives cmakedir: %s'%ncrystal_cmakedir)
504
+ _cmd = 'ncrystal-config --show libdir'
505
+ ok, ncrystal_libdir = _run_cmd(_cmd)
506
+ if not ok:
507
+ raise RuntimeError('Command failed: %s'%_cmd)
508
+ ncrystal_libdir = _singleline2str(ncrystal_libdir)
509
+ prfct('ncrystal-config gives libdir: %s'%ncrystal_libdir)
510
+
511
+ def prepend_to_path( envdict, pathvar, entry ):
512
+ from shlex import quote
513
+ entry = quote(((entry.decode() if hasattr(entry,'decode') else entry) or '').strip())
514
+ if not entry:
515
+ return
516
+ ll = [ entry ]
517
+ orig = envdict.get(pathvar,'')
518
+ if orig:
519
+ ll.append( orig )
520
+ envdict[pathvar] = ':'.join(ll)
521
+
522
+ import os
523
+ cmake_env = os.environ.copy()
524
+ runtime_env = os.environ.copy()
525
+ prfct('Testing with cmakedir added to CMAKE_PREFIX_PATH and libdir added to (DY)LD_LIBRARY_PATH')
526
+ prepend_to_path( cmake_env, 'CMAKE_PREFIX_PATH', ncrystal_cmakedir )
527
+ prepend_to_path( runtime_env, 'LD_LIBRARY_PATH', ncrystal_libdir )
528
+ prepend_to_path( runtime_env, 'DYLD_LIBRARY_PATH', ncrystal_libdir )
529
+ def runcmake(args,apply_CMAKE_ARGS=True):
530
+ cmake_args = cmake_env.get('CMAKE_ARGS','') if apply_CMAKE_ARGS else ''
531
+ cmd=f'{cmakecmd} {cmake_args} {args}'
532
+ ok, output = _run_cmd(cmd, env=cmake_env)
533
+ prfct(f'Launching: {cmd}')
534
+ if not ok:
535
+ raise RuntimeError(f'Test command failed: {cmd}')
536
+
537
+ downstream_cmake = """
538
+ cmake_minimum_required(VERSION 3.10...3.24)
539
+ project(MyExampleProject LANGUAGES CXX)
540
+ find_package(NCrystal REQUIRED)
541
+ file(WRITE "${PROJECT_BINARY_DIR}/cppsrc.cpp" "
542
+ #include \\"NCrystal/NCrystal.hh\\"
543
+ #include <iostream>
544
+ int main() {
545
+ auto pc = NCrystal::createScatter( \\"Al_sg225.ncmat;dcutoff=0.5;temp=25C\\" );
546
+ auto wl = NCrystal::NeutronWavelength{1.8};
547
+ auto xsect = pc.crossSectionIsotropic( wl );
548
+ std::cout << \\"Powder Al x-sect at \\" << wl << \\" is \\" << xsect << std::endl;
549
+ return 0;
550
+ }
551
+ ")
552
+ add_executable(exampleapp "${PROJECT_BINARY_DIR}/cppsrc.cpp")
553
+ target_link_libraries( exampleapp NCrystal::NCrystal )
554
+ install( TARGETS exampleapp DESTINATION bin )
555
+ """
556
+
557
+ import pathlib
558
+ with _work_in_tmpdir():
559
+ td = pathlib.Path('.').absolute()
560
+ ( td / 'src' ).mkdir()
561
+ ( td / 'src' / 'CMakeLists.txt' ).write_text(downstream_cmake)
562
+ ( td / 'bld' ).mkdir()
563
+ os.chdir( td / 'bld' )
564
+ runcmake('../src -DCMAKE_INSTALL_PREFIX=../install')
565
+ runcmake('--build . --target install --config Release',apply_CMAKE_ARGS=False)
566
+ os.chdir( td )
567
+ app = ( td / 'install' / 'bin' / 'exampleapp' )
568
+ if not app.exists():
569
+ raise RuntimeError('Could not produce example app in downstream cmake project')
570
+ prfct(f'Launching: {app}')
571
+ ok, output = _run_cmd(str(app),env=runtime_env)
572
+ if not ok:
573
+ raise RuntimeError('Could not launch example app compiled in downstream cmake project')
574
+ #envrun = os.environ.copy()
575
+ expected_output='Powder Al x-sect at 1.8Aa is 1.44816barn'
576
+ if not output.decode().strip()==expected_output:
577
+ raise RuntimeError('Example app compiled in downstream cmake project produces unexpected output')
578
+ prfct('App produced expected output')
579
+ prfct('testing of compiled downstream cmake-based projects done')