diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 194f990c..ec08101f 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -52,7 +52,7 @@ jobs: - name: checkout uses: actions/checkout@v4 - name: tree - run: $([[ $(cargo tree --color always --edges normal | wc -l) -eq 1 ]]) + run: cargo tree --color always --edges normal - name: build run: cargo build --color always --release --verbose - name: clippy diff --git a/Cargo.toml b/Cargo.toml index 616c6c49..ec598d54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,5 @@ python = ["dep:numpy", "dep:pyo3"] [dependencies] numpy = {version = "0.19", optional = true} pyo3 = {version = "0.19", features = ["extension-module"], optional = true} - -[dev-dependencies] rand = "0.8.5" diff --git a/conftest.py b/conftest.py index 92695a15..d21f2155 100644 --- a/conftest.py +++ b/conftest.py @@ -20,6 +20,10 @@ def pytest_collection_finish(session): run( ['sed', '-i', 's@: test@@', '__pycache__/cargo.tests'] ) + # remove monte carlo tests + run( + ['sed', '-i', '-r', '/monte/d', '__pycache__/cargo.tests'] + ) f = open("__pycache__/julia.tests", "w") run( ['grep', '-r', '@testset', 'src/'], diff --git a/src/math/mod.rs b/src/math/mod.rs index 72d96f6d..c01f9e7d 100644 --- a/src/math/mod.rs +++ b/src/math/mod.rs @@ -52,7 +52,7 @@ pub fn integrate_1d(f: &dyn Fn(&f64) -> f64, x_min: &f64, x_max: &f64, num_point pub fn integrate_1d_grid(f: &dyn Fn(&f64) -> f64, grid: &[f64], dx: &f64) -> f64 { - grid.iter().map(|x| f(x)).sum::()*dx + grid.iter().map(f).sum::()*dx } pub fn integrate_2d(f: &dyn Fn(&f64, &f64) -> f64, x_min: &f64, x_max: &f64, y_min: &f64, y_max: &f64, num_points: &u128) -> f64 diff --git a/src/physics/single_chain/fjc/thermodynamics/isometric/mod.rs b/src/physics/single_chain/fjc/thermodynamics/isometric/mod.rs index 8bb15a50..29185cbc 100644 --- a/src/physics/single_chain/fjc/thermodynamics/isometric/mod.rs +++ b/src/physics/single_chain/fjc/thermodynamics/isometric/mod.rs @@ -9,6 +9,9 @@ mod test; /// The freely-jointed chain (FJC) model thermodynamics in the isometric ensemble approximated using a Legendre transformation. pub mod legendre; +/// The freely-jointed chain (FJC) model thermodynamics in the isometric ensemble calculated using Monte Carlo methods. +pub mod monte_carlo; + use super:: { treloar_sums, diff --git a/src/physics/single_chain/fjc/thermodynamics/isometric/monte_carlo/mod.rs b/src/physics/single_chain/fjc/thermodynamics/isometric/monte_carlo/mod.rs new file mode 100644 index 00000000..da4aee4f --- /dev/null +++ b/src/physics/single_chain/fjc/thermodynamics/isometric/monte_carlo/mod.rs @@ -0,0 +1,58 @@ +mod test; + +use rand::prelude::*; + +use std::f64::consts::TAU as TWO_PI; + +pub fn random_configuration(rng: &mut ThreadRng) -> [[f64; 3]; NUMBER_OF_LINKS] +{ + let mut phi: f64 = 0.0; + let mut theta: f64 = 0.0; + let mut position = [0.0; 3]; + let mut configuration = [[0.0; 3]; NUMBER_OF_LINKS]; + configuration.iter_mut().for_each(|coordinate|{ + phi = TWO_PI * rng.gen::(); + theta = (1.0 - 2.0 * rng.gen::()).acos(); + position[0] += theta.sin() * phi.cos(); + position[1] += theta.sin() * phi.sin(); + position[2] += theta.cos(); + 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(rng: &mut ThreadRng) -> f64 +{ + random_configuration::(rng)[NUMBER_OF_LINKS - 1].iter().map(|entry| entry * entry).sum::().sqrt() +} + +pub fn nondimensional_equilibrium_radial_distribution(number_of_samples: usize) -> ([f64; NUMBER_OF_BINS], [f64; NUMBER_OF_BINS]) +{ + let mut rng = rand::thread_rng(); + 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 = ((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::(&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 = (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/fjc/thermodynamics/isometric/monte_carlo/test.rs b/src/physics/single_chain/fjc/thermodynamics/isometric/monte_carlo/test.rs new file mode 100644 index 00000000..10a0d132 --- /dev/null +++ b/src/physics/single_chain/fjc/thermodynamics/isometric/monte_carlo/test.rs @@ -0,0 +1,38 @@ +#![cfg(test)] + +use super::*; + +const NUMBER_OF_BINS: usize = 100; +const NUMBER_OF_LINKS: usize = 8; +const NUMBER_OF_SAMPLES: usize = 100000; + +#[test] +fn monte_carlo_random_configuration() +{ + let mut rng = rand::thread_rng(); + let configuration = random_configuration::(&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 gamma = random_nondimensional_end_to_end_length::(&mut rng)/(NUMBER_OF_LINKS as f64); + assert!(gamma >= 0.0); + assert!(gamma <= 1.0); +} + +#[test] +fn monte_carlo_nondimensional_equilibrium_radial_distribution() +{ + let (gamma, g_eq) = nondimensional_equilibrium_radial_distribution::(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 < &1.0); + assert!(g_eq_i >= &0.0); + }); +} \ No newline at end of file diff --git a/tests/fjc.rs b/tests/fjc.rs new file mode 100644 index 00000000..82966e27 --- /dev/null +++ b/tests/fjc.rs @@ -0,0 +1,23 @@ +use polymers::physics::single_chain::fjc::thermodynamics::isometric:: +{ + nondimensional_equilibrium_radial_distribution as check_geq, + monte_carlo::nondimensional_equilibrium_radial_distribution +}; + +const NUMBER_OF_BINS: usize = 1000; +const NUMBER_OF_LINKS: usize = 8; +const NUMBER_OF_SAMPLES: usize = 10000000; +const TOL: f64 = 5e-2; + +#[test] +fn monte_carlo_nondimensional_equilibrium_radial_distribution() +{ + let (gamma, g_eq) = nondimensional_equilibrium_radial_distribution::(NUMBER_OF_SAMPLES); + let mut check = 0.0; + let mut residual = 0.0; + gamma.iter().zip(g_eq.iter()).for_each(|(gamma_i, g_eq_i)|{ + check = check_geq(&(NUMBER_OF_LINKS as u8), &gamma_i); + residual = (g_eq_i - &check).abs(); + assert!(residual < TOL || residual/check < TOL || g_eq_i < &TOL); + }); +}