source: sasmodels/sasmodels/mixture.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 69aa451, checked in by Paul Kienzle <pkienzle@…>, 8 years ago

refactor parameter representation

  • Property mode set to 100644
File size: 5.0 KB
Line 
1"""
2Mixture model
3-------------
4
5The product model multiplies the structure factor by the form factor,
6modulated by the effective radius of the form.  The resulting model
7has a attributes of both the model description (with parameters, etc.)
8and the module evaluator (with call, release, etc.).
9
10To use it, first load form factor P and structure factor S, then create
11*ProductModel(P, S)*.
12"""
13from copy import copy
14import numpy as np
15
16from .modelinfo import Parameter, ParameterTable
17
18SCALE=0
19BACKGROUND=1
20EFFECT_RADIUS=2
21VOLFRACTION=3
22
23def make_mixture_info(parts):
24    """
25    Create info block for product model.
26    """
27    flatten = []
28    for part in parts:
29        if part['composition'] and part['composition'][0] == 'mixture':
30            flatten.extend(part['compostion'][1])
31        else:
32            flatten.append(part)
33    parts = flatten
34
35    # Build new parameter list
36    pars = []
37    for k, part in enumerate(parts):
38        # Parameter prefix per model, A_, B_, ...
39        # Note that prefix must also be applied to id and length_control
40        # to support vector parameters
41        prefix = chr(ord('A')+k) + '_'
42        pars.append(Parameter(prefix+'scale'))
43        for p in part['parameters'].kernel_pars:
44            p = copy(p)
45            p.name = prefix+p.name
46            p.id = prefix+p.id
47            if p.length_control is not None:
48                p.length_control = prefix+p.length_control
49            pars.append(p)
50    partable = ParameterTable(pars)
51
52    model_info = {}
53    model_info['id'] = '+'.join(part['id'])
54    model_info['name'] = ' + '.join(part['name'])
55    model_info['filename'] = None
56    model_info['title'] = 'Mixture model with ' + model_info['name']
57    model_info['description'] = model_info['title']
58    model_info['docs'] = model_info['title']
59    model_info['category'] = "custom"
60    model_info['parameters'] = partable
61    #model_info['single'] = any(part['single'] for part in parts)
62    model_info['structure_factor'] = False
63    model_info['variant_info'] = None
64    #model_info['tests'] = []
65    #model_info['source'] = []
66    # Iq, Iqxy, form_volume, ER, VR and sesans
67    # Remember the component info blocks so we can build the model
68    model_info['composition'] = ('mixture', parts)
69
70
71class MixtureModel(object):
72    def __init__(self, model_info, parts):
73        self.info = model_info
74        self.parts = parts
75
76    def __call__(self, q_vectors):
77        # Note: may be sending the q_vectors to the n times even though they
78        # are only needed once.  It would mess up modularity quite a bit to
79        # handle this optimally, especially since there are many cases where
80        # separate q vectors are needed (e.g., form in python and structure
81        # in opencl; or both in opencl, but one in single precision and the
82        # other in double precision).
83        kernels = [part(q_vectors) for part in self.parts]
84        return MixtureKernel(self.info, kernels)
85
86    def release(self):
87        """
88        Free resources associated with the model.
89        """
90        for part in self.parts:
91            part.release()
92
93
94class MixtureKernel(object):
95    def __init__(self, model_info, kernels):
96        dim = '2d' if kernels[0].q_input.is_2d else '1d'
97
98        # fixed offsets starts at 2 for scale and background
99        fixed_pars, pd_pars = [], []
100        offsets = [[2, 0]]
101        #vol_index = []
102        def accumulate(fixed, pd, volume):
103            # subtract 1 from fixed since we are removing background
104            fixed_offset, pd_offset = offsets[-1]
105            #vol_index.extend(k+pd_offset for k,v in pd if v in volume)
106            offsets.append([fixed_offset + len(fixed) - 1, pd_offset + len(pd)])
107            pd_pars.append(pd)
108        if dim == '2d':
109            for p in kernels:
110                partype = p.info['partype']
111                accumulate(partype['fixed-2d'], partype['pd-2d'], partype['volume'])
112        else:
113            for p in kernels:
114                partype = p.info['partype']
115                accumulate(partype['fixed-1d'], partype['pd-1d'], partype['volume'])
116
117        #self.vol_index = vol_index
118        self.offsets = offsets
119        self.fixed_pars = fixed_pars
120        self.pd_pars = pd_pars
121        self.info = model_info
122        self.kernels = kernels
123        self.results = None
124
125    def __call__(self, fixed_pars, pd_pars, cutoff=1e-5):
126        scale, background = fixed_pars[0:2]
127        total = 0.0
128        self.results = []  # remember the parts for plotting later
129        for k in range(len(self.offsets)-1):
130            start_fixed, start_pd = self.offsets[k]
131            end_fixed, end_pd = self.offsets[k+1]
132            part_fixed = [fixed_pars[start_fixed], 0.0] + fixed_pars[start_fixed+1:end_fixed]
133            part_pd = [pd_pars[start_pd], 0.0] + pd_pars[start_pd+1:end_pd]
134            part_result = self.kernels[k](part_fixed, part_pd)
135            total += part_result
136            self.results.append(scale*sum+background)
137
138        return scale*total + background
139
140    def release(self):
141        self.p_kernel.release()
142        self.q_kernel.release()
143
Note: See TracBrowser for help on using the repository browser.