source: sasmodels/sasmodels/direct_model.py @ ba32cdd

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

support autogenerated Iqxy in C models

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