#[cfg(test)]
mod test;
use crate::{
math::{Scalar, random_uniform, random_x2_normal, special::erf},
mechanics::CurrentCoordinate,
physics::{
BOLTZMANN_CONSTANT,
molecular::single_chain::{
Configuration, Ensemble, Extensible, Isometric, Isotensional, Legendre, MonteCarlo,
SingleChain, SingleChainError, Thermodynamics,
ufjc::{
nondimensional_extension as nondimensional_extension_asymptotic,
nondimensional_gibbs_free_energy_per_link as nondimensional_gibbs_free_energy_per_link_asymptotic,
},
},
},
};
use std::f64::consts::{PI, TAU};
#[derive(Clone, Debug)]
pub struct ExtensibleFreelyJointedChain {
pub link_length: Scalar,
pub link_stiffness: Scalar,
pub number_of_links: u8,
pub ensemble: Ensemble,
}
impl ExtensibleFreelyJointedChain {
fn nondimensional_link_stiffness(&self) -> Scalar {
self.link_stiffness * self.link_length().powi(2) / BOLTZMANN_CONSTANT / self.temperature()
}
}
impl SingleChain for ExtensibleFreelyJointedChain {
fn link_length(&self) -> Scalar {
self.link_length
}
fn number_of_links(&self) -> u8 {
self.number_of_links
}
}
impl Extensible for ExtensibleFreelyJointedChain {}
impl Thermodynamics for ExtensibleFreelyJointedChain {
fn ensemble(&self) -> Ensemble {
self.ensemble
}
}
impl Isometric for ExtensibleFreelyJointedChain {
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 Isotensional for ExtensibleFreelyJointedChain {
fn nondimensional_gibbs_free_energy_per_link(
&self,
nondimensional_force: Scalar,
) -> Result<Scalar, SingleChainError> {
let eta = nondimensional_force;
let kappa = self.nondimensional_link_stiffness();
let eta_over_kappa = eta / kappa;
let eta_exp = eta.exp();
Ok(nondimensional_gibbs_free_energy_per_link_asymptotic(
eta,
kappa,
-0.5 * eta.powi(2) / kappa,
1.0,
)? - (0.5
+ ((eta_over_kappa + 1.0) * eta_exp * erf(&((eta + kappa) / (2.0 * kappa).sqrt()))
- (eta_over_kappa - 1.0) / eta_exp * erf(&((eta - kappa) / (2.0 * kappa).sqrt())))
/ (4.0 * eta.sinh() * (1.0 + eta / eta.tanh() / kappa)))
.ln())
}
fn nondimensional_extension(
&self,
nondimensional_force: Scalar,
) -> Result<Scalar, SingleChainError> {
let eta = nondimensional_force;
let kappa = self.nondimensional_link_stiffness();
let eta_over_kappa = eta / kappa;
let denominator = 4.0 * eta.sinh() * (1.0 + eta / eta.tanh() / kappa);
let fraction = ((eta_over_kappa + 1.0)
* eta.exp()
* erf(&((eta + kappa) / (2.0 * kappa).sqrt()))
- (eta_over_kappa - 1.0) / eta.exp() * erf(&((eta - kappa) / (2.0 * kappa).sqrt())))
/ denominator;
Ok(
nondimensional_extension_asymptotic(eta, kappa, eta_over_kappa, 1.0)?
+ (eta.exp()
* ((2.0 / PI / kappa).sqrt()
* (eta_over_kappa + 1.0)
* (-(eta + kappa).powi(2) / 2.0 / kappa).exp()
+ (1.0 + (1.0 + eta) / kappa))
- 1.0 / eta.exp()
* ((2.0 / PI / kappa).sqrt()
* (eta_over_kappa - 1.0)
* (-(eta - kappa).powi(2) / 2.0 / kappa).exp()
+ (1.0 + (1.0 - eta) / kappa)
* erf(&((eta - kappa) / (2.0 * kappa).sqrt())))
- fraction
* (4.0
* (eta.cosh() * (1.0 + (1.0 + eta / eta.tanh()) / kappa)
- eta_over_kappa / eta.sinh())))
/ denominator
/ (1.0 + fraction),
)
}
fn nondimensional_compliance(
&self,
_nondimensional_force: Scalar,
) -> Result<Scalar, SingleChainError> {
unimplemented!()
}
}
impl Legendre for ExtensibleFreelyJointedChain {
fn nondimensional_spherical_distribution(
&self,
_nondimensional_extension: Scalar,
) -> Result<Scalar, SingleChainError> {
unimplemented!()
}
}
impl MonteCarlo for ExtensibleFreelyJointedChain {
fn random_nondimensional_link_vectors(&self, nondimensional_force: Scalar) -> Configuration {
let sigma = 1.0 / self.nondimensional_link_stiffness().sqrt();
(0..self.number_of_links())
.map(|_| {
let cos_theta = if nondimensional_force == 0.0 {
2.0 * random_uniform() - 1.0
} else {
todo!("Force biases the link stretch too.")
};
let sin_theta = (1.0 - cos_theta * cos_theta).sqrt();
let phi = TAU * random_uniform();
let (sin_phi, cos_phi) = phi.sin_cos();
let lambda = random_x2_normal(1.0, sigma);
CurrentCoordinate::from([
lambda * sin_theta * cos_phi,
lambda * sin_theta * sin_phi,
lambda * cos_theta,
])
})
.collect()
}
}