"""A module for basic single-chain model utilities.This module consist of the class ``BasicUtility`` which containsattributes and methods that are meant to be inherited and utilized asbasic utilities by an arbitrary single-chain model class.Example: Create a freely-jointed chain single-chain model class ``FJC`` that inherits the ``BasicUtility`` class in order to utilize the Langevin function for the nondimensional isotensional mechanical response: >>> from ufjc.utility import BasicUtility >>> class FJC(BasicUtility): ... def __init__(self): ... BasicUtility.__init__(self) ... def gamma(self, eta): ... return self.langevin(eta) >>> model = FJC() >>> model.gamma([0, 0.23, 1e88]) array([0. , 0.07639764, 1. ])"""# Import external modulesimportsysimportnumpyasnpfromscipy.optimizeimportroot_scalar
[docs]classBasicUtility(object):"""The single-chain model basic utilities class. This class contains attributes and methods that are meant to be inherited and utilized as basic utilities by an arbitrary single-chain model class. Attributes: minimum float: Helps avoid overflow when dividing by small numbers. maximum_float: Helps avoid overflow in function evaluations. maximum_exponent: Helps avoid overflow in exponential functions. """def__init__(self):"""Initializes the ``BasicUtility`` class. Inherits overflow-related parameters from ``sys.float_info``. """self.minimum_float=sys.float_info.minself.maximum_float=sys.float_info.maxself.maximum_exponent=np.log(self.maximum_float)
[docs]defnp_array(self,input):"""Function to return input as a numpy array. This function essentially serves as a wrapper for ``numpy.array`` that returns non-array type inputs as shape (1,) numpy arrays rather than shape () numpy arrays, which is useful for indexing. Args: input (array_like): Anything passable to ``numpy.array``. Returns: numpy.ndarray: The input as an index-able numpy array. Example: Compare attempts to index the numpy array of an integer: >>> import numpy as np >>> from ufjc.utility import BasicUtility >>> try: ... np.array(8)[0] ... except IndexError: ... print('Error indexing 0-dimensional array.') ... finally: ... BasicUtility().np_array(8)[0] Error indexing 0-dimensional array. 8 """ifnp.array(input).shape==():returnnp.array([input])else:returnnp.array(input)
[docs]definv_fun_1D(self,y,f,power=1,guess=None,**kwargs):"""Function to invert a mathematical function. This function returns the argument x given a function f(x) and y, the query value of the function, i.e. y = f(x). Note: This function is only meant for bijective f(x). Args: y (array_like): The value(s) of the function y. f (function): The function f(x). guess (list, optional): A list of two initial guesses. **kwargs: Arbitrary keyword arguments. Passed to ``root_scalar``. Returns: numpy.ndarray: The corresponding argument(s) x. Example: Invert a polynomial function: >>> from ufjc.utility import BasicUtility >>> f = lambda x: 1 + x**2/8 + x**3/23 >>> BasicUtility().inv_fun_1D([55, 88], f) array([ 9.8712079, 11.7121826]) """y=self.np_array(y)x=np.zeros(y.shape)foriinrange(len(y)):# Function whose root will be founddeffun(x):return(f(x)-y[i])**power# First two guesses when root-findingifguessisnotNone:guess_0=guess*0.95guess_1=guesselify[i]==0:guess_0=0guess_1=1e-2else:guess_0=y[i]*0.95guess_1=y[i]# Find the rootx[i]=root_scalar(fun,x0=guess_0,x1=guess_1,**kwargs).root# Return the rootsreturnx
[docs]deflog_over_sinh(self,eta):r"""The natural logarithm of the argument over the hyperbolic sine. This function avoids overflow when calculating the natural logarithm of the ratio of the argument to the hyperbolic sine of the argument, .. math:: f(\eta) = \ln\left[\frac{\eta}{\sinh(\eta)}\right]. It also returns zero for zero argument. Args: eta (array_like): The argument(s) of the function. Returns: numpy.ndarray: The value(s) of the function. Example: Compute the function for several argument values: >>> from ufjc.utility import BasicUtility >>> BasicUtility().log_over_sinh([0, 0.23, 888]) array([ 0.00000000e+00, -8.80117196e-03, -8.80517881e+02]) """# Determine when argument is sufficiently largeeta=self.np_array(eta)eta_is_big=np.nan_to_num(eta)>3e1y=np.zeros(eta.size)# Use asymptotic relation valid for sufficiently large argumentsifeta_is_big.any():y[eta_is_big]=np.log(2*eta[eta_is_big])-eta[eta_is_big]# Compute analytically otherwise, and zero where argument is zerowhere_eta_zero=eta==0valid_eta=~(eta_is_big+where_eta_zero)ifvalid_eta.any():y[valid_eta]=np.log(eta[valid_eta]/np.sinh(eta[valid_eta]))# Return the resultsreturny
[docs]defcoth(self,eta):"""The hyperbolic cotangent function. This function essentially serves as a wrapper for the algebraic inverse of ``numpy.tanh`` that avoids the singularity from evaluating at zero. Args: eta (array_like): The argument(s) of the hyperbolic cotangent function. Returns: numpy.ndarray: The hyperbolic cotangent(s). Example: Compute the hyperbolic cotangent for several argument values: >>> from ufjc.utility import BasicUtility >>> BasicUtility().coth([0, 0.23, np.inf]) array([4.49423284e+307, 4.42422373e+000, 1.00000000e+000]) """eta=self.np_array(eta)eta=np.where(eta==0,self.minimum_float,eta)return1/np.tanh(eta)
[docs]deflangevin(self,eta):r"""The Langevin function. This function computes and returns the Langevin function of the argument eta, which is .. math:: \mathcal{L}(\eta) = \coth(\eta) - 1/\eta. It also avoids dividing by zero at zero argument. Args: eta (array_like): The argument(s) of the Langevin function. Returns: numpy.ndarray: The evaluated Langevin function(s). Example: Compute the Langevin function for several argument values: >>> from ufjc.utility import BasicUtility >>> BasicUtility().langevin([0, 1, 8, np.inf]) array([0. , 0.31303529, 0.87500023, 1. ]) """eta=self.np_array(eta)eta=np.where(eta==0,self.minimum_float,eta)return1/np.tanh(eta)-1/eta
[docs]definv_langevin(self,gamma):r"""The inverse Langevin function. This function computes the inverse of the Langevin function given its query value. Args: gamma (array_like): The value(s) of the Langevin function. Returns: numpy.ndarray: The argument(s) of the Langevin function. Example: Check that :math:`\mathcal{L}^{-1}[\mathcal{L}(\eta)] = \eta\,`: >>> import numpy as np >>> from ufjc.utility import BasicUtility >>> def check_langevin(eta): ... langevin = BasicUtility().langevin ... inv_langevin = BasicUtility().inv_langevin ... return np.isclose(inv_langevin(langevin(eta))[0], eta) >>> check_langevin(np.random.rand()) True """returnself.inv_fun_1D(gamma,self.langevin)