diff --git a/src/physics/single_chain/efrc/mod.rs b/src/physics/single_chain/efrc/mod.rs new file mode 100644 index 00000000..cbed9d59 --- /dev/null +++ b/src/physics/single_chain/efrc/mod.rs @@ -0,0 +1,2 @@ +/// The extensible freely-rotating chain (EFRC) model thermodynamics. +pub mod thermodynamics; diff --git a/src/physics/single_chain/efrc/thermodynamics/isometric/mod.rs b/src/physics/single_chain/efrc/thermodynamics/isometric/mod.rs new file mode 100644 index 00000000..e58acb07 --- /dev/null +++ b/src/physics/single_chain/efrc/thermodynamics/isometric/mod.rs @@ -0,0 +1,2 @@ +/// The extensible freely-rotating chain (EFRC) model thermodynamics in the isometric ensemble calculated using Monte Carlo methods. +pub mod monte_carlo; diff --git a/src/physics/single_chain/efrc/thermodynamics/isometric/monte_carlo/mod.rs b/src/physics/single_chain/efrc/thermodynamics/isometric/monte_carlo/mod.rs new file mode 100644 index 00000000..361ca169 --- /dev/null +++ b/src/physics/single_chain/efrc/thermodynamics/isometric/monte_carlo/mod.rs @@ -0,0 +1,87 @@ +mod test; + +use rand::prelude::*; + +use rand_distr::{Normal, Distribution}; + +use std::f64::consts::TAU as TWO_PI; + +use crate::physics::single_chain::frc::thermodynamics::isometric::monte_carlo::cross; + +pub fn random_configuration(theta: &f64, dist: Normal, rng: &mut ThreadRng) -> [[f64; 3]; NUMBER_OF_LINKS] +{ + let mut lambda: f64 = 0.0; + let mut phi: f64 = 0.0; + let mut phi_cos: f64 = 0.0; + let mut phi_sin: f64 = 0.0; + let theta_cos: f64 = theta.cos(); + let theta_sin: f64 = theta.sin(); + let mut configuration = [[0.0; 3]; NUMBER_OF_LINKS]; + let mut position = [0.0; 3]; + let mut r = [1.0, 0.0, 0.0]; + let mut t = [0.0; 3]; + let mut u = [0.0; 3]; + let mut v = [0.0; 3]; + let mut u_normalization = 0.0; + let mut r_n = [1.0, 0.0, 0.0]; + let mut r_normalization = 0.0; + configuration.iter_mut().for_each(|coordinate|{ + lambda = dist.sample(rng); + r_n = r; + r_normalization = r_n.iter().map(|r_n_i| r_n_i * r_n_i).sum::().sqrt(); + r_n.iter_mut().for_each(|r_n_i| *r_n_i /= r_normalization); + phi = TWO_PI * rng.gen::(); + phi_cos = phi.cos(); + phi_sin = phi.sin(); + t = std::array::from_fn(|_| rng.gen::()); + u = cross(&r_n, &t); + u_normalization = u.iter().map(|u_i| u_i * u_i).sum::().sqrt(); + u.iter_mut().for_each(|u_i| *u_i /= u_normalization); + v = cross(&r_n, &u); + r.iter_mut().zip(r_n.iter().zip(u.iter().zip(v.iter()))).for_each(|(r_i, (r_n_i, (u_i, v_i)))| + *r_i = lambda * ((u_i * phi_cos + v_i * phi_sin) * theta_sin + *r_n_i * theta_cos) + ); + position.iter_mut().zip(r.iter()).for_each(|(position_i, r_i)| + *position_i += r_i + ); + coordinate.iter_mut().zip(position.iter()).for_each(|(coordinate_i, position_i)| + *coordinate_i = *position_i + ); + }); + configuration +} + +pub fn random_nondimensional_end_to_end_length(theta: &f64, dist: Normal, rng: &mut ThreadRng) -> f64 +{ + random_configuration::(theta, dist, rng)[NUMBER_OF_LINKS - 1].iter().map(|entry| entry * entry).sum::().sqrt() +} + +pub fn nondimensional_equilibrium_radial_distribution(gamma_max: &f64, kappa: &f64, theta: &f64, number_of_samples: usize) -> ([f64; NUMBER_OF_BINS], [f64; NUMBER_OF_BINS]) +{ + let mut rng = rand::thread_rng(); + let dist = Normal::new(1.0, 1.0/kappa.sqrt()).unwrap(); + let number_of_links_f64 = NUMBER_OF_LINKS as f64; + let mut bin_centers = [0.0_f64; NUMBER_OF_BINS]; + bin_centers.iter_mut().enumerate().for_each(|(bin_index, bin_center)| + *bin_center = gamma_max * ((bin_index as f64) + 0.5)/(NUMBER_OF_BINS as f64) + ); + let mut bin_counts = [0_u128; NUMBER_OF_BINS]; + let mut gamma: f64 = 0.0; + (0..number_of_samples).for_each(|_|{ + gamma = random_nondimensional_end_to_end_length::(theta, dist, &mut rng)/number_of_links_f64; + for (bin_center, bin_count) in bin_centers.iter().zip(bin_counts.iter_mut()) + { + if &gamma < bin_center + { + *bin_count += 1; + break + } + } + }); + let normalization = gamma_max * (number_of_samples as f64)/(NUMBER_OF_BINS as f64); + let mut bin_probabilities = [0.0_f64; NUMBER_OF_BINS]; + bin_probabilities.iter_mut().zip(bin_counts.iter()).for_each(|(bin_probability, bin_count)| + *bin_probability = (*bin_count as f64)/normalization + ); + (bin_centers, bin_probabilities) +} \ No newline at end of file diff --git a/src/physics/single_chain/efrc/thermodynamics/isometric/monte_carlo/test.rs b/src/physics/single_chain/efrc/thermodynamics/isometric/monte_carlo/test.rs new file mode 100644 index 00000000..854f0435 --- /dev/null +++ b/src/physics/single_chain/efrc/thermodynamics/isometric/monte_carlo/test.rs @@ -0,0 +1,45 @@ +#![cfg(test)] + +use super::*; + +use std::f64::consts::PI; + +const GAMMA_MAX: f64 = 1.5; +const KAPPA: f64 = 50.0; +const NUMBER_OF_BINS: usize = 100; +const NUMBER_OF_LINKS: usize = 8; +const NUMBER_OF_SAMPLES: usize = 100000; +const THETA: f64 = PI/8.0; + +#[test] +fn monte_carlo_random_configuration() +{ + let mut rng = rand::thread_rng(); + let dist = Normal::new(1.0, 1.0/KAPPA.sqrt()).unwrap(); + let configuration = random_configuration::(&THETA, dist, &mut rng); + assert_eq!(configuration.len(), NUMBER_OF_LINKS); + assert_eq!(configuration[0].len(), 3); +} + +#[test] +fn monte_carlo_random_nondimensional_end_to_end_length() +{ + let mut rng = rand::thread_rng(); + let dist = Normal::new(1.0, 1.0/KAPPA.sqrt()).unwrap(); + let gamma = random_nondimensional_end_to_end_length::(&THETA, dist, &mut rng)/(NUMBER_OF_LINKS as f64); + assert!(gamma >= 0.0); + assert!(gamma <= GAMMA_MAX); +} + +#[test] +fn monte_carlo_nondimensional_equilibrium_radial_distribution() +{ + let (gamma, g_eq) = nondimensional_equilibrium_radial_distribution::(&GAMMA_MAX, &KAPPA, &THETA, NUMBER_OF_SAMPLES); + assert_eq!(gamma.len(), NUMBER_OF_BINS); + assert_eq!(g_eq.len(), NUMBER_OF_BINS); + gamma.iter().zip(g_eq.iter()).for_each(|(gamma_i, g_eq_i)|{ + assert!(gamma_i > &0.0); + assert!(gamma_i < &GAMMA_MAX); + assert!(g_eq_i >= &0.0); + }); +} diff --git a/src/physics/single_chain/efrc/thermodynamics/mod.rs b/src/physics/single_chain/efrc/thermodynamics/mod.rs new file mode 100644 index 00000000..aa03b176 --- /dev/null +++ b/src/physics/single_chain/efrc/thermodynamics/mod.rs @@ -0,0 +1,2 @@ +/// The extensible freely-rotating chain (EFRC) model thermodynamics in the isometric ensemble. +pub mod isometric; diff --git a/src/physics/single_chain/mod.rs b/src/physics/single_chain/mod.rs index 3eaecc4c..fcb431f9 100644 --- a/src/physics/single_chain/mod.rs +++ b/src/physics/single_chain/mod.rs @@ -21,6 +21,9 @@ pub mod ufjc; /// The freely-rotating chain (FRC) single-chain model. pub mod frc; +/// The extensible freely-rotating chain (EFRC) single-chain model. +pub mod efrc; + /// The worm-like chain (WLC) single-chain model. pub mod wlc; diff --git a/tests/efjc.rs b/tests/efjc.rs index 9112aa76..87f745a4 100644 --- a/tests/efjc.rs +++ b/tests/efjc.rs @@ -10,7 +10,7 @@ const NUMBER_OF_SAMPLES: usize = 10000000; fn monte_carlo_nondimensional_equilibrium_radial_distribution() { let (gamma, g_eq) = nondimensional_equilibrium_radial_distribution::(&GAMMA_MAX, &KAPPA, NUMBER_OF_SAMPLES); - gamma.iter().zip(g_eq.iter()).for_each(|output|{ + gamma.iter().zip(g_eq.iter()).for_each(|output| println!("{:?}", output) - }); + ); } diff --git a/tests/efrc.rs b/tests/efrc.rs new file mode 100644 index 00000000..fca6a842 --- /dev/null +++ b/tests/efrc.rs @@ -0,0 +1,39 @@ +use polymers::physics::single_chain:: +{ + frc::thermodynamics::isometric::monte_carlo::nondimensional_equilibrium_radial_distribution as frc_nondimensional_equilibrium_radial_distribution, + efrc::thermodynamics::isometric::monte_carlo::nondimensional_equilibrium_radial_distribution +}; + +use std::f64::consts::PI; + +const GAMMA_MAX: f64 = 1.5; +const KAPPA: f64 = 50.0; +const KAPPA_LARGE: f64 = 1e5; +const NUMBER_OF_BINS: usize = 1000; +const NUMBER_OF_LINKS: usize = 8; +const NUMBER_OF_SAMPLES: usize = 10000000; +const THETA: f64 = PI/8.0; +const TOL: f64 = 5e-2; + +#[test] +fn monte_carlo_nondimensional_equilibrium_radial_distribution() +{ + let (gamma, g_eq) = nondimensional_equilibrium_radial_distribution::(&GAMMA_MAX, &KAPPA, &THETA, NUMBER_OF_SAMPLES); + gamma.iter().zip(g_eq.iter()).for_each(|output| + println!("{:?}", output) + ); +} + + +#[test] +fn monte_carlo_nondimensional_equilibrium_radial_distribution_frc_limit() +{ + let gamma_max = (2.0 - 2.0*(PI - THETA).cos()).sqrt()/2.0; + let (_, g_eq) = nondimensional_equilibrium_radial_distribution::(&gamma_max, &KAPPA_LARGE, &THETA, NUMBER_OF_SAMPLES); + let (_, g_eq_frc) = frc_nondimensional_equilibrium_radial_distribution::(&THETA, NUMBER_OF_SAMPLES); + let mut residual = 0.0; + g_eq.iter().zip(g_eq_frc.iter()).for_each(|(g_eq_i, g_eq_frc_i)|{ + residual = (g_eq_i - g_eq_frc_i).abs(); + assert!(residual < TOL || residual/g_eq_frc_i < TOL || g_eq_i < &TOL); + }); +} \ No newline at end of file