source: sasmodels/sasmodels/direct_model.py @ 48fbd50

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

twiddle with kernel interface

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