source: sasmodels/sasmodels/convert.py @ d119f34

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

onion: now matches sasview

  • Property mode set to 100644
File size: 11.6 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] = None
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
194    # Note: update compare.constrain_pars to match
195    name = model_info.id
196    if name in MODELS_WITHOUT_SCALE or model_info.structure_factor:
197        if oldpars.pop('scale', 1.0) != 1.0:
198            warnings.warn("parameter scale not used in sasview %s"%name)
199    if name in MODELS_WITHOUT_BACKGROUND or model_info.structure_factor:
200        if oldpars.pop('background', 0.0) != 0.0:
201            warnings.warn("parameter background not used in sasview %s"%name)
202
203    # Remove magnetic parameters from non-magnetic sasview models
204    if name not in MAGNETIC_SASVIEW_MODELS:
205        oldpars = dict((k,v) for k,v in oldpars.items() if ':' not in k)
206
207    # If it is a product model P*S, then check the individual forms for special
208    # cases.  Note: despite the structure factor alone not having scale or
209    # background, the product model does, so this is below the test for
210    # models without scale or background.
211    namelist = name.split('*') if '*' in name else [name]
212    for name in namelist:
213        if name in MODELS_WITHOUT_VOLFRACTION:
214            del oldpars['volfraction']
215        if name == 'stacked_disks':
216            _remove_pd(oldpars, 'n_stacking', name)
217        elif name == 'pearl_necklace':
218            _remove_pd(oldpars, 'num_pearls', name)
219            _remove_pd(oldpars, 'thick_string', name)
220        elif name == 'core_shell_parallelepiped':
221            _remove_pd(oldpars, 'rimA', name)
222            _remove_pd(oldpars, 'rimB', name)
223            _remove_pd(oldpars, 'rimC', name)
224        elif name == 'polymer_micelle':
225            if 'ndensity' in oldpars:
226                oldpars['ndensity'] *= 1e15
227        elif name == 'spherical_sld':
228            for k in range(1, int(pars['n_shells'])+1):
229                _remove_pd(oldpars, 'thick_flat'+str(k), 'thick_flat')
230                _remove_pd(oldpars, 'thick_inter'+str(k), 'thick_inter')
231        elif name == 'core_multi_shell':
232            # kill extra shells
233            for k in range(5, 11):
234                oldpars.pop('sld_shell'+str(k), 0)
235                oldpars.pop('thick_shell'+str(k), 0)
236                oldpars.pop('mtheta:sld'+str(k), 0)
237                oldpars.pop('mphi:sld'+str(k), 0)
238                oldpars.pop('M0:sld'+str(k), 0)
239                _remove_pd(oldpars, 'sld_shell'+str(k), 'sld')
240                _remove_pd(oldpars, 'thick_shell'+str(k), 'thickness')
241        elif name == 'core_shell_parallelepiped':
242            _remove_pd(oldpars, 'rimA', name)
243        elif name in ['mono_gauss_coil','poly_gauss_coil']:
244            del oldpars['i_zero']
245        elif name == 'onion':
246            oldpars.pop('n_shells', None)
247        elif name == 'rpa':
248            # convert scattering lengths from femtometers to centimeters
249            for p in "L1", "L2", "L3", "L4":
250                if p in oldpars: oldpars[p] *= 1e-13
251            if pars['case_num'] < 2:
252                for k in ("a", "b"):
253                    for p in ("L", "N", "Phi", "b", "v"):
254                        oldpars.pop(p+k, None)
255                for k in "Kab,Kac,Kad,Kbc,Kbd".split(','):
256                    oldpars.pop(k, None)
257            elif pars['case_num'] < 5:
258                for k in ("a",):
259                    for p in ("L", "N", "Phi", "b", "v"):
260                        oldpars.pop(p+k, None)
261                for k in "Kab,Kac,Kad".split(','):
262                    oldpars.pop(k, None)
263
264    return oldpars
265
266def constrain_new_to_old(model_info, pars):
267    """
268    Restrict parameter values to those that will match sasview.
269    """
270    name = model_info.id
271    # Note: update convert.revert_model to match
272    if name in MODELS_WITHOUT_SCALE or model_info.structure_factor:
273        pars['scale'] = 1
274    if name in MODELS_WITHOUT_BACKGROUND or model_info.structure_factor:
275        pars['background'] = 0
276    # sasview multiplies background by structure factor
277    if '*' in name:
278        pars['background'] = 0
279
280    # Shut off magnetism when comparing non-magnetic sasview models
281    if name not in MAGNETIC_SASVIEW_MODELS:
282        suppress_magnetism = False
283        for key in pars.keys():
284            if key.startswith("M0:"):
285                suppress_magnetism = suppress_magnetism or (pars[key] != 0)
286                pars[key] = 0
287        if suppress_magnetism:
288            warnings.warn("suppressing magnetism for comparison with sasview")
289
290    # Shut off theta polydispersity since algorithm has changed
291    if 'theta_pd_n' in pars:
292        if pars['theta_pd_n'] != 0:
293            warnings.warn("suppressing theta polydispersity for comparison with sasview")
294        pars['theta_pd_n'] = 0
295
296    # If it is a product model P*S, then check the individual forms for special
297    # cases.  Note: despite the structure factor alone not having scale or
298    # background, the product model does, so this is below the test for
299    # models without scale or background.
300    namelist = name.split('*') if '*' in name else [name]
301    for name in namelist:
302        if name in MODELS_WITHOUT_VOLFRACTION:
303            pars['volfraction'] = 1
304        if name == 'pearl_necklace':
305            pars['string_thickness_pd_n'] = 0
306            pars['number_of_pearls_pd_n'] = 0
307        elif name == 'line':
308            pars['scale'] = 1
309            pars['background'] = 0
310        elif name == 'rpa':
311            pars['case_num'] = int(pars['case_num'])
312        elif name == 'mono_gauss_coil':
313            pars['i_zero'] = 1
314        elif name == 'poly_gauss_coil':
315            pars['i_zero'] = 1
316        elif name == 'core_multi_shell':
317            pars['n'] = min(math.ceil(pars['n']), 4)
318        elif name == 'onion':
319            pars['n_shells'] = math.ceil(pars['n_shells'])
320        elif name == 'spherical_sld':
321            for k in range(1, 11):
322                pars['thick_flat%d_pd_n'%k] = 0
323                pars['thick_inter%d_pd_n'%k] = 0
324
Note: See TracBrowser for help on using the repository browser.