- Categories
- Cylinder
- nanodisc_simple
- nanodisc_simple.py
nanodisc_simple - nanodisc_simple.py
r"""
Definition
----------
This model provides the form factor for a circular cylinder with a
core-shell scattering length density profile. Thus this is a variation
of a core-shell cylinder or disc where the shell on the walls and ends
may be of different thicknesses and scattering length densities. The form
factor is normalized by the particle volume.
.. _core-shell-bicelle-geometry:
.. figure:: img/core_shell_bicelle_geometry.png
(Graphic from DOI: 10.1039/C0NP00002G, note however that the model here
calculates for rectangular, not curved, rims.)
The output of the 1D scattering intensity function for randomly oriented
cylinders is then given by the equation above.
The *theta* and *phi* parameters are not used for the 1D output.
Our implementation of the scattering kernel and the 1D scattering intensity
use the c-library from NIST.
.. figure:: img/cylinder_angle_definition.jpg
Definition of the angles for the oriented core shell bicelle tmodel.
.. figure:: img/cylinder_angle_projection.jpg
Examples of the angles for oriented pp against the detector plane.
References
----------
L A Feigin and D I Svergun,
*Structure Analysis by Small-Angle X-Ray and Neutron Scattering,*
Plenum Press, New York, (1987)
"""
import os
import sys
import copy
import collections
from ctypes import c_int, WINFUNCTYPE, windll
from ctypes.wintypes import HWND, LPCSTR, UINT
prototype = WINFUNCTYPE(c_int, HWND, LPCSTR, LPCSTR, UINT)
paramflags = (1, "hwnd", 0), (1, "text", "Hi"), (1, "caption", None), (1, "flags", 0)
MessageBox = prototype(("MessageBoxA", windll.user32), paramflags)
import numpy
from sas.sascalc.fit.pluginmodel import Model1DPlugin
from sasmodels.sasview_model import find_model
class Model(Model1DPlugin):
name = os.path.splitext(os.path.basename(__file__))[0]
is_multiplicity_model = False
def __init__(self, multiplicity=1):
Model1DPlugin.__init__(self, name='')
# Load the core_shell_bicelle model that is included with SasView
P1 = find_model('core_shell_bicelle')
p_model1 = P1()
# List of parameters that are derived from the fit parameters (i.e. they
# are not direct fit parameters, but are a function of fit parameters).
# These are taken as input to the built-in core_shell_bicelle model.
self.derived_params = ['p1_sld_core', 'p1_sld_face', 'p1_sld_rim', 'p1_sld_solvent']
## Setting model name model description
self.description = 'The core_shell_bicelle model with SLDs derived from molecular parameters'
self.name = 'nanodisc_simple'
## Define parameters
self.params = collections.OrderedDict()
## Parameter details [units, min, max]
self.details = {}
## Magnetic Panrameters
self.magnetic_params = []
# non-fittable parameters
self.non_fittable = p_model1.non_fittable
##models
self.p_model1=p_model1
## Polydispersity
self._set_dispersion()
## Define parameters
self._set_params()
# These are initialized to zero but true initial values will be calculated later
# based on molecular parameters, e.g. number of lipids of a given scattering
# length in a given layer volume defines the core SLD, etc.
self.sld_core = 0
self.sld_face = 0
self.sld_rim = 0
self.sld_solvent = 0
self.b_water = 0
# These are the sum of scattering lenghts of the specified molecule. Scattering
# lengths are for 6 Angstrom neutrons and should be modified for x-ray, etc.
# The numbers below are for DMPC lipids and MSP1D1-like belt proteins.
self.params['b_tail'] = -283.36
self.params['b_head'] = 602.13
self.params['b_msp_exch_H'] = 57795.66
self.params['b_msp_exch_D'] = 102145.04
self.params['b_H2O'] = -16.47
self.params['b_D2O'] = 190.77
# These are ballpark initial guesses.
self.params['n_wat_per_hg'] = 1.0
self.params['n_lipids'] = 150
self.params['n_wat_per_MSP'] = 200
self.params['n_MSPs'] = 2.0
# Change according to the experiment. Will be combined with b_H2O and b_D2O to
# calculate the SLD of the solvent. For x-ray, b_H2O and b_D2O are almost the
# same so this won't matter.
self.params['frac_D2O'] = 1.0
## Set approximate geometric parameters for nanodiscs
self.setParam('p1_radius', 30)
self.setParam('p1_thick_rim', 10)
self.setParam('p1_thick_face', 7)
self.setParam('p1_length', 26)
## Parameter details [units, min, max]
self._set_details()
self.details['b_tail'] = ['1e-6 Ang', -numpy.inf, numpy.inf]
self.details['b_head'] = ['1e-6 Ang', -numpy.inf, numpy.inf]
self.details['b_msp_exch_H'] = ['1e-6 Ang', -numpy.inf, numpy.inf]
self.details['b_msp_exch_D'] = ['1e-6 Ang', -numpy.inf, numpy.inf]
self.details['b_H2O'] = ['1e-6 Ang', -numpy.inf, numpy.inf]
self.details['b_D2O'] = ['1e-6 Ang', -numpy.inf, numpy.inf]
self.details['n_wat_per_hg'] = ['', 0, numpy.inf]
self.details['n_lipids'] = ['', 0, numpy.inf]
self.details['n_wat_per_MSP'] = ['', 0, numpy.inf]
self.details['n_MSPs'] = ['', 0, numpy.inf]
self.details['frac_D2O'] = ['', 0, 1]
# This calculates all the SLDs given the specified molecular quantities (e.g.
# number of DMPC molecules) and geometry of the disc. Basically, it just does:
# SLD = (sum of scattering lengths of all atoms in the layer) / (layer volume)
self._setDerivedParams()
#list of parameter that can be fitted
self._set_fixed_params()
## parameters with orientation
self.orientation_params = []
for item in self.p_model1.orientation_params:
new_item = "p1_" + item
if not new_item in self.orientation_params:
self.orientation_params.append(new_item)
## magnetic params
self.magnetic_params = []
for item in self.p_model1.magnetic_params:
new_item = "p1_" + item
if not new_item in self.magnetic_params:
self.magnetic_params.append(new_item)
# get multiplicity if model provide it, else 1.
try:
multiplicity1 = p_model1.multiplicity
except:
multiplicity1 = 1
## functional multiplicity of the model
self.multiplicity1 = multiplicity1
self.multiplicity_info = []
def _clone(self, obj):
obj.params = copy.deepcopy(self.params)
obj.description = copy.deepcopy(self.description)
obj.details = copy.deepcopy(self.details)
obj.dispersion = copy.deepcopy(self.dispersion)
obj.p_model1 = self.p_model1.clone()
obj.sld_core = copy.deepcopy(self.sld_core)
obj.sld_face = copy.deepcopy(self.sld_face)
obj.sld_rim = copy.deepcopy(self.sld_rim)
obj.sld_solvent = copy.deepcopy(self.sld_solvent)
obj.b_water = copy.deepcopy(self.b_water)
obj.derived_params = copy.deepcopy(self.derived_params)
#obj = copy.deepcopy(self)
return obj
def _get_name(self, name1, name2):
p1_name = self._get_upper_name(name1)
if not p1_name:
p1_name = name1
name = p1_name
name += "_and_"
p2_name = self._get_upper_name(name2)
if not p2_name:
p2_name = name2
name += p2_name
return name
def _get_upper_name(self, name=None):
if name == None:
return ""
upper_name = ""
str_name = str(name)
for index in range(len(str_name)):
if str_name[index].isupper():
upper_name += str_name[index]
return upper_name
def _set_dispersion(self):
self.dispersion = collections.OrderedDict()
##set dispersion only from p_model
for name , value in self.p_model1.dispersion.iteritems():
#if name.lower() not in self.p_model1.orientation_params:
new_name = "p1_" + name
self.dispersion[new_name]= value
def function(self, x=0.0):
return 0
def getProfile(self):
try:
x,y = self.p_model1.getProfile()
except:
x = None
y = None
return x, y
def _set_params(self):
# params_text = ''
for name , value in self.p_model1.params.iteritems():
new_name = "p1_" + name
# params_text += new_name + ': ' + str(value) + '
'
if new_name not in self.derived_params:
self.params[new_name]= value
# MessageBox(text=params_text)
def _set_details(self):
for name ,detail in self.p_model1.details.iteritems():
new_name = "p1_" + name
if new_name not in self.derived_params:
self.details[new_name]= detail
def _set_BackGround(self):
pass
# This function will be called to update the SLDs any time any
# of the other parameters are changed.
def _setDerivedParams(self):
# Retrieve all the molecular/geometric parameters
frac_D2O = self.params['frac_D2O']
b_D2O = self.params['b_D2O']
b_H2O = self.params['b_H2O']
n_MSPs = self.params['n_MSPs']
b_msp_exch_D = self.params['b_msp_exch_D']
b_msp_exch_H = self.params['b_msp_exch_H']
n_wat_per_MSP = self.params['n_wat_per_MSP']
p1_length = self.params['p1_length']
p1_thick_face = self.params['p1_thick_face']
p1_radius = self.params['p1_radius']
p1_thick_rim = self.params['p1_thick_rim']
n_lipids = self.params['n_lipids']
b_tail = self.params['b_tail']
b_head = self.params['b_head']
n_wat_per_hg = self.params['n_wat_per_hg']
# Do the SLD calculations
b_water = frac_D2O * b_D2O + (1.0 - frac_D2O) * b_H2O
sld_solvent = b_water / 30.0
sld_core = n_lipids * b_tail / (numpy.pi * (p1_radius ** 2) * p1_length)
sld_face = n_lipids * (b_head + n_wat_per_hg * b_water) / (2.0 * numpy.pi * (p1_radius ** 2) * p1_thick_face)
sld_rim = n_MSPs
* (frac_D2O * b_msp_exch_D + (1.0 - frac_D2O) * b_msp_exch_H + n_wat_per_MSP * b_water)
/ (numpy.pi * (p1_length + 2.0 * p1_thick_face) * ((p1_radius + p1_thick_rim) ** 2 - p1_radius ** 2))
# Update the object's SLD parameters with the calculated values
self.b_water = b_water
self.sld_solvent = sld_solvent
self.sld_core = sld_core
self.sld_face = sld_face
self.sld_rim = sld_rim
# Update the core_shell_bicelle model object with the new SLD parameters
self.p_model1.setParam('sld_solvent', sld_solvent)
self.p_model1.setParam('sld_core', sld_core)
self.p_model1.setParam('sld_face', sld_face)
self.p_model1.setParam('sld_rim', sld_rim)
def setParam(self, name, value):
# MessageBox(text=name + ' ' + str(value))
# set param to this (p1, p2) model
self._setParamHelper(name, value)
self._setDerivedParams()
model_pre = ''
new_name = ''
name_split = name.split('_', 1)
if len(name_split) == 2:
model_pre = name.split('_', 1)[0]
new_name = name.split('_', 1)[1]
if model_pre == "p1":
if new_name in self.p_model1.getParamList():
self.p_model1.setParam(new_name, value)
def getParam(self, name):
# Look for dispersion parameters
toks = name.split('.')
if len(toks)==2:
for item in self.dispersion.keys():
# 2D not supported
if item.lower()==toks[0].lower():
for par in self.dispersion[item]:
if par.lower() == toks[1].lower():
return self.dispersion[item][par]
else:
# Look for standard parameter
for item in self.params.keys():
if item.lower()==name.lower():
return self.params[item]
return
#raise ValueError, "Model does not contain parameter %s" % name
def _setParamHelper(self, name, value):
# Look for dispersion parameters
toks = name.split('.')
if len(toks)== 2:
for item in self.dispersion.keys():
if item.lower()== toks[0].lower():
for par in self.dispersion[item]:
if par.lower() == toks[1].lower():
self.dispersion[item][par] = value
return
else:
# Look for standard parameter
for item in self.params.keys():
if item.lower()== name.lower():
self.params[item] = value
# MessageBox(text=name + ' ' + str(value))
return
raise ValueError, "Model does not contain parameter %s" % name
def _set_fixed_params(self):
self.fixed = []
for item in self.p_model1.fixed:
new_item = "p1" + item
self.fixed.append(new_item)
self.fixed.sort()
def run(self, x = 0.0):
self._set_BackGround()
return (self.p_model1.run(x))
def runXY(self, x = 0.0):
self._set_BackGround()
return (self.p_model1.runXY(x))
## Now (May27,10) directly uses the model eval function
## instead of the for-loop in Base Component.
def evalDistribution(self, x = []):
self._set_BackGround()
return (self.p_model1.evalDistribution(x))
def set_dispersion(self, parameter, dispersion):
value= None
new_pre = parameter.split("_", 1)[0]
new_parameter = parameter.split("_", 1)[1]
try:
if new_pre == 'p1' and new_parameter in self.p_model1.dispersion.keys():
value= self.p_model1.set_dispersion(new_parameter, dispersion)
self._set_dispersion()
return value
except:
raise
def fill_description(self, p_model1, p_model2):
description = ""
description += "This model gives the summation or multiplication of"
description += "%s and %s. "% ( p_model1.name, p_model2.name )
self.description += description
if __name__ == "__main__":
m1= Model()
out1 = m1.runXY(0.01)
m2= Model()
out2 = m2.p_model1.runXY(0.01)
print "My name is %s."% m1.name
print out1, " = ", out2
if out1 == out2:
print "===> Simple Test: Passed!"
else:
print "===> Simple Test: Failed!"
Back to Model
Download