source: sasmodels/sasmodels/direct_model.py @ 4d8e0bb

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

fix parameter order for slit smearing

  • Property mode set to 100644
File size: 10.4 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        partype = model.info['partype']
70
71        if self.data_type == 'sesans':
72            q = sesans.make_q(data.sample.zacceptance, data.Rmax)
73            index = slice(None, None)
74            res = None
75            if data.y is not None:
76                Iq, dIq = data.y, data.dy
77            else:
78                Iq, dIq = None, None
79            #self._theory = np.zeros_like(q)
80            q_vectors = [q]           
81            q_mono = sesans.make_all_q(data)
82        elif self.data_type == 'Iqxy':
83            if not partype['orientation'] and not partype['magnetic']:
84                raise ValueError("not 2D without orientation or magnetic parameters")
85            q = np.sqrt(data.qx_data**2 + data.qy_data**2)
86            qmin = getattr(data, 'qmin', 1e-16)
87            qmax = getattr(data, 'qmax', np.inf)
88            accuracy = getattr(data, 'accuracy', 'Low')
89            index = ~data.mask & (q >= qmin) & (q <= qmax)
90            if data.data is not None:
91                index &= ~np.isnan(data.data)
92                Iq = data.data[index]
93                dIq = data.err_data[index]
94            else:
95                Iq, dIq = None, None
96            res = resolution2d.Pinhole2D(data=data, index=index,
97                                         nsigma=3.0, accuracy=accuracy)
98            #self._theory = np.zeros_like(self.Iq)
99            q_vectors = res.q_calc
100            q_mono = []
101        elif self.data_type == 'Iq':
102            index = (data.x >= data.qmin) & (data.x <= data.qmax)
103            if data.y is not None:
104                index &= ~np.isnan(data.y)
105                Iq = data.y[index]
106                dIq = data.dy[index]
107            else:
108                Iq, dIq = None, None
109            if getattr(data, 'dx', None) is not None:
110                q, dq = data.x[index], data.dx[index]
111                if (dq > 0).any():
112                    res = resolution.Pinhole1D(q, dq)
113                else:
114                    res = resolution.Perfect1D(q)
115            elif (getattr(data, 'dxl', None) is not None
116                  and getattr(data, 'dxw', None) is not None):
117                res = resolution.Slit1D(data.x[index],
118                                        qx_width=data.dxl[index],
119                                        qy_width=data.dxw[index])
120            else:
121                res = resolution.Perfect1D(data.x[index])
122
123            #self._theory = np.zeros_like(self.Iq)
124            q_vectors = [res.q_calc]
125            q_mono = []
126        elif self.data_type == 'Iq-oriented':
127            index = (data.x >= data.qmin) & (data.x <= data.qmax)
128            if data.y is not None:
129                index &= ~np.isnan(data.y)
130                Iq = data.y[index]
131                dIq = data.dy[index]
132            else:
133                Iq, dIq = None, None
134            if (getattr(data, 'dxl', None) is None
135                or getattr(data, 'dxw', None) is None):
136                raise ValueError("oriented sample with 1D data needs slit resolution")
137
138            res = resolution2d.Slit2D(data.x[index],
139                                      qx_width=data.dxw[index],
140                                      qy_width=data.dxl[index])
141            q_vectors = res.q_calc
142            q_mono = []
143        else:
144            raise ValueError("Unknown data type") # never gets here
145
146        # Remember function inputs so we can delay loading the function and
147        # so we can save/restore state
148        self._kernel_inputs = q_vectors
149        self._kernel_mono_inputs = q_mono
150        self._kernel = None
151        self.Iq, self.dIq, self.index = Iq, dIq, index
152        self.resolution = res
153
154    def _set_data(self, Iq, noise=None):
155        # pylint: disable=attribute-defined-outside-init
156        if noise is not None:
157            self.dIq = Iq*noise*0.01
158        dy = self.dIq
159        y = Iq + np.random.randn(*dy.shape) * dy
160        self.Iq = y
161        if self.data_type in ('Iq', 'Iq-oriented'):
162            self._data.dy[self.index] = dy
163            self._data.y[self.index] = y
164        elif self.data_type == 'Iqxy':
165            self._data.data[self.index] = y
166        elif self.data_type == 'sesans':
167            self._data.y[self.index] = y
168        else:
169            raise ValueError("Unknown model")
170
171    def _calc_theory(self, pars, cutoff=0.0):
172        if self._kernel is None:
173            self._kernel = self._model.make_kernel(self._kernel_inputs)  # pylint: disable=attribute-dedata_type
174            self._kernel_mono = (self._model.make_kernel(self._kernel_mono_inputs)
175                                 if self._kernel_mono_inputs else None)
176
177        Iq_calc = call_kernel(self._kernel, pars, cutoff=cutoff)
178        # TODO: may want to plot the raw Iq for other than oriented usans
179        self.Iq_calc = None
180        if self.data_type == 'sesans':
181            Iq_mono = (call_kernel(self._kernel_mono, pars, mono=True)
182                       if self._kernel_mono_inputs else None)
183            result = sesans.transform(self._data,
184                                   self._kernel_inputs[0], Iq_calc, 
185                                   self._kernel_mono_inputs, Iq_mono)
186        else:
187            result = self.resolution.apply(Iq_calc)
188            if hasattr(self.resolution, 'nx'):
189                self.Iq_calc = (
190                    self.resolution.qx_calc, self.resolution.qy_calc,
191                    np.reshape(Iq_calc, (self.resolution.ny, self.resolution.nx))
192                )
193        return result       
194
195
196class DirectModel(DataMixin):
197    """
198    Create a calculator object for a model.
199
200    *data* is 1D SAS, 2D SAS or SESANS data
201
202    *model* is a model calculator return from :func:`generate.load_model`
203
204    *cutoff* is the polydispersity weight cutoff.
205    """
206    def __init__(self, data, model, cutoff=1e-5):
207        self.model = model
208        self.cutoff = cutoff
209        # Note: _interpret_data defines the model attributes
210        self._interpret_data(data, model)
211
212    def __call__(self, **pars):
213        return self._calc_theory(pars, cutoff=self.cutoff)
214
215    def ER_VR(self, **pars):
216        """
217        Compute the equivalent radius and volume ratio for the model.
218        """
219        return call_ER_VR(self.model.info, pars)
220
221    def simulate_data(self, noise=None, **pars):
222        """
223        Generate simulated data for the model.
224        """
225        Iq = self.__call__(**pars)
226        self._set_data(Iq, noise=noise)
227
228def main():
229    """
230    Program to evaluate a particular model at a set of q values.
231    """
232    import sys
233    from .data import empty_data1D, empty_data2D
234    from .core import load_model_info, build_model
235
236    if len(sys.argv) < 3:
237        print("usage: python -m sasmodels.direct_model modelname (q|qx,qy) par=val ...")
238        sys.exit(1)
239    model_name = sys.argv[1]
240    call = sys.argv[2].upper()
241    if call != "ER_VR":
242        try:
243            values = [float(v) for v in call.split(',')]
244        except:
245            values = []
246        if len(values) == 1:
247            q, = values
248            data = empty_data1D([q])
249        elif len(values) == 2:
250            qx, qy = values
251            data = empty_data2D([qx], [qy])
252        else:
253            print("use q or qx,qy or ER or VR")
254            sys.exit(1)
255    else:
256        data = empty_data1D([0.001])  # Data not used in ER/VR
257
258    model_info = load_model_info(model_name)
259    model = build_model(model_info)
260    calculator = DirectModel(data, model)
261    pars = dict((k, float(v))
262                for pair in sys.argv[3:]
263                for k, v in [pair.split('=')])
264    if call == "ER_VR":
265        print(calculator.ER_VR(**pars))
266    else:
267        Iq = calculator(**pars)
268        print(Iq[0])
269
270if __name__ == "__main__":
271    main()
Note: See TracBrowser for help on using the repository browser.