"""The core module for the SWFJC single-chain model.
This module contains the core class ``SWFJC`` which, upon instantiation,
becomes a SWFJC single-chain model instance with methods for computing
single-chain quantities in either thermodynamic ensemble.
The ``SWFJCIsometric`` and ``SWFJCIsotensional`` classes are also
contained within this module.
The SWFJC is the uFJC model with a square-well link potential,
which is a special case that is efficiently treated separately
as it can be solved exactly :cite:`swfjc-buche2022freely`.
Example:
Import and create an instance of the model:
>>> from ufjc import SWFJC
>>> model = SWFJC()
"""
# Import internal modules
from .utility import BasicUtility
# Import external modules
import numpy as np
[docs]class SWFJCIsometric(BasicUtility):
"""The SWFJC model class for the isometric ensemble.
Attributes:
N (int): The number of links in the chain.
varsigma (float): The nondimensional well width.
"""
def __init__(self, N_b=8, varsigma=3):
"""Initializes the ``SWFJCIsometric`` class.
Initialize and inherit all attributes and methods
from a ``BasicUtility`` class instance.
Args:
N_b (int, optional, default=8):
The number of links in the chain.
varsigma (float, optional, default=3):
The nondimensional well width.
"""
super().__init__()
self.N_b = N_b
self.varsigma = varsigma
[docs]class SWFJCIsotensional(SWFJCIsometric):
"""The SWFJC model class for the isotensional ensemble.
Attributes:
N (int): The number of links in the chain.
varsigma (float): The nondimensional well width.
"""
def __init__(self, N_b=8, varsigma=2):
"""Initializes the ``SWFJCIsotensional`` class.
Initialize and inherit all attributes and methods
from a ``SWFJCIsometric`` class instance.
Args:
N_b (int, optional, default=8):
The number of links in the chain.
varsigma (float, optional, default=3):
One plus the nondimensional well width.
"""
super().__init__()
self.N_b = N_b
self.varsigma = varsigma
[docs] def z(self, eta):
r"""The nondimensional single-link isotensional partition function
as a function of the nondimensional force,
.. math::
\mathfrak{z}(\eta) =
\frac{1}{\eta^3}\left[
\varsigma\eta\cosh(\varsigma\eta)
- \sinh(\varsigma\eta)
- \eta\cosh(\eta) + \sinh(\eta)
\right].
Args:
v (array_like): The nondimensional force.
Returns:
numpy.ndarray: The nondimensional isotensional partition function.
"""
eta = self.np_array(eta)
eta_zero = eta == 0
eta[eta_zero] = -1
z = (
np.sinh(eta) - eta*np.cosh(eta)
- np.sinh(self.varsigma*eta)
+ self.varsigma*eta*np.cosh(self.varsigma*eta)
)/eta**3
z[eta_zero] = (self.varsigma**3 - 1)/3
return z
[docs] def beta_varphi(self, eta):
r"""The nondimensional isotensional free energy as a function of
the nondimensional force,
.. math::
\beta\varphi(\eta) =
-\ln\mathfrak{z}(\eta).
Note that this becomes the isotensional free energy
of the FJC model as :math:`\varsigma` goes to zero,
.. math::
\lim_{\varsigma\to 0}\beta\varphi(\eta) =
\ln\left[\frac{\eta}{\sinh(\eta)}\right].
Args:
v (array_like): The nondimensional force.
Returns:
numpy.ndarray: The nondimensional isotensional free energy.
Example:
Plot the nondimensional isotensional free energy as a function of
the nondimensional force for varying :math:`\varsigma`:
.. plot::
>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> from ufjc.swfjc import SWFJCIsotensional
>>> eta = np.linspace(0, 10, 1000)[1:]
>>> _ = plt.figure()
>>> for varsigma in [2, 3, 5, 10]:
... model = SWFJCIsotensional(varsigma=varsigma)
... _ = plt.plot(eta, model.beta_varphi(eta),
... label=r'$\varsigma=$'+str(varsigma))
>>> _ = plt.plot(eta, np.log(eta/np.sinh(eta)),
... 'k--', label='FJC')
>>> _ = plt.xlabel(r'$\eta$')
>>> _ = plt.ylabel(r'$\beta\varphi$')
>>> _ = plt.legend()
>>> plt.show()
"""
return (np.log(self.z(0)) - np.log(self.z(eta)))/self.varsigma
[docs] def gamma_isotensional(self, eta):
r"""The nondimensional end-to-end length as a function of
the nondimensional force in the isotensional ensemble,
.. math::
\gamma(\eta) =
\frac{\partial}{\partial\eta}\,\ln\mathfrak{z}(\eta) =
\frac{
\varsigma^2\eta\sinh(\varsigma\eta) - \eta\sinh(\eta)
}{
\varsigma\eta\cosh(\varsigma\eta)
- \sinh(\varsigma\eta)
- \eta\cosh(\eta) + \sinh(\eta)
}
Note that this becomes the Langevin function of the FJC model
as :math:`\varsigma` goes to zero,
.. math::
\lim_{\varsigma\to 0}\gamma(\eta) =
\coth(\eta) - \frac{1}{\eta} =
\mathcal{L}(\eta).
Args:
v (array_like): The nondimensional force.
Returns:
numpy.ndarray: The nondimensional end-to-end length.
Example:
Plot the nondimensional single-chain mechanical response
in the isotensional ensemble for varying :math:`\varsigma`:
.. plot::
>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> from ufjc.swfjc import SWFJCIsotensional
>>> eta = np.linspace(0, 10, 1000)[1:]
>>> _ = plt.figure()
>>> for varsigma in [2, 3, 5, 10]:
... model = SWFJCIsotensional(varsigma=varsigma)
... _ = plt.plot(
... model.gamma_isotensional(eta)/varsigma,
... eta, label=r'$\varsigma=$'+str(varsigma))
>>> _ = plt.plot(1/np.tanh(eta) - 1/eta, eta,
... 'k--', label='FJC')
>>> _ = plt.xlabel(r'$\gamma/\varsigma$')
>>> _ = plt.ylabel(r'$\eta$')
>>> _ = plt.legend()
>>> plt.show()
"""
return (
self.varsigma**2*eta*np.sinh(self.varsigma*eta)
- eta*np.sinh(eta)
)/(
np.sinh(eta) - eta*np.cosh(eta)
- np.sinh(self.varsigma*eta)
+ self.varsigma*eta*np.cosh(self.varsigma*eta)
) - 3/eta
[docs]class SWFJC(SWFJCIsotensional):
"""The SWFJC single-chain model class.
Attributes:
N (int): The number of links in the chain.
varsigma (float): The nondimensional well width.
"""
def __init__(self):
"""Initializes the ``SWFJC`` class.
Initialize and inherit all attributes and methods
from a ``SWFJCIsotensional`` class instance.
Args:
N_b (int, optional, default=8):
The number of links in the chain.
varsigma (float, optional, default=3):
The nondimensional well width.
"""
super().__init__()