Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EFRC isometric Monte Carlo #215

Merged
merged 4 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/physics/single_chain/efrc/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/// The extensible freely-rotating chain (EFRC) model thermodynamics.
pub mod thermodynamics;
2 changes: 2 additions & 0 deletions src/physics/single_chain/efrc/thermodynamics/isometric/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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<const NUMBER_OF_LINKS: usize>(theta: &f64, dist: Normal<f64>, 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::<f64>().sqrt();
r_n.iter_mut().for_each(|r_n_i| *r_n_i /= r_normalization);
phi = TWO_PI * rng.gen::<f64>();
phi_cos = phi.cos();
phi_sin = phi.sin();
t = std::array::from_fn(|_| rng.gen::<f64>());
u = cross(&r_n, &t);
u_normalization = u.iter().map(|u_i| u_i * u_i).sum::<f64>().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<const NUMBER_OF_LINKS: usize>(theta: &f64, dist: Normal<f64>, rng: &mut ThreadRng) -> f64
{
random_configuration::<NUMBER_OF_LINKS>(theta, dist, rng)[NUMBER_OF_LINKS - 1].iter().map(|entry| entry * entry).sum::<f64>().sqrt()
}

pub fn nondimensional_equilibrium_radial_distribution<const NUMBER_OF_BINS: usize, const NUMBER_OF_LINKS: usize>(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::<NUMBER_OF_LINKS>(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)
}
Original file line number Diff line number Diff line change
@@ -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::<NUMBER_OF_LINKS>(&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::<NUMBER_OF_LINKS>(&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::<NUMBER_OF_BINS, NUMBER_OF_LINKS>(&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);
});
}
2 changes: 2 additions & 0 deletions src/physics/single_chain/efrc/thermodynamics/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/// The extensible freely-rotating chain (EFRC) model thermodynamics in the isometric ensemble.
pub mod isometric;
3 changes: 3 additions & 0 deletions src/physics/single_chain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
4 changes: 2 additions & 2 deletions tests/efjc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<NUMBER_OF_BINS, NUMBER_OF_LINKS>(&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)
});
);
}
39 changes: 39 additions & 0 deletions tests/efrc.rs
Original file line number Diff line number Diff line change
@@ -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::<NUMBER_OF_BINS, NUMBER_OF_LINKS>(&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::<NUMBER_OF_BINS, NUMBER_OF_LINKS>(&gamma_max, &KAPPA_LARGE, &THETA, NUMBER_OF_SAMPLES);
let (_, g_eq_frc) = frc_nondimensional_equilibrium_radial_distribution::<NUMBER_OF_BINS, NUMBER_OF_LINKS>(&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);
});
}
Loading