source: sasmodels/sasmodels/convert.py @ bd49c79

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

improve handling of multiplicity in sasview comparison

  • Property mode set to 100644
File size: 12.0 KB
Line 
1"""
2Convert models to and from sasview.
3"""
4from __future__ import print_function
5
6from os.path import join as joinpath, abspath, dirname
7import math
8import warnings
9
10from .conversion_table import CONVERSION_TABLE
11
12# List of models which SasView versions don't contain the explicit 'scale' argument.
13# When converting such a model, please update this list.
14MODELS_WITHOUT_SCALE = [
15    'teubner_strey',
16    'broad_peak',
17    'two_lorentzian',
18    "two_power_law",
19    'gel_fit',
20    'gauss_lorentz_gel',
21    'be_polyelectrolyte',
22    'correlation_length',
23    'fractal_core_shell',
24    'binary_hard_sphere',
25    'raspberry'
26]
27
28# List of models which SasView versions don't contain the explicit 'background' argument.
29# When converting such a model, please update this list.
30MODELS_WITHOUT_BACKGROUND = [
31    'guinier',
32]
33
34MODELS_WITHOUT_VOLFRACTION = [
35    'fractal',
36    'vesicle',
37    'multilayer_vesicle',
38]
39
40MAGNETIC_SASVIEW_MODELS = [
41    'core_shell',
42    'core_multi_shell',
43    'cylinder',
44    'parallelepiped',
45    'sphere',
46]
47
48
49# Convert new style names for polydispersity info to old style names
50PD_DOT = [
51    ("", ""),
52    ("_pd", ".width"),
53    ("_pd_n", ".npts"),
54    ("_pd_nsigma", ".nsigmas"),
55    ("_pd_type", ".type"),
56    ]
57
58def _convert_pars(pars, mapping):
59    """
60    Rename the parameters and any associated polydispersity attributes.
61    """
62    newpars = pars.copy()
63    for new, old in mapping.items():
64        if old == new: continue
65        for pd, dot in PD_DOT:
66            if old+dot in newpars:
67                if new is not None:
68                    newpars[new+pd] = pars[old+dot]
69                del newpars[old+dot]
70    return newpars
71
72def convert_model(name, pars):
73    """
74    Convert model from old style parameter names to new style.
75    """
76    _, _ = name, pars # lint
77    raise NotImplementedError
78    # need to load all new models in order to determine old=>new
79    # model name mapping
80
81def _unscale(par, scale):
82    return [pk*scale for pk in par] if isinstance(par, list) else par*scale
83
84def _is_sld(modelinfo, id):
85    if id.startswith('M0:'):
86        return True
87    if (id.endswith('_pd') or id.endswith('_pd_n') or id.endswith('_pd_nsigma')
88            or id.endswith('_pd_width') or id.endswith('_pd_type')):
89        return False
90    for p in modelinfo.parameters.call_parameters:
91        if p.id == id:
92            return p.type == 'sld'
93    # check through kernel parameters in case it is a named as a vector
94    for p in modelinfo.parameters.kernel_parameters:
95        if p.id == id:
96            return p.type == 'sld'
97    raise ValueError("unknown parameter %r in conversion"%id)
98
99def _unscale_sld(modelinfo, pars):
100    """
101    rescale all sld parameters in the new model definition by 1e6 so the
102    numbers are nicer.  Relies on the fact that all sld parameters in the
103    new model definition end with sld.
104    """
105    return dict((id, (_unscale(v,1e-6) if _is_sld(modelinfo, id) else v))
106                for id, v in pars.items())
107
108def _remove_pd(pars, key, name):
109    """
110    Remove polydispersity from the parameter list.
111
112    Note: operates in place
113    """
114    # Bumps style parameter names
115    pd = pars.pop(key+".width", 0.0)
116    pd_n = pars.pop(key+".npts", 0)
117    if pd != 0.0 and pd_n != 0:
118        warnings.warn("parameter %s not polydisperse in sasview %s"%(key, name))
119    pars.pop(key+".nsigmas", None)
120    pars.pop(key+".type", None)
121    return pars
122
123def _revert_pars(pars, mapping):
124    """
125    Rename the parameters and any associated polydispersity attributes.
126    """
127    newpars = pars.copy()
128
129    for new, old in mapping.items():
130        for pd, dot in PD_DOT:
131            if old and old+pd == new+dot:
132                continue
133            if new+pd in newpars:
134                if old is not None:
135                    newpars[old+dot] = pars[new+pd]
136                del newpars[new+pd]
137    for k in list(newpars.keys()):
138        for pd, dot in PD_DOT[1:]:  # skip "" => ""
139            if k.endswith(pd):
140                newpars[k[:-len(pd)]+dot] = newpars[k]
141                del newpars[k]
142    return newpars
143
144def revert_name(model_info):
145    oldname, oldpars = CONVERSION_TABLE.get(model_info.id, [None, {}])
146    return oldname
147
148def _get_translation_table(model_info):
149    _, translation = CONVERSION_TABLE.get(model_info.id, [None, {}])
150    translation = translation.copy()
151    for p in model_info.parameters.kernel_parameters:
152        if p.length > 1:
153            newid = p.id
154            oldid = translation.get(p.id, p.id)
155            translation.pop(newid, None)
156            for k in range(1, p.length+1):
157                if newid+str(k) not in translation:
158                    translation[newid+str(k)] = oldid+str(k)
159    # Remove control parameter from the result
160    if model_info.control:
161        translation[model_info.control] = "CONTROL"
162    return translation
163
164def _trim_vectors(model_info, pars, oldpars):
165    _, translation = CONVERSION_TABLE.get(model_info.id, [None, {}])
166    for p in model_info.parameters.kernel_parameters:
167        if p.length_control is not None:
168            n = int(pars[p.length_control])
169            oldname = translation.get(p.id, p.id)
170            for k in range(n+1, p.length+1):
171                for _, old in PD_DOT:
172                    oldpars.pop(oldname+str(k)+old, None)
173    return oldpars
174
175def revert_pars(model_info, pars):
176    """
177    Convert model from new style parameter names to old style.
178    """
179    if model_info.composition is not None:
180        composition_type, parts = model_info.composition
181        if composition_type == 'product':
182            P, S = parts
183            oldpars = {'scale':'scale_factor'}
184            oldpars.update(_get_old_pars(P))
185            oldpars.update(_get_old_pars(S))
186        else:
187            raise NotImplementedError("cannot convert to sasview sum")
188    else:
189        translation = _get_translation_table(model_info)
190        oldpars = _revert_pars(_unscale_sld(model_info, pars), translation)
191        oldpars = _trim_vectors(model_info, pars, oldpars)
192
193    # Make sure the control parameter is an integer
194    if "CONTROL" in oldpars:
195        oldpars["CONTROL"] = int(oldpars["CONTROL"])
196
197    # Note: update compare.constrain_pars to match
198    name = model_info.id
199    if name in MODELS_WITHOUT_SCALE or model_info.structure_factor:
200        if oldpars.pop('scale', 1.0) != 1.0:
201            warnings.warn("parameter scale not used in sasview %s"%name)
202    if name in MODELS_WITHOUT_BACKGROUND or model_info.structure_factor:
203        if oldpars.pop('background', 0.0) != 0.0:
204            warnings.warn("parameter background not used in sasview %s"%name)
205
206    # Remove magnetic parameters from non-magnetic sasview models
207    if name not in MAGNETIC_SASVIEW_MODELS:
208        oldpars = dict((k,v) for k,v in oldpars.items() if ':' not in k)
209
210    # If it is a product model P*S, then check the individual forms for special
211    # cases.  Note: despite the structure factor alone not having scale or
212    # background, the product model does, so this is below the test for
213    # models without scale or background.
214    namelist = name.split('*') if '*' in name else [name]
215    for name in namelist:
216        if name in MODELS_WITHOUT_VOLFRACTION:
217            del oldpars['volfraction']
218        if name == 'stacked_disks':
219            _remove_pd(oldpars, 'n_stacking', name)
220        elif name == 'pearl_necklace':
221            _remove_pd(oldpars, 'num_pearls', name)
222            _remove_pd(oldpars, 'thick_string', name)
223        elif name == 'core_shell_parallelepiped':
224            _remove_pd(oldpars, 'rimA', name)
225            _remove_pd(oldpars, 'rimB', name)
226            _remove_pd(oldpars, 'rimC', name)
227        elif name == 'polymer_micelle':
228            if 'ndensity' in oldpars:
229                oldpars['ndensity'] *= 1e15
230        elif name == 'spherical_sld':
231            for k in range(1, int(pars['n_shells'])+1):
232                _remove_pd(oldpars, 'thick_flat'+str(k), 'thick_flat')
233                _remove_pd(oldpars, 'thick_inter'+str(k), 'thick_inter')
234        elif name == 'core_multi_shell':
235            # kill extra shells
236            for k in range(5, 11):
237                oldpars.pop('sld_shell'+str(k), 0)
238                oldpars.pop('thick_shell'+str(k), 0)
239                oldpars.pop('mtheta:sld'+str(k), 0)
240                oldpars.pop('mphi:sld'+str(k), 0)
241                oldpars.pop('M0:sld'+str(k), 0)
242                _remove_pd(oldpars, 'sld_shell'+str(k), 'sld')
243                _remove_pd(oldpars, 'thick_shell'+str(k), 'thickness')
244        elif name == 'core_shell_parallelepiped':
245            _remove_pd(oldpars, 'rimA', name)
246        elif name in ['mono_gauss_coil','poly_gauss_coil']:
247            del oldpars['i_zero']
248        elif name == 'onion':
249            oldpars.pop('n_shells', None)
250        elif name == 'rpa':
251            # convert scattering lengths from femtometers to centimeters
252            for p in "L1", "L2", "L3", "L4":
253                if p in oldpars: oldpars[p] *= 1e-13
254            if pars['case_num'] < 2:
255                for k in ("a", "b"):
256                    for p in ("L", "N", "Phi", "b", "v"):
257                        oldpars.pop(p+k, None)
258                for k in "Kab,Kac,Kad,Kbc,Kbd".split(','):
259                    oldpars.pop(k, None)
260            elif pars['case_num'] < 5:
261                for k in ("a",):
262                    for p in ("L", "N", "Phi", "b", "v"):
263                        oldpars.pop(p+k, None)
264                for k in "Kab,Kac,Kad".split(','):
265                    oldpars.pop(k, None)
266
267    return oldpars
268
269def constrain_new_to_old(model_info, pars):
270    """
271    Restrict parameter values to those that will match sasview.
272    """
273    name = model_info.id
274    # Note: update convert.revert_model to match
275    if name in MODELS_WITHOUT_SCALE or model_info.structure_factor:
276        pars['scale'] = 1
277    if name in MODELS_WITHOUT_BACKGROUND or model_info.structure_factor:
278        pars['background'] = 0
279    # sasview multiplies background by structure factor
280    if '*' in name:
281        pars['background'] = 0
282
283    # Shut off magnetism when comparing non-magnetic sasview models
284    if name not in MAGNETIC_SASVIEW_MODELS:
285        suppress_magnetism = False
286        for key in pars.keys():
287            if key.startswith("M0:"):
288                suppress_magnetism = suppress_magnetism or (pars[key] != 0)
289                pars[key] = 0
290        if suppress_magnetism:
291            warnings.warn("suppressing magnetism for comparison with sasview")
292
293    # Shut off theta polydispersity since algorithm has changed
294    if 'theta_pd_n' in pars:
295        if pars['theta_pd_n'] != 0:
296            warnings.warn("suppressing theta polydispersity for comparison with sasview")
297        pars['theta_pd_n'] = 0
298
299    # If it is a product model P*S, then check the individual forms for special
300    # cases.  Note: despite the structure factor alone not having scale or
301    # background, the product model does, so this is below the test for
302    # models without scale or background.
303    namelist = name.split('*') if '*' in name else [name]
304    for name in namelist:
305        if name in MODELS_WITHOUT_VOLFRACTION:
306            pars['volfraction'] = 1
307        if name == 'pearl_necklace':
308            pars['string_thickness_pd_n'] = 0
309            pars['number_of_pearls_pd_n'] = 0
310        elif name == 'line':
311            pars['scale'] = 1
312            pars['background'] = 0
313        elif name == 'rpa':
314            pars['case_num'] = int(pars['case_num'])
315        elif name == 'mono_gauss_coil':
316            pars['i_zero'] = 1
317        elif name == 'poly_gauss_coil':
318            pars['i_zero'] = 1
319        elif name == 'core_multi_shell':
320            pars['n'] = min(math.ceil(pars['n']), 4)
321        elif name == 'onion':
322            pars['n_shells'] = math.ceil(pars['n_shells'])
323        elif name == 'spherical_sld':
324            pars['n_shells'] = math.ceil(pars['n_shells'])
325            pars['npts_inter'] = math.ceil(pars['npts_inter'])
326            pars['func_inter0'] = math.trunc(pars['func_inter0']+0.5)
327            for k in range(1, 11):
328                pars['func_inter%d'%k] = math.trunc(pars['func_inter%d'%k]+0.5)
329                pars['thick_flat%d_pd_n'%k] = 0
330                pars['thick_inter%d_pd_n'%k] = 0
331
Note: See TracBrowser for help on using the repository browser.