source: sasmodels/sasmodels/model_test.py @ d2bb604

core_shell_microgelscostrafo411magnetic_modelrelease_v0.94release_v0.95ticket-1257-vesicle-productticket_1156ticket_1265_superballticket_822_more_unit_tests
Last change on this file since d2bb604 was d2bb604, checked in by Paul Kienzle <pkienzle@…>, 8 years ago

fix models so all dll tests pass

  • Property mode set to 100644
File size: 9.9 KB
Line 
1# -*- coding: utf-8 -*-
2"""
3Run model unit tests.
4
5Usage::
6
7    python -m sasmodels.model_test [opencl|dll|opencl_and_dll] model1 model2 ...
8
9    if model1 is 'all', then all except the remaining models will be tested
10
11Each model is tested using the default parameters at q=0.1, (qx, qy)=(0.1, 0.1),
12and the ER and VR are computed.  The return values at these points are not
13considered.  The test is only to verify that the models run to completion,
14and do not produce inf or NaN.
15
16Tests are defined with the *tests* attribute in the model.py file.  *tests*
17is a list of individual tests to run, where each test consists of the
18parameter values for the test, the q-values and the expected results.  For
19the effective radius test, the q-value should be 'ER'.  For the VR test,
20the q-value should be 'VR'.  For 1-D tests, either specify the q value or
21a list of q-values, and the corresponding I(q) value, or list of I(q) values.
22
23That is::
24
25    tests = [
26        [ {parameters}, q, I(q)],
27        [ {parameters}, [q], [I(q)] ],
28        [ {parameters}, [q1, q2, ...], [I(q1), I(q2), ...]],
29
30        [ {parameters}, (qx, qy), I(qx, Iqy)],
31        [ {parameters}, [(qx1, qy1), (qx2, qy2), ...],
32                        [I(qx1, qy1), I(qx2, qy2), ...]],
33
34        [ {parameters}, 'ER', ER(pars) ],
35        [ {parameters}, 'VR', VR(pars) ],
36        ...
37    ]
38
39Parameters are *key:value* pairs, where key is one of the parameters of the
40model and value is the value to use for the test.  Any parameters not given
41in the parameter list will take on the default parameter value.
42
43Precision defaults to 5 digits (relative).
44"""
45from __future__ import print_function
46
47import sys
48import unittest
49
50import numpy as np
51
52from .core import list_models, load_model_info, build_model, HAVE_OPENCL
53from .core import call_kernel, call_ER, call_VR
54from .exception import annotate_exception
55
56#TODO: rename to tests so that tab completion works better for models directory
57
58def make_suite(loaders, models):
59    """
60    Construct the pyunit test suite.
61
62    *loaders* is the list of kernel drivers to use, which is one of
63    *["dll", "opencl"]*, *["dll"]* or *["opencl"]*.  For python models,
64    the python driver is always used.
65
66    *models* is the list of models to test, or *["all"]* to test all models.
67    """
68
69    ModelTestCase = _hide_model_case_from_nosetests()
70    suite = unittest.TestSuite()
71
72    if models[0] == 'all':
73        skip = models[1:]
74        models = list_models()
75    else:
76        skip = []
77    for model_name in models:
78        if model_name in skip: continue
79        model_info = load_model_info(model_name)
80
81        #print('------')
82        #print('found tests in', model_name)
83        #print('------')
84
85        # if ispy then use the dll loader to call pykernel
86        # don't try to call cl kernel since it will not be
87        # available in some environmentes.
88        is_py = callable(model_info['Iq'])
89
90        if is_py:  # kernel implemented in python
91            continue # TODO: re-enable python tests
92            test_name = "Model: %s, Kernel: python"%model_name
93            test_method_name = "test_%s_python" % model_name
94            test = ModelTestCase(test_name, model_info,
95                                 test_method_name,
96                                 platform="dll",  # so that
97                                 dtype="double")
98            suite.addTest(test)
99        else:   # kernel implemented in C
100            # test using opencl if desired and available
101            if 'opencl' in loaders and HAVE_OPENCL:
102                test_name = "Model: %s, Kernel: OpenCL"%model_name
103                test_method_name = "test_%s_opencl" % model_name
104                # Using dtype=None so that the models that are only
105                # correct for double precision are not tested using
106                # single precision.  The choice is determined by the
107                # presence of *single=False* in the model file.
108                test = ModelTestCase(test_name, model_info,
109                                     test_method_name,
110                                     platform="ocl", dtype=None)
111                #print("defining", test_name)
112                suite.addTest(test)
113
114            # test using dll if desired
115            if 'dll' in loaders:
116                test_name = "Model: %s, Kernel: dll"%model_name
117                test_method_name = "test_%s_dll" % model_name
118                test = ModelTestCase(test_name, model_info,
119                                     test_method_name,
120                                     platform="dll",
121                                     dtype="double")
122                suite.addTest(test)
123
124    return suite
125
126
127def _hide_model_case_from_nosetests():
128    class ModelTestCase(unittest.TestCase):
129        """
130        Test suit for a particular model with a particular kernel driver.
131
132        The test suite runs a simple smoke test to make sure the model
133        functions, then runs the list of tests at the bottom of the model
134        description file.
135        """
136        def __init__(self, test_name, model_info, test_method_name,
137                     platform, dtype):
138            self.test_name = test_name
139            self.info = model_info
140            self.platform = platform
141            self.dtype = dtype
142
143            setattr(self, test_method_name, self._runTest)
144            unittest.TestCase.__init__(self, test_method_name)
145
146        def _runTest(self):
147            smoke_tests = [
148                [{}, 0.1, None],
149                [{}, (0.1, 0.1), None],
150                [{}, 'ER', None],
151                [{}, 'VR', None],
152                ]
153
154            tests = self.info['tests']
155            try:
156                model = build_model(self.info, dtype=self.dtype,
157                                    platform=self.platform)
158                for test in smoke_tests + tests:
159                    self._run_one_test(model, test)
160
161                if not tests and self.platform == "dll":
162                    ## Uncomment the following to make forgetting the test
163                    ## values an error.  Only do so for the "dll" tests
164                    ## to reduce noise from both opencl and dll, and because
165                    ## python kernels use platform="dll".
166                    #raise Exception("No test cases provided")
167                    pass
168
169            except:
170                annotate_exception(self.test_name)
171                raise
172
173        def _run_one_test(self, model, test):
174            pars, x, y = test
175
176            if not isinstance(y, list):
177                y = [y]
178            if not isinstance(x, list):
179                x = [x]
180
181            self.assertEqual(len(y), len(x))
182
183            if x[0] == 'ER':
184                actual = [call_ER(model.info, pars)]
185            elif x[0] == 'VR':
186                actual = [call_VR(model.info, pars)]
187            elif isinstance(x[0], tuple):
188                Qx, Qy = zip(*x)
189                q_vectors = [np.array(Qx), np.array(Qy)]
190                kernel = model.make_kernel(q_vectors)
191                actual = call_kernel(kernel, pars)
192            else:
193                q_vectors = [np.array(x)]
194                kernel = model.make_kernel(q_vectors)
195                actual = call_kernel(kernel, pars)
196
197            self.assertGreater(len(actual), 0)
198            self.assertEqual(len(y), len(actual))
199
200            for xi, yi, actual_yi in zip(x, y, actual):
201                if yi is None:
202                    # smoke test --- make sure it runs and produces a value
203                    self.assertTrue(np.isfinite(actual_yi),
204                                    'invalid f(%s): %s' % (xi, actual_yi))
205                else:
206                    self.assertTrue(is_near(yi, actual_yi, 5),
207                                    'f(%s); expected:%s; actual:%s'
208                                    % (xi, yi, actual_yi))
209
210    return ModelTestCase
211
212def is_near(target, actual, digits=5):
213    """
214    Returns true if *actual* is within *digits* significant digits of *target*.
215    """
216    import math
217    shift = 10**math.ceil(math.log10(abs(target)))
218    return abs(target-actual)/shift < 1.5*10**-digits
219
220def main():
221    """
222    Run tests given is sys.argv.
223
224    Returns 0 if success or 1 if any tests fail.
225    """
226    import xmlrunner
227
228    models = sys.argv[1:]
229    if models and models[0] == '-v':
230        verbosity = 2
231        models = models[1:]
232    else:
233        verbosity = 1
234    if models and models[0] == 'opencl':
235        if not HAVE_OPENCL:
236            print("opencl is not available")
237            return 1
238        loaders = ['opencl']
239        models = models[1:]
240    elif models and models[0] == 'dll':
241        # TODO: test if compiler is available?
242        loaders = ['dll']
243        models = models[1:]
244    elif models and models[0] == 'opencl_and_dll':
245        loaders = ['opencl', 'dll']
246        models = models[1:]
247    else:
248        loaders = ['opencl', 'dll']
249    if not models:
250        print("""\
251usage:
252  python -m sasmodels.model_test [-v] [opencl|dll] model1 model2 ...
253
254If -v is included on the command line, then use verboe output.
255
256If neither opencl nor dll is specified, then models will be tested with
257both opencl and dll; the compute target is ignored for pure python models.
258
259If model1 is 'all', then all except the remaining models will be tested.
260
261""")
262
263        return 1
264
265    #runner = unittest.TextTestRunner()
266    runner = xmlrunner.XMLTestRunner(output='logs', verbosity=verbosity)
267    result = runner.run(make_suite(loaders, models))
268    return 1 if result.failures or result.errors else 0
269
270
271def model_tests():
272    """
273    Test runner visible to nosetests.
274
275    Run "nosetests sasmodels" on the command line to invoke it.
276    """
277    tests = make_suite(['opencl', 'dll'], ['all'])
278    for test_i in tests:
279        yield test_i._runTest
280
281
282if __name__ == "__main__":
283    sys.exit(main())
Note: See TracBrowser for help on using the repository browser.