#[cfg(test)]
mod test;
use crate::{
math::{
Scalar,
special::{langevin, langevin_derivative},
},
physics::molecular::{
potential::{Harmonic, Potential},
single_chain::{
Ensemble, Extensible, Isometric, Isotensional, IsotensionalExtensible, Legendre,
SingleChain, SingleChainError, Thermodynamics, ThermodynamicsExtensible,
},
},
};
use std::f64::consts::TAU;
#[derive(Clone, Debug)]
pub struct ArbitraryPotentialFreelyJointedChain<T>
where
T: Potential,
{
pub link_potential: T,
pub number_of_links: u8,
pub ensemble: Ensemble,
}
impl<T> ArbitraryPotentialFreelyJointedChain<T>
where
T: Potential,
{
fn correction(&self) -> Scalar {
1.0 / (1.0
- 0.5
* self
.link_potential
.nondimensional_anharmonicity(1.0, self.temperature())
/ self
.link_potential
.nondimensional_stiffness(1.0, self.temperature()))
}
fn nondimensional_link_stiffness(&self) -> Scalar {
self.link_potential
.nondimensional_stiffness(1.0, self.temperature())
}
}
impl<T> SingleChain for ArbitraryPotentialFreelyJointedChain<T>
where
T: Potential,
{
fn link_length(&self) -> Scalar {
self.link_potential.rest_length()
}
fn number_of_links(&self) -> u8 {
self.number_of_links
}
}
impl<T> Extensible for ArbitraryPotentialFreelyJointedChain<T> where T: Potential {}
impl<T> Thermodynamics for ArbitraryPotentialFreelyJointedChain<T>
where
T: Potential,
{
fn ensemble(&self) -> Ensemble {
self.ensemble
}
}
impl ThermodynamicsExtensible for ArbitraryPotentialFreelyJointedChain<Harmonic> {}
impl<T> Isometric for ArbitraryPotentialFreelyJointedChain<T>
where
T: Potential,
{
fn nondimensional_helmholtz_free_energy(
&self,
_nondimensional_extension: Scalar,
) -> Result<Scalar, SingleChainError> {
unimplemented!()
}
fn nondimensional_force(
&self,
_nondimensional_extension: Scalar,
) -> Result<Scalar, SingleChainError> {
unimplemented!()
}
fn nondimensional_stiffness(
&self,
_nondimensional_extension: Scalar,
) -> Result<Scalar, SingleChainError> {
unimplemented!()
}
fn nondimensional_spherical_distribution(
&self,
_nondimensional_extension: Scalar,
) -> Result<Scalar, SingleChainError> {
unimplemented!()
}
}
impl<T> Isotensional for ArbitraryPotentialFreelyJointedChain<T>
where
T: Potential,
{
fn nondimensional_gibbs_free_energy_per_link(
&self,
nondimensional_force: Scalar,
) -> Result<Scalar, SingleChainError> {
nondimensional_gibbs_free_energy_per_link(
nondimensional_force,
self.nondimensional_link_stiffness(),
self.link_potential
.nondimensional_legendre(nondimensional_force, self.temperature()),
self.correction(),
)
}
fn nondimensional_extension(
&self,
nondimensional_force: Scalar,
) -> Result<Scalar, SingleChainError> {
nondimensional_extension(
nondimensional_force,
self.nondimensional_link_stiffness(),
self.link_potential
.nondimensional_extension(nondimensional_force, self.temperature()),
self.correction(),
)
}
fn nondimensional_compliance(
&self,
nondimensional_force: Scalar,
) -> Result<Scalar, SingleChainError> {
nondimensional_compliance(
nondimensional_force,
self.nondimensional_link_stiffness(),
self.link_potential
.nondimensional_compliance(nondimensional_force, self.temperature()),
self.correction(),
)
}
}
impl IsotensionalExtensible for ArbitraryPotentialFreelyJointedChain<Harmonic> {
fn nondimensional_link_energy_average(
&self,
nondimensional_force: Scalar,
) -> Result<Scalar, SingleChainError> {
Ok(0.5
+ helper(
nondimensional_force,
self.nondimensional_link_stiffness(),
self.correction(),
)
+ self
.link_potential
.nondimensional_energy_at_nondimensional_force(
nondimensional_force,
self.temperature(),
))
}
fn nondimensional_link_energy_variance(
&self,
nondimensional_force: Scalar,
) -> Result<Scalar, SingleChainError> {
let hlpr = helper(
nondimensional_force,
self.nondimensional_link_stiffness(),
self.correction(),
);
Ok(0.5
+ hlpr * (2.0 - hlpr)
+ 2.0
* self
.link_potential
.nondimensional_energy_at_nondimensional_force(
nondimensional_force,
self.temperature(),
))
}
fn nondimensional_link_energy_probability(
&self,
nondimensional_energy: Scalar,
nondimensional_force: Scalar,
) -> Result<Scalar, SingleChainError> {
self.link_potential
.nondimensional_forces_at_nondimensional_energy(
nondimensional_energy,
self.temperature(),
)
.into_iter()
.zip(
self.link_potential
.nondimensional_lengths_at_nondimensional_energy(
nondimensional_energy,
self.temperature(),
),
)
.map(|(eta, nondimensional_length)| {
Ok(
IsotensionalExtensible::nondimensional_link_length_probability(
self,
nondimensional_length,
nondimensional_force,
)? / eta.abs(),
)
})
.sum()
}
fn nondimensional_link_length_average(
&self,
nondimensional_force: Scalar,
) -> Result<Scalar, SingleChainError> {
let eta = nondimensional_force;
let kappa = self.nondimensional_link_stiffness();
if eta == 0.0 {
Ok(1.0 + 2.0 / (1.0 * kappa + 1.0))
} else {
let eta_coth = 1.0 / eta.tanh();
let eta_over_kappa = eta / kappa;
Ok(1.0
+ (1.0 / kappa + eta_over_kappa * (1.0 - eta_over_kappa) * (eta_coth - 1.0))
/ (1.0 + eta_over_kappa * eta_coth)
+ eta_over_kappa)
}
}
fn nondimensional_link_length_variance(
&self,
nondimensional_force: Scalar,
) -> Result<Scalar, SingleChainError> {
let eta = nondimensional_force;
let kappa = self.nondimensional_link_stiffness();
let mean_squared =
ThermodynamicsExtensible::nondimensional_link_length_average(self, eta)?.powi(2);
if eta == 0.0 {
Ok(1.0 + 3.0 / kappa + 2.0 / (kappa + 1.0) - mean_squared)
} else {
let eta_coth = 1.0 / eta.tanh();
let eta_over_kappa = eta / kappa;
let eta_over_kappa_coth = eta_over_kappa * eta_coth;
Ok(1.0
+ (3.0 / kappa
+ 2.0 * eta_over_kappa.powi(2)
+ (3.0 / kappa + 2.0) * eta_over_kappa_coth)
/ (1.0 + eta_over_kappa_coth)
+ eta_over_kappa.powi(2)
- mean_squared)
}
}
fn nondimensional_link_length_probability(
&self,
nondimensional_length: Scalar,
nondimensional_force: Scalar,
) -> Result<Scalar, SingleChainError> {
let eta = nondimensional_force;
let lambda = nondimensional_length;
let kappa = self.nondimensional_link_stiffness();
let upsilon_twice = self
.link_potential
.nondimensional_energy(nondimensional_length, self.temperature())
+ eta.powi(2) / 2.0 / kappa;
Ok((kappa / TAU).sqrt()
* lambda
* ((eta * (lambda - 1.0) - upsilon_twice).exp()
- (-eta * (lambda + 1.0) - upsilon_twice).exp())
/ (1.0 - (-2.0 * eta).exp())
/ (1.0 + eta / kappa / self.correction() / eta.tanh()))
}
}
pub fn nondimensional_gibbs_free_energy_per_link(
eta: Scalar,
kappa: Scalar,
nu: Scalar,
c: Scalar,
) -> Result<Scalar, SingleChainError> {
Ok(nu
- eta
- (0.5 - 0.5 * (-2.0 * eta).exp()).ln()
- (1.0 / eta + 1.0 / c / kappa / eta.tanh()).ln())
}
pub fn nondimensional_extension(
eta: Scalar,
kappa: Scalar,
delta_lambda: Scalar,
c: Scalar,
) -> Result<Scalar, SingleChainError> {
if eta == 0.0 {
Ok(0.0)
} else {
let eta_coth = 1.0 / eta.tanh();
let gamma_0 = langevin(eta);
let eta_over_kappa = eta / kappa;
Ok(gamma_0
+ eta_over_kappa * (1.0 - gamma_0 * eta_coth) / (c + eta_over_kappa * eta_coth)
+ delta_lambda)
}
}
pub fn nondimensional_compliance(
eta: Scalar,
kappa: Scalar,
zeta: Scalar,
c: Scalar,
) -> Result<Scalar, SingleChainError> {
if eta == 0.0 {
Ok(1.0 / 3.0 + 2.0 / 3.0 / c / kappa + zeta)
} else {
let eta_tanh = eta.tanh();
let eta_coth = 1.0 / eta_tanh;
let gamma_0 = langevin(eta);
let eta_over_kappa = eta / kappa;
let c_0 = langevin_derivative(eta);
let g = 1.0 - gamma_0 * eta_coth;
let h = c + eta_over_kappa * eta_coth;
let dcth = 1.0 - 1.0 / (eta_tanh * eta_tanh);
let dg = -(c_0 * eta_coth + gamma_0 * dcth);
let dh = eta_coth / kappa + eta_over_kappa * dcth;
Ok(c_0 + (g / h) / kappa + eta_over_kappa * (dg * h - g * dh) / (h * h) + zeta)
}
}
fn helper(
nondimensional_force: Scalar,
nondimensional_stiffness: Scalar,
correction: Scalar,
) -> Scalar {
let eta_over_kappa = nondimensional_force / nondimensional_stiffness;
eta_over_kappa / (eta_over_kappa + correction * nondimensional_force.tanh())
}
impl<T> Legendre for ArbitraryPotentialFreelyJointedChain<T>
where
T: Potential,
{
fn nondimensional_spherical_distribution(
&self,
_nondimensional_extension: Scalar,
) -> Result<Scalar, SingleChainError> {
unimplemented!()
}
}