source: sasmodels/sasmodels/direct_model.py @ ee8f734

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

only use bare except when annotating an exception

  • Property mode set to 100644
File size: 10.3 KB
Line 
1"""
2Class interface to the model calculator.
3
4Calling a model is somewhat non-trivial since the functions called depend
5on the data type.  For 1D data the *Iq* kernel needs to be called, for
62D data the *Iqxy* kernel needs to be called, and for SESANS data the
7*Iq* kernel needs to be called followed by a Hankel transform.  Before
8the kernel is called an appropriate *q* calculation vector needs to be
9constructed.  This is not the simple *q* vector where you have measured
10the data since the resolution calculation will require values beyond the
11range of the measured data.  After the calculation the resolution calculator
12must be called to return the predicted value for each measured data point.
13
14:class:`DirectModel` is a callable object that takes *parameter=value*
15keyword arguments and returns the appropriate theory values for the data.
16
17:class:`DataMixin` does the real work of interpreting the data and calling
18the model calculator.  This is used by :class:`DirectModel`, which uses
19direct parameter values and by :class:`bumps_model.Experiment` which wraps
20the parameter values in boxes so that the user can set fitting ranges, etc.
21on the individual parameters and send the model to the Bumps optimizers.
22"""
23from __future__ import print_function
24
25import numpy as np
26
27from .core import call_kernel, call_ER_VR
28from . import sesans
29from . import resolution
30from . import resolution2d
31
32class DataMixin(object):
33    """
34    DataMixin captures the common aspects of evaluating a SAS model for a
35    particular data set, including calculating Iq and evaluating the
36    resolution function.  It is used in particular by :class:`DirectModel`,
37    which evaluates a SAS model parameters as key word arguments to the
38    calculator method, and by :class:`bumps_model.Experiment`, which wraps the
39    model and data for use with the Bumps fitting engine.  It is not
40    currently used by :class:`sasview_model.SasviewModel` since this will
41    require a number of changes to SasView before we can do it.
42
43    :meth:`_interpret_data` initializes the data structures necessary
44    to manage the calculations.  This sets attributes in the child class
45    such as *data_type* and *resolution*.
46
47    :meth:`_calc_theory` evaluates the model at the given control values.
48
49    :meth:`_set_data` sets the intensity data in the data object,
50    possibly with random noise added.  This is useful for simulating a
51    dataset with the results from :meth:`_calc_theory`.
52    """
53    def _interpret_data(self, data, model):
54        # pylint: disable=attribute-defined-outside-init
55
56        self._data = data
57        self._model = model
58
59        # interpret data
60        if hasattr(data, 'lam'):
61            self.data_type = 'sesans'
62        elif hasattr(data, 'qx_data'):
63            self.data_type = 'Iqxy'
64        elif getattr(data, 'oriented', False):
65            self.data_type = 'Iq-oriented'
66        else:
67            self.data_type = 'Iq'
68
69        if self.data_type == 'sesans':
70           
71            q = sesans.make_q(data.sample.zacceptance, data.Rmax)
72            index = slice(None, None)
73            res = None
74            if data.y is not None:
75                Iq, dIq = data.y, data.dy
76            else:
77                Iq, dIq = None, None
78            #self._theory = np.zeros_like(q)
79            q_vectors = [q]           
80            q_mono = sesans.make_all_q(data)
81        elif self.data_type == 'Iqxy':
82            #if not model.info['parameters'].has_2d:
83            #    raise ValueError("not 2D without orientation or magnetic parameters")
84            q = np.sqrt(data.qx_data**2 + data.qy_data**2)
85            qmin = getattr(data, 'qmin', 1e-16)
86            qmax = getattr(data, 'qmax', np.inf)
87            accuracy = getattr(data, 'accuracy', 'Low')
88            index = ~data.mask & (q >= qmin) & (q <= qmax)
89            if data.data is not None:
90                index &= ~np.isnan(data.data)
91                Iq = data.data[index]
92                dIq = data.err_data[index]
93            else:
94                Iq, dIq = None, None
95            res = resolution2d.Pinhole2D(data=data, index=index,
96                                         nsigma=3.0, accuracy=accuracy)
97            #self._theory = np.zeros_like(self.Iq)
98            q_vectors = res.q_calc
99            q_mono = []
100        elif self.data_type == 'Iq':
101            index = (data.x >= data.qmin) & (data.x <= data.qmax)
102            if data.y is not None:
103                index &= ~np.isnan(data.y)
104                Iq = data.y[index]
105                dIq = data.dy[index]
106            else:
107                Iq, dIq = None, None
108            if getattr(data, 'dx', None) is not None:
109                q, dq = data.x[index], data.dx[index]
110                if (dq > 0).any():
111                    res = resolution.Pinhole1D(q, dq)
112                else:
113                    res = resolution.Perfect1D(q)
114            elif (getattr(data, 'dxl', None) is not None
115                  and getattr(data, 'dxw', None) is not None):
116                res = resolution.Slit1D(data.x[index],
117                                        qx_width=data.dxw[index],
118                                        qy_width=data.dxl[index])
119            else:
120                res = resolution.Perfect1D(data.x[index])
121
122            #self._theory = np.zeros_like(self.Iq)
123            q_vectors = [res.q_calc]
124            q_mono = []
125        elif self.data_type == 'Iq-oriented':
126            index = (data.x >= data.qmin) & (data.x <= data.qmax)
127            if data.y is not None:
128                index &= ~np.isnan(data.y)
129                Iq = data.y[index]
130                dIq = data.dy[index]
131            else:
132                Iq, dIq = None, None
133            if (getattr(data, 'dxl', None) is None
134                or getattr(data, 'dxw', None) is None):
135                raise ValueError("oriented sample with 1D data needs slit resolution")
136
137            res = resolution2d.Slit2D(data.x[index],
138                                      qx_width=data.dxw[index],
139                                      qy_width=data.dxl[index])
140            q_vectors = res.q_calc
141            q_mono = []
142        else:
143            raise ValueError("Unknown data type") # never gets here
144
145        # Remember function inputs so we can delay loading the function and
146        # so we can save/restore state
147        self._kernel_inputs = q_vectors
148        self._kernel_mono_inputs = q_mono
149        self._kernel = None
150        self.Iq, self.dIq, self.index = Iq, dIq, index
151        self.resolution = res
152
153    def _set_data(self, Iq, noise=None):
154        # pylint: disable=attribute-defined-outside-init
155        if noise is not None:
156            self.dIq = Iq*noise*0.01
157        dy = self.dIq
158        y = Iq + np.random.randn(*dy.shape) * dy
159        self.Iq = y
160        if self.data_type in ('Iq', 'Iq-oriented'):
161            self._data.dy[self.index] = dy
162            self._data.y[self.index] = y
163        elif self.data_type == 'Iqxy':
164            self._data.data[self.index] = y
165        elif self.data_type == 'sesans':
166            self._data.y[self.index] = y
167        else:
168            raise ValueError("Unknown model")
169
170    def _calc_theory(self, pars, cutoff=0.0):
171        if self._kernel is None:
172            self._kernel = self._model.make_kernel(self._kernel_inputs)
173            self._kernel_mono = (self._model.make_kernel(self._kernel_mono_inputs)
174                                 if self._kernel_mono_inputs else None)
175
176        Iq_calc = call_kernel(self._kernel, pars, cutoff=cutoff)
177        # TODO: may want to plot the raw Iq for other than oriented usans
178        self.Iq_calc = None
179        if self.data_type == 'sesans':
180            Iq_mono = (call_kernel(self._kernel_mono, pars, mono=True)
181                       if self._kernel_mono_inputs else None)
182            result = sesans.transform(self._data,
183                                   self._kernel_inputs[0], Iq_calc, 
184                                   self._kernel_mono_inputs, Iq_mono)
185        else:
186            result = self.resolution.apply(Iq_calc)
187            if hasattr(self.resolution, 'nx'):
188                self.Iq_calc = (
189                    self.resolution.qx_calc, self.resolution.qy_calc,
190                    np.reshape(Iq_calc, (self.resolution.ny, self.resolution.nx))
191                )
192        return result       
193
194
195class DirectModel(DataMixin):
196    """
197    Create a calculator object for a model.
198
199    *data* is 1D SAS, 2D SAS or SESANS data
200
201    *model* is a model calculator return from :func:`generate.load_model`
202
203    *cutoff* is the polydispersity weight cutoff.
204    """
205    def __init__(self, data, model, cutoff=1e-5):
206        self.model = model
207        self.cutoff = cutoff
208        # Note: _interpret_data defines the model attributes
209        self._interpret_data(data, model)
210
211    def __call__(self, **pars):
212        return self._calc_theory(pars, cutoff=self.cutoff)
213
214    def ER_VR(self, **pars):
215        """
216        Compute the equivalent radius and volume ratio for the model.
217        """
218        return call_ER_VR(self.model.info, pars)
219
220    def simulate_data(self, noise=None, **pars):
221        """
222        Generate simulated data for the model.
223        """
224        Iq = self.__call__(**pars)
225        self._set_data(Iq, noise=noise)
226
227def main():
228    """
229    Program to evaluate a particular model at a set of q values.
230    """
231    import sys
232    from .data import empty_data1D, empty_data2D
233    from .core import load_model_info, build_model
234
235    if len(sys.argv) < 3:
236        print("usage: python -m sasmodels.direct_model modelname (q|qx,qy) par=val ...")
237        sys.exit(1)
238    model_name = sys.argv[1]
239    call = sys.argv[2].upper()
240    if call != "ER_VR":
241        try:
242            values = [float(v) for v in call.split(',')]
243        except Exception:
244            values = []
245        if len(values) == 1:
246            q, = values
247            data = empty_data1D([q])
248        elif len(values) == 2:
249            qx, qy = values
250            data = empty_data2D([qx], [qy])
251        else:
252            print("use q or qx,qy or ER or VR")
253            sys.exit(1)
254    else:
255        data = empty_data1D([0.001])  # Data not used in ER/VR
256
257    model_info = load_model_info(model_name)
258    model = build_model(model_info)
259    calculator = DirectModel(data, model)
260    pars = dict((k, float(v))
261                for pair in sys.argv[3:]
262                for k, v in [pair.split('=')])
263    if call == "ER_VR":
264        print(calculator.ER_VR(**pars))
265    else:
266        Iq = calculator(**pars)
267        print(Iq[0])
268
269if __name__ == "__main__":
270    main()
Note: See TracBrowser for help on using the repository browser.