source: sasmodels/sasmodels/direct_model.py @ 02e70ff

core_shell_microgelscostrafo411magnetic_modelrelease_v0.94release_v0.95ticket-1257-vesicle-productticket_1156ticket_1265_superballticket_822_more_unit_tests
Last change on this file since 02e70ff was 02e70ff, checked in by jhbakker, 8 years ago

Beginnings of handling acceptance angle in sesans

  • Property mode set to 100644
File size: 9.2 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 make_kernel
28from .core import call_kernel, call_ER_VR
29from . import sesans
30from . import resolution
31from . import resolution2d
32
33class DataMixin(object):
34    """
35    DataMixin captures the common aspects of evaluating a SAS model for a
36    particular data set, including calculating Iq and evaluating the
37    resolution function.  It is used in particular by :class:`DirectModel`,
38    which evaluates a SAS model parameters as key word arguments to the
39    calculator method, and by :class:`bumps_model.Experiment`, which wraps the
40    model and data for use with the Bumps fitting engine.  It is not
41    currently used by :class:`sasview_model.SasviewModel` since this will
42    require a number of changes to SasView before we can do it.
43
44    :meth:`_interpret_data` initializes the data structures necessary
45    to manage the calculations.  This sets attributes in the child class
46    such as *data_type* and *resolution*.
47
48    :meth:`_calc_theory` evaluates the model at the given control values.
49
50    :meth:`_set_data` sets the intensity data in the data object,
51    possibly with random noise added.  This is useful for simulating a
52    dataset with the results from :meth:`_calc_theory`.
53    """
54    def _interpret_data(self, data, model):
55        # pylint: disable=attribute-defined-outside-init
56
57        self._data = data
58        self._model = model
59
60        # interpret data
61        if hasattr(data, 'lam'):
62            self.data_type = 'sesans'
63        elif hasattr(data, 'qx_data'):
64            self.data_type = 'Iqxy'
65        else:
66            self.data_type = 'Iq'
67
68        partype = model.info['partype']
69
70        if self.data_type == 'sesans':
71           
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                                        width=data.dxh[index],
119                                        height=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        else:
127            raise ValueError("Unknown data type") # never gets here
128
129        # Remember function inputs so we can delay loading the function and
130        # so we can save/restore state
131        self._kernel_inputs = q_vectors
132        self._kernel_mono_inputs = q_mono
133        self._kernel = None
134        self.Iq, self.dIq, self.index = Iq, dIq, index
135        self.resolution = res
136
137    def _set_data(self, Iq, noise=None):
138        # pylint: disable=attribute-defined-outside-init
139        if noise is not None:
140            self.dIq = Iq*noise*0.01
141        dy = self.dIq
142        y = Iq + np.random.randn(*dy.shape) * dy
143        self.Iq = y
144        if self.data_type == 'Iq':
145            self._data.dy[self.index] = dy
146            self._data.y[self.index] = y
147        elif self.data_type == 'Iqxy':
148            self._data.data[self.index] = y
149        elif self.data_type == 'sesans':
150            self._data.y[self.index] = y
151        else:
152            raise ValueError("Unknown model")
153
154    def _calc_theory(self, pars, cutoff=0.0):
155        if self._kernel is None:
156            self._kernel = make_kernel(self._model, self._kernel_inputs)  # pylint: disable=attribute-dedata_type
157            self._kernel_mono = make_kernel(self._model, self._kernel_mono_inputs) if self._kernel_mono_inputs else None
158
159        Iq_calc = call_kernel(self._kernel, pars, cutoff=cutoff)
160        Iq_mono = call_kernel(self._kernel_mono, pars, mono=True) if self._kernel_mono_inputs else None
161        if self.data_type == 'sesans':
162            result = sesans.transform(self._data,
163                                   self._kernel_inputs[0], Iq_calc, 
164                                   self._kernel_mono_inputs, Iq_mono)
165        else:
166            result = self.resolution.apply(Iq_calc)
167        return result       
168
169
170class DirectModel(DataMixin):
171    """
172    Create a calculator object for a model.
173
174    *data* is 1D SAS, 2D SAS or SESANS data
175
176    *model* is a model calculator return from :func:`generate.load_model`
177
178    *cutoff* is the polydispersity weight cutoff.
179    """
180    def __init__(self, data, model, cutoff=1e-5):
181        self.model = model
182        self.cutoff = cutoff
183        # Note: _interpret_data defines the model attributes
184        self._interpret_data(data, model)
185
186    def __call__(self, **pars):
187        return self._calc_theory(pars, cutoff=self.cutoff)
188
189    def ER_VR(self, **pars):
190        """
191        Compute the equivalent radius and volume ratio for the model.
192        """
193        return call_ER_VR(self.model.info, pars)
194
195    def simulate_data(self, noise=None, **pars):
196        """
197        Generate simulated data for the model.
198        """
199        Iq = self.__call__(**pars)
200        self._set_data(Iq, noise=noise)
201
202def main():
203    """
204    Program to evaluate a particular model at a set of q values.
205    """
206    import sys
207    from .data import empty_data1D, empty_data2D
208    from .core import load_model_info, build_model
209
210    if len(sys.argv) < 3:
211        print("usage: python -m sasmodels.direct_model modelname (q|qx,qy) par=val ...")
212        sys.exit(1)
213    model_name = sys.argv[1]
214    call = sys.argv[2].upper()
215    if call != "ER_VR":
216        try:
217            values = [float(v) for v in call.split(',')]
218        except:
219            values = []
220        if len(values) == 1:
221            q, = values
222            data = empty_data1D([q])
223        elif len(values) == 2:
224            qx, qy = values
225            data = empty_data2D([qx], [qy])
226        else:
227            print("use q or qx,qy or ER or VR")
228            sys.exit(1)
229    else:
230        data = empty_data1D([0.001])  # Data not used in ER/VR
231
232    model_info = load_model_info(model_name)
233    model = build_model(model_info)
234    calculator = DirectModel(data, model)
235    pars = dict((k, float(v))
236                for pair in sys.argv[3:]
237                for k, v in [pair.split('=')])
238    if call == "ER_VR":
239        print(calculator.ER_VR(**pars))
240    else:
241        Iq = calculator(**pars)
242        print(Iq[0])
243
244if __name__ == "__main__":
245    main()
Note: See TracBrowser for help on using the repository browser.