source: sasmodels/sasmodels/direct_model.py @ 303d8d6

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

new calculator says hello before crashing

  • 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        if self.data_type == 'sesans':
69           
70            q = sesans.make_q(data.sample.zacceptance, data.Rmax)
71            index = slice(None, None)
72            res = None
73            if data.y is not None:
74                Iq, dIq = data.y, data.dy
75            else:
76                Iq, dIq = None, None
77            #self._theory = np.zeros_like(q)
78            q_vectors = [q]           
79            q_mono = sesans.make_all_q(data)
80        elif self.data_type == 'Iqxy':
81            partype = model.info['par_type']
82            if not partype['orientation'] and not partype['magnetic']:
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                                        width=data.dxh[index],
118                                        height=data.dxw[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        else:
126            raise ValueError("Unknown data type") # never gets here
127
128        # Remember function inputs so we can delay loading the function and
129        # so we can save/restore state
130        self._kernel_inputs = q_vectors
131        self._kernel_mono_inputs = q_mono
132        self._kernel = None
133        self.Iq, self.dIq, self.index = Iq, dIq, index
134        self.resolution = res
135
136    def _set_data(self, Iq, noise=None):
137        # pylint: disable=attribute-defined-outside-init
138        if noise is not None:
139            self.dIq = Iq*noise*0.01
140        dy = self.dIq
141        y = Iq + np.random.randn(*dy.shape) * dy
142        self.Iq = y
143        if self.data_type == 'Iq':
144            self._data.dy[self.index] = dy
145            self._data.y[self.index] = y
146        elif self.data_type == 'Iqxy':
147            self._data.data[self.index] = y
148        elif self.data_type == 'sesans':
149            self._data.y[self.index] = y
150        else:
151            raise ValueError("Unknown model")
152
153    def _calc_theory(self, pars, cutoff=0.0):
154        if self._kernel is None:
155            self._kernel = make_kernel(self._model, self._kernel_inputs)  # pylint: disable=attribute-dedata_type
156            self._kernel_mono = make_kernel(self._model, self._kernel_mono_inputs) if self._kernel_mono_inputs else None
157
158        Iq_calc = call_kernel(self._kernel, pars, cutoff=cutoff)
159        Iq_mono = call_kernel(self._kernel_mono, pars, mono=True) if self._kernel_mono_inputs else None
160        if self.data_type == 'sesans':
161            result = sesans.transform(self._data,
162                                   self._kernel_inputs[0], Iq_calc, 
163                                   self._kernel_mono_inputs, Iq_mono)
164        else:
165            result = self.resolution.apply(Iq_calc)
166        return result       
167
168
169class DirectModel(DataMixin):
170    """
171    Create a calculator object for a model.
172
173    *data* is 1D SAS, 2D SAS or SESANS data
174
175    *model* is a model calculator return from :func:`generate.load_model`
176
177    *cutoff* is the polydispersity weight cutoff.
178    """
179    def __init__(self, data, model, cutoff=1e-5):
180        self.model = model
181        self.cutoff = cutoff
182        # Note: _interpret_data defines the model attributes
183        self._interpret_data(data, model)
184
185    def __call__(self, **pars):
186        return self._calc_theory(pars, cutoff=self.cutoff)
187
188    def ER_VR(self, **pars):
189        """
190        Compute the equivalent radius and volume ratio for the model.
191        """
192        return call_ER_VR(self.model.info, pars)
193
194    def simulate_data(self, noise=None, **pars):
195        """
196        Generate simulated data for the model.
197        """
198        Iq = self.__call__(**pars)
199        self._set_data(Iq, noise=noise)
200
201def main():
202    """
203    Program to evaluate a particular model at a set of q values.
204    """
205    import sys
206    from .data import empty_data1D, empty_data2D
207    from .core import load_model_info, build_model
208
209    if len(sys.argv) < 3:
210        print("usage: python -m sasmodels.direct_model modelname (q|qx,qy) par=val ...")
211        sys.exit(1)
212    model_name = sys.argv[1]
213    call = sys.argv[2].upper()
214    if call != "ER_VR":
215        try:
216            values = [float(v) for v in call.split(',')]
217        except:
218            values = []
219        if len(values) == 1:
220            q, = values
221            data = empty_data1D([q])
222        elif len(values) == 2:
223            qx, qy = values
224            data = empty_data2D([qx], [qy])
225        else:
226            print("use q or qx,qy or ER or VR")
227            sys.exit(1)
228    else:
229        data = empty_data1D([0.001])  # Data not used in ER/VR
230
231    model_info = load_model_info(model_name)
232    model = build_model(model_info)
233    calculator = DirectModel(data, model)
234    pars = dict((k, float(v))
235                for pair in sys.argv[3:]
236                for k, v in [pair.split('=')])
237    if call == "ER_VR":
238        print(calculator.ER_VR(**pars))
239    else:
240        Iq = calculator(**pars)
241        print(Iq[0])
242
243if __name__ == "__main__":
244    main()
Note: See TracBrowser for help on using the repository browser.