"""A module for the uFJC single-chain model in the isometric ensemble.This module consist of the class ``uFJCIsometric`` which containsmethods for computing single-chain quantitiesin the isometric (constant end-to-end vector) thermodynamic ensemble.Example: Import and instantiate the class: >>> from ufjc.isometric import uFJCIsometric >>> class_instance = uFJCIsometric()"""# Import internal modulesfrom.monte_carloimportMHMCMCfrom.isotensionalimportuFJCIsotensional# Import external modulesimportnumpyasnpimportnumpy.linalgasla
[docs]classuFJCIsometric(uFJCIsotensional):"""The uFJC single-chain model class for the isometric ensemble. This class contains methods for computing single-chain quantities in the isometric (constant end-to-end vector) thermodynamic ensemble. It inherits all attributes and methods from the ``uFJCIsotensional`` class, which inherits all attributes and methods from the ``BasicUtility`` class. """def__init__(self):"""Initializes the ``uFJCIsometric`` class. Initialize and inherit all attributes and methods from a ``uFJCIsotensional`` class instance. """uFJCIsotensional.__init__(self)
[docs]defeta_isometric(self,gamma,**kwargs):r"""Main function for the isometric :math:`\eta(\gamma)`. This is the main function utilized to compute the isometric nondimensional single-chain mechanical response. Keyword arguments specify and are passed onto the method. Args: gamma (array_like): The nondimensional end-to-end length(s). **kwargs: Arbitrary keyword arguments. Passed to the chosen method. Returns: numpy.ndarray: The nondimensional force(s). Example: Compute the nondimensional force for an eight-link Morse-FJC at a nondimensional end-to-end length of 0.8 in the isometric ensemble, using the Legendre transformation method from the isotensional ensemble, and using the reduced asymptotic approach to compute quantities in the isotensional ensemble: >>> from ufjc import uFJC >>> model = uFJC(N_b=8, potential='morse') >>> model.eta_isometric([0, 0.8], \ ... method='legendre', approach='reduced') array([0. , 4.41715473]) Warning: Only the Legendre transformation method is currently unavailable: >>> from ufjc import uFJC >>> uFJC().eta_isometric(0.8, method='exact') array([nan]) """gamma=self.np_array(gamma)method=kwargs.get('method','legendre')ifmethod=='legendre':returnself.eta_isometric_legendre(gamma,**kwargs)else:returnnp.nan*gamma
[docs]defeta_isometric_legendre(self,gamma,**kwargs):r"""The Legendre transformation method of approximating the isometric :math:`\eta(\gamma)`. This function uses the Legendre transformation method to obtain an approximate isometric nondimensional single-chain mechanical response. The result is to simply use the isotensional :math:`\eta(\gamma)`, and this approximation is asymptotically valid for :math:`N_b\gg 1` and appreciable loads :cite:`isometric-buche2020statistical`. Args: gamma (array_like): The nondimensional end-to-end length(s). **kwargs: Arbitrary keyword arguments. Passed to ``_eta_isotensional``. Returns: numpy.ndarray: The nondimensional force(s). Example: Compute the nondimensional force at a large nondimensional end-to-end length using the Legendre transformation method: >>> from ufjc import uFJC >>> model = uFJC() >>> model.eta_isometric_legendre(1.3) array([28.71102552]) """returnself.eta_isotensional(gamma,**kwargs)
[docs]defgamma_isometric(self,eta,**kwargs):r"""Main function for the isometric :math:`\gamma(\eta)`. This function obtains the isometric nondimensional single-chain mechanical response :math:`\gamma(\eta)` by inverting the isometric :math:`\eta(\gamma)`. Args: eta (array_like): the nondimensional force(s). **kwargs: Arbitrary keyword arguments. Passed to ``_eta_isometric``. Returns: numpy.ndarray: The nondimensional end-to-end length(s). Example: Check that :math:`\gamma[\eta(\gamma)] = \gamma\,`: >>> import numpy as np >>> from ufjc import uFJC >>> model = uFJC() >>> def check_eta(gamma): ... eta_fun = lambda gamma: model.eta_isometric(gamma) ... gamma_fun = lambda eta: model.gamma_isometric(eta) ... return np.isclose(gamma_fun(eta_fun(gamma))[0], gamma) >>> check_eta(np.random.rand()) True """defeta_fun(gamma):returnself.eta_isometric(gamma,**kwargs)returnself.inv_fun_1D(eta,eta_fun)
[docs]defvartheta_isometric(self,gamma,**kwargs):r"""Main function for the isometric :math:`\vartheta(\gamma)`. This is the main function utilized to compute the nondimensional Helmholtz free energy per link, an isometric quantity. Keyword arguments specify and are passed onto the method. Args: gamma (array_like): The nondimensional end-to-end length(s). **kwargs: Arbitrary keyword arguments. Passed to the chosen method. Returns: numpy.ndarray: The nondimensional Helmholtz free energy per link. Example: Compute the nondimensional Helmholtz free energy per link for an eight-link Morse-FJC at a nondimensional end-to-end length of 0.8 in the isometric ensemble, using the Legendre transformation method from the isotensional ensemble, and using the reduced asymptotic approach to compute quantities in the isotensional ensemble: >>> from ufjc import uFJC >>> model = uFJC(N_b=8, potential='morse') >>> model.vartheta_isometric(0.8, \ ... method='legendre', approach='reduced') array([1.23847534]) Warning: The exact method is currently unavailable: >>> from ufjc import uFJC >>> uFJC().vartheta_isometric(0.8, method='exact') nan """method=kwargs.get('method','legendre')ifmethod=='exact':returnnp.nan*gammaelifmethod=='legendre':returnself.vartheta_isometric_legendre(gamma,**kwargs)
[docs]defvartheta_isometric_legendre(self,gamma,**kwargs):r"""The Legendre transformation method of approximating the isometric :math:`\vartheta(\gamma)`. This function uses the Legendre transformation method to obtain an approximate isometric Helmholtz free energy per link. The result is independent of the number of links :math:`N_b`, and this approximation is asymptotically valid for :math:`N_b\gg 1` and appreciable loads :cite:`isometric-buche2021chain`. For example, using the reduced asymptotic approach, this is .. math:: \vartheta(\gamma) \sim \ln\left\{\frac{ \eta\exp[\eta\mathcal{L}(\eta)]}{\sinh(\eta)}\right\} + \beta u[\lambda(\eta)] , valid when :math:`\varepsilon\gg 1` and :math:`N_b\gg 1` are simultaneously true. Note that :math:`\eta=\eta(\gamma)` is implied, and obtained through inverting the isotensional :math:`\gamma(\eta)`. Args: gamma (array_like): The nondimensional end-to-end length(s). **kwargs: Arbitrary keyword arguments. Passed to the chosen method. Returns: numpy.ndarray: The nondimensional Helmholtz free energy per link. Example: Approximate the nondimensional Helmholtz free energy per link using the Legendre method and both asymptotic approaches: >>> from ufjc import uFJC >>> model = uFJC(potential='log-squared', varepsilon=23) >>> model.vartheta_isometric_legendre(1.1) array([1.90431381]) >>> model.vartheta_isometric_legendre(1.1, approach='reduced') array([2.09238198]) Warning: Only the asymptotic approaches are currently unavailable: >>> from ufjc import uFJC >>> model = uFJC(potential='log-squared', varepsilon=23) >>> model.vartheta_isometric_legendre(1.1, approach='exact') nan """# Invert gamma=gamma(eta) for the corresponding etaeta=self.eta_isotensional(gamma,**kwargs)# Avoid overflow, important for integrating P_eqeta[eta>self.maximum_exponent]=self.maximum_exponent# Find the corresponding bond stretch under direct etalambda_=1+self.delta_lambda(eta)# Need to finish this portionapproach=kwargs.get('approach','asymptotic')ifapproach=='asymptotic':Ln=self.langevin(eta)coth=self.coth(eta)returneta*Ln+self.log_over_sinh(eta)+ \
self.varepsilon*(self.phi(lambda_)-self.phi(1))+ \
eta**2/self.kappa*((1-Ln*coth)/(self.c+eta/self.kappa*coth))-np.log(1+eta*coth/self.kappa)elifapproach=='reduced':returneta*self.langevin(eta)+self.log_over_sinh(eta)+ \
self.varepsilon*(self.phi(lambda_)-self.phi(1))else:returnnp.nan*gamma
[docs]defbeta_U_config(self,config):r"""The nondimensional potential energy of a configuration. This function provides the nondimensional potential energy :math:`\beta U` given the configuration of the chain, i.e. the vector position of each atom/hinge relative to the first one. Args: config (numpy.ndarray): The configuration of the chain, a :math:`(N_b+1)`-by-3 numpy array. Returns: float: The nondimensional potential energy :math:`\beta U`. Example: Compute the potential energy of the uniformly-stretched default initial configuration: >>> from ufjc import uFJC >>> model = uFJC(N_b=8, potential='lennard-jones') >>> model.beta_U_config(1.1*model.init_config) 133.5368021523727 """beta_U=0forjinrange(1,len(config)):lambda_=la.norm(config[j,:]-config[j-1,:])beta_U+=self.beta_u(lambda_)returnbeta_U