source: sasmodels/sasmodels/product.py @ ea05c87

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

Merge remote-tracking branch 'origin/master' into polydisp

Conflicts:

sasmodels/models/spherical_sld.py

  • Property mode set to 100644
File size: 6.5 KB
Line 
1"""
2Product 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"""
13import numpy as np
14
15from .core import call_ER_VR
16
17SCALE=0
18BACKGROUND=1
19RADIUS_EFFECTIVE=2
20VOLFRACTION=3
21
22# TODO: core_shell_sphere model has suppressed the volume ratio calculation
23# revert it after making VR and ER available at run time as constraints.
24def make_product_info(p_info, s_info):
25    """
26    Create info block for product model.
27    """
28    p_id, p_name, p_pars = p_info['id'], p_info['name'], p_info['parameters']
29    s_id, s_name, s_pars = s_info['id'], s_info['name'], s_info['parameters']
30    # We require models to start with scale and background
31    assert s_pars[SCALE].name == 'scale'
32    assert s_pars[BACKGROUND].name == 'background'
33    # We require structure factors to start with effect radius and volfraction
34    assert s_pars[RADIUS_EFFECTIVE].name == 'radius_effective'
35    assert s_pars[VOLFRACTION].name == 'volfraction'
36    # Combine the parameter sets.  We are skipping the first three
37    # parameters of S since scale, background are defined in P and
38    # effect_radius is set from P.ER().
39    pars = p_pars + s_pars[3:]
40    # check for duplicates; can't use assertion since they may never be checked
41    if len(set(p.name for p in pars)) != len(pars):
42        raise ValueError("Duplicate parameters in %s and %s"%(p_id))
43    model_info = {}
44    model_info['id'] = '*'.join((p_id, s_id))
45    model_info['name'] = ' X '.join((p_name, s_name))
46    model_info['filename'] = None
47    model_info['title'] = 'Product of %s and structure factor %s'%(p_name, s_name)
48    model_info['description'] = model_info['title']
49    model_info['docs'] = model_info['title']
50    model_info['category'] = "custom"
51    model_info['parameters'] = pars
52    #model_info['single'] = p_info['single'] and s_info['single']
53    model_info['structure_factor'] = False
54    model_info['variant_info'] = None
55    #model_info['tests'] = []
56    #model_info['source'] = []
57    # Iq, Iqxy, form_volume, ER, VR and sesans
58    model_info['composition'] = ('product', [p_info, s_info])
59    return model_info
60
61class ProductModel(object):
62    def __init__(self, model_info, P, S):
63        self.info = model_info
64        self.P = P
65        self.S = S
66
67    def __call__(self, q_vectors):
68        # Note: may be sending the q_vectors to the GPU twice even though they
69        # are only needed once.  It would mess up modularity quite a bit to
70        # handle this optimally, especially since there are many cases where
71        # separate q vectors are needed (e.g., form in python and structure
72        # in opencl; or both in opencl, but one in single precision and the
73        # other in double precision).
74        p_kernel = self.P(q_vectors)
75        s_kernel = self.S(q_vectors)
76        return ProductKernel(self.info, p_kernel, s_kernel)
77
78    def release(self):
79        """
80        Free resources associated with the model.
81        """
82        self.P.release()
83        self.S.release()
84
85
86class ProductKernel(object):
87    def __init__(self, model_info, p_kernel, s_kernel):
88        dim = '2d' if p_kernel.q_input.is_2d else '1d'
89
90        # Need to know if we want 2D and magnetic parameters when constructing
91        # a parameter map.
92        par_map = {}
93        p_info = p_kernel.info['par_type']
94        s_info = s_kernel.info['par_type']
95        vol_pars = set(p_info['volume'])
96        if dim == '2d':
97            num_p_fixed = len(p_info['fixed-2d'])
98            num_p_pd = len(p_info['pd-2d'])
99            num_s_fixed = len(s_info['fixed-2d'])
100            num_s_pd = len(s_info['pd-2d']) - 1 # exclude effect_radius
101            # volume parameters are amongst the pd pars for P, not S
102            vol_par_idx = [k for k,v in enumerate(p_info['pd-2d'])
103                           if v in vol_pars]
104        else:
105            num_p_fixed = len(p_info['fixed-1d'])
106            num_p_pd = len(p_info['pd-1d'])
107            num_s_fixed = len(s_info['fixed-1d'])
108            num_s_pd = len(s_info['pd-1d']) - 1  # exclude effect_radius
109            # volume parameters are amongst the pd pars for P, not S
110            vol_par_idx = [k for k,v in enumerate(p_info['pd-1d'])
111                           if v in vol_pars]
112
113        start = 0
114        par_map['p_fixed'] = np.arange(start, start+num_p_fixed)
115        # User doesn't set scale, background or effect_radius for S in P*S,
116        # so borrow values from end of p_fixed.  This makes volfraction the
117        # first S parameter.
118        start += num_p_fixed
119        par_map['s_fixed'] = np.hstack(([start,start],
120                                        np.arange(start, start+num_s_fixed-2)))
121        par_map['volfraction'] = num_p_fixed
122        start += num_s_fixed-2
123        # vol pars offset from the start of pd pars
124        par_map['vol_pars'] = [start+k for k in vol_par_idx]
125        par_map['p_pd'] = np.arange(start, start+num_p_pd)
126        start += num_p_pd-1
127        par_map['s_pd'] = np.hstack((start,
128                                     np.arange(start, start+num_s_pd-1)))
129
130        self.fixed_pars = model_info['partype']['fixed-' + dim]
131        self.pd_pars = model_info['partype']['pd-' + dim]
132        self.info = model_info
133        self.p_kernel = p_kernel
134        self.s_kernel = s_kernel
135        self.par_map = par_map
136
137    def __call__(self, fixed_pars, pd_pars, cutoff=1e-5):
138        pars = fixed_pars + pd_pars
139        scale = pars[SCALE]
140        background = pars[BACKGROUND]
141        s_volfraction = pars[self.par_map['volfraction']]
142        p_fixed = [pars[k] for k in self.par_map['p_fixed']]
143        s_fixed = [pars[k] for k in self.par_map['s_fixed']]
144        p_pd = [pars[k] for k in self.par_map['p_pd']]
145        s_pd = [pars[k] for k in self.par_map['s_pd']]
146        vol_pars = [pars[k] for k in self.par_map['vol_pars']]
147
148        effect_radius, vol_ratio = call_ER_VR(self.p_kernel.info, vol_pars)
149
150        p_fixed[SCALE] = s_volfraction
151        p_fixed[BACKGROUND] = 0.0
152        s_fixed[SCALE] = scale
153        s_fixed[BACKGROUND] = 0.0
154        s_fixed[2] = s_volfraction/vol_ratio
155        s_pd[0] = [effect_radius], [1.0]
156
157        p_res = self.p_kernel(p_fixed, p_pd, cutoff)
158        s_res = self.s_kernel(s_fixed, s_pd, cutoff)
159        #print s_fixed, s_pd, p_fixed, p_pd
160
161        return p_res*s_res + background
162
163    def release(self):
164        self.p_kernel.release()
165        self.q_kernel.release()
166
Note: See TracBrowser for help on using the repository browser.