#!/usr/bin/env python
"""
Provide base functionality for all model components
"""
# imports
import copy
from collections import OrderedDict
import numpy as np
#TO DO: that about a way to make the parameter
#is self return if it is fittable or not
[docs]class BaseComponent:
"""
Basic model component
Since version 0.5.0, basic operations are no longer supported.
"""
[docs] def __init__(self):
""" Initialization"""
## Name of the model
self.name = "BaseComponent"
## Parameters to be accessed by client
self.params = {}
self.details = {}
## Dictionary used to store the dispersity/averaging
# parameters of dispersed/averaged parameters.
self.dispersion = {}
# string containing information about the model such as the equation
#of the given model, exception or possible use
self.description = ''
#list of parameter that can be fitted
self.fixed = []
#list of non-fittable parameter
self.non_fittable = []
## parameters with orientation
self.orientation_params = []
## magnetic parameters
self.magnetic_params = []
## store dispersity reference
self._persistency_dict = {}
## independent parameter name and unit [string]
self.input_name = "Q"
self.input_unit = "A^{-1}"
## output name and unit [string]
self.output_name = "Intensity"
self.output_unit = "cm^{-1}"
self.is_multiplicity_model = False
self.is_structure_factor = False
self.is_form_factor = False
[docs] def __str__(self):
"""
:return: string representatio
"""
return self.name
[docs] def is_fittable(self, par_name):
"""
Check if a given parameter is fittable or not
:param par_name: the parameter name to check
"""
return par_name.lower() in self.fixed
#For the future
#return self.params[str(par_name)].is_fittable()
[docs] def run(self, x):
"""
run 1d
"""
return NotImplemented
[docs] def runXY(self, x):
"""
run 2d
"""
return NotImplemented
[docs] def calculate_ER(self):
"""
Calculate effective radius
"""
return NotImplemented
[docs] def calculate_VR(self):
"""
Calculate volume fraction ratio
"""
return NotImplemented
[docs] def evalDistribution(self, qdist):
"""
Evaluate a distribution of q-values.
* For 1D, a numpy array is expected as input: ::
evalDistribution(q)
where q is a numpy array.
* For 2D, a list of numpy arrays are expected: [qx_prime,qy_prime],
where 1D arrays, ::
qx_prime = [ qx[0], qx[1], qx[2], ....]
and ::
qy_prime = [ qy[0], qy[1], qy[2], ....]
Then get ::
q = np.sqrt(qx_prime^2+qy_prime^2)
that is a qr in 1D array; ::
q = [q[0], q[1], q[2], ....]
.. note:: Due to 2D speed issue, no anisotropic scattering
is supported for python models, thus C-models should have
their own evalDistribution methods.
The method is then called the following way: ::
evalDistribution(q)
where q is a numpy array.
:param qdist: ndarray of scalar q-values or list [qx,qy] where qx,qy are 1D ndarrays
"""
if qdist.__class__.__name__ == 'list':
# Check whether we have a list of ndarrays [qx,qy]
if len(qdist)!=2 or \
qdist[0].__class__.__name__ != 'ndarray' or \
qdist[1].__class__.__name__ != 'ndarray':
msg = "evalDistribution expects a list of 2 ndarrays"
raise RuntimeError(msg)
# Extract qx and qy for code clarity
qx = qdist[0]
qy = qdist[1]
# calculate q_r component for 2D isotropic
q = np.sqrt(qx**2+qy**2)
# vectorize the model function runXY
v_model = np.vectorize(self.runXY, otypes=[float])
# calculate the scattering
iq_array = v_model(q)
return iq_array
elif qdist.__class__.__name__ == 'ndarray':
# We have a simple 1D distribution of q-values
v_model = np.vectorize(self.runXY, otypes=[float])
iq_array = v_model(qdist)
return iq_array
else:
mesg = "evalDistribution is expecting an ndarray of scalar q-values"
mesg += " or a list [qx,qy] where qx,qy are 2D ndarrays."
raise RuntimeError(mesg)
[docs] def clone(self):
""" Returns a new object identical to the current object """
obj = copy.deepcopy(self)
return self._clone(obj)
[docs] def _clone(self, obj):
"""
Internal utility function to copy the internal
data members to a fresh copy.
"""
obj.params = copy.deepcopy(self.params)
obj.details = copy.deepcopy(self.details)
obj.dispersion = copy.deepcopy(self.dispersion)
obj._persistency_dict = copy.deepcopy( self._persistency_dict)
return obj
[docs] def set_dispersion(self, parameter, dispersion):
"""
model dispersions
"""
##Not Implemented
return None
[docs] def getProfile(self):
"""
Get SLD profile
: return: (z, beta) where z is a list of depth of the transition points
beta is a list of the corresponding SLD values
"""
#Not Implemented
return None, None
[docs] def setParam(self, name, value):
"""
Set the value of a model parameter
:param name: name of the parameter
:param value: value of the parameter
"""
# 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
return
raise ValueError("Model does not contain parameter %s" % name)
[docs] def getParam(self, name):
"""
Set the value of a model parameter
:param name: name of the parameter
"""
# 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():
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]
raise ValueError("Model does not contain parameter %s" % name)
[docs] def getParamList(self):
"""
Return a list of all available parameters for the model
"""
list = _ordered_keys(self.params)
# WARNING: Extending the list with the dispersion parameters
list.extend(self.getDispParamList())
return list
[docs] def getDispParamList(self):
"""
Return a list of all available parameters for the model
"""
list = []
for item in _ordered_keys(self.dispersion):
for p in _ordered_keys(self.dispersion[item]):
if p not in ['type']:
list.append('%s.%s' % (item.lower(), p.lower()))
return list
# Old-style methods that are no longer used
[docs] def setParamWithToken(self, name, value, token, member):
"""
set Param With Token
"""
return NotImplemented
[docs] def getParamWithToken(self, name, token, member):
"""
get Param With Token
"""
return NotImplemented
[docs] def getParamListWithToken(self, token, member):
"""
get Param List With Token
"""
return NotImplemented
[docs] def __add__(self, other):
"""
add
"""
raise ValueError("Model operation are no longer supported")
[docs] def __sub__(self, other):
"""
sub
"""
raise ValueError("Model operation are no longer supported")
[docs] def __mul__(self, other):
"""
mul
"""
raise ValueError("Model operation are no longer supported")
[docs] def __div__(self, other):
"""
div
"""
raise ValueError("Model operation are no longer supported")
[docs]def _ordered_keys(d):
keys = list(d.keys())
if not isinstance(d, OrderedDict):
keys.sort()
return keys