From 14813476a132ad37e264de6a45ca752b78f9732e Mon Sep 17 00:00:00 2001 From: mrbuche Date: Fri, 14 Apr 2023 18:10:03 -0600 Subject: [PATCH 1/5] do dist here like fjc since finite integration and temperature independent --- .../single_chain/swfjc/thermodynamics/test.rs | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/physics/single_chain/swfjc/thermodynamics/test.rs b/src/physics/single_chain/swfjc/thermodynamics/test.rs index fa36a123..01a429a5 100644 --- a/src/physics/single_chain/swfjc/thermodynamics/test.rs +++ b/src/physics/single_chain/swfjc/thermodynamics/test.rs @@ -74,3 +74,63 @@ mod base } } } +#[test] +fn should_implement_isometric_legendre_distributions_here_since_not_temperature_dependent_and_has_finite_integration_limit() +{ + assert!(0.0 == 1.0); +} +mod legendre +{ + use super::*; + use rand::Rng; + #[test] + fn force() + { + assert!(0.0 == 1.0); + } + #[test] + fn nondimensional_force() + { + assert!(0.0 == 1.0); + } + #[test] + fn helmholtz_free_energy() + { + assert!(0.0 == 1.0); + } + #[test] + fn helmholtz_free_energy_per_link() + { + assert!(0.0 == 1.0); + } + #[test] + fn relative_helmholtz_free_energy() + { + assert!(0.0 == 1.0); + } + #[test] + fn relative_helmholtz_free_energy_per_link() + { + assert!(0.0 == 1.0); + } + #[test] + fn nondimensional_helmholtz_free_energy() + { + assert!(0.0 == 1.0); + } + #[test] + fn nondimensional_helmholtz_free_energy_per_link() + { + assert!(0.0 == 1.0); + } + #[test] + fn nondimensional_relative_helmholtz_free_energy() + { + assert!(0.0 == 1.0); + } + #[test] + fn nondimensional_relative_helmholtz_free_energy_per_link() + { + assert!(0.0 == 1.0); + } +} \ No newline at end of file From 140e9bf134ff5bb1a42a1faf5a7d00fd597f6e76 Mon Sep 17 00:00:00 2001 From: mrbuche Date: Mon, 17 Apr 2023 11:30:41 -0600 Subject: [PATCH 2/5] only really rated to small well widths (ZERO scale for eta/gamma disparate for large well widths; also many other tests fail) --- .../thermodynamics/isometric/legendre/mod.rs | 239 +++++++ .../thermodynamics/isometric/legendre/test.rs | 657 ++++++++++++++++++ .../swfjc/thermodynamics/isometric/mod.rs | 43 ++ .../swfjc/thermodynamics/isometric/test.rs | 76 ++ .../swfjc/thermodynamics/isotensional/mod.rs | 4 - .../single_chain/swfjc/thermodynamics/mod.rs | 9 +- .../single_chain/swfjc/thermodynamics/test.rs | 211 +++++- 7 files changed, 1219 insertions(+), 20 deletions(-) create mode 100644 src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/mod.rs create mode 100644 src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/test.rs create mode 100644 src/physics/single_chain/swfjc/thermodynamics/isometric/mod.rs create mode 100644 src/physics/single_chain/swfjc/thermodynamics/isometric/test.rs diff --git a/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/mod.rs b/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/mod.rs new file mode 100644 index 00000000..84c7b772 --- /dev/null +++ b/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/mod.rs @@ -0,0 +1,239 @@ +#[cfg(feature = "extern")] +pub mod ex; + +#[cfg(feature = "python")] +pub mod py; + +mod test; + +use std::f64::consts::PI; +use crate::math:: +{ + inverse_langevin, + inverse_newton_raphson, + integrate_1d +}; +use crate::physics:: +{ + PLANCK_CONSTANT, + BOLTZMANN_CONSTANT +}; +use crate::physics::single_chain:: +{ + ONE, + ZERO, + POINTS +}; + +/// The structure of the thermodynamics of the SWFJC model in the isometric ensemble approximated using a Legendre transformation. +pub struct SWFJC +{ + /// The mass of each hinge in the chain in units of kg/mol. + pub hinge_mass: f64, + + /// The length of each link in the chain in units of nm. + pub link_length: f64, + + /// The number of links in the chain. + pub number_of_links: u8, + + /// The width of the well in units of nm. + pub well_width: f64, + + normalization_nondimensional_equilibrium_distribution: f64 +} + +/// The expected force as a function of the applied end-to-end length and temperature, parameterized by the number of links, link length, and well width. +pub fn force(number_of_links: &u8, link_length: &f64, well_width: &f64, end_to_end_length: &f64, temperature: &f64) -> f64 +{ + BOLTZMANN_CONSTANT*temperature/link_length*nondimensional_force(link_length, well_width, &(end_to_end_length/((*number_of_links as f64)*link_length))) +} + +/// The expected nondimensional force as a function of the applied nondimensional end-to-end length per link, parameterized by the link length and well width. +pub fn nondimensional_force(link_length: &f64, well_width: &f64, nondimensional_end_to_end_length_per_link: &f64) -> f64 +{ + let nondimensional_well_parameter = 1.0 + well_width/link_length; + if nondimensional_end_to_end_length_per_link <= &1e-3 + { + nondimensional_end_to_end_length_per_link*5.0*(nondimensional_well_parameter.powi(2) + nondimensional_well_parameter + 1.0)/(nondimensional_well_parameter.powi(4) + nondimensional_well_parameter.powi(3) + nondimensional_well_parameter.powi(2) + nondimensional_well_parameter + 1.0) + } + else + { + let guess: f64 = if nondimensional_end_to_end_length_per_link/nondimensional_well_parameter < 0.9 + { + inverse_langevin(&(nondimensional_end_to_end_length_per_link/nondimensional_well_parameter)) + } + else + { + 10.0 + }; + inverse_newton_raphson(nondimensional_end_to_end_length_per_link, &|nondimensional_force: &f64| (nondimensional_well_parameter.powi(2)*nondimensional_force*(nondimensional_well_parameter*nondimensional_force).sinh() - nondimensional_force*nondimensional_force.sinh())/(nondimensional_well_parameter*nondimensional_force*(nondimensional_well_parameter*nondimensional_force).cosh() - (nondimensional_well_parameter*nondimensional_force).sinh() - nondimensional_force*nondimensional_force.cosh() + nondimensional_force.sinh()) - 3.0/nondimensional_force, &|nondimensional_force: &f64| (nondimensional_force.sinh() + nondimensional_force*nondimensional_force.cosh() - nondimensional_well_parameter.powi(3)*nondimensional_force*(nondimensional_well_parameter*nondimensional_force).cosh() - nondimensional_well_parameter.powi(2)*(nondimensional_well_parameter*nondimensional_force).sinh())/((nondimensional_well_parameter*nondimensional_force).sinh() - nondimensional_well_parameter*nondimensional_force*(nondimensional_well_parameter*nondimensional_force).cosh() - nondimensional_force.sinh() + nondimensional_force*nondimensional_force.cosh()) - (nondimensional_force.powi(2)*(nondimensional_force.sinh() - nondimensional_well_parameter.powi(2)*(nondimensional_well_parameter*nondimensional_force).sinh()).powi(2))/((nondimensional_well_parameter*nondimensional_force).sinh() - nondimensional_well_parameter*nondimensional_force*(nondimensional_well_parameter*nondimensional_force).cosh() - nondimensional_force.sinh() + nondimensional_force*nondimensional_force.cosh()).powi(2) + 3.0/nondimensional_force.powi(2), &guess, &1e-2, &100) + } +} + +/// The Helmholtz free energy as a function of the applied end-to-end length and temperature, parameterized by the number of links, link length, well width, and hinge mass. +pub fn helmholtz_free_energy(number_of_links: &u8, link_length: &f64, hinge_mass: &f64, well_width: &f64, end_to_end_length: &f64, temperature: &f64) -> f64 +{ + nondimensional_helmholtz_free_energy(number_of_links, link_length, hinge_mass, well_width, &(end_to_end_length/((*number_of_links as f64)*link_length)), temperature)*BOLTZMANN_CONSTANT*temperature +} + +/// The Helmholtz free energy per link as a function of the applied end-to-end length and temperature, parameterized by the number of links, link length, well width, and hinge mass. +pub fn helmholtz_free_energy_per_link(number_of_links: &u8, link_length: &f64, hinge_mass: &f64, well_width: &f64, end_to_end_length: &f64, temperature: &f64) -> f64 +{ + nondimensional_helmholtz_free_energy_per_link(number_of_links, link_length, hinge_mass, well_width, &(end_to_end_length/((*number_of_links as f64)*link_length)), temperature)*BOLTZMANN_CONSTANT*temperature +} + +/// The relative Helmholtz free energy as a function of the applied end-to-end length and temperature, parameterized by the number of links, link length, and well width. +pub fn relative_helmholtz_free_energy(number_of_links: &u8, link_length: &f64, well_width: &f64, end_to_end_length: &f64, temperature: &f64) -> f64 +{ + helmholtz_free_energy(number_of_links, link_length, &1.0, well_width, end_to_end_length, temperature) - helmholtz_free_energy(number_of_links, link_length, &1.0, well_width, &(ZERO*(*number_of_links as f64)*link_length), temperature) +} + +/// The relative Helmholtz free energy per link as a function of the applied end-to-end length and temperature, parameterized by the number of links, link length, and well width. +pub fn relative_helmholtz_free_energy_per_link(number_of_links: &u8, link_length: &f64, well_width: &f64, end_to_end_length: &f64, temperature: &f64) -> f64 +{ + helmholtz_free_energy_per_link(number_of_links, link_length, &1.0, well_width, end_to_end_length, temperature) - helmholtz_free_energy_per_link(number_of_links, link_length, &1.0, well_width, &(ZERO*(*number_of_links as f64)*link_length), temperature) +} + +/// The nondimensional Helmholtz free energy as a function of the nondimensional end-to-end length per link and temperature, parameterized by the number of links, link length, well width, and hinge mass. +pub fn nondimensional_helmholtz_free_energy(number_of_links: &u8, link_length: &f64, hinge_mass: &f64, well_width: &f64, nondimensional_end_to_end_length_per_link: &f64, temperature: &f64) -> f64 +{ + (*number_of_links as f64)*nondimensional_helmholtz_free_energy_per_link(number_of_links, link_length, hinge_mass, well_width, nondimensional_end_to_end_length_per_link, temperature) +} + +/// The nondimensional Helmholtz free energy per link as a function of the nondimensional end-to-end length per link and temperature, parameterized by the number of links, link length, well width, and hinge mass. +pub fn nondimensional_helmholtz_free_energy_per_link(number_of_links: &u8, link_length: &f64, hinge_mass: &f64, well_width: &f64, nondimensional_end_to_end_length_per_link: &f64, temperature: &f64) -> f64 +{ + let nondimensional_well_parameter = 1.0 + well_width/link_length; + let nondimensional_force = nondimensional_force(link_length, well_width, nondimensional_end_to_end_length_per_link); + nondimensional_force**nondimensional_end_to_end_length_per_link + 3.0*nondimensional_force.ln() - (nondimensional_well_parameter*nondimensional_force*(nondimensional_well_parameter*nondimensional_force).cosh() - (nondimensional_well_parameter*nondimensional_force).sinh() - nondimensional_force*nondimensional_force.cosh() + nondimensional_force.sinh()).ln() - (1.0 - 1.0/(*number_of_links as f64))*(8.0*PI.powi(2)*hinge_mass*link_length.powi(2)*BOLTZMANN_CONSTANT*temperature/PLANCK_CONSTANT.powi(2)).ln() +} + +/// The nondimensional relative Helmholtz free energy as a function of the nondimensional end-to-end length per link, parameterized by the number of links, link length, and well width. +pub fn nondimensional_relative_helmholtz_free_energy(number_of_links: &u8, link_length: &f64, well_width: &f64, nondimensional_end_to_end_length_per_link: &f64) -> f64 +{ + nondimensional_helmholtz_free_energy(number_of_links, link_length, &1.0, well_width, nondimensional_end_to_end_length_per_link, &300.0) - nondimensional_helmholtz_free_energy(number_of_links, link_length, &1.0, well_width, &ZERO, &300.0) +} + +/// The nondimensional relative Helmholtz free energy per link as a function of the nondimensional end-to-end length per link, parameterized by the link length and well width. +pub fn nondimensional_relative_helmholtz_free_energy_per_link(link_length: &f64, well_width: &f64, nondimensional_end_to_end_length_per_link: &f64) -> f64 +{ + nondimensional_helmholtz_free_energy_per_link(&8, link_length, &1.0, well_width, nondimensional_end_to_end_length_per_link, &300.0) - nondimensional_helmholtz_free_energy_per_link(&8, link_length, &1.0, well_width, &ZERO, &300.0) +} + +/// The equilibrium probability density of end-to-end vectors as a function of the end-to-end length, parameterized by the number of links and link length. +pub fn equilibrium_distribution(number_of_links: &u8, link_length: &f64, well_width: &f64, normalization_nondimensional_equilibrium_distribution: &f64, end_to_end_length: &f64) -> f64 +{ + let contour_length = (*number_of_links as f64)*link_length; + nondimensional_equilibrium_distribution(number_of_links, link_length, well_width, normalization_nondimensional_equilibrium_distribution, &(end_to_end_length/contour_length))/contour_length.powi(3) +} + +/// The nondimensional equilibrium probability density of nondimensional end-to-end vectors per link as a function of the nondimensional end-to-end length per link, parameterized by the number of links. +pub fn nondimensional_equilibrium_distribution(number_of_links: &u8, link_length: &f64, well_width: &f64, normalization_nondimensional_equilibrium_distribution: &f64, nondimensional_end_to_end_length_per_link: &f64) -> f64 +{ + let nondimensional_well_parameter = 1.0 + well_width/link_length; + let nondimensional_force = nondimensional_force(link_length, well_width, nondimensional_end_to_end_length_per_link); + ((nondimensional_well_parameter*nondimensional_force*(nondimensional_well_parameter*nondimensional_force).cosh() - (nondimensional_well_parameter*nondimensional_force).sinh() - nondimensional_force*nondimensional_force.cosh() + nondimensional_force.sinh())/nondimensional_force.powi(3)*(-nondimensional_force**nondimensional_end_to_end_length_per_link).exp()).powi(*number_of_links as i32)/normalization_nondimensional_equilibrium_distribution +} + +/// The equilibrium probability density of end-to-end lengths as a function of the end-to-end length, parameterized by the number of links and link length. +pub fn equilibrium_radial_distribution(number_of_links: &u8, link_length: &f64, well_width: &f64, normalization_nondimensional_equilibrium_distribution: &f64, end_to_end_length: &f64) -> f64 +{ + let contour_length = (*number_of_links as f64)*link_length; + nondimensional_equilibrium_radial_distribution(number_of_links, link_length, well_width, normalization_nondimensional_equilibrium_distribution, &(end_to_end_length/contour_length))/contour_length +} + +/// The nondimensional equilibrium probability density of nondimensional end-to-end lengths per link as a function of the nondimensional end-to-end length per link, parameterized by the number of links. +pub fn nondimensional_equilibrium_radial_distribution(number_of_links: &u8, link_length: &f64, well_width: &f64, normalization_nondimensional_equilibrium_distribution: &f64, nondimensional_end_to_end_length_per_link: &f64) -> f64 +{ + 4.0*PI*nondimensional_end_to_end_length_per_link.powi(2)*nondimensional_equilibrium_distribution(number_of_links, link_length, well_width, normalization_nondimensional_equilibrium_distribution, nondimensional_end_to_end_length_per_link) +} + +/// The implemented functionality of the thermodynamics of the SWFJC model in the isotensional ensemble approximated using a Legendre transformation. +impl SWFJC +{ + /// Initializes and returns an instance of the thermodynamics of the SWFJC model in the isotensional ensemble approximated using a Legendre transformation. + pub fn init(number_of_links: u8, link_length: f64, hinge_mass: f64, well_width: f64) -> Self + { + let nondimensional_well_parameter = 1.0 + well_width/link_length; + let normalization = integrate_1d(&|nondimensional_end_to_end_length_per_link: &f64| nondimensional_equilibrium_radial_distribution(&number_of_links, &link_length, &well_width, &1.0, nondimensional_end_to_end_length_per_link), &ZERO, &(ONE*nondimensional_well_parameter), &POINTS); + SWFJC + { + hinge_mass, + link_length, + number_of_links, + well_width, + normalization_nondimensional_equilibrium_distribution: normalization + } + } + /// The expected force as a function of the applied end-to-end length and temperature. + pub fn force(&self, end_to_end_length: &f64, temperature: &f64) -> f64 + { + force(&self.number_of_links, &self.link_length, &self.well_width, end_to_end_length, temperature) + } + /// The expected nondimensional force as a function of the applied nondimensional end-to-end length per link. + pub fn nondimensional_force(&self, nondimensional_end_to_end_length_per_link: &f64) -> f64 + { + nondimensional_force(&self.link_length, &self.well_width, nondimensional_end_to_end_length_per_link) + } + /// The Helmholtz free energy as a function of the applied end-to-end length and temperature. + pub fn helmholtz_free_energy(&self, end_to_end_length: &f64, temperature: &f64) -> f64 + { + helmholtz_free_energy(&self.number_of_links, &self.link_length, &self.hinge_mass, &self.well_width, end_to_end_length, temperature) + } + /// The Helmholtz free energy per link as a function of the applied end-to-end length and temperature. + pub fn helmholtz_free_energy_per_link(&self, end_to_end_length: &f64, temperature: &f64) -> f64 + { + helmholtz_free_energy_per_link(&self.number_of_links, &self.link_length, &self.hinge_mass, &self.well_width, end_to_end_length, temperature) + } + /// The relative Helmholtz free energy as a function of the applied end-to-end length and temperature. + pub fn relative_helmholtz_free_energy(&self, end_to_end_length: &f64, temperature: &f64) -> f64 + { + relative_helmholtz_free_energy(&self.number_of_links, &self.link_length, &self.well_width, end_to_end_length, temperature) + } + /// The relative Helmholtz free energy per link as a function of the applied end-to-end length and temperature. + pub fn relative_helmholtz_free_energy_per_link(&self, end_to_end_length: &f64, temperature: &f64) -> f64 + { + relative_helmholtz_free_energy_per_link(&self.number_of_links, &self.link_length, &self.well_width, end_to_end_length, temperature) + } + /// The nondimensional Helmholtz free energy as a function of the applied nondimensional end-to-end length per link and temperature. + pub fn nondimensional_helmholtz_free_energy(&self, nondimensional_end_to_end_length_per_link: &f64, temperature: &f64) -> f64 + { + nondimensional_helmholtz_free_energy(&self.number_of_links, &self.link_length, &self.hinge_mass, &self.well_width, nondimensional_end_to_end_length_per_link, temperature) + } + /// The nondimensional Helmholtz free energy per link as a function of the applied nondimensional end-to-end length per link and temperature. + pub fn nondimensional_helmholtz_free_energy_per_link(&self, nondimensional_end_to_end_length_per_link: &f64, temperature: &f64) -> f64 + { + nondimensional_helmholtz_free_energy_per_link(&self.number_of_links, &self.link_length, &self.hinge_mass, &self.well_width, nondimensional_end_to_end_length_per_link, temperature) + } + /// The nondimensional relative Helmholtz free energy as a function of the applied nondimensional end-to-end length per link. + pub fn nondimensional_relative_helmholtz_free_energy(&self, nondimensional_end_to_end_length_per_link: &f64) -> f64 + { + nondimensional_relative_helmholtz_free_energy(&self.number_of_links, &self.link_length, &self.well_width, nondimensional_end_to_end_length_per_link) + } + /// The nondimensional relative Helmholtz free energy per link as a function of the applied nondimensional end-to-end length per link. + pub fn nondimensional_relative_helmholtz_free_energy_per_link(&self, nondimensional_end_to_end_length_per_link: &f64) -> f64 + { + nondimensional_relative_helmholtz_free_energy_per_link(&self.link_length, &self.well_width, nondimensional_end_to_end_length_per_link) + } + /// The equilibrium probability density of end-to-end vectors as a function of the end-to-end length. + pub fn equilibrium_distribution(&self, end_to_end_length: &f64) -> f64 + { + equilibrium_distribution(&self.number_of_links, &self.link_length, &self.well_width, &self.normalization_nondimensional_equilibrium_distribution, end_to_end_length) + } + /// The equilibrium probability density of nondimensional end-to-end vectors per link as a function of the nondimensional end-to-end length per link. + pub fn nondimensional_equilibrium_distribution(&self, nondimensional_end_to_end_length_per_link: &f64) -> f64 + { + nondimensional_equilibrium_distribution(&self.number_of_links, &self.link_length, &self.well_width, &self.normalization_nondimensional_equilibrium_distribution, nondimensional_end_to_end_length_per_link) + } + /// The equilibrium probability density of end-to-end lengths as a function of the end-to-end length. + pub fn equilibrium_radial_distribution(&self, end_to_end_length: &f64) -> f64 + { + equilibrium_radial_distribution(&self.number_of_links, &self.link_length, &self.well_width, &self.normalization_nondimensional_equilibrium_distribution, end_to_end_length) + } + /// The equilibrium probability density of nondimensional end-to-end lengths per link as a function of the nondimensional end-to-end length per link. + pub fn nondimensional_equilibrium_radial_distribution(&self, nondimensional_end_to_end_length_per_link: &f64) -> f64 + { + nondimensional_equilibrium_radial_distribution(&self.number_of_links, &self.link_length, &self.well_width, &self.normalization_nondimensional_equilibrium_distribution, nondimensional_end_to_end_length_per_link) + } +} diff --git a/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/test.rs b/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/test.rs new file mode 100644 index 00000000..a5d052de --- /dev/null +++ b/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/test.rs @@ -0,0 +1,657 @@ +#![cfg(test)] +use super::*; +use crate::physics::single_chain::test::Parameters; +mod base +{ + use super::*; + use rand::Rng; + #[test] + fn init() + { + let parameters = Parameters::default(); + let _ = SWFJC::init(parameters.number_of_links_minimum, parameters.link_length_reference, parameters.hinge_mass_reference, parameters.well_width_reference); + } + #[test] + fn number_of_links() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + assert_eq!(number_of_links, SWFJC::init(number_of_links, parameters.link_length_reference, parameters.hinge_mass_reference, parameters.well_width_reference).number_of_links); + } + } + #[test] + fn link_length() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + assert_eq!(link_length, SWFJC::init(parameters.number_of_links_minimum, link_length, parameters.hinge_mass_reference, parameters.well_width_reference).link_length); + } + } + #[test] + fn hinge_mass() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + assert_eq!(hinge_mass, SWFJC::init(parameters.number_of_links_minimum, parameters.link_length_reference, hinge_mass, parameters.well_width_reference).hinge_mass); + } + } + #[test] + fn well_width() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + assert_eq!(well_width, SWFJC::init(parameters.number_of_links_minimum, parameters.link_length_reference, parameters.hinge_mass_reference, well_width).well_width); + } + } + #[test] + fn all_parameters() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + assert_eq!(number_of_links, model.number_of_links); + assert_eq!(link_length, model.link_length); + assert_eq!(hinge_mass, model.hinge_mass); + assert_eq!(well_width, model.well_width); + } + } +} +mod normalization +{ + use super::*; + use rand::Rng; + use crate::math::integrate_1d; + #[test] + fn equilibrium_distribution() + { + let parameters = Parameters::default(); + let mut rng = rand::thread_rng(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let integrand = |end_to_end_length: &f64| 4.0*PI*end_to_end_length.powi(2)*model.equilibrium_distribution(&end_to_end_length); + let integral = integrate_1d(&integrand, &ZERO, &(ONE*(number_of_links as f64)*(link_length + well_width)), &POINTS); + assert!((integral - 1.0).abs() <= parameters.rel_tol); + } + } + #[test] + fn nondimensional_equilibrium_distribution() + { + let parameters = Parameters::default(); + let mut rng = rand::thread_rng(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let integrand = |nondimensional_end_to_end_length_per_link_per_link: &f64| 4.0*PI*nondimensional_end_to_end_length_per_link_per_link.powi(2)*model.nondimensional_equilibrium_distribution(&nondimensional_end_to_end_length_per_link_per_link); + let nondimensional_well_parameter = 1.0 + well_width/link_length; + let integral = integrate_1d(&integrand, &ZERO, &(ONE*nondimensional_well_parameter), &POINTS); + assert!((integral - 1.0).abs() <= parameters.rel_tol); + } + } + #[test] + fn equilibrium_radial_distribution() + { + let parameters = Parameters::default(); + let mut rng = rand::thread_rng(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let integrand = |end_to_end_length: &f64| model.equilibrium_radial_distribution(&end_to_end_length); + let integral = integrate_1d(&integrand, &ZERO, &(ONE*(number_of_links as f64)*(link_length + well_width)), &POINTS); + assert!((integral - 1.0).abs() <= parameters.rel_tol); + } + } + #[test] + fn nondimensional_equilibrium_radial_distribution() + { + let parameters = Parameters::default(); + let mut rng = rand::thread_rng(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let integrand = |nondimensional_end_to_end_length_per_link_per_link: &f64| model.nondimensional_equilibrium_radial_distribution(&nondimensional_end_to_end_length_per_link_per_link); + let nondimensional_well_parameter = 1.0 + well_width/link_length; + let integral = integrate_1d(&integrand, &ZERO, &(ONE*nondimensional_well_parameter), &POINTS); + assert!((integral - 1.0).abs() <= parameters.rel_tol); + } + } +} +mod nondimensional +{ + use super::*; + use rand::Rng; + #[test] + fn force() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_end_to_end_length_per_link = parameters.nondimensional_end_to_end_length_per_link_reference + parameters.nondimensional_end_to_end_length_per_link_scale*(0.5 - rng.gen::()); + let temperature = parameters.temperature_reference + parameters.temperature_scale*(0.5 - rng.gen::()); + let nondimensional_force = model.nondimensional_force(&nondimensional_end_to_end_length_per_link); + let end_to_end_length = nondimensional_end_to_end_length_per_link*(number_of_links as f64)*link_length; + let force = model.force(&end_to_end_length, &temperature); + let residual_abs = &force/BOLTZMANN_CONSTANT/temperature*link_length - &nondimensional_force; + let residual_rel = &residual_abs/&nondimensional_force; + assert!(residual_abs.abs() <= parameters.abs_tol); + assert!(residual_rel.abs() <= parameters.rel_tol); + } + } + #[test] + fn helmholtz_free_energy() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_end_to_end_length_per_link = parameters.nondimensional_end_to_end_length_per_link_reference + parameters.nondimensional_end_to_end_length_per_link_scale*(0.5 - rng.gen::()); + let temperature = parameters.temperature_reference + parameters.temperature_scale*(0.5 - rng.gen::()); + let nondimensional_helmholtz_free_energy = model.nondimensional_helmholtz_free_energy(&nondimensional_end_to_end_length_per_link, &temperature); + let end_to_end_length = nondimensional_end_to_end_length_per_link*(number_of_links as f64)*link_length; + let helmholtz_free_energy = model.helmholtz_free_energy(&end_to_end_length, &temperature); + let residual_abs = helmholtz_free_energy/BOLTZMANN_CONSTANT/temperature - nondimensional_helmholtz_free_energy; + let residual_rel = residual_abs/nondimensional_helmholtz_free_energy; + assert!(residual_abs.abs() <= parameters.abs_tol); + assert!(residual_rel.abs() <= parameters.rel_tol); + } + } + #[test] + fn helmholtz_free_energy_per_link() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_end_to_end_length_per_link = parameters.nondimensional_end_to_end_length_per_link_reference + parameters.nondimensional_end_to_end_length_per_link_scale*(0.5 - rng.gen::()); + let temperature = parameters.temperature_reference + parameters.temperature_scale*(0.5 - rng.gen::()); + let nondimensional_helmholtz_free_energy_per_link = model.nondimensional_helmholtz_free_energy_per_link(&nondimensional_end_to_end_length_per_link, &temperature); + let end_to_end_length = nondimensional_end_to_end_length_per_link*(number_of_links as f64)*link_length; + let helmholtz_free_energy_per_link = model.helmholtz_free_energy_per_link(&end_to_end_length, &temperature); + let residual_abs = helmholtz_free_energy_per_link/BOLTZMANN_CONSTANT/temperature - nondimensional_helmholtz_free_energy_per_link; + let residual_rel = residual_abs/nondimensional_helmholtz_free_energy_per_link; + assert!(residual_abs.abs() <= parameters.abs_tol); + assert!(residual_rel.abs() <= parameters.rel_tol); + } + } + #[test] + fn relative_helmholtz_free_energy() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_end_to_end_length_per_link = parameters.nondimensional_end_to_end_length_per_link_reference + parameters.nondimensional_end_to_end_length_per_link_scale*(0.5 - rng.gen::()); + let temperature = parameters.temperature_reference + parameters.temperature_scale*(0.5 - rng.gen::()); + let nondimensional_relative_helmholtz_free_energy = model.nondimensional_relative_helmholtz_free_energy(&nondimensional_end_to_end_length_per_link); + let end_to_end_length = nondimensional_end_to_end_length_per_link*(number_of_links as f64)*link_length; + let relative_helmholtz_free_energy = model.relative_helmholtz_free_energy(&end_to_end_length, &temperature); + let residual_abs = relative_helmholtz_free_energy/BOLTZMANN_CONSTANT/temperature - nondimensional_relative_helmholtz_free_energy; + let residual_rel = residual_abs/nondimensional_relative_helmholtz_free_energy; + assert!(residual_abs.abs() <= parameters.abs_tol); + assert!(residual_rel.abs() <= parameters.rel_tol); + } + } + #[test] + fn relative_helmholtz_free_energy_per_link() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_end_to_end_length_per_link = parameters.nondimensional_end_to_end_length_per_link_reference + parameters.nondimensional_end_to_end_length_per_link_scale*(0.5 - rng.gen::()); + let temperature = parameters.temperature_reference + parameters.temperature_scale*(0.5 - rng.gen::()); + let nondimensional_relative_helmholtz_free_energy_per_link = model.nondimensional_relative_helmholtz_free_energy_per_link(&nondimensional_end_to_end_length_per_link); + let end_to_end_length = nondimensional_end_to_end_length_per_link*(number_of_links as f64)*link_length; + let relative_helmholtz_free_energy_per_link = model.relative_helmholtz_free_energy_per_link(&end_to_end_length, &temperature); + let residual_abs = relative_helmholtz_free_energy_per_link/BOLTZMANN_CONSTANT/temperature - nondimensional_relative_helmholtz_free_energy_per_link; + let residual_rel = residual_abs/nondimensional_relative_helmholtz_free_energy_per_link; + assert!(residual_abs.abs() <= parameters.abs_tol); + assert!(residual_rel.abs() <= parameters.rel_tol); + } + } +} +mod per_link +{ + use super::*; + use rand::Rng; + #[test] + fn helmholtz_free_energy() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_end_to_end_length_per_link = parameters.nondimensional_end_to_end_length_per_link_reference + parameters.nondimensional_end_to_end_length_per_link_scale*(0.5 - rng.gen::()); + let end_to_end_length = nondimensional_end_to_end_length_per_link*(number_of_links as f64)*link_length; + let temperature = parameters.temperature_reference + parameters.temperature_scale*(0.5 - rng.gen::()); + let helmholtz_free_energy = model.helmholtz_free_energy(&end_to_end_length, &temperature); + let helmholtz_free_energy_per_link = model.helmholtz_free_energy_per_link(&end_to_end_length, &temperature); + let residual_abs = helmholtz_free_energy/(number_of_links as f64) - helmholtz_free_energy_per_link; + let residual_rel = residual_abs/helmholtz_free_energy_per_link; + assert!(residual_abs.abs() <= parameters.abs_tol); + assert!(residual_rel.abs() <= parameters.rel_tol); + } + } + #[test] + fn relative_helmholtz_free_energy() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_end_to_end_length_per_link = parameters.nondimensional_end_to_end_length_per_link_reference + parameters.nondimensional_end_to_end_length_per_link_scale*(0.5 - rng.gen::()); + let end_to_end_length = nondimensional_end_to_end_length_per_link*(number_of_links as f64)*link_length; + let temperature = parameters.temperature_reference + parameters.temperature_scale*(0.5 - rng.gen::()); + let relative_helmholtz_free_energy = model.relative_helmholtz_free_energy(&end_to_end_length, &temperature); + let relative_helmholtz_free_energy_per_link = model.relative_helmholtz_free_energy_per_link(&end_to_end_length, &temperature); + let residual_abs = relative_helmholtz_free_energy/(number_of_links as f64) - relative_helmholtz_free_energy_per_link; + let residual_rel = residual_abs/relative_helmholtz_free_energy_per_link; + assert!(residual_abs.abs() <= parameters.abs_tol); + assert!(residual_rel.abs() <= parameters.rel_tol); + } + } + #[test] + fn nondimensional_helmholtz_free_energy() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_end_to_end_length_per_link = parameters.nondimensional_end_to_end_length_per_link_reference + parameters.nondimensional_end_to_end_length_per_link_scale*(0.5 - rng.gen::()); + let temperature = parameters.temperature_reference + parameters.temperature_scale*(0.5 - rng.gen::()); + let nondimensional_helmholtz_free_energy = model.nondimensional_helmholtz_free_energy(&nondimensional_end_to_end_length_per_link, &temperature); + let nondimensional_helmholtz_free_energy_per_link = model.nondimensional_helmholtz_free_energy_per_link(&nondimensional_end_to_end_length_per_link, &temperature); + let residual_abs = nondimensional_helmholtz_free_energy/(number_of_links as f64) - nondimensional_helmholtz_free_energy_per_link; + let residual_rel = residual_abs/nondimensional_helmholtz_free_energy_per_link; + assert!(residual_abs.abs() <= parameters.abs_tol); + assert!(residual_rel.abs() <= parameters.rel_tol); + } + } + #[test] + fn nondimensional_relative_helmholtz_free_energy() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_end_to_end_length_per_link = parameters.nondimensional_end_to_end_length_per_link_reference + parameters.nondimensional_end_to_end_length_per_link_scale*(0.5 - rng.gen::()); + let nondimensional_relative_helmholtz_free_energy = model.nondimensional_relative_helmholtz_free_energy(&nondimensional_end_to_end_length_per_link); + let nondimensional_relative_helmholtz_free_energy_per_link = model.nondimensional_relative_helmholtz_free_energy_per_link(&nondimensional_end_to_end_length_per_link); + let residual_abs = nondimensional_relative_helmholtz_free_energy/(number_of_links as f64) - nondimensional_relative_helmholtz_free_energy_per_link; + let residual_rel = residual_abs/nondimensional_relative_helmholtz_free_energy_per_link; + assert!(residual_abs.abs() <= parameters.abs_tol); + assert!(residual_rel.abs() <= parameters.rel_tol); + } + } +} +mod relative +{ + use super::*; + use rand::Rng; + use crate::physics::single_chain::ZERO; + #[test] + fn helmholtz_free_energy() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_end_to_end_length_per_link = parameters.nondimensional_end_to_end_length_per_link_reference + parameters.nondimensional_end_to_end_length_per_link_scale*(0.5 - rng.gen::()); + let end_to_end_length = nondimensional_end_to_end_length_per_link*(number_of_links as f64)*link_length; + let temperature = parameters.temperature_reference + parameters.temperature_scale*(0.5 - rng.gen::()); + let helmholtz_free_energy = model.helmholtz_free_energy(&end_to_end_length, &temperature); + let helmholtz_free_energy_0 = model.helmholtz_free_energy(&(ZERO*(number_of_links as f64)*link_length), &temperature); + let relative_helmholtz_free_energy = model.relative_helmholtz_free_energy(&end_to_end_length, &temperature); + let residual_abs = &helmholtz_free_energy - &helmholtz_free_energy_0 - &relative_helmholtz_free_energy; + let residual_rel = &residual_abs/&helmholtz_free_energy_0; + assert!(residual_rel.abs() <= parameters.rel_tol); + } + } + #[test] + fn helmholtz_free_energy_per_link() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_end_to_end_length_per_link = parameters.nondimensional_end_to_end_length_per_link_reference + parameters.nondimensional_end_to_end_length_per_link_scale*(0.5 - rng.gen::()); + let end_to_end_length = nondimensional_end_to_end_length_per_link*(number_of_links as f64)*link_length; + let temperature = parameters.temperature_reference + parameters.temperature_scale*(0.5 - rng.gen::()); + let helmholtz_free_energy_per_link = model.helmholtz_free_energy_per_link(&end_to_end_length, &temperature); + let helmholtz_free_energy_per_link_0 = model.helmholtz_free_energy_per_link(&(ZERO*(number_of_links as f64)*link_length), &temperature); + let relative_helmholtz_free_energy_per_link = model.relative_helmholtz_free_energy_per_link(&end_to_end_length, &temperature); + let residual_abs = &helmholtz_free_energy_per_link - &helmholtz_free_energy_per_link_0 - &relative_helmholtz_free_energy_per_link; + let residual_rel = &residual_abs/&helmholtz_free_energy_per_link_0; + assert!(residual_rel.abs() <= parameters.rel_tol); + } + } + #[test] + fn nondimensional_helmholtz_free_energy() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_end_to_end_length_per_link = parameters.nondimensional_end_to_end_length_per_link_reference + parameters.nondimensional_end_to_end_length_per_link_scale*(0.5 - rng.gen::()); + let temperature = parameters.temperature_reference + parameters.temperature_scale*(0.5 - rng.gen::()); + let nondimensional_helmholtz_free_energy = model.nondimensional_helmholtz_free_energy(&nondimensional_end_to_end_length_per_link, &temperature); + let nondimensional_helmholtz_free_energy_0 = model.nondimensional_helmholtz_free_energy(&ZERO, &temperature); + let nondimensional_relative_helmholtz_free_energy = model.nondimensional_relative_helmholtz_free_energy(&nondimensional_end_to_end_length_per_link); + let residual_abs = &nondimensional_helmholtz_free_energy - &nondimensional_helmholtz_free_energy_0 - &nondimensional_relative_helmholtz_free_energy; + let residual_rel = &residual_abs/&nondimensional_helmholtz_free_energy_0; + assert!(residual_rel.abs() <= parameters.rel_tol); + } + } + #[test] + fn nondimensional_helmholtz_free_energy_per_link() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_end_to_end_length_per_link = parameters.nondimensional_end_to_end_length_per_link_reference + parameters.nondimensional_end_to_end_length_per_link_scale*(0.5 - rng.gen::()); + let temperature = parameters.temperature_reference + parameters.temperature_scale*(0.5 - rng.gen::()); + let nondimensional_helmholtz_free_energy_per_link = model.nondimensional_helmholtz_free_energy_per_link(&nondimensional_end_to_end_length_per_link, &temperature); + let nondimensional_helmholtz_free_energy_per_link_0 = model.nondimensional_helmholtz_free_energy_per_link(&ZERO, &temperature); + let nondimensional_relative_helmholtz_free_energy_per_link = model.nondimensional_relative_helmholtz_free_energy_per_link(&nondimensional_end_to_end_length_per_link); + let residual_abs = &nondimensional_helmholtz_free_energy_per_link - &nondimensional_helmholtz_free_energy_per_link_0 - &nondimensional_relative_helmholtz_free_energy_per_link; + let residual_rel = &residual_abs/&nondimensional_helmholtz_free_energy_per_link_0; + assert!(residual_rel.abs() <= parameters.rel_tol); + } + } +} +mod zero +{ + use super::*; + use rand::Rng; + use crate::physics::single_chain::ZERO; + #[test] + fn relative_helmholtz_free_energy() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let temperature = parameters.temperature_reference + parameters.temperature_scale*(0.5 - rng.gen::()); + let relative_helmholtz_free_energy_0 = model.relative_helmholtz_free_energy(&(ZERO*(number_of_links as f64)*link_length), &temperature); + assert!(relative_helmholtz_free_energy_0.abs() <= BOLTZMANN_CONSTANT*temperature*(number_of_links as f64)*ZERO); + } + } + #[test] + fn relative_helmholtz_free_energy_per_link() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let temperature = parameters.temperature_reference + parameters.temperature_scale*(0.5 - rng.gen::()); + let relative_helmholtz_free_energy_per_link_0 = model.relative_helmholtz_free_energy_per_link(&(ZERO*(number_of_links as f64)*link_length), &temperature); + assert!(relative_helmholtz_free_energy_per_link_0.abs() <= BOLTZMANN_CONSTANT*temperature*ZERO); + } + } + #[test] + fn nondimensional_relative_helmholtz_free_energy() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_relative_helmholtz_free_energy_0 = model.nondimensional_relative_helmholtz_free_energy(&ZERO); + assert!(nondimensional_relative_helmholtz_free_energy_0.abs() <= (number_of_links as f64)*ZERO); + } + } + #[test] + fn nondimensional_relative_helmholtz_free_energy_per_link() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_relative_helmholtz_free_energy_per_link_0 = model.nondimensional_relative_helmholtz_free_energy_per_link(&ZERO); + assert!(nondimensional_relative_helmholtz_free_energy_per_link_0.abs() <= ZERO); + } + } + #[test] + fn equilibrium_radial_distribution() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let equilibrium_radial_distribution_0 = model.equilibrium_radial_distribution(&(ZERO*(number_of_links as f64)*link_length)); + assert!(equilibrium_radial_distribution_0.abs() <= ZERO); + } + } + #[test] + fn nondimensional_equilibrium_radial_distribution() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_equilibrium_radial_distribution_0 = model.nondimensional_equilibrium_radial_distribution(&ZERO); + assert!(nondimensional_equilibrium_radial_distribution_0.abs() <= ZERO); + } + } +} +mod connection +{ + use super::*; + use rand::Rng; + #[test] + fn force() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_end_to_end_length_per_link = parameters.nondimensional_end_to_end_length_per_link_reference + 0.5*parameters.nondimensional_end_to_end_length_per_link_scale*(0.5 - rng.gen::()); + let end_to_end_length = nondimensional_end_to_end_length_per_link*(number_of_links as f64)*link_length; + let temperature = parameters.temperature_reference + parameters.temperature_scale*(0.5 - rng.gen::()); + let force = model.force(&end_to_end_length, &temperature); + let h = parameters.rel_tol*(number_of_links as f64)*link_length; + let force_from_derivative = (model.relative_helmholtz_free_energy(&(end_to_end_length + 0.5*h), &temperature) - model.relative_helmholtz_free_energy(&(end_to_end_length - 0.5*h), &temperature))/h; + let residual_abs = &force - &force_from_derivative; + let residual_rel = &residual_abs/&force; + assert!(residual_rel.abs() <= h); + } + } + #[test] + fn nondimensional_force() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_end_to_end_length_per_link = parameters.nondimensional_end_to_end_length_per_link_reference + 0.5*parameters.nondimensional_end_to_end_length_per_link_scale*(0.5 - rng.gen::()); + let nondimensional_force = model.nondimensional_force(&nondimensional_end_to_end_length_per_link); + let h = parameters.rel_tol; + let nondimensional_force_from_derivative = (model.nondimensional_relative_helmholtz_free_energy_per_link(&(nondimensional_end_to_end_length_per_link + 0.5*h)) - model.nondimensional_relative_helmholtz_free_energy_per_link(&(nondimensional_end_to_end_length_per_link - 0.5*h)))/h; + let residual_abs = &nondimensional_force - &nondimensional_force_from_derivative; + let residual_rel = &residual_abs/&nondimensional_force; + assert!(residual_rel.abs() <= h); + } + } + #[test] + fn relative_helmholtz_free_energy() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_end_to_end_length_per_link = parameters.nondimensional_end_to_end_length_per_link_reference + 0.5*parameters.nondimensional_end_to_end_length_per_link_scale*(0.5 - rng.gen::()); + let end_to_end_length = nondimensional_end_to_end_length_per_link*(number_of_links as f64)*link_length; + let temperature = parameters.temperature_reference + parameters.temperature_scale*(0.5 - rng.gen::()); + let relative_helmholtz_free_energy = model.relative_helmholtz_free_energy(&end_to_end_length, &temperature); + let relative_helmholtz_free_energy_from_connection = BOLTZMANN_CONSTANT*temperature*(model.equilibrium_distribution(&(ZERO*(number_of_links as f64)*link_length))/model.equilibrium_distribution(&end_to_end_length)).ln(); + let residual_abs = &relative_helmholtz_free_energy - &relative_helmholtz_free_energy_from_connection; + let residual_rel = &residual_abs/&relative_helmholtz_free_energy; + assert!(residual_rel.abs() <= parameters.rel_tol); + } + } + #[test] + fn nondimensional_relative_helmholtz_free_energy() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_end_to_end_length_per_link = parameters.nondimensional_end_to_end_length_per_link_reference + 0.5*parameters.nondimensional_end_to_end_length_per_link_scale*(0.5 - rng.gen::()); + let nondimensional_relative_helmholtz_free_energy = model.nondimensional_relative_helmholtz_free_energy(&nondimensional_end_to_end_length_per_link); + let nondimensional_relative_helmholtz_free_energy_from_connection = (model.nondimensional_equilibrium_distribution(&ZERO)/model.nondimensional_equilibrium_distribution(&nondimensional_end_to_end_length_per_link)).ln(); + let residual_abs = &nondimensional_relative_helmholtz_free_energy - &nondimensional_relative_helmholtz_free_energy_from_connection; + let residual_rel = &residual_abs/&nondimensional_relative_helmholtz_free_energy; + assert!(residual_rel.abs() <= parameters.rel_tol); + } + } +} \ No newline at end of file diff --git a/src/physics/single_chain/swfjc/thermodynamics/isometric/mod.rs b/src/physics/single_chain/swfjc/thermodynamics/isometric/mod.rs new file mode 100644 index 00000000..5ea64e01 --- /dev/null +++ b/src/physics/single_chain/swfjc/thermodynamics/isometric/mod.rs @@ -0,0 +1,43 @@ +#[cfg(feature = "python")] +pub mod py; + +mod test; + +/// The square-well freely-jointed chain (SWFJC) model thermodynamics in the isometric ensemble approximated using a Legendre transformation. +pub mod legendre; + +/// The structure of the thermodynamics of the SWFJC model in the isometric ensemble. +pub struct SWFJC +{ + /// The mass of each hinge in the chain in units of kg/mol. + pub hinge_mass: f64, + + /// The length of each link in the chain in units of nm. + pub link_length: f64, + + /// The number of links in the chain. + pub number_of_links: u8, + + /// The width of the well in units of nm. + pub well_width: f64, + + /// The thermodynamic functions of the model in the isometric ensemble approximated using a Legendre transformation. + pub legendre: legendre::SWFJC +} + +/// The implemented functionality of the SWFJC model in the isometric ensemble. +impl SWFJC +{ + /// Initializes and returns an instance of the SWFJC model in the isometric ensemble. + pub fn init(number_of_links: u8, link_length: f64, hinge_mass: f64, well_width: f64) -> Self + { + SWFJC + { + hinge_mass, + link_length, + number_of_links, + well_width, + legendre: self::legendre::SWFJC::init(number_of_links, link_length, hinge_mass, well_width) + } + } +} diff --git a/src/physics/single_chain/swfjc/thermodynamics/isometric/test.rs b/src/physics/single_chain/swfjc/thermodynamics/isometric/test.rs new file mode 100644 index 00000000..1081e67d --- /dev/null +++ b/src/physics/single_chain/swfjc/thermodynamics/isometric/test.rs @@ -0,0 +1,76 @@ +#![cfg(test)] +use super::*; +use crate::physics::single_chain::test::Parameters; +mod base +{ + use super::*; + use rand::Rng; + #[test] + fn init() + { + let parameters = Parameters::default(); + let _ = SWFJC::init(parameters.number_of_links_minimum, parameters.link_length_reference, parameters.hinge_mass_reference, parameters.well_width_reference); + } + #[test] + fn number_of_links() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + assert_eq!(number_of_links, SWFJC::init(number_of_links, parameters.link_length_reference, parameters.hinge_mass_reference, parameters.well_width_reference).number_of_links); + } + } + #[test] + fn link_length() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + assert_eq!(link_length, SWFJC::init(parameters.number_of_links_minimum, link_length, parameters.hinge_mass_reference, parameters.well_width_reference).link_length); + } + } + #[test] + fn hinge_mass() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + assert_eq!(hinge_mass, SWFJC::init(parameters.number_of_links_minimum, parameters.link_length_reference, hinge_mass, parameters.well_width_reference).hinge_mass); + } + } + #[test] + fn well_width() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + assert_eq!(well_width, SWFJC::init(parameters.number_of_links_minimum, parameters.link_length_reference, parameters.hinge_mass_reference, well_width).well_width); + } + } + #[test] + fn all_parameters() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + assert_eq!(number_of_links, model.number_of_links); + assert_eq!(link_length, model.link_length); + assert_eq!(hinge_mass, model.hinge_mass); + assert_eq!(well_width, model.well_width); + } + } +} \ No newline at end of file diff --git a/src/physics/single_chain/swfjc/thermodynamics/isotensional/mod.rs b/src/physics/single_chain/swfjc/thermodynamics/isotensional/mod.rs index efb51c75..100004dd 100644 --- a/src/physics/single_chain/swfjc/thermodynamics/isotensional/mod.rs +++ b/src/physics/single_chain/swfjc/thermodynamics/isotensional/mod.rs @@ -36,10 +36,6 @@ pub struct SWFJC pub legendre: legendre::SWFJC } -/// -/// use nondimensional_well_parameter as input for nondimensional functions, like efjc depending on nondimensionall link stiffness -/// - /// The expected end-to-end length as a function of the applied force and temperature, parameterized by the number of links, link length, and well width. pub fn end_to_end_length(number_of_links: &u8, link_length: &f64, well_width: &f64, force: &f64, temperature: &f64) -> f64 { diff --git a/src/physics/single_chain/swfjc/thermodynamics/mod.rs b/src/physics/single_chain/swfjc/thermodynamics/mod.rs index 84b6de35..3a21e903 100644 --- a/src/physics/single_chain/swfjc/thermodynamics/mod.rs +++ b/src/physics/single_chain/swfjc/thermodynamics/mod.rs @@ -3,6 +3,9 @@ pub mod py; mod test; +/// The square-well freely-jointed chain (SWFJC) model thermodynamics in the isometric ensemble. +pub mod isometric; + /// The square-well freely-jointed chain (SWFJC) model thermodynamics in the isotensional ensemble. pub mod isotensional; @@ -21,6 +24,9 @@ pub struct SWFJC /// The width of the well in units of nm. pub well_width: f64, + /// The thermodynamic functions of the model in the isometric ensemble. + pub isometric: isometric::SWFJC, + /// The thermodynamic functions of the model in the isotensional ensemble. pub isotensional: isotensional::SWFJC } @@ -37,7 +43,8 @@ impl SWFJC link_length, number_of_links, well_width, - isotensional: self::isotensional::SWFJC::init(number_of_links, link_length, hinge_mass, well_width), + isometric: self::isometric::SWFJC::init(number_of_links, link_length, hinge_mass, well_width), + isotensional: self::isotensional::SWFJC::init(number_of_links, link_length, hinge_mass, well_width) } } } diff --git a/src/physics/single_chain/swfjc/thermodynamics/test.rs b/src/physics/single_chain/swfjc/thermodynamics/test.rs index 01a429a5..1bef593b 100644 --- a/src/physics/single_chain/swfjc/thermodynamics/test.rs +++ b/src/physics/single_chain/swfjc/thermodynamics/test.rs @@ -1,5 +1,11 @@ #![cfg(test)] use super::*; +use std::f64::consts::PI; +use crate::physics:: +{ + BOLTZMANN_CONSTANT, + PLANCK_CONSTANT +}; use crate::physics::single_chain::test::Parameters; mod base { @@ -74,11 +80,6 @@ mod base } } } -#[test] -fn should_implement_isometric_legendre_distributions_here_since_not_temperature_dependent_and_has_finite_integration_limit() -{ - assert!(0.0 == 1.0); -} mod legendre { use super::*; @@ -86,51 +87,231 @@ mod legendre #[test] fn force() { - assert!(0.0 == 1.0); + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_force = parameters.nondimensional_force_reference + parameters.nondimensional_force_scale*(0.5 - rng.gen::()); + let temperature = parameters.temperature_reference + parameters.temperature_scale*(0.5 - rng.gen::()); + let force = nondimensional_force*BOLTZMANN_CONSTANT*temperature/link_length; + let end_to_end_length = model.isotensional.end_to_end_length(&force, &temperature); + let force_out = model.isometric.legendre.force(&end_to_end_length, &temperature); + let residual_abs = &force - &force_out; + let residual_rel = &residual_abs/&force; + assert!(residual_abs.abs() <= parameters.abs_tol); + assert!(residual_rel.abs() <= parameters.rel_tol); + } } #[test] fn nondimensional_force() { - assert!(0.0 == 1.0); + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_force = parameters.nondimensional_force_reference + parameters.nondimensional_force_scale*(0.5 - rng.gen::()); + let nondimensional_end_to_end_length_per_link= model.isotensional.nondimensional_end_to_end_length_per_link(&nondimensional_force); + let nondimensional_force_out = model.isometric.legendre.nondimensional_force(&nondimensional_end_to_end_length_per_link); + let residual_abs = &nondimensional_force - &nondimensional_force_out; + let residual_rel = &residual_abs/&nondimensional_force; + assert!(residual_abs.abs() <= parameters.abs_tol); + assert!(residual_rel.abs() <= parameters.rel_tol); + } } #[test] fn helmholtz_free_energy() { - assert!(0.0 == 1.0); + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_force = parameters.nondimensional_force_reference + parameters.nondimensional_force_scale*(0.5 - rng.gen::()); + let temperature = parameters.temperature_reference + parameters.temperature_scale*(0.5 - rng.gen::()); + let force = nondimensional_force*BOLTZMANN_CONSTANT*temperature/link_length; + let end_to_end_length = model.isotensional.end_to_end_length(&force, &temperature); + let helmholtz_free_energy_legendre = model.isotensional.gibbs_free_energy(&force, &temperature) + force*end_to_end_length; + let helmholtz_free_energy_legendre_out = model.isometric.legendre.helmholtz_free_energy(&end_to_end_length, &temperature); + let residual_abs = &helmholtz_free_energy_legendre - &helmholtz_free_energy_legendre_out + BOLTZMANN_CONSTANT*temperature*(8.0*PI.powi(2)*hinge_mass*link_length.powi(2)*BOLTZMANN_CONSTANT*temperature/PLANCK_CONSTANT.powi(2)).ln(); + let residual_rel = &residual_abs/&helmholtz_free_energy_legendre; + assert!(residual_abs.abs() <= parameters.abs_tol); + assert!(residual_rel.abs() <= parameters.rel_tol); + } } #[test] fn helmholtz_free_energy_per_link() { - assert!(0.0 == 1.0); + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_force = parameters.nondimensional_force_reference + parameters.nondimensional_force_scale*(0.5 - rng.gen::()); + let temperature = parameters.temperature_reference + parameters.temperature_scale*(0.5 - rng.gen::()); + let force = nondimensional_force*BOLTZMANN_CONSTANT*temperature/link_length; + let end_to_end_length = model.isotensional.end_to_end_length(&force, &temperature); + let end_to_end_length_per_link = model.isotensional.end_to_end_length_per_link(&force, &temperature); + let helmholtz_free_energy_per_link_legendre = model.isotensional.gibbs_free_energy_per_link(&force, &temperature) + force*end_to_end_length_per_link; + let helmholtz_free_energy_per_link_legendre_out = model.isometric.legendre.helmholtz_free_energy_per_link(&end_to_end_length, &temperature); + let residual_abs = &helmholtz_free_energy_per_link_legendre - &helmholtz_free_energy_per_link_legendre_out + BOLTZMANN_CONSTANT*temperature*(8.0*PI.powi(2)*hinge_mass*link_length.powi(2)*BOLTZMANN_CONSTANT*temperature/PLANCK_CONSTANT.powi(2)).ln()/(number_of_links as f64); + let residual_rel = &residual_abs/&helmholtz_free_energy_per_link_legendre; + assert!(residual_abs.abs() <= parameters.abs_tol); + assert!(residual_rel.abs() <= parameters.rel_tol); + } } #[test] fn relative_helmholtz_free_energy() { - assert!(0.0 == 1.0); + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_force = parameters.nondimensional_force_reference + parameters.nondimensional_force_scale*(0.5 - rng.gen::()); + let temperature = parameters.temperature_reference + parameters.temperature_scale*(0.5 - rng.gen::()); + let force = nondimensional_force*BOLTZMANN_CONSTANT*temperature/link_length; + let end_to_end_length = model.isotensional.end_to_end_length(&force, &temperature); + let relative_helmholtz_free_energy_legendre = model.isotensional.relative_gibbs_free_energy(&force, &temperature) + force*end_to_end_length; + let relative_helmholtz_free_energy_legendre_out = model.isometric.legendre.relative_helmholtz_free_energy(&end_to_end_length, &temperature); + let residual_abs = &relative_helmholtz_free_energy_legendre - &relative_helmholtz_free_energy_legendre_out; + let residual_rel = &residual_abs/&relative_helmholtz_free_energy_legendre; + assert!(residual_rel.abs() <= 3e1 * parameters.rel_tol); + } } #[test] fn relative_helmholtz_free_energy_per_link() { - assert!(0.0 == 1.0); + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_force = parameters.nondimensional_force_reference + parameters.nondimensional_force_scale*(0.5 - rng.gen::()); + let temperature = parameters.temperature_reference + parameters.temperature_scale*(0.5 - rng.gen::()); + let force = nondimensional_force*BOLTZMANN_CONSTANT*temperature/link_length; + let end_to_end_length = model.isotensional.end_to_end_length(&force, &temperature); + let end_to_end_length_per_link = model.isotensional.end_to_end_length_per_link(&force, &temperature); + let relative_helmholtz_free_energy_per_link_legendre = model.isotensional.relative_gibbs_free_energy_per_link(&force, &temperature) + force*end_to_end_length_per_link; + let relative_helmholtz_free_energy_per_link_legendre_out = model.isometric.legendre.relative_helmholtz_free_energy_per_link(&end_to_end_length, &temperature); + let residual_abs = &relative_helmholtz_free_energy_per_link_legendre - &relative_helmholtz_free_energy_per_link_legendre_out; + let residual_rel = &residual_abs/&relative_helmholtz_free_energy_per_link_legendre; + assert!(residual_rel.abs() <= 3e1 * parameters.rel_tol); + } } #[test] fn nondimensional_helmholtz_free_energy() { - assert!(0.0 == 1.0); + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_force = parameters.nondimensional_force_reference + parameters.nondimensional_force_scale*(0.5 - rng.gen::()); + let temperature = parameters.temperature_reference + parameters.temperature_scale*(0.5 - rng.gen::()); + let nondimensional_end_to_end_length = model.isotensional.nondimensional_end_to_end_length(&nondimensional_force); + let nondimensional_end_to_end_length_per_link = model.isotensional.nondimensional_end_to_end_length_per_link(&nondimensional_force); + let nondimensional_helmholtz_free_energy_legendre = model.isotensional.nondimensional_gibbs_free_energy(&nondimensional_force, &temperature) + nondimensional_force*nondimensional_end_to_end_length; + let nondimensional_helmholtz_free_energy_legendre_out = model.isometric.legendre.nondimensional_helmholtz_free_energy(&nondimensional_end_to_end_length_per_link, &temperature); + let residual_abs = &nondimensional_helmholtz_free_energy_legendre - &nondimensional_helmholtz_free_energy_legendre_out + (8.0*PI.powi(2)*hinge_mass*link_length.powi(2)*BOLTZMANN_CONSTANT*temperature/PLANCK_CONSTANT.powi(2)).ln(); + let residual_rel = &residual_abs/&nondimensional_helmholtz_free_energy_legendre; + assert!(residual_abs.abs() <= parameters.abs_tol); + assert!(residual_rel.abs() <= parameters.rel_tol); + } } #[test] fn nondimensional_helmholtz_free_energy_per_link() { - assert!(0.0 == 1.0); + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_force = parameters.nondimensional_force_reference + parameters.nondimensional_force_scale*(0.5 - rng.gen::()); + let temperature = parameters.temperature_reference + parameters.temperature_scale*(0.5 - rng.gen::()); + let nondimensional_end_to_end_length_per_link = model.isotensional.nondimensional_end_to_end_length_per_link(&nondimensional_force); + let nondimensional_helmholtz_free_energy_per_link_legendre = model.isotensional.nondimensional_gibbs_free_energy_per_link(&nondimensional_force, &temperature) + nondimensional_force*nondimensional_end_to_end_length_per_link; + let nondimensional_helmholtz_free_energy_per_link_legendre_out = model.isometric.legendre.nondimensional_helmholtz_free_energy_per_link(&nondimensional_end_to_end_length_per_link, &temperature); + let residual_abs = &nondimensional_helmholtz_free_energy_per_link_legendre - &nondimensional_helmholtz_free_energy_per_link_legendre_out + (8.0*PI.powi(2)*hinge_mass*link_length.powi(2)*BOLTZMANN_CONSTANT*temperature/PLANCK_CONSTANT.powi(2)).ln()/(number_of_links as f64); + let residual_rel = &residual_abs/&nondimensional_helmholtz_free_energy_per_link_legendre; + assert!(residual_abs.abs() <= parameters.abs_tol); + assert!(residual_rel.abs() <= parameters.rel_tol); + } } #[test] fn nondimensional_relative_helmholtz_free_energy() { - assert!(0.0 == 1.0); + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_force = parameters.nondimensional_force_reference + parameters.nondimensional_force_scale*(0.5 - rng.gen::()); + let nondimensional_end_to_end_length = model.isotensional.nondimensional_end_to_end_length(&nondimensional_force); + let nondimensional_end_to_end_length_per_link = model.isotensional.nondimensional_end_to_end_length_per_link(&nondimensional_force); + let nondimensional_relative_helmholtz_free_energy_legendre = model.isotensional.nondimensional_relative_gibbs_free_energy(&nondimensional_force) + nondimensional_force*nondimensional_end_to_end_length; + let nondimensional_relative_helmholtz_free_energy_legendre_out = model.isometric.legendre.nondimensional_relative_helmholtz_free_energy(&nondimensional_end_to_end_length_per_link); + let residual_abs = &nondimensional_relative_helmholtz_free_energy_legendre - &nondimensional_relative_helmholtz_free_energy_legendre_out; + let residual_rel = &residual_abs/&nondimensional_relative_helmholtz_free_energy_legendre; + assert!(residual_rel.abs() <= 3e1 * parameters.rel_tol); + } } #[test] fn nondimensional_relative_helmholtz_free_energy_per_link() { - assert!(0.0 == 1.0); + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_force = parameters.nondimensional_force_reference + parameters.nondimensional_force_scale*(0.5 - rng.gen::()); + let nondimensional_end_to_end_length_per_link = model.isotensional.nondimensional_end_to_end_length_per_link(&nondimensional_force); + let nondimensional_relative_helmholtz_free_energy_per_link_legendre = model.isotensional.nondimensional_relative_gibbs_free_energy_per_link(&nondimensional_force) + nondimensional_force*nondimensional_end_to_end_length_per_link; + let nondimensional_relative_helmholtz_free_energy_per_link_legendre_out = model.isometric.legendre.nondimensional_relative_helmholtz_free_energy_per_link(&nondimensional_end_to_end_length_per_link); + let residual_abs = &nondimensional_relative_helmholtz_free_energy_per_link_legendre - &nondimensional_relative_helmholtz_free_energy_per_link_legendre_out; + let residual_rel = &residual_abs/&nondimensional_relative_helmholtz_free_energy_per_link_legendre; + assert!(residual_rel.abs() <= 3e1 * parameters.rel_tol); + } } } \ No newline at end of file From 2bc2189bee2abafad97efbfd5c58998c99710b20 Mon Sep 17 00:00:00 2001 From: mrbuche Date: Mon, 17 Apr 2023 16:25:14 -0600 Subject: [PATCH 3/5] python side of things --- .../thermodynamics/isometric/legendre/test.py | 8 +- .../thermodynamics/isometric/__init__.py | 0 .../isometric/legendre/__init__.py | 0 .../thermodynamics/isometric/legendre/py.rs | 257 +++ .../thermodynamics/isometric/legendre/test.py | 1681 +++++++++++++++++ .../thermodynamics/isometric/legendre/test.rs | 33 + .../swfjc/thermodynamics/isometric/py.rs | 53 + .../swfjc/thermodynamics/isometric/test.py | 143 ++ .../single_chain/swfjc/thermodynamics/py.rs | 6 + .../single_chain/swfjc/thermodynamics/test.py | 610 ++++++ 10 files changed, 2787 insertions(+), 4 deletions(-) create mode 100644 src/physics/single_chain/swfjc/thermodynamics/isometric/__init__.py create mode 100644 src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/__init__.py create mode 100644 src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/py.rs create mode 100644 src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/test.py create mode 100644 src/physics/single_chain/swfjc/thermodynamics/isometric/py.rs create mode 100644 src/physics/single_chain/swfjc/thermodynamics/isometric/test.py diff --git a/src/physics/single_chain/fjc/thermodynamics/isometric/legendre/test.py b/src/physics/single_chain/fjc/thermodynamics/isometric/legendre/test.py index 28d8ef7f..a4ebb131 100644 --- a/src/physics/single_chain/fjc/thermodynamics/isometric/legendre/test.py +++ b/src/physics/single_chain/fjc/thermodynamics/isometric/legendre/test.py @@ -1908,7 +1908,7 @@ def test_nondimensional_relative_helmholtz_free_energy(self): ) nondimensional_relative_helmholtz_free_energy_0 = \ model.nondimensional_relative_helmholtz_free_energy( - np.array(parameters.zero*number_of_links*link_length) + np.array(parameters.zero) ) self.assertLessEqual( np.abs(nondimensional_relative_helmholtz_free_energy_0), @@ -1939,7 +1939,7 @@ def test_nondimensional_relative_helmholtz_free_energy_per_link(self): ) nondimensional_relative_helmholtz_free_energy_per_link_0 = \ model.nondimensional_relative_helmholtz_free_energy_per_link( - np.array(parameters.zero*number_of_links*link_length) + np.array(parameters.zero) ) self.assertLessEqual( np.abs( @@ -2043,7 +2043,7 @@ def test_nondimensional_relative_gibbs_free_energy(self): ) nondimensional_relative_gibbs_free_energy_0 = \ model.nondimensional_relative_gibbs_free_energy( - np.array(parameters.zero*number_of_links*link_length) + np.array(parameters.zero) ) self.assertLessEqual( np.abs(nondimensional_relative_gibbs_free_energy_0), @@ -2074,7 +2074,7 @@ def test_nondimensional_relative_gibbs_free_energy_per_link(self): ) nondimensional_relative_gibbs_free_energy_per_link_0 = \ model.nondimensional_relative_gibbs_free_energy_per_link( - np.array(parameters.zero*number_of_links*link_length) + np.array(parameters.zero) ) self.assertLessEqual( np.abs( diff --git a/src/physics/single_chain/swfjc/thermodynamics/isometric/__init__.py b/src/physics/single_chain/swfjc/thermodynamics/isometric/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/__init__.py b/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/py.rs b/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/py.rs new file mode 100644 index 00000000..3d2d2ca5 --- /dev/null +++ b/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/py.rs @@ -0,0 +1,257 @@ +use pyo3::prelude::*; +use numpy:: +{ + IntoPyArray, + PyArrayDyn, + PyReadonlyArrayDyn +}; +use crate::math::integrate_1d; +use crate::physics::single_chain:: +{ + ONE, + ZERO, + POINTS +}; + +pub fn register_module(py: Python<'_>, parent_module: &PyModule) -> PyResult<()> +{ + let legendre = PyModule::new(py, "legendre")?; + parent_module.add_submodule(legendre)?; + legendre.add_class::()?; + Ok(()) +} + +/// The square-well freely-jointed chain (SWFJC) model thermodynamics in the isometric ensemble approximated using a Legendre transformation. +#[pyclass] +#[derive(Copy, Clone)] +pub struct SWFJC +{ + /// The mass of each hinge in the chain in units of kg/mol. + #[pyo3(get)] + pub hinge_mass: f64, + + /// The length of each link in the chain in units of nm. + #[pyo3(get)] + pub link_length: f64, + + /// The number of links in the chain. + #[pyo3(get)] + pub number_of_links: u8, + + /// The width of the well in units of nm. + #[pyo3(get)] + pub well_width: f64, + + normalization_nondimensional_equilibrium_distribution: f64 +} + +#[pymethods] +impl SWFJC +{ + #[new] + pub fn init(number_of_links: u8, link_length: f64, hinge_mass: f64, well_width: f64) -> Self + { + let nondimensional_well_parameter = 1.0 + well_width/link_length; + let normalization = integrate_1d(&|nondimensional_end_to_end_length_per_link: &f64| super::nondimensional_equilibrium_radial_distribution(&number_of_links, &link_length, &well_width, &1.0, nondimensional_end_to_end_length_per_link), &ZERO, &(ONE*nondimensional_well_parameter), &POINTS); + SWFJC + { + hinge_mass, + link_length, + number_of_links, + well_width, + normalization_nondimensional_equilibrium_distribution: normalization + } + } + /// The expected force as a function of the applied end-to-end length and temperature. + /// + /// Args: + /// end_to_end_length (numpy.ndarray): The end-to-end length :math:`\xi`. + /// temperature (float): The temperature :math:`T`. + /// + /// Returns: + /// numpy.ndarray: The force :math:`f`. + /// + pub fn force<'py>(&self, py: Python<'py>, end_to_end_length: PyReadonlyArrayDyn, temperature: f64) -> &'py PyArrayDyn + { + end_to_end_length.as_array().mapv(|end_to_end_length: f64| super::force(&self.number_of_links, &self.link_length, &self.well_width, &end_to_end_length, &temperature)).into_pyarray(py) + } + /// The expected nondimensional force as a function of the applied nondimensional end-to-end length per link. + /// + /// Args: + /// nondimensional_end_to_end_length_per_link (numpy.ndarray): The nondimensional end-to-end length per link :math:`\gamma\equiv \xi/N_b\ell_b`. + /// + /// Returns: + /// numpy.ndarray: The nondimensional force :math:`\eta\equiv\beta f\ell_b`. + /// + pub fn nondimensional_force<'py>(&self, py: Python<'py>, nondimensional_end_to_end_length_per_link: PyReadonlyArrayDyn) -> &'py PyArrayDyn + { + nondimensional_end_to_end_length_per_link.as_array().mapv(|nondimensional_end_to_end_length_per_link: f64| super::nondimensional_force(&self.link_length, &self.well_width, &nondimensional_end_to_end_length_per_link)).into_pyarray(py) + } + /// The Helmholtz free energy as a function of the applied end-to-end length and temperature, + /// + /// .. math:: + /// \psi(\xi, T) \sim \varphi\left[f(\xi, T)\right] + \xi f(\xi, T) \quad \text{for } N_b\gg 1, + /// + /// where :math:`f(\xi, T)` is given by the Legendre transformation approximation above. + /// + /// Args: + /// end_to_end_length (numpy.ndarray): The end-to-end length :math:`\xi`. + /// temperature (float): The temperature :math:`T`. + /// + /// Returns: + /// numpy.ndarray: The Helmholtz free energy :math:`\psi`. + /// + pub fn helmholtz_free_energy<'py>(&self, py: Python<'py>, end_to_end_length: PyReadonlyArrayDyn, temperature: f64) -> &'py PyArrayDyn + { + end_to_end_length.as_array().mapv(|end_to_end_length: f64| super::helmholtz_free_energy(&self.number_of_links, &self.link_length, &self.hinge_mass, &self.well_width, &end_to_end_length, &temperature)).into_pyarray(py) + } + /// The Helmholtz free energy per link as a function of the applied end-to-end length and temperature. + /// + /// Args: + /// end_to_end_length (numpy.ndarray): The end-to-end length :math:`\xi`. + /// temperature (float): The temperature :math:`T`. + /// + /// Returns: + /// numpy.ndarray: The Helmholtz free energy per link :math:`\psi/N_b`. + /// + pub fn helmholtz_free_energy_per_link<'py>(&self, py: Python<'py>, end_to_end_length: PyReadonlyArrayDyn, temperature: f64) -> &'py PyArrayDyn + { + end_to_end_length.as_array().mapv(|end_to_end_length: f64| super::helmholtz_free_energy_per_link(&self.number_of_links, &self.link_length, &self.hinge_mass, &self.well_width, &end_to_end_length, &temperature)).into_pyarray(py) + } + /// The relative Helmholtz free energy as a function of the applied end-to-end length and temperature. + /// + /// Args: + /// end_to_end_length (numpy.ndarray): The end-to-end length :math:`\xi`. + /// temperature (float): The temperature :math:`T`. + /// + /// Returns: + /// numpy.ndarray: The relative Helmholtz free energy :math:`\Delta\psi\equiv\psi(\xi,T)-\psi(0,T)`. + /// + pub fn relative_helmholtz_free_energy<'py>(&self, py: Python<'py>, end_to_end_length: PyReadonlyArrayDyn, temperature: f64) -> &'py PyArrayDyn + { + end_to_end_length.as_array().mapv(|end_to_end_length: f64| super::relative_helmholtz_free_energy(&self.number_of_links, &self.link_length, &self.well_width, &end_to_end_length, &temperature)).into_pyarray(py) + } + /// The relative Helmholtz free energy per link as a function of the applied end-to-end length and temperature. + /// + /// Args: + /// end_to_end_length (numpy.ndarray): The end-to-end length :math:`\xi`. + /// temperature (float): The temperature :math:`T`. + /// + /// Returns: + /// numpy.ndarray: The relative Helmholtz free energy per link :math:`\Delta\psi/N_b`. + /// + pub fn relative_helmholtz_free_energy_per_link<'py>(&self, py: Python<'py>, end_to_end_length: PyReadonlyArrayDyn, temperature: f64) -> &'py PyArrayDyn + { + end_to_end_length.as_array().mapv(|end_to_end_length: f64| super::relative_helmholtz_free_energy_per_link(&self.number_of_links, &self.link_length, &self.well_width, &end_to_end_length, &temperature)).into_pyarray(py) + } + /// The nondimensional Helmholtz free energy as a function of the applied nondimensional end-to-end length per link and temperature. + /// + /// Args: + /// nondimensional_end_to_end_length_per_link (numpy.ndarray): The nondimensional end-to-end length per link :math:`\gamma\equiv \xi/N_b\ell_b`. + /// temperature (float): The temperature :math:`T`. + /// + /// Returns: + /// numpy.ndarray: The nondimensional Helmholtz free energy :math:`\beta\psi=N_b\vartheta`. + /// + pub fn nondimensional_helmholtz_free_energy<'py>(&self, py: Python<'py>, nondimensional_end_to_end_length_per_link: PyReadonlyArrayDyn, temperature: f64) -> &'py PyArrayDyn + { + nondimensional_end_to_end_length_per_link.as_array().mapv(|nondimensional_end_to_end_length_per_link: f64| super::nondimensional_helmholtz_free_energy(&self.number_of_links, &self.link_length, &self.hinge_mass, &self.well_width, &nondimensional_end_to_end_length_per_link, &temperature)).into_pyarray(py) + } + /// The nondimensional Helmholtz free energy per link as a function of the applied nondimensional end-to-end length per link and temperature. + /// + /// Args: + /// nondimensional_end_to_end_length_per_link (numpy.ndarray): The nondimensional end-to-end length per link :math:`\gamma\equiv \xi/N_b\ell_b`. + /// temperature (float): The temperature :math:`T`. + /// + /// Returns: + /// numpy.ndarray: The nondimensional Helmholtz free energy per link :math:`\vartheta\equiv\beta\psi/N_b`. + /// + pub fn nondimensional_helmholtz_free_energy_per_link<'py>(&self, py: Python<'py>, nondimensional_end_to_end_length_per_link: PyReadonlyArrayDyn, temperature: f64) -> &'py PyArrayDyn + { + nondimensional_end_to_end_length_per_link.as_array().mapv(|nondimensional_end_to_end_length_per_link: f64| super::nondimensional_helmholtz_free_energy_per_link(&self.number_of_links, &self.link_length, &self.hinge_mass, &self.well_width, &nondimensional_end_to_end_length_per_link, &temperature)).into_pyarray(py) + } + /// The nondimensional relative Helmholtz free energy as a function of the applied nondimensional end-to-end length per link. + /// + /// Args: + /// nondimensional_end_to_end_length_per_link (numpy.ndarray): The nondimensional end-to-end length per link :math:`\gamma\equiv \xi/N_b\ell_b`. + /// + /// Returns: + /// numpy.ndarray: The nondimensional relative Helmholtz free energy :math:`\beta\Delta\psi=N_b\Delta\vartheta`. + /// + pub fn nondimensional_relative_helmholtz_free_energy<'py>(&self, py: Python<'py>, nondimensional_end_to_end_length_per_link: PyReadonlyArrayDyn) -> &'py PyArrayDyn + { + nondimensional_end_to_end_length_per_link.as_array().mapv(|nondimensional_end_to_end_length_per_link: f64| super::nondimensional_relative_helmholtz_free_energy(&self.number_of_links, &self.link_length, &self.well_width, &nondimensional_end_to_end_length_per_link)).into_pyarray(py) + } + /// The nondimensional relative Helmholtz free energy per link as a function of the applied nondimensional end-to-end length per link. + /// + /// Args: + /// nondimensional_end_to_end_length_per_link (numpy.ndarray): The nondimensional end-to-end length per link :math:`\gamma\equiv \xi/N_b\ell_b`. + /// + /// Returns: + /// numpy.ndarray: The nondimensional relative Helmholtz free energy per link :math:`\Delta\vartheta\equiv\beta\Delta\psi/N_b`. + /// + pub fn nondimensional_relative_helmholtz_free_energy_per_link<'py>(&self, py: Python<'py>, nondimensional_end_to_end_length_per_link: PyReadonlyArrayDyn) -> &'py PyArrayDyn + { + nondimensional_end_to_end_length_per_link.as_array().mapv(|nondimensional_end_to_end_length_per_link: f64| super::nondimensional_relative_helmholtz_free_energy_per_link(&self.link_length, &self.well_width, &nondimensional_end_to_end_length_per_link)).into_pyarray(py) + } + /// The nondimensional equilibrium probability density of nondimensional end-to-end vectors per link as a function of the nondimensional end-to-end length per link, + /// + /// .. math:: + /// P_\mathrm{eq}(\xi) = \frac{e^{-\beta\psi(\xi, T)}}{4\pi\int e^{-\beta\psi(\xi', T)} \,{\xi'}{}^2 d\xi'}. + /// + /// Args: + /// end_to_end_length (numpy.ndarray): The end-to-end length :math:`\xi`. + /// + /// Returns: + /// numpy.ndarray: The equilibrium probability density :math:`P_\mathrm{eq}`. + /// + pub fn equilibrium_distribution<'py>(&self, py: Python<'py>, end_to_end_length: PyReadonlyArrayDyn) -> &'py PyArrayDyn + { + end_to_end_length.as_array().mapv(|end_to_end_length: f64| super::equilibrium_distribution(&self.number_of_links, &self.link_length, &self.well_width, &self.normalization_nondimensional_equilibrium_distribution, &end_to_end_length)).into_pyarray(py) + } + /// The nondimensional equilibrium probability density of nondimensional end-to-end vectors per link as a function of the nondimensional end-to-end length per link, + /// + /// .. math:: + /// \mathscr{P}_\mathrm{eq}(\gamma) = \frac{e^{-\Delta\vartheta(\gamma)}}{4\pi\int e^{-\Delta\vartheta(\gamma')} \,{\gamma'}{}^2 d\gamma'}. + /// + /// Args: + /// nondimensional_end_to_end_length_per_link (numpy.ndarray): The nondimensional end-to-end length per link :math:`\gamma\equiv \xi/N_b\ell_b`. + /// + /// Returns: + /// numpy.ndarray: The nondimensional equilibrium probability density :math:`\mathscr{P}_\mathrm{eq}\equiv (N_b\ell_b)^3 P_\mathrm{eq}`. + /// + pub fn nondimensional_equilibrium_distribution<'py>(&self, py: Python<'py>, nondimensional_end_to_end_length_per_link: PyReadonlyArrayDyn) -> &'py PyArrayDyn + { + nondimensional_end_to_end_length_per_link.as_array().mapv(|nondimensional_end_to_end_length_per_link: f64| super::nondimensional_equilibrium_distribution(&self.number_of_links, &self.link_length, &self.well_width, &self.normalization_nondimensional_equilibrium_distribution, &nondimensional_end_to_end_length_per_link)).into_pyarray(py) + } + /// The equilibrium probability density of end-to-end lengths as a function of the end-to-end length, + /// + /// .. math:: + /// g_\mathrm{eq}(\xi) = 4\pi\xi^2 P_\mathrm{eq}(\xi). + /// + /// Args: + /// end_to_end_length (numpy.ndarray): The end-to-end length :math:`\xi`. + /// + /// Returns: + /// numpy.ndarray: The equilibrium probability density :math:`g_\mathrm{eq}`. + /// + pub fn equilibrium_radial_distribution<'py>(&self, py: Python<'py>, end_to_end_length: PyReadonlyArrayDyn) -> &'py PyArrayDyn + { + end_to_end_length.as_array().mapv(|end_to_end_length: f64| super::equilibrium_radial_distribution(&self.number_of_links, &self.link_length, &self.well_width, &self.normalization_nondimensional_equilibrium_distribution, &end_to_end_length)).into_pyarray(py) + } + /// The nondimensional equilibrium probability density of nondimensional end-to-end lengths per link as a function of the nondimensional end-to-end length per link, + /// + /// .. math:: + /// \mathscr{g}_\mathrm{eq}(\gamma) = 4\pi\gamma^2 \mathscr{P}_\mathrm{eq}(\gamma). + /// + /// Args: + /// nondimensional_end_to_end_length_per_link (numpy.ndarray): The nondimensional end-to-end length per link :math:`\gamma\equiv \xi/N_b\ell_b`. + /// + /// Returns: + /// numpy.ndarray: The nondimensional equilibrium probability density :math:`\mathscr{g}_\mathrm{eq}\equiv N_b\ell_b g_\mathrm{eq}`. + /// + pub fn nondimensional_equilibrium_radial_distribution<'py>(&self, py: Python<'py>, nondimensional_end_to_end_length_per_link: PyReadonlyArrayDyn) -> &'py PyArrayDyn + { + nondimensional_end_to_end_length_per_link.as_array().mapv(|nondimensional_end_to_end_length_per_link: f64| super::nondimensional_equilibrium_radial_distribution(&self.number_of_links, &self.link_length, &self.well_width, &self.normalization_nondimensional_equilibrium_distribution, &nondimensional_end_to_end_length_per_link)).into_pyarray(py) + } +} \ No newline at end of file diff --git a/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/test.py b/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/test.py new file mode 100644 index 00000000..1729cc8f --- /dev/null +++ b/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/test.py @@ -0,0 +1,1681 @@ +"""Module to test the local module. + +""" +import unittest +import numpy as np +from polymers import physics +from ..test import Parameters +from .....test import integrate + +parameters = Parameters() +SWFJC = physics.single_chain.swfjc.thermodynamics.isometric.legendre.SWFJC + + +class Base(unittest.TestCase): + """Class for basic tests. + + """ + def test_init(self): + """Function to test instantiation. + + """ + for _ in range(parameters.number_of_loops): + _ = SWFJC( + parameters.number_of_links_minimum, + parameters.link_length_reference, + parameters.hinge_mass_reference, + parameters.well_width_reference + ) + + def test_number_of_links(self): + """Function to test the number of links during instantiation. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + self.assertEqual( + number_of_links, + SWFJC( + number_of_links, + parameters.link_length_reference, + parameters.hinge_mass_reference, + parameters.well_width_reference + ).number_of_links + ) + + def test_link_length(self): + """Function to test the link length during instantiation. + + """ + for _ in range(parameters.number_of_loops): + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + self.assertEqual( + link_length, + SWFJC( + parameters.number_of_links_minimum, + link_length, + parameters.hinge_mass_reference, + parameters.well_width_reference + ).link_length + ) + + def test_hinge_mass(self): + """Function to test the hinge mass during instantiation. + + """ + for _ in range(parameters.number_of_loops): + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + self.assertEqual( + hinge_mass, + SWFJC( + parameters.number_of_links_minimum, + parameters.link_length_reference, + hinge_mass, + parameters.well_width_reference + ).hinge_mass + ) + + def test_well_width(self): + """Function to test the well width during instantiation. + + """ + for _ in range(parameters.number_of_loops): + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + self.assertEqual( + well_width, + SWFJC( + parameters.number_of_links_minimum, + parameters.link_length_reference, + parameters.hinge_mass_reference, + well_width + ).well_width + ) + + def test_all_parameters(self): + """Function to test all parameters during instantiation. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + self.assertEqual( + number_of_links, + model.number_of_links + ) + self.assertEqual( + link_length, + model.link_length + ) + self.assertEqual( + hinge_mass, + model.hinge_mass + ) + self.assertEqual( + well_width, + model.well_width + ) + + +class Normalization(unittest.TestCase): + """Class for normalization tests. + + """ + def test_equilibrium_distribution(self): + """Function to test the normalization + of the equilibrium distribution. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + + def integrand(end_to_end_length): + return 4.0*np.pi*end_to_end_length**2 * \ + model.equilibrium_distribution( + end_to_end_length + ) + + integral = integrate( + integrand, + parameters.zero*number_of_links*link_length, + parameters.one*number_of_links*(link_length + well_width), + parameters.points + ) + self.assertLessEqual( + np.abs(integral - 1.0), + parameters.rel_tol + ) + + def test_nondimensional_equilibrium_distribution(self): + """Function to test the normalization + of the nondimensional equilibrium distribution. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + + def integrand(nondimensional_end_to_end_length_per_link_per_link): + return 4.0*np.pi * \ + nondimensional_end_to_end_length_per_link_per_link**2 * \ + model.nondimensional_equilibrium_distribution( + nondimensional_end_to_end_length_per_link_per_link + ) + + integral = integrate( + integrand, + parameters.zero, + parameters.one*(1.0 + well_width/link_length), + parameters.points + ) + self.assertLessEqual( + np.abs(integral - 1.0), + parameters.rel_tol + ) + + def test_equilibrium_radial_distribution(self): + """Function to test the normalization + of the equilibrium radial distribution. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + + def integrand(end_to_end_length): + return model.equilibrium_radial_distribution( + end_to_end_length + ) + + integral = integrate( + integrand, + parameters.zero*number_of_links*link_length, + parameters.one*number_of_links*(link_length + well_width), + parameters.points + ) + self.assertLessEqual( + np.abs(integral - 1.0), + parameters.rel_tol + ) + + def test_nondimensional_equilibrium_radial_distribution(self): + """Function to test the normalization + of the nondimensional equilibrium radial distribution. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + + def integrand(nondimensional_end_to_end_length_per_link_per_link): + return model.nondimensional_equilibrium_radial_distribution( + nondimensional_end_to_end_length_per_link_per_link + ) + + integral = integrate( + integrand, + parameters.zero, + parameters.one*(1.0 + well_width/link_length), + parameters.points + ) + self.assertLessEqual( + np.abs(integral - 1.0), + parameters.rel_tol + ) + + +class Nondimensional(unittest.TestCase): + """Class for nondimensionalization tests. + + """ + def test_force(self): + """Function to test the nondimensionalization + of the force. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_end_to_end_length_per_link = \ + parameters. \ + nondimensional_end_to_end_length_per_link_reference + \ + parameters. \ + nondimensional_end_to_end_length_per_link_scale * \ + (0.5 - np.random.rand()) + temperature = \ + parameters.temperature_reference + \ + parameters.temperature_scale*(0.5 - np.random.rand()) + nondimensional_force = \ + model.nondimensional_force( + np.array(nondimensional_end_to_end_length_per_link) + ) + end_to_end_length = nondimensional_end_to_end_length_per_link * \ + number_of_links*link_length + force = \ + model.force( + np.array(end_to_end_length), + temperature + ) + residual_abs = \ + force / \ + parameters.boltzmann_constant/temperature*link_length \ + - nondimensional_force + residual_rel = \ + residual_abs / \ + nondimensional_force + self.assertLessEqual( + np.abs(residual_abs), + parameters.abs_tol + ) + self.assertLessEqual( + np.abs(residual_rel), + parameters.rel_tol + ) + + def test_helmholtz_free_energy(self): + """Function to test the nondimensionalization + of the Helmholtz free energy. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_end_to_end_length_per_link = \ + parameters. \ + nondimensional_end_to_end_length_per_link_reference + \ + parameters. \ + nondimensional_end_to_end_length_per_link_scale * \ + (0.5 - np.random.rand()) + temperature = \ + parameters.temperature_reference + \ + parameters.temperature_scale*(0.5 - np.random.rand()) + nondimensional_helmholtz_free_energy = \ + model.nondimensional_helmholtz_free_energy( + np.array(nondimensional_end_to_end_length_per_link), + temperature + ) + end_to_end_length = nondimensional_end_to_end_length_per_link * \ + number_of_links*link_length + helmholtz_free_energy = \ + model.helmholtz_free_energy( + np.array(end_to_end_length), + temperature + ) + residual_abs = \ + helmholtz_free_energy / \ + parameters.boltzmann_constant/temperature \ + - nondimensional_helmholtz_free_energy + residual_rel = \ + residual_abs / \ + nondimensional_helmholtz_free_energy + self.assertLessEqual( + np.abs(residual_abs), + parameters.abs_tol + ) + self.assertLessEqual( + np.abs(residual_rel), + parameters.rel_tol + ) + + def test_helmholtz_free_energy_per_link(self): + """Function to test the nondimensionalization + of the Helmholtz free energy per link. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_end_to_end_length_per_link = \ + parameters. \ + nondimensional_end_to_end_length_per_link_reference + \ + parameters. \ + nondimensional_end_to_end_length_per_link_scale * \ + (0.5 - np.random.rand()) + temperature = \ + parameters.temperature_reference + \ + parameters.temperature_scale*(0.5 - np.random.rand()) + nondimensional_helmholtz_free_energy_per_link = \ + model.nondimensional_helmholtz_free_energy_per_link( + np.array(nondimensional_end_to_end_length_per_link), + temperature + ) + end_to_end_length = nondimensional_end_to_end_length_per_link * \ + number_of_links*link_length + helmholtz_free_energy_per_link = \ + model.helmholtz_free_energy_per_link( + np.array(end_to_end_length), + temperature + ) + residual_abs = \ + helmholtz_free_energy_per_link / \ + parameters.boltzmann_constant/temperature \ + - nondimensional_helmholtz_free_energy_per_link + residual_rel = \ + residual_abs / \ + nondimensional_helmholtz_free_energy_per_link + self.assertLessEqual( + np.abs(residual_abs), + parameters.abs_tol + ) + self.assertLessEqual( + np.abs(residual_rel), + parameters.rel_tol + ) + + def test_relative_helmholtz_free_energy(self): + """Function to test the nondimensionalization + of the relative Helmholtz free energy. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_end_to_end_length_per_link = \ + parameters. \ + nondimensional_end_to_end_length_per_link_reference + \ + parameters. \ + nondimensional_end_to_end_length_per_link_scale * \ + (0.5 - np.random.rand()) + temperature = \ + parameters.temperature_reference + \ + parameters.temperature_scale*(0.5 - np.random.rand()) + nondimensional_relative_helmholtz_free_energy = \ + model.nondimensional_relative_helmholtz_free_energy( + np.array(nondimensional_end_to_end_length_per_link) + ) + end_to_end_length = nondimensional_end_to_end_length_per_link * \ + number_of_links*link_length + relative_helmholtz_free_energy = \ + model.relative_helmholtz_free_energy( + np.array(end_to_end_length), + temperature + ) + residual_abs = \ + relative_helmholtz_free_energy / \ + parameters.boltzmann_constant/temperature \ + - nondimensional_relative_helmholtz_free_energy + residual_rel = \ + residual_abs / \ + nondimensional_relative_helmholtz_free_energy + self.assertLessEqual( + np.abs(residual_abs), + parameters.abs_tol + ) + self.assertLessEqual( + np.abs(residual_rel), + parameters.rel_tol + ) + + def test_relative_helmholtz_free_energy_per_link(self): + """Function to test the nondimensionalization + of the relative Helmholtz free energy per link. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_end_to_end_length_per_link = \ + parameters. \ + nondimensional_end_to_end_length_per_link_reference + \ + parameters. \ + nondimensional_end_to_end_length_per_link_scale * \ + (0.5 - np.random.rand()) + temperature = \ + parameters.temperature_reference + \ + parameters.temperature_scale*(0.5 - np.random.rand()) + nondimensional_relative_helmholtz_free_energy_per_link = \ + model.nondimensional_relative_helmholtz_free_energy_per_link( + np.array(nondimensional_end_to_end_length_per_link) + ) + end_to_end_length = nondimensional_end_to_end_length_per_link * \ + number_of_links*link_length + relative_helmholtz_free_energy_per_link = \ + model.relative_helmholtz_free_energy_per_link( + np.array(end_to_end_length), + temperature + ) + residual_abs = \ + relative_helmholtz_free_energy_per_link / \ + parameters.boltzmann_constant/temperature \ + - nondimensional_relative_helmholtz_free_energy_per_link + residual_rel = \ + residual_abs / \ + nondimensional_relative_helmholtz_free_energy_per_link + self.assertLessEqual( + np.abs(residual_abs), + parameters.abs_tol + ) + self.assertLessEqual( + np.abs(residual_rel), + parameters.rel_tol + ) + + +class PerLink(unittest.TestCase): + """Class for per-linkness tests. + + """ + def test_helmholtz_free_energy(self): + """Function to test the per-linkness + of the Helmholtz free energy. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_end_to_end_length_per_link = \ + parameters. \ + nondimensional_end_to_end_length_per_link_reference + \ + parameters. \ + nondimensional_end_to_end_length_per_link_scale * \ + (0.5 - np.random.rand()) + temperature = \ + parameters.temperature_reference + \ + parameters.temperature_scale*(0.5 - np.random.rand()) + end_to_end_length = nondimensional_end_to_end_length_per_link * \ + number_of_links*link_length + helmholtz_free_energy = \ + model.helmholtz_free_energy( + np.array(end_to_end_length), + temperature + ) + helmholtz_free_energy_per_link = \ + model.helmholtz_free_energy_per_link( + np.array(end_to_end_length), + temperature + ) + residual_abs = \ + helmholtz_free_energy / \ + number_of_links \ + - helmholtz_free_energy_per_link + residual_rel = \ + residual_abs / \ + helmholtz_free_energy_per_link + self.assertLessEqual( + np.abs(residual_abs), + parameters.abs_tol + ) + self.assertLessEqual( + np.abs(residual_rel), + parameters.rel_tol + ) + + def test_relative_helmholtz_free_energy(self): + """Function to test the per-linkness + of the relative Helmholtz free energy. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_end_to_end_length_per_link = \ + parameters. \ + nondimensional_end_to_end_length_per_link_reference + \ + parameters. \ + nondimensional_end_to_end_length_per_link_scale * \ + (0.5 - np.random.rand()) + temperature = \ + parameters.temperature_reference + \ + parameters.temperature_scale*(0.5 - np.random.rand()) + end_to_end_length = nondimensional_end_to_end_length_per_link * \ + number_of_links*link_length + relative_helmholtz_free_energy = \ + model.relative_helmholtz_free_energy( + np.array(end_to_end_length), + temperature + ) + relative_helmholtz_free_energy_per_link = \ + model.relative_helmholtz_free_energy_per_link( + np.array(end_to_end_length), + temperature + ) + residual_abs = \ + relative_helmholtz_free_energy / \ + number_of_links \ + - relative_helmholtz_free_energy_per_link + residual_rel = \ + residual_abs / \ + relative_helmholtz_free_energy_per_link + self.assertLessEqual( + np.abs(residual_abs), + parameters.abs_tol + ) + self.assertLessEqual( + np.abs(residual_rel), + parameters.rel_tol + ) + + def test_nondimensional_helmholtz_free_energy(self): + """Function to test the per-linkness + of the nondimensional Helmholtz free energy. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_end_to_end_length_per_link = \ + parameters. \ + nondimensional_end_to_end_length_per_link_reference + \ + parameters. \ + nondimensional_end_to_end_length_per_link_scale * \ + (0.5 - np.random.rand()) + temperature = \ + parameters.temperature_reference + \ + parameters.temperature_scale*(0.5 - np.random.rand()) + nondimensional_helmholtz_free_energy = \ + model.nondimensional_helmholtz_free_energy( + np.array(nondimensional_end_to_end_length_per_link), + temperature + ) + nondimensional_helmholtz_free_energy_per_link = \ + model.nondimensional_helmholtz_free_energy_per_link( + np.array(nondimensional_end_to_end_length_per_link), + temperature + ) + residual_abs = \ + nondimensional_helmholtz_free_energy / \ + number_of_links \ + - nondimensional_helmholtz_free_energy_per_link + residual_rel = \ + residual_abs / \ + nondimensional_helmholtz_free_energy_per_link + self.assertLessEqual( + np.abs(residual_abs), + parameters.abs_tol + ) + self.assertLessEqual( + np.abs(residual_rel), + parameters.rel_tol + ) + + def test_nondimensional_relative_helmholtz_free_energy(self): + """Function to test the per-linkness + of the nondimensional relative Helmholtz free energy. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_end_to_end_length_per_link = \ + parameters. \ + nondimensional_end_to_end_length_per_link_reference + \ + parameters. \ + nondimensional_end_to_end_length_per_link_scale * \ + (0.5 - np.random.rand()) + nondimensional_relative_helmholtz_free_energy = \ + model.nondimensional_relative_helmholtz_free_energy( + np.array(nondimensional_end_to_end_length_per_link) + ) + nondimensional_relative_helmholtz_free_energy_per_link = \ + model.nondimensional_relative_helmholtz_free_energy_per_link( + np.array(nondimensional_end_to_end_length_per_link) + ) + residual_abs = \ + nondimensional_relative_helmholtz_free_energy / \ + number_of_links \ + - nondimensional_relative_helmholtz_free_energy_per_link + residual_rel = \ + residual_abs / \ + nondimensional_relative_helmholtz_free_energy_per_link + self.assertLessEqual( + np.abs(residual_abs), + parameters.abs_tol + ) + self.assertLessEqual( + np.abs(residual_rel), + parameters.rel_tol + ) + + +class Relative(unittest.TestCase): + """Class for relativeness tests. + + """ + def test_helmholtz_free_energy(self): + """Function to test the relativeness + of the Helmholtz free energy. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_end_to_end_length_per_link = \ + parameters. \ + nondimensional_end_to_end_length_per_link_reference + \ + parameters. \ + nondimensional_end_to_end_length_per_link_scale * \ + (0.5 - np.random.rand()) + temperature = \ + parameters.temperature_reference + \ + parameters.temperature_scale*(0.5 - np.random.rand()) + end_to_end_length = nondimensional_end_to_end_length_per_link * \ + number_of_links*link_length + helmholtz_free_energy = \ + model.helmholtz_free_energy( + np.array(end_to_end_length), + temperature + ) + helmholtz_free_energy_0 = \ + model.helmholtz_free_energy( + np.array(parameters.zero*number_of_links*link_length), + temperature + ) + relative_helmholtz_free_energy = \ + model.relative_helmholtz_free_energy( + np.array(end_to_end_length), + temperature + ) + residual_abs = \ + helmholtz_free_energy \ + - helmholtz_free_energy_0 \ + - relative_helmholtz_free_energy + residual_rel = \ + residual_abs / \ + relative_helmholtz_free_energy + self.assertLessEqual( + np.abs(residual_rel), + parameters.rel_tol + ) + + def test_helmholtz_free_energy_per_link(self): + """Function to test the relativeness + of the Helmholtz free energy per link. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_end_to_end_length_per_link = \ + parameters. \ + nondimensional_end_to_end_length_per_link_reference + \ + parameters. \ + nondimensional_end_to_end_length_per_link_scale * \ + (0.5 - np.random.rand()) + temperature = \ + parameters.temperature_reference + \ + parameters.temperature_scale*(0.5 - np.random.rand()) + end_to_end_length = nondimensional_end_to_end_length_per_link * \ + number_of_links*link_length + helmholtz_free_energy_per_link = \ + model.helmholtz_free_energy_per_link( + np.array(end_to_end_length), + temperature + ) + helmholtz_free_energy_per_link_0 = \ + model.helmholtz_free_energy_per_link( + np.array(parameters.zero*number_of_links*link_length), + temperature + ) + relative_helmholtz_free_energy_per_link = \ + model.relative_helmholtz_free_energy_per_link( + np.array(end_to_end_length), + temperature + ) + residual_abs = \ + helmholtz_free_energy_per_link \ + - helmholtz_free_energy_per_link_0 \ + - relative_helmholtz_free_energy_per_link + residual_rel = \ + residual_abs / \ + relative_helmholtz_free_energy_per_link + self.assertLessEqual( + np.abs(residual_rel), + parameters.rel_tol + ) + + def test_nondimensional_helmholtz_free_energy(self): + """Function to test the relativeness + of the nondimensional Helmholtz free energy. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_end_to_end_length_per_link = \ + parameters. \ + nondimensional_end_to_end_length_per_link_reference + \ + parameters. \ + nondimensional_end_to_end_length_per_link_scale * \ + (0.5 - np.random.rand()) + temperature = \ + parameters.temperature_reference + \ + parameters.temperature_scale*(0.5 - np.random.rand()) + nondimensional_helmholtz_free_energy = \ + model.nondimensional_helmholtz_free_energy( + np.array(nondimensional_end_to_end_length_per_link), + temperature + ) + nondimensional_helmholtz_free_energy_0 = \ + model.nondimensional_helmholtz_free_energy( + np.array(parameters.zero), + temperature + ) + nondimensional_relative_helmholtz_free_energy = \ + model.nondimensional_relative_helmholtz_free_energy( + np.array(nondimensional_end_to_end_length_per_link) + ) + residual_abs = \ + nondimensional_helmholtz_free_energy \ + - nondimensional_helmholtz_free_energy_0 \ + - nondimensional_relative_helmholtz_free_energy + residual_rel = \ + residual_abs / \ + nondimensional_relative_helmholtz_free_energy + self.assertLessEqual( + np.abs(residual_rel), + parameters.rel_tol + ) + + def test_nondimensional_helmholtz_free_energy_per_link(self): + """Function to test the relativeness + of the nondimensional Helmholtz free energy per link. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_end_to_end_length_per_link = \ + parameters. \ + nondimensional_end_to_end_length_per_link_reference + \ + parameters. \ + nondimensional_end_to_end_length_per_link_scale * \ + (0.5 - np.random.rand()) + temperature = \ + parameters.temperature_reference + \ + parameters.temperature_scale*(0.5 - np.random.rand()) + nondimensional_helmholtz_free_energy_per_link = \ + model.nondimensional_helmholtz_free_energy_per_link( + np.array(nondimensional_end_to_end_length_per_link), + temperature + ) + nondimensional_helmholtz_free_energy_per_link_0 = \ + model.nondimensional_helmholtz_free_energy_per_link( + np.array(parameters.zero), + temperature + ) + nondimensional_relative_helmholtz_free_energy_per_link = \ + model.nondimensional_relative_helmholtz_free_energy_per_link( + np.array(nondimensional_end_to_end_length_per_link) + ) + residual_abs = \ + nondimensional_helmholtz_free_energy_per_link \ + - nondimensional_helmholtz_free_energy_per_link_0 \ + - nondimensional_relative_helmholtz_free_energy_per_link + residual_rel = \ + residual_abs / \ + nondimensional_relative_helmholtz_free_energy_per_link + self.assertLessEqual( + np.abs(residual_rel), + parameters.rel_tol + ) + + +class Zero(unittest.TestCase): + """Class for zero tests. + + """ + def test_force(self): + """Function to test the zero + of the force. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + temperature = \ + parameters.temperature_reference + \ + parameters.temperature_scale*(0.5 - np.random.rand()) + force_0 = \ + model.force( + np.array(parameters.zero*number_of_links*link_length), + temperature + ) + self.assertLessEqual( + np.abs(force_0), + number_of_links*parameters.boltzmann_constant*temperature / + link_length*parameters.zero + ) + + def test_nondimensional_force(self): + """Function to test the zero + of the nondimensional force. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_force_0 = \ + model.nondimensional_force( + np.array(parameters.zero) + ) + self.assertLessEqual( + np.abs(nondimensional_force_0), + number_of_links*parameters.zero + ) + + def test_relative_helmholtz_free_energy(self): + """Function to test the zero + of the relative Helmholtz free energy. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + temperature = \ + parameters.temperature_reference + \ + parameters.temperature_scale*(0.5 - np.random.rand()) + relative_helmholtz_free_energy_0 = \ + model.relative_helmholtz_free_energy( + np.array(parameters.zero*number_of_links*link_length), + temperature + ) + self.assertLessEqual( + np.abs(relative_helmholtz_free_energy_0), + parameters.boltzmann_constant*temperature * + number_of_links*parameters.zero + ) + + def test_relative_helmholtz_free_energy_per_link(self): + """Function to test the zero + of the relative Helmholtz free energy per link. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + temperature = \ + parameters.temperature_reference + \ + parameters.temperature_scale*(0.5 - np.random.rand()) + relative_helmholtz_free_energy_per_link_0 = \ + model.relative_helmholtz_free_energy_per_link( + np.array(parameters.zero*number_of_links*link_length), + temperature + ) + self.assertLessEqual( + np.abs(relative_helmholtz_free_energy_per_link_0), + parameters.boltzmann_constant*temperature * + parameters.zero + ) + + def test_nondimensional_relative_helmholtz_free_energy(self): + """Function to test the zero + of the nondimensional relative Helmholtz free energy. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_relative_helmholtz_free_energy_0 = \ + model.nondimensional_relative_helmholtz_free_energy( + np.array(parameters.zero) + ) + self.assertLessEqual( + np.abs(nondimensional_relative_helmholtz_free_energy_0), + number_of_links*parameters.zero + ) + + def test_nondimensional_relative_helmholtz_free_energy_per_link(self): + """Function to test the zero + of the nondimensional relative Helmholtz free energy per link. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_relative_helmholtz_free_energy_per_link_0 = \ + model.nondimensional_relative_helmholtz_free_energy_per_link( + np.array(parameters.zero) + ) + self.assertLessEqual( + np.abs( + nondimensional_relative_helmholtz_free_energy_per_link_0 + ), parameters.zero + ) + + def test_equilibrium_radial_distribution(self): + """Function to test the zero + of the equilibrium radial distribution. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + equilibrium_radial_distribution_0 = \ + model.equilibrium_radial_distribution( + np.array(parameters.zero*number_of_links*link_length) + ) + self.assertLessEqual( + np.abs(equilibrium_radial_distribution_0), + parameters.zero + ) + + def test_nondimensional_equilibrium_radial_distribution(self): + """Function to test the zero + of the nondimensional equilibrium radial distribution. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_equilibrium_radial_distribution_0 = \ + model.nondimensional_equilibrium_radial_distribution( + np.array(parameters.zero) + ) + self.assertLessEqual( + np.abs(nondimensional_equilibrium_radial_distribution_0), + parameters.zero + ) + + +class Connection(unittest.TestCase): + """Class for connection tests. + + """ + def test_force(self): + """Function to test the connection + of the force. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_end_to_end_length_per_link = \ + parameters. \ + nondimensional_end_to_end_length_per_link_reference + \ + parameters. \ + nondimensional_end_to_end_length_per_link_scale * \ + (0.5 - np.random.rand()) + temperature = \ + parameters.temperature_reference + \ + parameters.temperature_scale*(0.5 - np.random.rand()) + end_to_end_length = nondimensional_end_to_end_length_per_link * \ + number_of_links*link_length + force = \ + model.force( + np.array(end_to_end_length), + temperature + ) + h_step = parameters.rel_tol * \ + number_of_links*link_length + force_from_derivative = ( + model.relative_helmholtz_free_energy( + np.array(end_to_end_length + 0.5*h_step), + temperature + ) + - model.relative_helmholtz_free_energy( + np.array(end_to_end_length - 0.5*h_step), + temperature + ))/h_step + residual_abs = \ + force \ + - force_from_derivative + residual_rel = residual_abs/force + self.assertLessEqual( + np.abs(residual_rel), h_step + ) + + def test_nondimensional_force(self): + """Function to test the connection + of the nondimensional force. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_end_to_end_length_per_link = \ + parameters. \ + nondimensional_end_to_end_length_per_link_reference + \ + parameters. \ + nondimensional_end_to_end_length_per_link_scale * \ + (0.5 - np.random.rand()) + nondimensional_force = \ + model.nondimensional_force( + np.array(nondimensional_end_to_end_length_per_link) + ) + h_step = parameters.rel_tol * \ + number_of_links*link_length + nondimensional_force_from_derivative = ( + model.nondimensional_relative_helmholtz_free_energy_per_link( + np.array( + nondimensional_end_to_end_length_per_link + 0.5*h_step + ) + ) + - model.nondimensional_relative_helmholtz_free_energy_per_link( + np.array( + nondimensional_end_to_end_length_per_link - 0.5*h_step + ) + ))/h_step + residual_abs = \ + nondimensional_force \ + - nondimensional_force_from_derivative + residual_rel = residual_abs/nondimensional_force + self.assertLessEqual( + np.abs(residual_rel), h_step + ) + + def test_relative_helmholtz_free_energy(self): + """Function to test the connection + of the relative Helmholtz free energy. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_end_to_end_length_per_link = \ + parameters. \ + nondimensional_end_to_end_length_per_link_reference + \ + parameters. \ + nondimensional_end_to_end_length_per_link_scale * \ + (0.5 - np.random.rand()) + temperature = \ + parameters.temperature_reference + \ + parameters.temperature_scale*(0.5 - np.random.rand()) + end_to_end_length = nondimensional_end_to_end_length_per_link * \ + number_of_links*link_length + relative_helmholtz_free_energy = \ + model.relative_helmholtz_free_energy( + np.array(end_to_end_length), + temperature + ) + relative_helmholtz_free_energy_from_connection = \ + parameters.boltzmann_constant*temperature * np.log( + model.equilibrium_distribution(np.array( + parameters.zero*number_of_links*link_length + )) / + model.equilibrium_distribution(np.array( + end_to_end_length + )) + ) + residual_abs = \ + relative_helmholtz_free_energy \ + - relative_helmholtz_free_energy_from_connection + residual_rel = residual_abs/relative_helmholtz_free_energy + self.assertLessEqual( + np.abs(residual_rel), parameters.rel_tol + ) + + def test_nondimensional_relative_helmholtz_free_energy(self): + """Function to test the connection + of the nondimensional relative Helmholtz free energy. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_end_to_end_length_per_link = \ + parameters. \ + nondimensional_end_to_end_length_per_link_reference + \ + parameters. \ + nondimensional_end_to_end_length_per_link_scale * \ + (0.5 - np.random.rand()) + nondimensional_relative_helmholtz_free_energy = \ + model.nondimensional_relative_helmholtz_free_energy( + np.array(nondimensional_end_to_end_length_per_link) + ) + nondimensional_relative_helmholtz_free_energy_from_connection = \ + np.log( + model.nondimensional_equilibrium_distribution(np.array( + parameters.zero + )) / + model.nondimensional_equilibrium_distribution(np.array( + nondimensional_end_to_end_length_per_link + )) + ) + residual_abs = \ + nondimensional_relative_helmholtz_free_energy \ + - nondimensional_relative_helmholtz_free_energy_from_connection + residual_rel = residual_abs / \ + nondimensional_relative_helmholtz_free_energy + self.assertLessEqual( + np.abs(residual_rel), parameters.rel_tol + ) diff --git a/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/test.rs b/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/test.rs index a5d052de..f9ecf564 100644 --- a/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/test.rs +++ b/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/test.rs @@ -466,6 +466,39 @@ mod zero use rand::Rng; use crate::physics::single_chain::ZERO; #[test] + fn force() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let temperature = parameters.temperature_reference + parameters.temperature_scale*(0.5 - rng.gen::()); + let force_0 = model.force(&(ZERO*(number_of_links as f64)*link_length), &temperature); + assert!(force_0.abs() <= BOLTZMANN_CONSTANT*temperature/link_length*(number_of_links as f64)*ZERO); + } + } + #[test] + fn nondimensional_force() + { + let mut rng = rand::thread_rng(); + let parameters = Parameters::default(); + for _ in 0..parameters.number_of_loops + { + let number_of_links: u8 = rng.gen_range(parameters.number_of_links_minimum..parameters.number_of_links_maximum); + let link_length = parameters.link_length_reference + parameters.link_length_scale*(0.5 - rng.gen::()); + let hinge_mass = parameters.hinge_mass_reference + parameters.hinge_mass_scale*(0.5 - rng.gen::()); + let well_width = parameters.well_width_reference + parameters.well_width_scale*(0.5 - rng.gen::()); + let model = SWFJC::init(number_of_links, link_length, hinge_mass, well_width); + let nondimensional_force_0 = model.nondimensional_force(&ZERO); + assert!(nondimensional_force_0.abs() <= (number_of_links as f64)*ZERO); + } + } + #[test] fn relative_helmholtz_free_energy() { let mut rng = rand::thread_rng(); diff --git a/src/physics/single_chain/swfjc/thermodynamics/isometric/py.rs b/src/physics/single_chain/swfjc/thermodynamics/isometric/py.rs new file mode 100644 index 00000000..76260a59 --- /dev/null +++ b/src/physics/single_chain/swfjc/thermodynamics/isometric/py.rs @@ -0,0 +1,53 @@ +use pyo3::prelude::*; + +pub fn register_module(py: Python<'_>, parent_module: &PyModule) -> PyResult<()> +{ + let isometric = PyModule::new(py, "isometric")?; + super::legendre::py::register_module(py, isometric)?; + parent_module.add_submodule(isometric)?; + isometric.add_class::()?; + Ok(()) +} + +/// The square-well freely-jointed chain (SWFJC) model thermodynamics in the isometric ensemble. +#[pyclass] +#[derive(Copy, Clone)] +pub struct SWFJC +{ + /// The mass of each hinge in the chain in units of kg/mol. + #[pyo3(get)] + pub hinge_mass: f64, + + /// The length of each link in the chain in units of nm. + #[pyo3(get)] + pub link_length: f64, + + /// The number of links in the chain. + #[pyo3(get)] + pub number_of_links: u8, + + /// The width of the well in units of nm. + #[pyo3(get)] + pub well_width: f64, + + /// The thermodynamic functions of the model in the isometric ensemble approximated using a Legendre transformation. + #[pyo3(get)] + pub legendre: super::legendre::py::SWFJC +} + +#[pymethods] +impl SWFJC +{ + #[new] + pub fn init(number_of_links: u8, link_length: f64, hinge_mass: f64, well_width: f64) -> Self + { + SWFJC + { + hinge_mass, + link_length, + number_of_links, + well_width, + legendre: super::legendre::py::SWFJC::init(number_of_links, link_length, hinge_mass, well_width) + } + } +} \ No newline at end of file diff --git a/src/physics/single_chain/swfjc/thermodynamics/isometric/test.py b/src/physics/single_chain/swfjc/thermodynamics/isometric/test.py new file mode 100644 index 00000000..f68bb106 --- /dev/null +++ b/src/physics/single_chain/swfjc/thermodynamics/isometric/test.py @@ -0,0 +1,143 @@ +"""Module to test the local module. + +""" +import unittest +import numpy as np +from polymers import physics +from ..test import Parameters + +parameters = Parameters() +SWFJC = physics.single_chain.swfjc.thermodynamics.isometric.SWFJC + + +class Base(unittest.TestCase): + """Class for basic tests. + + """ + def test_init(self): + """Function to test instantiation. + + """ + for _ in range(parameters.number_of_loops): + _ = SWFJC( + parameters.number_of_links_minimum, + parameters.link_length_reference, + parameters.hinge_mass_reference, + parameters.well_width_reference + ) + + def test_number_of_links(self): + """Function to test the number of links during instantiation. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + self.assertEqual( + number_of_links, + SWFJC( + number_of_links, + parameters.link_length_reference, + parameters.hinge_mass_reference, + parameters.well_width_reference + ).number_of_links + ) + + def test_link_length(self): + """Function to test the link length during instantiation. + + """ + for _ in range(parameters.number_of_loops): + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + self.assertEqual( + link_length, + SWFJC( + parameters.number_of_links_minimum, + link_length, + parameters.hinge_mass_reference, + parameters.well_width_reference + ).link_length + ) + + def test_hinge_mass(self): + """Function to test the hinge mass during instantiation. + + """ + for _ in range(parameters.number_of_loops): + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + self.assertEqual( + hinge_mass, + SWFJC( + parameters.number_of_links_minimum, + parameters.link_length_reference, + hinge_mass, + parameters.well_width_reference + ).hinge_mass + ) + + def test_well_width(self): + """Function to test the well width during instantiation. + + """ + for _ in range(parameters.number_of_loops): + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + self.assertEqual( + well_width, + SWFJC( + parameters.number_of_links_minimum, + parameters.link_length_reference, + parameters.hinge_mass_reference, + well_width + ).well_width + ) + + def test_all_parameters(self): + """Function to test all parameters during instantiation. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = \ + np.random.randint( + parameters.number_of_links_minimum, + high=parameters.number_of_links_maximum + ) + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + self.assertEqual( + number_of_links, + model.number_of_links + ) + self.assertEqual( + link_length, + model.link_length + ) + self.assertEqual( + hinge_mass, + model.hinge_mass + ) + self.assertEqual( + well_width, + model.well_width + ) diff --git a/src/physics/single_chain/swfjc/thermodynamics/py.rs b/src/physics/single_chain/swfjc/thermodynamics/py.rs index ecbc643c..5b564e9d 100644 --- a/src/physics/single_chain/swfjc/thermodynamics/py.rs +++ b/src/physics/single_chain/swfjc/thermodynamics/py.rs @@ -3,6 +3,7 @@ use pyo3::prelude::*; pub fn register_module(py: Python<'_>, parent_module: &PyModule) -> PyResult<()> { let thermodynamics = PyModule::new(py, "thermodynamics")?; + super::isometric::py::register_module(py, thermodynamics)?; super::isotensional::py::register_module(py, thermodynamics)?; parent_module.add_submodule(thermodynamics)?; thermodynamics.add_class::()?; @@ -30,6 +31,10 @@ pub struct SWFJC #[pyo3(get)] pub well_width: f64, + /// The thermodynamic functions of the model in the isometric ensemble. + #[pyo3(get)] + pub isometric: super::isometric::py::SWFJC, + /// The thermodynamic functions of the model in the isotensional ensemble. #[pyo3(get)] pub isotensional: super::isotensional::py::SWFJC @@ -47,6 +52,7 @@ impl SWFJC link_length, number_of_links, well_width, + isometric: super::isometric::py::SWFJC::init(number_of_links, link_length, hinge_mass, well_width), isotensional: super::isotensional::py::SWFJC::init(number_of_links, link_length, hinge_mass, well_width) } } diff --git a/src/physics/single_chain/swfjc/thermodynamics/test.py b/src/physics/single_chain/swfjc/thermodynamics/test.py index e47ca274..487250a1 100644 --- a/src/physics/single_chain/swfjc/thermodynamics/test.py +++ b/src/physics/single_chain/swfjc/thermodynamics/test.py @@ -141,3 +141,613 @@ def test_all_parameters(self): well_width, model.well_width ) + + +class Legendre(unittest.TestCase): + """Class for Legendre transformation tests. + + """ + def test_force(self): + """Function to test the Legendre transformation + of the force. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = parameters.number_of_links_maximum + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_force = \ + parameters. \ + nondimensional_force_reference + \ + parameters. \ + nondimensional_force_scale * \ + (0.5 - np.random.rand()) + temperature = \ + parameters.temperature_reference + \ + parameters.temperature_scale*(0.5 - np.random.rand()) + force = nondimensional_force * \ + parameters.boltzmann_constant*temperature/link_length + end_to_end_length = \ + model.isotensional.end_to_end_length( + np.array(force), + temperature + ) + force_out = \ + model.isometric.legendre.force( + np.array(end_to_end_length), + temperature + ) + residual_abs = \ + force \ + - force_out + residual_rel = residual_abs/force + self.assertLessEqual( + np.abs(residual_abs), + parameters.abs_tol + ) + self.assertLessEqual( + np.abs(residual_rel), + parameters.rel_tol + ) + + def test_nondimensional_force(self): + """Function to test the Legendre transformation + of the nondimensional force. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = parameters.number_of_links_maximum + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_force = \ + parameters. \ + nondimensional_force_reference + \ + parameters. \ + nondimensional_force_scale * \ + (0.5 - np.random.rand()) + nondimensional_end_to_end_length_per_link = \ + model.isotensional.nondimensional_end_to_end_length_per_link( + np.array(nondimensional_force) + ) + nondimensional_force_out = \ + model.isometric.legendre.nondimensional_force( + np.array(nondimensional_end_to_end_length_per_link) + ) + residual_abs = \ + nondimensional_force \ + - nondimensional_force_out + residual_rel = residual_abs/nondimensional_force + self.assertLessEqual( + np.abs(residual_abs), + parameters.abs_tol + ) + self.assertLessEqual( + np.abs(residual_rel), + parameters.rel_tol + ) + + def test_helmholtz_free_energy(self): + """Function to test the Legendre transformation + of the Helmholtz free energy. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = parameters.number_of_links_maximum + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_force = \ + parameters. \ + nondimensional_force_reference + \ + parameters. \ + nondimensional_force_scale * \ + (0.5 - np.random.rand()) + temperature = \ + parameters.temperature_reference + \ + parameters.temperature_scale*(0.5 - np.random.rand()) + force = nondimensional_force * \ + parameters.boltzmann_constant*temperature/link_length + end_to_end_length = \ + model.isotensional.end_to_end_length( + np.array(force), + temperature + ) + helmholtz_free_energy_legendre = \ + model.isotensional.gibbs_free_energy( + np.array(force), + temperature + ) + force*end_to_end_length + helmholtz_free_energy_legendre_out = \ + model.isometric.legendre.helmholtz_free_energy( + np.array(end_to_end_length), + temperature + ) + residual_abs = \ + helmholtz_free_energy_legendre \ + - helmholtz_free_energy_legendre_out \ + + parameters.boltzmann_constant*temperature*np.log( + 8*np.pi**2*hinge_mass*link_length**2 * + parameters.boltzmann_constant*temperature / + parameters.planck_constant**2 + ) + residual_rel = residual_abs/helmholtz_free_energy_legendre + self.assertLessEqual( + np.abs(residual_abs), + parameters.abs_tol + ) + self.assertLessEqual( + np.abs(residual_rel), + parameters.rel_tol + ) + + def test_helmholtz_free_energy_per_link(self): + """Function to test the Legendre transformation + of the Helmholtz free energy per link. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = parameters.number_of_links_maximum + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_force = \ + parameters. \ + nondimensional_force_reference + \ + parameters. \ + nondimensional_force_scale * \ + (0.5 - np.random.rand()) + temperature = \ + parameters.temperature_reference + \ + parameters.temperature_scale*(0.5 - np.random.rand()) + force = nondimensional_force * \ + parameters.boltzmann_constant*temperature/link_length + end_to_end_length = \ + model.isotensional.end_to_end_length( + np.array(force), + temperature + ) + end_to_end_length_per_link = \ + model.isotensional.end_to_end_length_per_link( + np.array(force), + temperature + ) + helmholtz_free_energy_per_link_legendre = \ + model.isotensional.gibbs_free_energy_per_link( + np.array(force), + temperature + ) + force*end_to_end_length_per_link + helmholtz_free_energy_per_link_legendre_out = \ + model.isometric.legendre.helmholtz_free_energy_per_link( + np.array(end_to_end_length), + temperature + ) + residual_abs = \ + helmholtz_free_energy_per_link_legendre \ + - helmholtz_free_energy_per_link_legendre_out \ + + parameters.boltzmann_constant*temperature*np.log( + 8*np.pi**2*hinge_mass*link_length**2 * + parameters.boltzmann_constant*temperature / + parameters.planck_constant**2 + )/number_of_links + residual_rel = residual_abs/helmholtz_free_energy_per_link_legendre + self.assertLessEqual( + np.abs(residual_abs), + parameters.abs_tol + ) + self.assertLessEqual( + np.abs(residual_rel), + parameters.rel_tol + ) + + def test_relative_helmholtz_free_energy(self): + """Function to test the Legendre transformation + of the relative Helmholtz free energy. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = parameters.number_of_links_maximum + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_force = \ + parameters. \ + nondimensional_force_reference + \ + parameters. \ + nondimensional_force_scale * \ + (0.5 - np.random.rand()) + temperature = \ + parameters.temperature_reference + \ + parameters.temperature_scale*(0.5 - np.random.rand()) + force = nondimensional_force * \ + parameters.boltzmann_constant*temperature/link_length + end_to_end_length = \ + model.isotensional.end_to_end_length( + np.array(force), + temperature + ) + relative_helmholtz_free_energy_legendre = \ + model.isotensional.relative_gibbs_free_energy( + np.array(force), + temperature + ) + force*end_to_end_length + relative_helmholtz_free_energy_legendre_out = \ + model.isometric.legendre.relative_helmholtz_free_energy( + np.array(end_to_end_length), + temperature + ) + residual_abs = \ + relative_helmholtz_free_energy_legendre \ + - relative_helmholtz_free_energy_legendre_out + residual_rel = residual_abs/relative_helmholtz_free_energy_legendre + self.assertLessEqual( + np.abs(residual_rel), + 3e1 * parameters.rel_tol + ) + + def test_relative_helmholtz_free_energy_per_link(self): + """Function to test the Legendre transformation + of the relative Helmholtz free energy per link. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = parameters.number_of_links_maximum + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_force = \ + parameters. \ + nondimensional_force_reference + \ + parameters. \ + nondimensional_force_scale * \ + (0.5 - np.random.rand()) + temperature = \ + parameters.temperature_reference + \ + parameters.temperature_scale*(0.5 - np.random.rand()) + force = nondimensional_force * \ + parameters.boltzmann_constant*temperature/link_length + end_to_end_length = \ + model.isotensional.end_to_end_length( + np.array(force), + temperature + ) + end_to_end_length_per_link = \ + model.isotensional.end_to_end_length_per_link( + np.array(force), + temperature + ) + relative_helmholtz_free_energy_per_link_legendre = \ + model.isotensional.relative_gibbs_free_energy_per_link( + np.array(force), + temperature + ) + force*end_to_end_length_per_link + relative_helmholtz_free_energy_per_link_legendre_out = \ + model.isometric.legendre. \ + relative_helmholtz_free_energy_per_link( + np.array(end_to_end_length), + temperature + ) + residual_abs = \ + relative_helmholtz_free_energy_per_link_legendre \ + - relative_helmholtz_free_energy_per_link_legendre_out + residual_rel = residual_abs / \ + relative_helmholtz_free_energy_per_link_legendre + self.assertLessEqual( + np.abs(residual_rel), + 3e1 * parameters.rel_tol + ) + + def test_nondimensional_helmholtz_free_energy(self): + """Function to test the Legendre transformation + of the nondimensional Helmholtz free energy. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = parameters.number_of_links_maximum + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_force = \ + parameters. \ + nondimensional_force_reference + \ + parameters. \ + nondimensional_force_scale * \ + (0.5 - np.random.rand()) + temperature = \ + parameters.temperature_reference + \ + parameters.temperature_scale*(0.5 - np.random.rand()) + nondimensional_end_to_end_length = \ + model.isotensional.nondimensional_end_to_end_length( + np.array(nondimensional_force), + ) + nondimensional_end_to_end_length_per_link = \ + model.isotensional.nondimensional_end_to_end_length_per_link( + np.array(nondimensional_force), + ) + nondimensional_helmholtz_free_energy_legendre = \ + model.isotensional.nondimensional_gibbs_free_energy( + np.array(nondimensional_force), + temperature + ) + nondimensional_force*nondimensional_end_to_end_length + nondimensional_helmholtz_free_energy_legendre_out = \ + model.isometric.legendre.nondimensional_helmholtz_free_energy( + np.array(nondimensional_end_to_end_length_per_link), + temperature + ) + residual_abs = \ + nondimensional_helmholtz_free_energy_legendre \ + - nondimensional_helmholtz_free_energy_legendre_out \ + + np.log( + 8*np.pi**2*hinge_mass*link_length**2 * + parameters.boltzmann_constant*temperature / + parameters.planck_constant**2 + ) + residual_rel = residual_abs / \ + nondimensional_helmholtz_free_energy_legendre + self.assertLessEqual( + np.abs(residual_abs), + parameters.abs_tol + ) + self.assertLessEqual( + np.abs(residual_rel), + parameters.rel_tol + ) + + def test_nondimensional_helmholtz_free_energy_per_link(self): + """Function to test the Legendre transformation + of the nondimensional Helmholtz free energy per link. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = parameters.number_of_links_maximum + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_force = \ + parameters. \ + nondimensional_force_reference + \ + parameters. \ + nondimensional_force_scale * \ + (0.5 - np.random.rand()) + temperature = \ + parameters.temperature_reference + \ + parameters.temperature_scale*(0.5 - np.random.rand()) + nondimensional_end_to_end_length_per_link = \ + model.isotensional.nondimensional_end_to_end_length_per_link( + np.array(nondimensional_force), + ) + nondimensional_helmholtz_free_energy_per_link_legendre = \ + model.isotensional.nondimensional_gibbs_free_energy_per_link( + np.array(nondimensional_force), + temperature + ) + nondimensional_force * \ + nondimensional_end_to_end_length_per_link + nondimensional_helmholtz_free_energy_per_link_legendre_out = \ + model.isometric.legendre. \ + nondimensional_helmholtz_free_energy_per_link( + np.array(nondimensional_end_to_end_length_per_link), + temperature + ) + residual_abs = \ + nondimensional_helmholtz_free_energy_per_link_legendre \ + - nondimensional_helmholtz_free_energy_per_link_legendre_out \ + + np.log( + 8*np.pi**2*hinge_mass*link_length**2 * + parameters.boltzmann_constant*temperature / + parameters.planck_constant**2 + )/number_of_links + residual_rel = residual_abs / \ + nondimensional_helmholtz_free_energy_per_link_legendre + self.assertLessEqual( + np.abs(residual_abs), + parameters.abs_tol + ) + self.assertLessEqual( + np.abs(residual_rel), + parameters.rel_tol + ) + + def test_nondimensional_relative_helmholtz_free_energy(self): + """Function to test the Legendre transformation + of the nondimensional relative Helmholtz free energy. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = parameters.number_of_links_maximum + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_force = \ + parameters. \ + nondimensional_force_reference + \ + parameters. \ + nondimensional_force_scale * \ + (0.5 - np.random.rand()) + nondimensional_end_to_end_length = \ + model.isotensional.nondimensional_end_to_end_length( + np.array(nondimensional_force), + ) + nondimensional_end_to_end_length_per_link = \ + model.isotensional.nondimensional_end_to_end_length_per_link( + np.array(nondimensional_force), + ) + nondimensional_relative_helmholtz_free_energy_legendre = \ + model.isotensional.nondimensional_relative_gibbs_free_energy( + np.array(nondimensional_force) + ) + nondimensional_force*nondimensional_end_to_end_length + nondimensional_relative_helmholtz_free_energy_legendre_out = \ + model.isometric.legendre. \ + nondimensional_relative_helmholtz_free_energy( + np.array(nondimensional_end_to_end_length_per_link) + ) + residual_abs = \ + nondimensional_relative_helmholtz_free_energy_legendre \ + - nondimensional_relative_helmholtz_free_energy_legendre_out + residual_rel = residual_abs / \ + nondimensional_relative_helmholtz_free_energy_legendre + self.assertLessEqual( + np.abs(residual_rel), + 3e1 * parameters.rel_tol + ) + + def test_nondimensional_relative_helmholtz_free_energy_per_link(self): + """Function to test the Legendre transformation + of the nondimensional relative Helmholtz free energy per link. + + """ + for _ in range(parameters.number_of_loops): + number_of_links = parameters.number_of_links_maximum + link_length = \ + parameters.link_length_reference + \ + parameters.link_length_scale*(0.5 - np.random.rand()) + hinge_mass = \ + parameters.hinge_mass_reference + \ + parameters.hinge_mass_scale*(0.5 - np.random.rand()) + well_width = \ + parameters.well_width_reference + \ + parameters.well_width_scale*(0.5 - np.random.rand()) + model = SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width + ) + nondimensional_force = \ + parameters. \ + nondimensional_force_reference + \ + parameters. \ + nondimensional_force_scale * \ + (0.5 - np.random.rand()) + nondimensional_end_to_end_length_per_link = \ + model.isotensional.nondimensional_end_to_end_length_per_link( + np.array(nondimensional_force), + ) + nondim_relative_helmholtz_free_energy_per_link_legendre = \ + model.isotensional. \ + nondimensional_relative_gibbs_free_energy_per_link( + np.array(nondimensional_force) + ) + nondimensional_force * \ + nondimensional_end_to_end_length_per_link + nondim_relative_helmholtz_free_energy_per_link_legendre_out = \ + model.isometric.legendre. \ + nondimensional_relative_helmholtz_free_energy_per_link( + np.array(nondimensional_end_to_end_length_per_link) + ) + residual_abs = \ + nondim_relative_helmholtz_free_energy_per_link_legendre \ + - nondim_relative_helmholtz_free_energy_per_link_legendre_out + residual_rel = residual_abs / \ + nondim_relative_helmholtz_free_energy_per_link_legendre + self.assertLessEqual( + np.abs(residual_rel), + 3e1 * parameters.rel_tol + ) From ac7cbf64a2f6cee8f9e90718692c75e8fbeff483 Mon Sep 17 00:00:00 2001 From: mrbuche Date: Mon, 17 Apr 2023 16:46:05 -0600 Subject: [PATCH 4/5] julia setup --- .../single_chain/fjc/thermodynamics/test.jl | 3 +- .../thermodynamics/isometric/legendre/ex.rs | 70 ++++++++++++++ .../thermodynamics/isometric/legendre/mod.jl | 46 +++++++++ .../thermodynamics/isometric/legendre/test.jl | 95 +++++++++++++++++++ .../swfjc/thermodynamics/isometric/mod.jl | 59 ++++++++++++ .../swfjc/thermodynamics/isometric/test.jl | 95 +++++++++++++++++++ .../single_chain/swfjc/thermodynamics/mod.jl | 6 ++ test/runtests.jl | 2 + 8 files changed, 375 insertions(+), 1 deletion(-) create mode 100644 src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/ex.rs create mode 100644 src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/mod.jl create mode 100644 src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/test.jl create mode 100644 src/physics/single_chain/swfjc/thermodynamics/isometric/mod.jl create mode 100644 src/physics/single_chain/swfjc/thermodynamics/isometric/test.jl diff --git a/src/physics/single_chain/fjc/thermodynamics/test.jl b/src/physics/single_chain/fjc/thermodynamics/test.jl index 8e8c154a..6efee727 100644 --- a/src/physics/single_chain/fjc/thermodynamics/test.jl +++ b/src/physics/single_chain/fjc/thermodynamics/test.jl @@ -335,7 +335,8 @@ end ) residual_abs = nondimensional_helmholtz_free_energy_per_link_legendre - - nondimensional_helmholtz_free_energy_per_link_legendre_out + log( + nondimensional_helmholtz_free_energy_per_link_legendre_out + + log( 8.0 * pi^2 * hinge_mass * link_length^2 * BOLTZMANN_CONSTANT * temperature / PLANCK_CONSTANT^2, ) / number_of_links diff --git a/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/ex.rs b/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/ex.rs new file mode 100644 index 00000000..5da36d76 --- /dev/null +++ b/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/ex.rs @@ -0,0 +1,70 @@ +#[no_mangle] +pub extern fn physics_single_chain_swfjc_thermodynamics_isometric_legendre_force(number_of_links: u8, link_length: f64, well_width: f64, end_to_end_length: f64, temperature: f64) -> f64 +{ + super::force(&number_of_links, &link_length, &well_width, &end_to_end_length, &temperature) +} +#[no_mangle] +pub extern fn physics_single_chain_swfjc_thermodynamics_isometric_legendre_nondimensional_force(link_length: f64, well_width: f64, nondimensional_end_to_end_length_per_link: f64) -> f64 +{ + super::nondimensional_force(&link_length, &well_width, &nondimensional_end_to_end_length_per_link) +} +#[no_mangle] +pub extern fn physics_single_chain_swfjc_thermodynamics_isometric_legendre_helmholtz_free_energy(number_of_links: u8, link_length: f64, hinge_mass: f64, well_width: f64, end_to_end_length: f64, temperature: f64) -> f64 +{ + super::helmholtz_free_energy(&&number_of_links, &link_length, &hinge_mass, &well_width, &end_to_end_length, &temperature) +} +#[no_mangle] +pub extern fn physics_single_chain_swfjc_thermodynamics_isometric_legendre_helmholtz_free_energy_per_link(number_of_links: u8, link_length: f64, hinge_mass: f64, well_width: f64, end_to_end_length: f64, temperature: f64) -> f64 +{ + super::helmholtz_free_energy_per_link(&&number_of_links, &link_length, &hinge_mass, &well_width, &end_to_end_length, &temperature) +} +#[no_mangle] +pub extern fn physics_single_chain_swfjc_thermodynamics_isometric_legendre_relative_helmholtz_free_energy(number_of_links: u8, link_length: f64, well_width: f64, end_to_end_length: f64, temperature: f64) -> f64 +{ + super::relative_helmholtz_free_energy(&number_of_links, &link_length, &well_width, &end_to_end_length, &temperature) +} +#[no_mangle] +pub extern fn physics_single_chain_swfjc_thermodynamics_isometric_legendre_relative_helmholtz_free_energy_per_link(number_of_links: u8, link_length: f64, well_width: f64, end_to_end_length: f64, temperature: f64) -> f64 +{ + super::relative_helmholtz_free_energy_per_link(&number_of_links, &link_length, &well_width, &end_to_end_length, &temperature) +} +#[no_mangle] +pub extern fn physics_single_chain_swfjc_thermodynamics_isometric_legendre_nondimensional_helmholtz_free_energy(number_of_links: u8, link_length: f64, hinge_mass: f64, well_width: f64, nondimensional_end_to_end_length_per_link: f64, temperature: f64) -> f64 +{ + super::nondimensional_helmholtz_free_energy(&&number_of_links, &link_length, &hinge_mass, &well_width, &nondimensional_end_to_end_length_per_link, &temperature) +} +#[no_mangle] +pub extern fn physics_single_chain_swfjc_thermodynamics_isometric_legendre_nondimensional_helmholtz_free_energy_per_link(number_of_links: u8, link_length: f64, hinge_mass: f64, well_width: f64, nondimensional_end_to_end_length_per_link: f64, temperature: f64) -> f64 +{ + super::nondimensional_helmholtz_free_energy_per_link(&&number_of_links, &link_length, &hinge_mass, &well_width, &nondimensional_end_to_end_length_per_link, &temperature) +} +#[no_mangle] +pub extern fn physics_single_chain_swfjc_thermodynamics_isometric_legendre_nondimensional_relative_helmholtz_free_energy(number_of_links: u8, link_length: f64, well_width: f64, nondimensional_end_to_end_length_per_link: f64) -> f64 +{ + super::nondimensional_relative_helmholtz_free_energy(&number_of_links, &link_length, &well_width, &nondimensional_end_to_end_length_per_link) +} +#[no_mangle] +pub extern fn physics_single_chain_swfjc_thermodynamics_isometric_legendre_nondimensional_relative_helmholtz_free_energy_per_link(link_length: f64, well_width: f64, nondimensional_end_to_end_length_per_link: f64) -> f64 +{ + super::nondimensional_relative_helmholtz_free_energy_per_link(&link_length, &well_width, &nondimensional_end_to_end_length_per_link) +} +#[no_mangle] +pub extern fn physics_single_chain_swfjc_thermodynamics_isometric_legendre_equilibrium_distribution(number_of_links: u8, link_length: f64, well_width: f64, normalization_nondimensional_equilibrium_distribution: f64, end_to_end_length: f64) -> f64 +{ + super::equilibrium_distribution(&number_of_links, &link_length, &well_width, &normalization_nondimensional_equilibrium_distribution, &end_to_end_length) +} +#[no_mangle] +pub extern fn physics_single_chain_swfjc_thermodynamics_isometric_legendre_nondimensional_equilibrium_distribution(number_of_links: u8, link_length: f64, well_width: f64, normalization_nondimensional_equilibrium_distribution: f64, nondimensional_end_to_end_length_per_link: f64) -> f64 +{ + super::nondimensional_equilibrium_distribution(&number_of_links, &link_length, &well_width, &normalization_nondimensional_equilibrium_distribution, &nondimensional_end_to_end_length_per_link) +} +#[no_mangle] +pub extern fn physics_single_chain_swfjc_thermodynamics_isometric_legendre_equilibrium_radial_distribution(number_of_links: u8, link_length: f64, well_width: f64, normalization_nondimensional_equilibrium_distribution: f64, end_to_end_length: f64) -> f64 +{ + super::equilibrium_radial_distribution(&number_of_links, &link_length, &well_width, &normalization_nondimensional_equilibrium_distribution, &end_to_end_length) +} +#[no_mangle] +pub extern fn physics_single_chain_swfjc_thermodynamics_isometric_legendre_nondimensional_equilibrium_radial_distribution(number_of_links: u8, link_length: f64, well_width: f64, normalization_nondimensional_equilibrium_distribution: f64, nondimensional_end_to_end_length_per_link: f64) -> f64 +{ + super::nondimensional_equilibrium_radial_distribution(&number_of_links, &link_length, &well_width, &normalization_nondimensional_equilibrium_distribution, &nondimensional_end_to_end_length_per_link) +} \ No newline at end of file diff --git a/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/mod.jl b/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/mod.jl new file mode 100644 index 00000000..7cd70f0a --- /dev/null +++ b/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/mod.jl @@ -0,0 +1,46 @@ +""" +The square-well freely-jointed chain (SWFJC) model thermodynamics in the isometric ensemble approximated using a Legendre transformation. +""" +module Legendre + +using DocStringExtensions +using .......Polymers: PROJECT_ROOT + +""" +The structure of the thermodynamics of the SWFJC model in the isometric ensemble approximated using a Legendre transformation. +$(FIELDS) +""" +struct SWFJC + """ + The number of links in the chain ``N_b``. + """ + number_of_links::UInt8 + """ + The length of each link in the chain ``\\ell_b`` in units of nm. + """ + link_length::Float64 + """ + The mass of each hinge in the chain ``m`` in units of kg/mol. + """ + hinge_mass::Float64 + """ + The width of the well ``w`` in units of nm. + """ + well_width::Float64 +end + +""" +Initializes and returns an instance of the thermodynamics of the SWFJC model in the isometric ensemble approximated using a Legendre transformation. + +$(TYPEDSIGNATURES) +""" +function SWFJC( + number_of_links::UInt8, + link_length::Float64, + hinge_mass::Float64, + well_width::Float64, +) + return SWFJC(number_of_links, link_length, hinge_mass, well_width) +end + +end diff --git a/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/test.jl b/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/test.jl new file mode 100644 index 00000000..ba985a36 --- /dev/null +++ b/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/test.jl @@ -0,0 +1,95 @@ +module Test + +using Test +using Polymers.Physics: BOLTZMANN_CONSTANT +using Polymers.Physics.SingleChain: ZERO, parameters +using Polymers.Physics.SingleChain.Swfjc.Thermodynamics.Isometric.Legendre: SWFJC + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::base::init" begin + @test isa( + SWFJC( + parameters.number_of_links_minimum, + parameters.link_length_reference, + parameters.hinge_mass_reference, + parameters.well_width_reference, + ), + Any, + ) +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::base::number_of_links" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + @test SWFJC( + number_of_links, + parameters.link_length_reference, + parameters.hinge_mass_reference, + parameters.well_width_reference, + ).number_of_links == number_of_links + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::base::link_length" begin + for _ = 1:parameters.number_of_loops + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + @test SWFJC( + parameters.number_of_links_minimum, + link_length, + parameters.hinge_mass_reference, + parameters.well_width_reference, + ).link_length == link_length + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::base::hinge_mass" begin + for _ = 1:parameters.number_of_loops + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + @test SWFJC( + parameters.number_of_links_minimum, + parameters.link_length_reference, + hinge_mass, + parameters.well_width_reference, + ).hinge_mass == hinge_mass + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::base::well_width" begin + for _ = 1:parameters.number_of_loops + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + @test SWFJC( + parameters.number_of_links_minimum, + parameters.link_length_reference, + parameters.hinge_mass_reference, + well_width, + ).well_width == well_width + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::base::all_parameters" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + @test all( + SWFJC(number_of_links, link_length, hinge_mass, well_width).number_of_links == + number_of_links && + SWFJC(number_of_links, link_length, hinge_mass, well_width).link_length == + link_length && + SWFJC(number_of_links, link_length, hinge_mass, well_width).hinge_mass == + hinge_mass && + SWFJC(number_of_links, link_length, hinge_mass, well_width).well_width == + well_width, + ) + end +end + +end diff --git a/src/physics/single_chain/swfjc/thermodynamics/isometric/mod.jl b/src/physics/single_chain/swfjc/thermodynamics/isometric/mod.jl new file mode 100644 index 00000000..95d16c0f --- /dev/null +++ b/src/physics/single_chain/swfjc/thermodynamics/isometric/mod.jl @@ -0,0 +1,59 @@ +""" +The square-well freely-jointed chain (SWFJC) model thermodynamics in the isometric ensemble. +""" +module Isometric + +using DocStringExtensions +using ......Polymers: PROJECT_ROOT + +include("legendre/mod.jl") + +""" +The structure of the thermodynamics of the SWFJC model in the isometric ensemble. + +$(FIELDS) +""" +struct SWFJC + """ + The number of links in the chain ``N_b``. + """ + number_of_links::UInt8 + """ + The length of each link in the chain ``\\ell_b`` in units of nm. + """ + link_length::Float64 + """ + The mass of each hinge in the chain ``m`` in units of kg/mol. + """ + hinge_mass::Float64 + """ + The width of the well ``w`` in units of nm. + """ + well_width::Float64 + """ + The thermodynamic functions of the model in the isometric ensemble approximated using a Legendre transformation. + """ + legendre::Any +end + +""" +Initializes and returns an instance of the thermodynamics of the SWFJC model in the isometric ensemble. + +$(TYPEDSIGNATURES) +""" +function SWFJC( + number_of_links::UInt8, + link_length::Float64, + hinge_mass::Float64, + well_width::Float64, +) + return SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width, + Legendre.SWFJC(number_of_links, link_length, hinge_mass, well_width), + ) +end + +end diff --git a/src/physics/single_chain/swfjc/thermodynamics/isometric/test.jl b/src/physics/single_chain/swfjc/thermodynamics/isometric/test.jl new file mode 100644 index 00000000..2324c64c --- /dev/null +++ b/src/physics/single_chain/swfjc/thermodynamics/isometric/test.jl @@ -0,0 +1,95 @@ +module Test + +using Test +using Polymers.Physics: BOLTZMANN_CONSTANT +using Polymers.Physics.SingleChain: ZERO, parameters +using Polymers.Physics.SingleChain.Swfjc.Thermodynamics.Isometric: SWFJC + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::test::base::init" begin + @test isa( + SWFJC( + parameters.number_of_links_minimum, + parameters.link_length_reference, + parameters.hinge_mass_reference, + parameters.well_width_reference, + ), + Any, + ) +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::test::base::number_of_links" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + @test SWFJC( + number_of_links, + parameters.link_length_reference, + parameters.hinge_mass_reference, + parameters.well_width_reference, + ).number_of_links == number_of_links + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::test::base::link_length" begin + for _ = 1:parameters.number_of_loops + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + @test SWFJC( + parameters.number_of_links_minimum, + link_length, + parameters.hinge_mass_reference, + parameters.well_width_reference, + ).link_length == link_length + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::test::base::hinge_mass" begin + for _ = 1:parameters.number_of_loops + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + @test SWFJC( + parameters.number_of_links_minimum, + parameters.link_length_reference, + hinge_mass, + parameters.well_width_reference, + ).hinge_mass == hinge_mass + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::test::base::well_width" begin + for _ = 1:parameters.number_of_loops + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + @test SWFJC( + parameters.number_of_links_minimum, + parameters.link_length_reference, + parameters.hinge_mass_reference, + well_width, + ).well_width == well_width + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::test::base::all_parameters" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + @test all( + SWFJC(number_of_links, link_length, hinge_mass, well_width).number_of_links == + number_of_links && + SWFJC(number_of_links, link_length, hinge_mass, well_width).link_length == + link_length && + SWFJC(number_of_links, link_length, hinge_mass, well_width).hinge_mass == + hinge_mass && + SWFJC(number_of_links, link_length, hinge_mass, well_width).well_width == + well_width, + ) + end +end + +end diff --git a/src/physics/single_chain/swfjc/thermodynamics/mod.jl b/src/physics/single_chain/swfjc/thermodynamics/mod.jl index 1266e200..cad93a01 100644 --- a/src/physics/single_chain/swfjc/thermodynamics/mod.jl +++ b/src/physics/single_chain/swfjc/thermodynamics/mod.jl @@ -5,6 +5,7 @@ module Thermodynamics using DocStringExtensions +include("isometric/mod.jl") include("isotensional/mod.jl") """ @@ -30,6 +31,10 @@ struct SWFJC """ well_width::Float64 """ + The thermodynamic functions of the model in the isometric ensemble. + """ + isometric::Any + """ The thermodynamic functions of the model in the isotensional ensemble. """ isotensional::Any @@ -51,6 +56,7 @@ function SWFJC( link_length, hinge_mass, well_width, + Isometric.SWFJC(number_of_links, link_length, hinge_mass, well_width), Isotensional.SWFJC(number_of_links, link_length, hinge_mass, well_width), ) end diff --git a/test/runtests.jl b/test/runtests.jl index 9b18bdba..12029146 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -44,6 +44,8 @@ include( ) include("../src/physics/single_chain/swfjc/test.jl") include("../src/physics/single_chain/swfjc/thermodynamics/test.jl") +include("../src/physics/single_chain/swfjc/thermodynamics/isometric/test.jl") +include("../src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/test.jl") include("../src/physics/single_chain/swfjc/thermodynamics/isotensional/test.jl") include("../src/physics/single_chain/swfjc/thermodynamics/isotensional/legendre/test.jl") include("../src/physics/single_chain/ufjc/test.jl") From ffdfc9217d458368d4187ec2c6e60031edd150cf Mon Sep 17 00:00:00 2001 From: mrbuche Date: Tue, 18 Apr 2023 12:56:02 -0600 Subject: [PATCH 5/5] done --- .../thermodynamics/isometric/legendre/mod.jl | 23 +- .../thermodynamics/isometric/legendre/test.jl | 13 +- .../fjc/thermodynamics/isometric/test.jl | 12 +- src/physics/single_chain/mod.jl | 12 +- .../thermodynamics/isometric/legendre/mod.jl | 745 +++++++++++++++- .../thermodynamics/isometric/legendre/test.jl | 807 +++++++++++++++++- .../swfjc/thermodynamics/isometric/test.jl | 2 +- .../single_chain/swfjc/thermodynamics/test.jl | 363 ++++++++ 8 files changed, 1944 insertions(+), 33 deletions(-) diff --git a/src/physics/single_chain/fjc/thermodynamics/isometric/legendre/mod.jl b/src/physics/single_chain/fjc/thermodynamics/isometric/legendre/mod.jl index 1f10a43d..d90589e0 100644 --- a/src/physics/single_chain/fjc/thermodynamics/isometric/legendre/mod.jl +++ b/src/physics/single_chain/fjc/thermodynamics/isometric/legendre/mod.jl @@ -5,6 +5,7 @@ module Legendre using DocStringExtensions using .......Polymers: PROJECT_ROOT +using .....SingleChain: ONE, ZERO, POINTS, integrate """ The structure of the thermodynamics of the FJC model in the isometric ensemble approximated using a Legendre transformation. @@ -153,7 +154,7 @@ function force( end """ -The expected nondimensional force as a function ``\\eta`` of the applied nondimensional end-to-end length per link ``\\gamma``. +The expected nondimensional force as a function ``\\eta`` of the applied nondimensional end-to-end length per link ``\\gamma``, ```math \\eta(\\gamma) \\sim \\mathcal{L}^{-1}(\\gamma) \\quad \\text{for } N_b\\gg 1, @@ -902,17 +903,17 @@ Initializes and returns an instance of the thermodynamics of the FJC model in th $(TYPEDSIGNATURES) """ function FJC(number_of_links::UInt8, link_length::Float64, hinge_mass::Float64) - normalization_nondimensional_equilibrium_distribution = - sum( - map( - index -> nondimensional_equilibrium_radial_distribution( - number_of_links, - 1.0, - 1e-6 + (0.5 + index) * 0.00999999, - ), - collect(0.0:99.0), + normalization_nondimensional_equilibrium_distribution = integrate( + nondimensional_end_to_end_length_per_link -> + nondimensional_equilibrium_radial_distribution( + number_of_links, + 1.0, + nondimensional_end_to_end_length_per_link, ), - ) * 0.00999999 + ZERO, + ONE, + POINTS, + ) return FJC( number_of_links, link_length, diff --git a/src/physics/single_chain/fjc/thermodynamics/isometric/legendre/test.jl b/src/physics/single_chain/fjc/thermodynamics/isometric/legendre/test.jl index fe490476..5cf140d0 100644 --- a/src/physics/single_chain/fjc/thermodynamics/isometric/legendre/test.jl +++ b/src/physics/single_chain/fjc/thermodynamics/isometric/legendre/test.jl @@ -85,10 +85,10 @@ end end_to_end_length^2 * model.equilibrium_distribution(end_to_end_length), ZERO, - ONE, + ONE * number_of_links * link_length, POINTS, ) - @test normalization - 1.0 <= parameters.rel_tol + @test abs(normalization - 1.0) <= parameters.rel_tol end end @@ -105,10 +105,10 @@ end end_to_end_length -> model.equilibrium_radial_distribution(end_to_end_length), ZERO, - ONE, + ONE * number_of_links * link_length, POINTS, ) - @test normalization - 1.0 <= parameters.rel_tol + @test abs(normalization - 1.0) <= parameters.rel_tol end end @@ -133,7 +133,7 @@ end ONE, POINTS, ) - @test normalization - 1.0 <= parameters.rel_tol + @test abs(normalization - 1.0) <= parameters.rel_tol end end @@ -155,7 +155,7 @@ end ONE, POINTS, ) - @test normalization - 1.0 <= parameters.rel_tol + @test abs(normalization - 1.0) <= parameters.rel_tol end end @@ -1250,4 +1250,5 @@ end @test abs(residual_rel) <= parameters.rel_tol end end + end diff --git a/src/physics/single_chain/fjc/thermodynamics/isometric/test.jl b/src/physics/single_chain/fjc/thermodynamics/isometric/test.jl index 52af0161..58b9c3db 100644 --- a/src/physics/single_chain/fjc/thermodynamics/isometric/test.jl +++ b/src/physics/single_chain/fjc/thermodynamics/isometric/test.jl @@ -85,10 +85,10 @@ end end_to_end_length^2 * model.equilibrium_distribution(end_to_end_length), ZERO, - ONE, + ONE * number_of_links * link_length, POINTS, ) - @test normalization - 1.0 <= parameters.rel_tol + @test abs(normalization - 1.0) <= parameters.rel_tol end end @@ -105,10 +105,10 @@ end end_to_end_length -> model.equilibrium_radial_distribution(end_to_end_length), ZERO, - ONE, + ONE * number_of_links * link_length, POINTS, ) - @test normalization - 1.0 <= parameters.rel_tol + @test abs(normalization - 1.0) <= parameters.rel_tol end end @@ -133,7 +133,7 @@ end ONE, POINTS, ) - @test normalization - 1.0 <= parameters.rel_tol + @test abs(normalization - 1.0) <= parameters.rel_tol end end @@ -155,7 +155,7 @@ end ONE, POINTS, ) - @test normalization - 1.0 <= parameters.rel_tol + @test abs(normalization - 1.0) <= parameters.rel_tol end end diff --git a/src/physics/single_chain/mod.jl b/src/physics/single_chain/mod.jl index f3a24189..1a94e7e9 100644 --- a/src/physics/single_chain/mod.jl +++ b/src/physics/single_chain/mod.jl @@ -3,12 +3,6 @@ Single-chain models for polymer physics. """ module SingleChain -include("ideal/mod.jl") -include("fjc/mod.jl") -include("efjc/mod.jl") -include("swfjc/mod.jl") -include("ufjc/mod.jl") - const ONE::Float64 = 1.0 const ZERO::Float64 = 1e-6 const POINTS::UInt128 = 64 @@ -107,4 +101,10 @@ parameters = Parameters( 1e2, ) +include("ideal/mod.jl") +include("fjc/mod.jl") +include("efjc/mod.jl") +include("swfjc/mod.jl") +include("ufjc/mod.jl") + end diff --git a/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/mod.jl b/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/mod.jl index 7cd70f0a..021591b3 100644 --- a/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/mod.jl +++ b/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/mod.jl @@ -5,6 +5,7 @@ module Legendre using DocStringExtensions using .......Polymers: PROJECT_ROOT +using .....SingleChain: ONE, ZERO, POINTS, integrate """ The structure of the thermodynamics of the SWFJC model in the isometric ensemble approximated using a Legendre transformation. @@ -27,6 +28,631 @@ struct SWFJC The width of the well ``w`` in units of nm. """ well_width::Float64 + normalization_nondimensional_equilibrium_distribution::Float64 + """ + The expected force ``f`` as a function of the applied end-to-end length ``\\xi`` and temperature ``T``. + """ + force::Function + """ + The expected nondimensional force ``\\eta`` as a function of the applied nondimensional end-to-end length per link ``\\gamma``. + """ + nondimensional_force::Function + """ + The Helmholtz free energy ``\\psi`` as a function of the applied end-to-end length ``\\xi`` and temperature ``T``. + """ + helmholtz_free_energy::Function + """ + The Helmholtz free energy per link ``\\psi/N_b`` as a function of the applied end-to-end length ``\\xi`` and temperature ``T``. + """ + helmholtz_free_energy_per_link::Function + """ + The relative Helmholtz free energy ``\\Delta\\psi\\equiv\\psi(\\xi,T)-\\psi(0,T)`` as a function of the applied end-to-end length ``\\xi`` and temperature ``T``. + """ + relative_helmholtz_free_energy::Function + """ + The relative Helmholtz free energy per link ``\\Delta\\psi/N_b`` as a function of the applied end-to-end length ``\\xi`` and temperature ``T``. + """ + relative_helmholtz_free_energy_per_link::Function + """ + The nondimensional Helmholtz free energy ``N_b\\vartheta=\\beta\\psi`` as a function of the applied nondimensional end-to-end length per link ``\\gamma`` and temperature ``T``. + """ + nondimensional_helmholtz_free_energy::Function + """ + The nondimensional Helmholtz free energy per link ``\\vartheta\\equiv\\beta\\psi/N_b`` as a function of the applied nondimensional end-to-end length per link ``\\gamma`` and temperature ``T``. + """ + nondimensional_helmholtz_free_energy_per_link::Function + """ + The nondimensional relative Helmholtz free energy ``N_b\\Delta\\vartheta=\\beta\\Delta\\psi`` as a function of the applied nondimensional end-to-end length per link ``\\gamma``. + """ + nondimensional_relative_helmholtz_free_energy::Function + """ + The nondimensional relative Helmholtz free energy per link ``\\Delta\\vartheta\\equiv\\beta\\Delta\\psi/N_b`` as a function of the applied nondimensional end-to-end length per link ``\\gamma`` + """ + nondimensional_relative_helmholtz_free_energy_per_link::Function + """ + The equilibrium probability density of end-to-end vectors ``P_\\mathrm{eq}`` as a function of the end-to-end length ``\\xi``. + """ + equilibrium_distribution::Function + """ + The nondimensional equilibrium probability density of end-to-end vectors ``\\mathscr{P}_\\mathrm{eq}`` as a function of the nondimensional end-to-end length per link ``\\gamma``. + """ + nondimensional_equilibrium_distribution::Function + """ + The equilibrium probability density of end-to-end lengths ``g_\\mathrm{eq}`` as a function of the end-to-end length ``\\xi``. + """ + equilibrium_radial_distribution::Function + """ + The nondimensional equilibrium probability density of end-to-end lengths ``\\mathscr{g}_\\mathrm{eq}`` as a function of the nondimensional end-to-end length per link ``\\gamma``. + """ + nondimensional_equilibrium_radial_distribution::Function +end + +""" +The expected force as a function ``f`` of the applied end-to-end length ``\\xi`` and temperature ``T``, +parameterized by the number of links ``N_b``, link length ``\\ell_b``, and well width ``w``. + +$(TYPEDSIGNATURES) +""" +function force( + number_of_links::Union{UInt8,Vector,Matrix,Array}, + link_length::Union{Float64,Vector,Matrix,Array}, + well_width::Union{Float64,Vector,Matrix,Array}, + end_to_end_length::Union{Float64,Vector,Matrix,Array}, + temperature::Union{Float64,Vector,Matrix,Array}, +)::Union{Float64,Vector,Matrix,Array} + return broadcast( + ( + number_of_links_i, + link_length_i, + well_width_i, + end_to_end_length_i, + temperature_i, + ) -> ccall( + ( + :physics_single_chain_swfjc_thermodynamics_isometric_legendre_force, + string(PROJECT_ROOT, "target/debug/libpolymers"), + ), + Float64, + (UInt8, Float64, Float64, Float64, Float64), + number_of_links_i, + link_length_i, + well_width_i, + end_to_end_length_i, + temperature_i, + ), + number_of_links, + link_length, + well_width, + end_to_end_length, + temperature, + ) +end + +""" +The expected nondimensional force as a function ``\\eta`` of the applied nondimensional end-to-end length per link ``\\gamma``, +parameterized by the link length ``\\ell_b`` and well width ``w``. + +$(TYPEDSIGNATURES) +""" +function nondimensional_force( + link_length::Union{Float64,Vector,Matrix,Array}, + well_width::Union{Float64,Vector,Matrix,Array}, + nondimensional_end_to_end_length_per_link::Union{Float64,Vector,Matrix,Array}, +)::Union{Float64,Vector,Matrix,Array} + return broadcast( + (link_length_i, well_width_i, nondimensional_end_to_end_length_per_link_i) -> + ccall( + ( + :physics_single_chain_swfjc_thermodynamics_isometric_legendre_nondimensional_force, + string(PROJECT_ROOT, "target/debug/libpolymers"), + ), + Float64, + (Float64, Float64, Float64), + link_length_i, + well_width_i, + nondimensional_end_to_end_length_per_link_i, + ), + link_length, + well_width, + nondimensional_end_to_end_length_per_link, + ) +end + +""" +The Helmholtz free energy ``\\psi`` as a function of the applied end-to-end length ``\\xi`` and temperature ``T``, +parameterized by the number of links ``N_b``, link length ``\\ell_b``, well width ``w``, and hinge mass ``m``, + +```math +\\psi(\\xi, T) \\sim \\varphi\\left[f(\\xi, T)\\right] + \\xi f(\\xi, T) \\quad \\text{for } N_b\\gg 1, +``` + +where ``f(\\xi, T)`` is given by the Legendre transformation approximation above. + +$(TYPEDSIGNATURES) +""" +function helmholtz_free_energy( + number_of_links::Union{UInt8,Vector,Matrix,Array}, + link_length::Union{Float64,Vector,Matrix,Array}, + hinge_mass::Union{Float64,Vector,Matrix,Array}, + well_width::Union{Float64,Vector,Matrix,Array}, + end_to_end_length::Union{Float64,Vector,Matrix,Array}, + temperature::Union{Float64,Vector,Matrix,Array}, +)::Union{Float64,Vector,Matrix,Array} + return broadcast( + ( + number_of_links_i, + link_length_i, + hinge_mass_i, + well_width_i, + end_to_end_length_i, + temperature_i, + ) -> ccall( + ( + :physics_single_chain_swfjc_thermodynamics_isometric_legendre_helmholtz_free_energy, + string(PROJECT_ROOT, "target/debug/libpolymers"), + ), + Float64, + (UInt8, Float64, Float64, Float64, Float64, Float64), + number_of_links_i, + link_length_i, + hinge_mass_i, + well_width_i, + end_to_end_length_i, + temperature_i, + ), + number_of_links, + link_length, + hinge_mass, + well_width, + end_to_end_length, + temperature, + ) +end + +""" +The Helmholtz free energy per link ``\\psi/N_b`` as a function of the applied end-to-end length ``\\xi`` and temperature ``T``, +parameterized by the number of links ``N_b``, link length ``\\ell_b``, well width ``w``, and hinge mass ``m``. + +$(TYPEDSIGNATURES) +""" +function helmholtz_free_energy_per_link( + number_of_links::Union{UInt8,Vector,Matrix,Array}, + link_length::Union{Float64,Vector,Matrix,Array}, + hinge_mass::Union{Float64,Vector,Matrix,Array}, + well_width::Union{Float64,Vector,Matrix,Array}, + end_to_end_length::Union{Float64,Vector,Matrix,Array}, + temperature::Union{Float64,Vector,Matrix,Array}, +)::Union{Float64,Vector,Matrix,Array} + return broadcast( + ( + number_of_links_i, + link_length_i, + hinge_mass_i, + well_width_i, + end_to_end_length_i, + temperature_i, + ) -> ccall( + ( + :physics_single_chain_swfjc_thermodynamics_isometric_legendre_helmholtz_free_energy_per_link, + string(PROJECT_ROOT, "target/debug/libpolymers"), + ), + Float64, + (UInt8, Float64, Float64, Float64, Float64, Float64), + number_of_links_i, + link_length_i, + hinge_mass_i, + well_width_i, + end_to_end_length_i, + temperature_i, + ), + number_of_links, + link_length, + hinge_mass, + well_width, + end_to_end_length, + temperature, + ) +end + +""" +The relative Helmholtz free energy ``\\Delta\\psi\\equiv\\psi(\\xi,T)-\\psi(0,T)`` as a function of the applied end-to-end length ``\\xi`` and temperature ``T``, +parameterized by the number of links ``N_b``, link length ``\\ell_b``, and well width ``w``. + +$(TYPEDSIGNATURES) +""" +function relative_helmholtz_free_energy( + number_of_links::Union{UInt8,Vector,Matrix,Array}, + link_length::Union{Float64,Vector,Matrix,Array}, + well_width::Union{Float64,Vector,Matrix,Array}, + end_to_end_length::Union{Float64,Vector,Matrix,Array}, + temperature::Union{Float64,Vector,Matrix,Array}, +)::Union{Float64,Vector,Matrix,Array} + return broadcast( + ( + number_of_links_i, + link_length_i, + well_width_i, + end_to_end_length_i, + temperature_i, + ) -> ccall( + ( + :physics_single_chain_swfjc_thermodynamics_isometric_legendre_relative_helmholtz_free_energy, + string(PROJECT_ROOT, "target/debug/libpolymers"), + ), + Float64, + (UInt8, Float64, Float64, Float64, Float64), + number_of_links_i, + link_length_i, + well_width_i, + end_to_end_length_i, + temperature_i, + ), + number_of_links, + link_length, + well_width, + end_to_end_length, + temperature, + ) +end + +""" +The relative Helmholtz free energy per link ``\\Delta\\psi/N_b`` as a function of the applied end-to-end length ``\\xi`` and temperature ``T``, +parameterized by the number of links ``N_b``, link length ``\\ell_b``, and well width ``w``. + +$(TYPEDSIGNATURES) +""" +function relative_helmholtz_free_energy_per_link( + number_of_links::Union{UInt8,Vector,Matrix,Array}, + link_length::Union{Float64,Vector,Matrix,Array}, + well_width::Union{Float64,Vector,Matrix,Array}, + end_to_end_length::Union{Float64,Vector,Matrix,Array}, + temperature::Union{Float64,Vector,Matrix,Array}, +)::Union{Float64,Vector,Matrix,Array} + return broadcast( + ( + number_of_links_i, + link_length_i, + well_width_i, + end_to_end_length_i, + temperature_i, + ) -> ccall( + ( + :physics_single_chain_swfjc_thermodynamics_isometric_legendre_relative_helmholtz_free_energy_per_link, + string(PROJECT_ROOT, "target/debug/libpolymers"), + ), + Float64, + (UInt8, Float64, Float64, Float64, Float64), + number_of_links_i, + link_length_i, + well_width_i, + end_to_end_length_i, + temperature_i, + ), + number_of_links, + link_length, + well_width, + end_to_end_length, + temperature, + ) +end + +""" +The nondimensional Helmholtz free energy ``N_b\\vartheta=\\beta\\psi`` as a function of the applied nondimensional end-to-end length per link ``\\gamma`` and temperature ``T``, +parameterized by the number of links ``N_b``, link length ``\\ell_b``, well width ``w``, and hinge mass ``m``. + +$(TYPEDSIGNATURES) +""" +function nondimensional_helmholtz_free_energy( + number_of_links::Union{UInt8,Vector,Matrix,Array}, + link_length::Union{Float64,Vector,Matrix,Array}, + hinge_mass::Union{Float64,Vector,Matrix,Array}, + well_width::Union{Float64,Vector,Matrix,Array}, + nondimensional_end_to_end_length_per_link::Union{Float64,Vector,Matrix,Array}, + temperature::Union{Float64,Vector,Matrix,Array}, +)::Union{Float64,Vector,Matrix,Array} + return broadcast( + ( + number_of_links_i, + link_length_i, + hinge_mass_i, + well_width_i, + nondimensional_end_to_end_length_per_link_i, + temperature_i, + ) -> ccall( + ( + :physics_single_chain_swfjc_thermodynamics_isometric_legendre_nondimensional_helmholtz_free_energy, + string(PROJECT_ROOT, "target/debug/libpolymers"), + ), + Float64, + (UInt8, Float64, Float64, Float64, Float64, Float64), + number_of_links_i, + link_length_i, + hinge_mass_i, + well_width_i, + nondimensional_end_to_end_length_per_link_i, + temperature_i, + ), + number_of_links, + link_length, + hinge_mass, + well_width, + nondimensional_end_to_end_length_per_link, + temperature, + ) +end + +""" +The nondimensional Helmholtz free energy per link ``\\vartheta\\equiv\\beta\\psi/N_b`` as a function of the applied nondimensional end-to-end length per link ``\\gamma`` and temperature ``T``, +parameterized by the number of links ``N_b``, link length ``\\ell_b``, well width ``w``, and hinge mass ``m``. + +$(TYPEDSIGNATURES) +""" +function nondimensional_helmholtz_free_energy_per_link( + number_of_links::Union{UInt8,Vector,Matrix,Array}, + link_length::Union{Float64,Vector,Matrix,Array}, + hinge_mass::Union{Float64,Vector,Matrix,Array}, + well_width::Union{Float64,Vector,Matrix,Array}, + nondimensional_end_to_end_length_per_link::Union{Float64,Vector,Matrix,Array}, + temperature::Union{Float64,Vector,Matrix,Array}, +)::Union{Float64,Vector,Matrix,Array} + return broadcast( + ( + number_of_links_i, + link_length_i, + hinge_mass_i, + well_width_i, + nondimensional_end_to_end_length_per_link_i, + temperature_i, + ) -> ccall( + ( + :physics_single_chain_swfjc_thermodynamics_isometric_legendre_nondimensional_helmholtz_free_energy_per_link, + string(PROJECT_ROOT, "target/debug/libpolymers"), + ), + Float64, + (UInt8, Float64, Float64, Float64, Float64, Float64), + number_of_links_i, + link_length_i, + hinge_mass_i, + well_width_i, + nondimensional_end_to_end_length_per_link_i, + temperature_i, + ), + number_of_links, + link_length, + hinge_mass, + well_width, + nondimensional_end_to_end_length_per_link, + temperature, + ) +end + +""" +The nondimensional relative Helmholtz free energy ``N_b\\Delta\\vartheta=\\beta\\Delta\\psi`` as a function of the applied nondimensional end-to-end length per link ``\\gamma`` and temperature ``T``, +parameterized by the number of links ``N_b``, link length ``\\ell_b``, and well width ``w``. + +$(TYPEDSIGNATURES) +""" +function nondimensional_relative_helmholtz_free_energy( + number_of_links::Union{UInt8,Vector,Matrix,Array}, + link_length::Union{Float64,Vector,Matrix,Array}, + well_width::Union{Float64,Vector,Matrix,Array}, + nondimensional_end_to_end_length_per_link::Union{Float64,Vector,Matrix,Array}, +)::Union{Float64,Vector,Matrix,Array} + return broadcast( + ( + number_of_links_i, + link_length_i, + well_width_i, + nondimensional_end_to_end_length_per_link_i, + ) -> ccall( + ( + :physics_single_chain_swfjc_thermodynamics_isometric_legendre_nondimensional_relative_helmholtz_free_energy, + string(PROJECT_ROOT, "target/debug/libpolymers"), + ), + Float64, + (UInt8, Float64, Float64, Float64), + number_of_links_i, + link_length_i, + well_width_i, + nondimensional_end_to_end_length_per_link_i, + ), + number_of_links, + link_length, + well_width, + nondimensional_end_to_end_length_per_link, + ) +end + +""" +The nondimensional relative Helmholtz free energy per link ``\\Delta\\vartheta\\equiv\\beta\\Delta\\psi/N_b`` as a function of the applied nondimensional end-to-end length per link ``\\gamma`` and temperature ``T``, +parameterized by the link length ``\\ell_b`` and well width ``w``. + +$(TYPEDSIGNATURES) +""" +function nondimensional_relative_helmholtz_free_energy_per_link( + link_length::Union{Float64,Vector,Matrix,Array}, + well_width::Union{Float64,Vector,Matrix,Array}, + nondimensional_end_to_end_length_per_link::Union{Float64,Vector,Matrix,Array}, +)::Union{Float64,Vector,Matrix,Array} + return broadcast( + (link_length_i, well_width_i, nondimensional_end_to_end_length_per_link_i) -> + ccall( + ( + :physics_single_chain_swfjc_thermodynamics_isometric_legendre_nondimensional_relative_helmholtz_free_energy_per_link, + string(PROJECT_ROOT, "target/debug/libpolymers"), + ), + Float64, + (Float64, Float64, Float64), + link_length_i, + well_width_i, + nondimensional_end_to_end_length_per_link_i, + ), + link_length, + well_width, + nondimensional_end_to_end_length_per_link, + ) +end + +""" +The equilibrium probability density of end-to-end vectors ``P_\\mathrm{eq}`` as a function of the end-to-end length ``\\xi``, +parameterized by the number of links ``N_b``, link length ``\\ell_b``, and well width ``w``, + +```math +P_\\mathrm{eq}(\\xi) = \\frac{e^{-\\beta\\psi(\\xi, T)}}{4\\pi\\int e^{-\\beta\\psi(\\xi', T)} \\,{\\xi'}{}^2 d\\xi'}. +``` + +$(TYPEDSIGNATURES) +""" +function equilibrium_distribution( + number_of_links::Union{UInt8,Vector,Matrix,Array}, + link_length::Union{Float64,Vector,Matrix,Array}, + well_width::Union{Float64,Vector,Matrix,Array}, + normalization_nondimensional_equilibrium_distribution::Float64, + end_to_end_length::Union{Float64,Vector,Matrix,Array}, +)::Union{Float64,Vector,Matrix,Array} + return broadcast( + (number_of_links_i, link_length_i, well_width_i, end_to_end_length_i) -> ccall( + ( + :physics_single_chain_swfjc_thermodynamics_isometric_legendre_equilibrium_distribution, + string(PROJECT_ROOT, "target/debug/libpolymers"), + ), + Float64, + (UInt8, Float64, Float64, Float64, Float64), + number_of_links_i, + link_length_i, + well_width_i, + normalization_nondimensional_equilibrium_distribution, + end_to_end_length_i, + ), + number_of_links, + link_length, + well_width, + end_to_end_length, + ) +end + +""" +The nondimensional equilibrium probability density of nondimensional end-to-end vectors per link ``\\mathscr{P}_\\mathrm{eq}`` as a function of the nondimensional end-to-end length per link ``\\gamma``, +parameterized by the number of links ``N_b``, link length ``\\ell_b``, and well width ``w``, + +```math +\\mathscr{P}_\\mathrm{eq}(\\gamma) = \\frac{e^{-\\Delta\\vartheta(\\gamma)}}{4\\pi\\int e^{-\\Delta\\vartheta(\\gamma')} \\,{\\gamma'}{}^2 d\\gamma'}. +``` + +$(TYPEDSIGNATURES) +""" +function nondimensional_equilibrium_distribution( + number_of_links::Union{UInt8,Vector,Matrix,Array}, + link_length::Union{Float64,Vector,Matrix,Array}, + well_width::Union{Float64,Vector,Matrix,Array}, + normalization_nondimensional_equilibrium_distribution::Float64, + nondimensional_end_to_end_length_per_link::Union{Float64,Vector,Matrix,Array}, +)::Union{Float64,Vector,Matrix,Array} + return broadcast( + ( + number_of_links_i, + link_length_i, + well_width_i, + nondimensional_end_to_end_length_per_link_i, + ) -> ccall( + ( + :physics_single_chain_swfjc_thermodynamics_isometric_legendre_nondimensional_equilibrium_distribution, + string(PROJECT_ROOT, "target/debug/libpolymers"), + ), + Float64, + (UInt8, Float64, Float64, Float64, Float64), + number_of_links_i, + link_length_i, + well_width_i, + normalization_nondimensional_equilibrium_distribution, + nondimensional_end_to_end_length_per_link_i, + ), + number_of_links, + link_length, + well_width, + nondimensional_end_to_end_length_per_link, + ) +end + +""" +The equilibrium probability density of end-to-end lengths ``g_\\mathrm{eq}`` as a function of the end-to-end length ``\\xi``, +parameterized by the number of links ``N_b``, link length ``\\ell_b``, and well width ``w``, + +```math +g_\\mathrm{eq}(\\xi) = 4\\pi\\xi^2 P_\\mathrm{eq}(\\xi). +``` + +$(TYPEDSIGNATURES) +""" +function equilibrium_radial_distribution( + number_of_links::Union{UInt8,Vector,Matrix,Array}, + link_length::Union{Float64,Vector,Matrix,Array}, + well_width::Union{Float64,Vector,Matrix,Array}, + normalization_nondimensional_equilibrium_distribution::Float64, + end_to_end_length::Union{Float64,Vector,Matrix,Array}, +)::Union{Float64,Vector,Matrix,Array} + return broadcast( + (number_of_links_i, link_length_i, well_width_i, end_to_end_length_i) -> ccall( + ( + :physics_single_chain_swfjc_thermodynamics_isometric_legendre_equilibrium_radial_distribution, + string(PROJECT_ROOT, "target/debug/libpolymers"), + ), + Float64, + (UInt8, Float64, Float64, Float64, Float64), + number_of_links_i, + link_length_i, + well_width_i, + normalization_nondimensional_equilibrium_distribution, + end_to_end_length_i, + ), + number_of_links, + link_length, + well_width, + end_to_end_length, + ) +end + +""" +The nondimensional equilibrium probability density of nondimensional end-to-end lenghts per link ``\\mathscr{g}_\\mathrm{eq}`` as a function of the nondimensional end-to-end length per link ``\\gamma``, +parameterized by the number of links ``N_b``, link length ``\\ell_b``, and well width ``w``, + +```math +\\mathscr{g}_\\mathrm{eq}(\\gamma) = 4\\pi\\gamma^2 \\mathscr{P}_\\mathrm{eq}(\\gamma). +``` + +$(TYPEDSIGNATURES) +""" +function nondimensional_equilibrium_radial_distribution( + number_of_links::Union{UInt8,Vector,Matrix,Array}, + link_length::Union{Float64,Vector,Matrix,Array}, + well_width::Union{Float64,Vector,Matrix,Array}, + normalization_nondimensional_equilibrium_distribution::Float64, + nondimensional_end_to_end_length_per_link::Union{Float64,Vector,Matrix,Array}, +)::Union{Float64,Vector,Matrix,Array} + return broadcast( + ( + number_of_links_i, + link_length_i, + well_width_i, + nondimensional_end_to_end_length_per_link_i, + ) -> ccall( + ( + :physics_single_chain_swfjc_thermodynamics_isometric_legendre_nondimensional_equilibrium_radial_distribution, + string(PROJECT_ROOT, "target/debug/libpolymers"), + ), + Float64, + (UInt8, Float64, Float64, Float64, Float64), + number_of_links_i, + link_length_i, + well_width_i, + normalization_nondimensional_equilibrium_distribution, + nondimensional_end_to_end_length_per_link_i, + ), + number_of_links, + link_length, + well_width, + nondimensional_end_to_end_length_per_link, + ) end """ @@ -40,7 +666,124 @@ function SWFJC( hinge_mass::Float64, well_width::Float64, ) - return SWFJC(number_of_links, link_length, hinge_mass, well_width) + normalization_nondimensional_equilibrium_distribution = integrate( + nondimensional_end_to_end_length_per_link -> + nondimensional_equilibrium_radial_distribution( + number_of_links, + link_length, + well_width, + 1.0, + nondimensional_end_to_end_length_per_link, + ), + ZERO, + ONE * (1.0 + well_width / link_length), + POINTS, + ) + return SWFJC( + number_of_links, + link_length, + hinge_mass, + well_width, + normalization_nondimensional_equilibrium_distribution, + (end_to_end_length, temperature) -> + force(number_of_links, link_length, well_width, end_to_end_length, temperature), + (nondimensional_end_to_end_length_per_link) -> nondimensional_force( + link_length, + well_width, + nondimensional_end_to_end_length_per_link, + ), + (end_to_end_length, temperature) -> helmholtz_free_energy( + number_of_links, + link_length, + hinge_mass, + well_width, + end_to_end_length, + temperature, + ), + (end_to_end_length, temperature) -> helmholtz_free_energy_per_link( + number_of_links, + link_length, + hinge_mass, + well_width, + end_to_end_length, + temperature, + ), + (end_to_end_length, temperature) -> relative_helmholtz_free_energy( + number_of_links, + link_length, + well_width, + end_to_end_length, + temperature, + ), + (end_to_end_length, temperature) -> relative_helmholtz_free_energy_per_link( + number_of_links, + link_length, + well_width, + end_to_end_length, + temperature, + ), + (nondimensional_end_to_end_length_per_link, temperature) -> + nondimensional_helmholtz_free_energy( + number_of_links, + link_length, + hinge_mass, + well_width, + nondimensional_end_to_end_length_per_link, + temperature, + ), + (nondimensional_end_to_end_length_per_link, temperature) -> + nondimensional_helmholtz_free_energy_per_link( + number_of_links, + link_length, + hinge_mass, + well_width, + nondimensional_end_to_end_length_per_link, + temperature, + ), + (nondimensional_end_to_end_length_per_link) -> + nondimensional_relative_helmholtz_free_energy( + number_of_links, + link_length, + well_width, + nondimensional_end_to_end_length_per_link, + ), + (nondimensional_end_to_end_length_per_link) -> + nondimensional_relative_helmholtz_free_energy_per_link( + link_length, + well_width, + nondimensional_end_to_end_length_per_link, + ), + (end_to_end_length) -> equilibrium_distribution( + number_of_links, + link_length, + well_width, + normalization_nondimensional_equilibrium_distribution, + end_to_end_length, + ), + (nondimensional_end_to_end_length_per_link) -> + nondimensional_equilibrium_distribution( + number_of_links, + link_length, + well_width, + normalization_nondimensional_equilibrium_distribution, + nondimensional_end_to_end_length_per_link, + ), + (end_to_end_length) -> equilibrium_radial_distribution( + number_of_links, + link_length, + well_width, + normalization_nondimensional_equilibrium_distribution, + end_to_end_length, + ), + (nondimensional_end_to_end_length_per_link) -> + nondimensional_equilibrium_radial_distribution( + number_of_links, + link_length, + well_width, + normalization_nondimensional_equilibrium_distribution, + nondimensional_end_to_end_length_per_link, + ), + ) end end diff --git a/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/test.jl b/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/test.jl index ba985a36..2b252433 100644 --- a/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/test.jl +++ b/src/physics/single_chain/swfjc/thermodynamics/isometric/legendre/test.jl @@ -2,7 +2,7 @@ module Test using Test using Polymers.Physics: BOLTZMANN_CONSTANT -using Polymers.Physics.SingleChain: ZERO, parameters +using Polymers.Physics.SingleChain: ONE, ZERO, POINTS, integrate, parameters using Polymers.Physics.SingleChain.Swfjc.Thermodynamics.Isometric.Legendre: SWFJC @testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::base::init" begin @@ -92,4 +92,807 @@ end end end -end +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::normalization::equilibrium_distribution" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + normalization = integrate( + end_to_end_length -> + 4.0 * + pi * + end_to_end_length^2 * + model.equilibrium_distribution(end_to_end_length), + ZERO, + ONE * number_of_links * (link_length + well_width), + POINTS, + ) + @test abs(normalization - 1.0) <= parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::normalization::equilibrium_radial_distribution" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + normalization = integrate( + end_to_end_length -> + model.equilibrium_radial_distribution(end_to_end_length), + ZERO, + ONE * number_of_links * (link_length + well_width), + POINTS, + ) + @test abs(normalization - 1.0) <= parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::normalization::nondimensional_equilibrium_distribution" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + normalization = integrate( + nondimensional_end_to_end_length_per_link -> + 4.0 * + pi * + nondimensional_end_to_end_length_per_link^2 * + model.nondimensional_equilibrium_distribution( + nondimensional_end_to_end_length_per_link, + ), + ZERO, + ONE * (1.0 + well_width / link_length), + POINTS, + ) + @test abs(normalization - 1.0) <= parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::normalization::nondimensional_equilibrium_radial_distribution" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + normalization = integrate( + nondimensional_end_to_end_length_per_link -> + model.nondimensional_equilibrium_radial_distribution( + nondimensional_end_to_end_length_per_link, + ), + ZERO, + ONE * (1.0 + well_width / link_length), + POINTS, + ) + @test abs(normalization - 1.0) <= parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::nondimensional::force" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_end_to_end_length_per_link = + parameters.nondimensional_end_to_end_length_per_link_reference + + parameters.nondimensional_end_to_end_length_per_link_scale * (0.5 - rand()) + temperature = + parameters.temperature_reference + parameters.temperature_scale * (0.5 - rand()) + nondimensional_force = + model.nondimensional_force(nondimensional_end_to_end_length_per_link) + end_to_end_length = + nondimensional_end_to_end_length_per_link * number_of_links * link_length + force = model.force(end_to_end_length, temperature) + residual_abs = + force / BOLTZMANN_CONSTANT / temperature * link_length - nondimensional_force + residual_rel = residual_abs / nondimensional_force + @test abs(residual_abs) <= parameters.abs_tol && + abs(residual_rel) <= parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::nondimensional::helmholtz_free_energy" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_end_to_end_length_per_link = + parameters.nondimensional_end_to_end_length_per_link_reference + + parameters.nondimensional_end_to_end_length_per_link_scale * (0.5 - rand()) + temperature = + parameters.temperature_reference + parameters.temperature_scale * (0.5 - rand()) + nondimensional_helmholtz_free_energy = model.nondimensional_helmholtz_free_energy( + nondimensional_end_to_end_length_per_link, + temperature, + ) + end_to_end_length = + nondimensional_end_to_end_length_per_link * number_of_links * link_length + helmholtz_free_energy = model.helmholtz_free_energy(end_to_end_length, temperature) + residual_abs = + helmholtz_free_energy / BOLTZMANN_CONSTANT / temperature - + nondimensional_helmholtz_free_energy + residual_rel = residual_abs / nondimensional_helmholtz_free_energy + @test abs(residual_abs) <= parameters.abs_tol && + abs(residual_rel) <= parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::nondimensional::helmholtz_free_energy_per_link" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_end_to_end_length_per_link = + parameters.nondimensional_end_to_end_length_per_link_reference + + parameters.nondimensional_end_to_end_length_per_link_scale * (0.5 - rand()) + temperature = + parameters.temperature_reference + parameters.temperature_scale * (0.5 - rand()) + nondimensional_helmholtz_free_energy_per_link = + model.nondimensional_helmholtz_free_energy_per_link( + nondimensional_end_to_end_length_per_link, + temperature, + ) + end_to_end_length = + nondimensional_end_to_end_length_per_link * number_of_links * link_length + helmholtz_free_energy_per_link = + model.helmholtz_free_energy_per_link(end_to_end_length, temperature) + residual_abs = + helmholtz_free_energy_per_link / BOLTZMANN_CONSTANT / temperature - + nondimensional_helmholtz_free_energy_per_link + residual_rel = residual_abs / nondimensional_helmholtz_free_energy_per_link + @test abs(residual_abs) <= parameters.abs_tol && + abs(residual_rel) <= parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::nondimensional::relative_helmholtz_free_energy" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_end_to_end_length_per_link = + parameters.nondimensional_end_to_end_length_per_link_reference + + parameters.nondimensional_end_to_end_length_per_link_scale * (0.5 - rand()) + temperature = + parameters.temperature_reference + parameters.temperature_scale * (0.5 - rand()) + nondimensional_relative_helmholtz_free_energy = + model.nondimensional_relative_helmholtz_free_energy( + nondimensional_end_to_end_length_per_link, + ) + end_to_end_length = + nondimensional_end_to_end_length_per_link * number_of_links * link_length + relative_helmholtz_free_energy = + model.relative_helmholtz_free_energy(end_to_end_length, temperature) + residual_abs = + relative_helmholtz_free_energy / BOLTZMANN_CONSTANT / temperature - + nondimensional_relative_helmholtz_free_energy + residual_rel = residual_abs / nondimensional_relative_helmholtz_free_energy + @test abs(residual_abs) <= parameters.abs_tol && + abs(residual_rel) <= parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::nondimensional::relative_helmholtz_free_energy_per_link" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_end_to_end_length_per_link = + parameters.nondimensional_end_to_end_length_per_link_reference + + parameters.nondimensional_end_to_end_length_per_link_scale * (0.5 - rand()) + temperature = + parameters.temperature_reference + parameters.temperature_scale * (0.5 - rand()) + nondimensional_relative_helmholtz_free_energy_per_link = + model.nondimensional_relative_helmholtz_free_energy_per_link( + nondimensional_end_to_end_length_per_link, + ) + end_to_end_length = + nondimensional_end_to_end_length_per_link * number_of_links * link_length + relative_helmholtz_free_energy_per_link = + model.relative_helmholtz_free_energy_per_link(end_to_end_length, temperature) + residual_abs = + relative_helmholtz_free_energy_per_link / BOLTZMANN_CONSTANT / temperature - + nondimensional_relative_helmholtz_free_energy_per_link + residual_rel = residual_abs / nondimensional_relative_helmholtz_free_energy_per_link + @test abs(residual_abs) <= parameters.abs_tol && + abs(residual_rel) <= parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::per_link::helmholtz_free_energy" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_end_to_end_length_per_link = + parameters.nondimensional_end_to_end_length_per_link_reference + + parameters.nondimensional_end_to_end_length_per_link_scale * (0.5 - rand()) + temperature = + parameters.temperature_reference + parameters.temperature_scale * (0.5 - rand()) + end_to_end_length = + nondimensional_end_to_end_length_per_link * number_of_links * link_length + helmholtz_free_energy = model.helmholtz_free_energy(end_to_end_length, temperature) + helmholtz_free_energy_per_link = + model.helmholtz_free_energy_per_link(end_to_end_length, temperature) + residual_abs = + helmholtz_free_energy / number_of_links - helmholtz_free_energy_per_link + residual_rel = residual_abs / helmholtz_free_energy_per_link + @test abs(residual_abs) <= parameters.abs_tol && + abs(residual_rel) <= parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::per_link::relative_helmholtz_free_energy" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_end_to_end_length_per_link = + parameters.nondimensional_end_to_end_length_per_link_reference + + parameters.nondimensional_end_to_end_length_per_link_scale * (0.5 - rand()) + temperature = + parameters.temperature_reference + parameters.temperature_scale * (0.5 - rand()) + end_to_end_length = + nondimensional_end_to_end_length_per_link * number_of_links * link_length + relative_helmholtz_free_energy = + model.relative_helmholtz_free_energy(end_to_end_length, temperature) + relative_helmholtz_free_energy_per_link = + model.relative_helmholtz_free_energy_per_link(end_to_end_length, temperature) + residual_abs = + relative_helmholtz_free_energy / number_of_links - + relative_helmholtz_free_energy_per_link + residual_rel = residual_abs / relative_helmholtz_free_energy_per_link + @test abs(residual_abs) <= parameters.abs_tol && + abs(residual_rel) <= parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::per_link::nondimensional_helmholtz_free_energy" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_end_to_end_length_per_link = + parameters.nondimensional_end_to_end_length_per_link_reference + + parameters.nondimensional_end_to_end_length_per_link_scale * (0.5 - rand()) + temperature = + parameters.temperature_reference + parameters.temperature_scale * (0.5 - rand()) + nondimensional_helmholtz_free_energy = model.nondimensional_helmholtz_free_energy( + nondimensional_end_to_end_length_per_link, + temperature, + ) + nondimensional_helmholtz_free_energy_per_link = + model.nondimensional_helmholtz_free_energy_per_link( + nondimensional_end_to_end_length_per_link, + temperature, + ) + residual_abs = + nondimensional_helmholtz_free_energy / number_of_links - + nondimensional_helmholtz_free_energy_per_link + residual_rel = residual_abs / nondimensional_helmholtz_free_energy_per_link + @test abs(residual_abs) <= parameters.abs_tol && + abs(residual_rel) <= parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::per_link::nondimensional_relative_helmholtz_free_energy" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_end_to_end_length_per_link = + parameters.nondimensional_end_to_end_length_per_link_reference + + parameters.nondimensional_end_to_end_length_per_link_scale * (0.5 - rand()) + temperature = + parameters.temperature_reference + parameters.temperature_scale * (0.5 - rand()) + nondimensional_relative_helmholtz_free_energy = + model.nondimensional_relative_helmholtz_free_energy( + nondimensional_end_to_end_length_per_link, + ) + nondimensional_relative_helmholtz_free_energy_per_link = + model.nondimensional_relative_helmholtz_free_energy_per_link( + nondimensional_end_to_end_length_per_link, + ) + residual_abs = + nondimensional_relative_helmholtz_free_energy / number_of_links - + nondimensional_relative_helmholtz_free_energy_per_link + residual_rel = residual_abs / nondimensional_relative_helmholtz_free_energy_per_link + @test abs(residual_abs) <= parameters.abs_tol && + abs(residual_rel) <= parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::relative::helmholtz_free_energy" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_end_to_end_length_per_link = + parameters.nondimensional_end_to_end_length_per_link_reference + + parameters.nondimensional_end_to_end_length_per_link_scale * (0.5 - rand()) + temperature = + parameters.temperature_reference + parameters.temperature_scale * (0.5 - rand()) + end_to_end_length = + nondimensional_end_to_end_length_per_link * number_of_links * link_length + helmholtz_free_energy = model.helmholtz_free_energy(end_to_end_length, temperature) + helmholtz_free_energy_0 = + model.helmholtz_free_energy(ZERO * number_of_links * link_length, temperature) + relative_helmholtz_free_energy = + model.relative_helmholtz_free_energy(end_to_end_length, temperature) + residual_abs = + helmholtz_free_energy - helmholtz_free_energy_0 - relative_helmholtz_free_energy + residual_rel = residual_abs / relative_helmholtz_free_energy + @test abs(residual_rel) <= parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::relative::helmholtz_free_energy_per_link" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_end_to_end_length_per_link = + parameters.nondimensional_end_to_end_length_per_link_reference + + parameters.nondimensional_end_to_end_length_per_link_scale * (0.5 - rand()) + temperature = + parameters.temperature_reference + parameters.temperature_scale * (0.5 - rand()) + end_to_end_length = + nondimensional_end_to_end_length_per_link * number_of_links * link_length + helmholtz_free_energy_per_link = + model.helmholtz_free_energy_per_link(end_to_end_length, temperature) + helmholtz_free_energy_per_link_0 = model.helmholtz_free_energy_per_link( + ZERO * number_of_links * link_length, + temperature, + ) + relative_helmholtz_free_energy_per_link = + model.relative_helmholtz_free_energy_per_link(end_to_end_length, temperature) + residual_abs = + helmholtz_free_energy_per_link - helmholtz_free_energy_per_link_0 - + relative_helmholtz_free_energy_per_link + residual_rel = residual_abs / relative_helmholtz_free_energy_per_link + @test abs(residual_rel) <= parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::relative::nondimensional_helmholtz_free_energy" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_end_to_end_length_per_link = + parameters.nondimensional_end_to_end_length_per_link_reference + + parameters.nondimensional_end_to_end_length_per_link_scale * (0.5 - rand()) + temperature = + parameters.temperature_reference + parameters.temperature_scale * (0.5 - rand()) + nondimensional_helmholtz_free_energy = model.nondimensional_helmholtz_free_energy( + nondimensional_end_to_end_length_per_link, + temperature, + ) + nondimensional_helmholtz_free_energy_0 = + model.nondimensional_helmholtz_free_energy(ZERO, temperature) + nondimensional_relative_helmholtz_free_energy = + model.nondimensional_relative_helmholtz_free_energy( + nondimensional_end_to_end_length_per_link, + ) + residual_abs = + nondimensional_helmholtz_free_energy - nondimensional_helmholtz_free_energy_0 - + nondimensional_relative_helmholtz_free_energy + residual_rel = residual_abs / nondimensional_relative_helmholtz_free_energy + @test abs(residual_rel) <= parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::relative::nondimensional_helmholtz_free_energy_per_link" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_end_to_end_length_per_link = + parameters.nondimensional_end_to_end_length_per_link_reference + + parameters.nondimensional_end_to_end_length_per_link_scale * (0.5 - rand()) + temperature = + parameters.temperature_reference + parameters.temperature_scale * (0.5 - rand()) + nondimensional_helmholtz_free_energy_per_link = + model.nondimensional_helmholtz_free_energy_per_link( + nondimensional_end_to_end_length_per_link, + temperature, + ) + nondimensional_helmholtz_free_energy_per_link_0 = + model.nondimensional_helmholtz_free_energy_per_link(ZERO, temperature) + nondimensional_relative_helmholtz_free_energy_per_link = + model.nondimensional_relative_helmholtz_free_energy_per_link( + nondimensional_end_to_end_length_per_link, + ) + residual_abs = + nondimensional_helmholtz_free_energy_per_link - + nondimensional_helmholtz_free_energy_per_link_0 - + nondimensional_relative_helmholtz_free_energy_per_link + residual_rel = residual_abs / nondimensional_relative_helmholtz_free_energy_per_link + @test abs(residual_rel) <= parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::zero::force" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + temperature = + parameters.temperature_reference + parameters.temperature_scale * (0.5 - rand()) + force_0 = model.force(ZERO * number_of_links * link_length, temperature) + @test abs(force_0) <= + 3.1 * ZERO * number_of_links * BOLTZMANN_CONSTANT * temperature + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::zero::nondimensional_force" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_force_0 = model.nondimensional_force(ZERO) + @test abs(nondimensional_force_0) <= 3.1 * ZERO * number_of_links + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::zero::relative_helmholtz_free_energy" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + temperature = + parameters.temperature_reference + parameters.temperature_scale * (0.5 - rand()) + relative_helmholtz_free_energy_0 = model.relative_helmholtz_free_energy( + ZERO * number_of_links * link_length, + temperature, + ) + @test abs(relative_helmholtz_free_energy_0) <= + ZERO * number_of_links * BOLTZMANN_CONSTANT * temperature + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::zero::relative_helmholtz_free_energy_per_link" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + temperature = + parameters.temperature_reference + parameters.temperature_scale * (0.5 - rand()) + relative_helmholtz_free_energy_per_link_0 = + model.relative_helmholtz_free_energy_per_link( + ZERO * number_of_links * link_length, + temperature, + ) + @test abs(relative_helmholtz_free_energy_per_link_0) <= + ZERO * BOLTZMANN_CONSTANT * temperature + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::zero::nondimensional_relative_helmholtz_free_energy" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_relative_helmholtz_free_energy_0 = + model.nondimensional_relative_helmholtz_free_energy(ZERO) + @test abs(nondimensional_relative_helmholtz_free_energy_0) <= ZERO * number_of_links + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::zero::nondimensional_relative_helmholtz_free_energy_per_link" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_relative_helmholtz_free_energy_per_link_0 = + model.nondimensional_relative_helmholtz_free_energy_per_link(ZERO) + @test abs(nondimensional_relative_helmholtz_free_energy_per_link_0) <= ZERO + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::zero::equilibrium_radial_distribution" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + equilibrium_radial_distribution_0 = + model.equilibrium_radial_distribution(ZERO * number_of_links * link_length) + @test abs(equilibrium_radial_distribution_0) <= ZERO + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::zero::nondimensional_equilibrium_radial_distribution" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_equilibrium_radial_distribution_0 = + model.equilibrium_radial_distribution(ZERO) + @test abs(nondimensional_equilibrium_radial_distribution_0) <= ZERO + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::connection::force" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_end_to_end_length_per_link = + parameters.nondimensional_end_to_end_length_per_link_reference + + parameters.nondimensional_end_to_end_length_per_link_scale * (0.5 - rand()) + temperature = + parameters.temperature_reference + parameters.temperature_scale * (0.5 - rand()) + end_to_end_length = + nondimensional_end_to_end_length_per_link * number_of_links * link_length + force = model.force(end_to_end_length, temperature) + h = parameters.rel_tol * number_of_links * link_length + force_from_derivative = + ( + model.relative_helmholtz_free_energy( + end_to_end_length + 0.5 * h, + temperature, + ) - model.relative_helmholtz_free_energy( + end_to_end_length - 0.5 * h, + temperature, + ) + ) / h + residual_abs = force - force_from_derivative + residual_rel = residual_abs / force + @test abs(residual_rel) <= h + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::connection::nondimensional_force" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_end_to_end_length_per_link = + parameters.nondimensional_end_to_end_length_per_link_reference + + parameters.nondimensional_end_to_end_length_per_link_scale * (0.5 - rand()) + temperature = + parameters.temperature_reference + parameters.temperature_scale * (0.5 - rand()) + nondimensional_force = + model.nondimensional_force(nondimensional_end_to_end_length_per_link) + h = parameters.rel_tol + nondimensional_force_from_derivative = + ( + model.nondimensional_relative_helmholtz_free_energy_per_link( + nondimensional_end_to_end_length_per_link + 0.5 * h, + ) - model.nondimensional_relative_helmholtz_free_energy_per_link( + nondimensional_end_to_end_length_per_link - 0.5 * h, + ) + ) / h + residual_abs = nondimensional_force - nondimensional_force_from_derivative + residual_rel = residual_abs / nondimensional_force + @test abs(residual_rel) <= h + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::connection::relative_helmholtz_free_energy" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_end_to_end_length_per_link = + parameters.nondimensional_end_to_end_length_per_link_reference + + parameters.nondimensional_end_to_end_length_per_link_scale * (0.5 - rand()) + temperature = + parameters.temperature_reference + parameters.temperature_scale * (0.5 - rand()) + end_to_end_length = + nondimensional_end_to_end_length_per_link * number_of_links * link_length + relative_helmholtz_free_energy = + model.relative_helmholtz_free_energy(end_to_end_length, temperature) + relative_helmholtz_free_energy_from_connection = + BOLTZMANN_CONSTANT * + temperature * + log(( + model.equilibrium_distribution(ZERO * number_of_links * link_length) / + model.equilibrium_distribution(end_to_end_length) + )) + residual_abs = + relative_helmholtz_free_energy - relative_helmholtz_free_energy_from_connection + residual_rel = residual_abs / relative_helmholtz_free_energy + @test abs(residual_rel) <= parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::isometric::legendre::test::connection::nondimensional_relative_helmholtz_free_energy" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_end_to_end_length_per_link = + parameters.nondimensional_end_to_end_length_per_link_reference + + parameters.nondimensional_end_to_end_length_per_link_scale * (0.5 - rand()) + nondimensional_relative_helmholtz_free_energy = + model.nondimensional_relative_helmholtz_free_energy( + nondimensional_end_to_end_length_per_link, + ) + nondimensional_relative_helmholtz_free_energy_from_connection = log(( + model.nondimensional_equilibrium_distribution(ZERO) / + model.nondimensional_equilibrium_distribution( + nondimensional_end_to_end_length_per_link, + ) + )) + residual_abs = + nondimensional_relative_helmholtz_free_energy - + nondimensional_relative_helmholtz_free_energy_from_connection + residual_rel = residual_abs / nondimensional_relative_helmholtz_free_energy + @test abs(residual_rel) <= parameters.rel_tol + end +end + +end diff --git a/src/physics/single_chain/swfjc/thermodynamics/isometric/test.jl b/src/physics/single_chain/swfjc/thermodynamics/isometric/test.jl index 2324c64c..e741321b 100644 --- a/src/physics/single_chain/swfjc/thermodynamics/isometric/test.jl +++ b/src/physics/single_chain/swfjc/thermodynamics/isometric/test.jl @@ -92,4 +92,4 @@ end end end -end +end diff --git a/src/physics/single_chain/swfjc/thermodynamics/test.jl b/src/physics/single_chain/swfjc/thermodynamics/test.jl index 1fbaf2bf..6ea4c11a 100644 --- a/src/physics/single_chain/swfjc/thermodynamics/test.jl +++ b/src/physics/single_chain/swfjc/thermodynamics/test.jl @@ -1,6 +1,7 @@ module Test using Test +using Polymers.Physics: BOLTZMANN_CONSTANT, PLANCK_CONSTANT using Polymers.Physics.SingleChain: parameters using Polymers.Physics.SingleChain.Swfjc.Thermodynamics: SWFJC @@ -91,4 +92,366 @@ end end end +@testset "physics::single_chain::swfjc::thermodynamics::test::legendre::force" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_force = + parameters.nondimensional_force_reference + + parameters.nondimensional_force_scale * (0.5 - rand()) + temperature = + parameters.temperature_reference + parameters.temperature_scale * (0.5 - rand()) + force = nondimensional_force * BOLTZMANN_CONSTANT * temperature / link_length + end_to_end_length = model.isotensional.end_to_end_length(force, temperature) + force_out = model.isometric.legendre.force(end_to_end_length, temperature) + residual_abs = force - force_out + residual_rel = residual_abs / force + @test abs(residual_abs) <= parameters.abs_tol && + abs(residual_rel) <= parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::test::legendre::nondimensional_force" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_force = + parameters.nondimensional_force_reference + + parameters.nondimensional_force_scale * (0.5 - rand()) + nondimensional_end_to_end_length_per_link = + model.isotensional.nondimensional_end_to_end_length_per_link( + nondimensional_force, + ) + nondimensional_force_out = model.isometric.legendre.nondimensional_force( + nondimensional_end_to_end_length_per_link, + ) + residual_abs = nondimensional_force - nondimensional_force_out + residual_rel = residual_abs / nondimensional_force + @test abs(residual_abs) <= parameters.abs_tol && + abs(residual_rel) <= parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::test::legendre::helmholtz_free_energy" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_force = + parameters.nondimensional_force_reference + + parameters.nondimensional_force_scale * (0.5 - rand()) + temperature = + parameters.temperature_reference + parameters.temperature_scale * (0.5 - rand()) + force = nondimensional_force * BOLTZMANN_CONSTANT * temperature / link_length + end_to_end_length = model.isotensional.end_to_end_length(force, temperature) + helmholtz_free_energy_legendre = + model.isotensional.gibbs_free_energy(force, temperature) + + force * end_to_end_length + helmholtz_free_energy_legendre_out = + model.isometric.legendre.helmholtz_free_energy(end_to_end_length, temperature) + residual_abs = + helmholtz_free_energy_legendre - helmholtz_free_energy_legendre_out + + BOLTZMANN_CONSTANT * + temperature * + log( + 8.0 * pi^2 * hinge_mass * link_length^2 * BOLTZMANN_CONSTANT * temperature / + PLANCK_CONSTANT^2, + ) + residual_rel = residual_abs / helmholtz_free_energy_legendre + @test abs(residual_abs) <= parameters.abs_tol && + abs(residual_rel) <= parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::test::legendre::helmholtz_free_energy_per_link" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_force = + parameters.nondimensional_force_reference + + parameters.nondimensional_force_scale * (0.5 - rand()) + temperature = + parameters.temperature_reference + parameters.temperature_scale * (0.5 - rand()) + force = nondimensional_force * BOLTZMANN_CONSTANT * temperature / link_length + end_to_end_length = model.isotensional.end_to_end_length(force, temperature) + end_to_end_length_per_link = + model.isotensional.end_to_end_length_per_link(force, temperature) + helmholtz_free_energy_per_link_legendre = + model.isotensional.gibbs_free_energy_per_link(force, temperature) + + force * end_to_end_length_per_link + helmholtz_free_energy_per_link_legendre_out = + model.isometric.legendre.helmholtz_free_energy_per_link( + end_to_end_length, + temperature, + ) + residual_abs = + helmholtz_free_energy_per_link_legendre - + helmholtz_free_energy_per_link_legendre_out + + BOLTZMANN_CONSTANT * + temperature * + log( + 8.0 * pi^2 * hinge_mass * link_length^2 * BOLTZMANN_CONSTANT * temperature / + PLANCK_CONSTANT^2, + ) / number_of_links + residual_rel = residual_abs / helmholtz_free_energy_per_link_legendre + @test abs(residual_abs) <= parameters.abs_tol && + abs(residual_rel) <= parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::test::legendre::relative_helmholtz_free_energy" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_force = + parameters.nondimensional_force_reference + + parameters.nondimensional_force_scale * (0.5 - rand()) + temperature = + parameters.temperature_reference + parameters.temperature_scale * (0.5 - rand()) + force = nondimensional_force * BOLTZMANN_CONSTANT * temperature / link_length + end_to_end_length = model.isotensional.end_to_end_length(force, temperature) + relative_helmholtz_free_energy_legendre = + model.isotensional.relative_gibbs_free_energy(force, temperature) + + force * end_to_end_length + relative_helmholtz_free_energy_legendre_out = + model.isometric.legendre.relative_helmholtz_free_energy( + end_to_end_length, + temperature, + ) + residual_abs = + relative_helmholtz_free_energy_legendre - + relative_helmholtz_free_energy_legendre_out + residual_rel = residual_abs / relative_helmholtz_free_energy_legendre + @test abs(residual_rel) <= 3e1 * parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::test::legendre::relative_helmholtz_free_energy_per_link" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_force = + parameters.nondimensional_force_reference + + parameters.nondimensional_force_scale * (0.5 - rand()) + temperature = + parameters.temperature_reference + parameters.temperature_scale * (0.5 - rand()) + force = nondimensional_force * BOLTZMANN_CONSTANT * temperature / link_length + end_to_end_length = model.isotensional.end_to_end_length(force, temperature) + end_to_end_length_per_link = + model.isotensional.end_to_end_length_per_link(force, temperature) + relative_helmholtz_free_energy_per_link_legendre = + model.isotensional.relative_gibbs_free_energy_per_link(force, temperature) + + force * end_to_end_length_per_link + relative_helmholtz_free_energy_per_link_legendre_out = + model.isometric.legendre.relative_helmholtz_free_energy_per_link( + end_to_end_length, + temperature, + ) + residual_abs = + relative_helmholtz_free_energy_per_link_legendre - + relative_helmholtz_free_energy_per_link_legendre_out + residual_rel = residual_abs / relative_helmholtz_free_energy_per_link_legendre + @test abs(residual_rel) <= 3e1 * parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::test::legendre::nondimensional_helmholtz_free_energy" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_force = + parameters.nondimensional_force_reference + + parameters.nondimensional_force_scale * (0.5 - rand()) + temperature = + parameters.temperature_reference + parameters.temperature_scale * (0.5 - rand()) + nondimensional_end_to_end_length = + model.isotensional.nondimensional_end_to_end_length(nondimensional_force) + nondimensional_end_to_end_length_per_link = + model.isotensional.nondimensional_end_to_end_length_per_link( + nondimensional_force, + ) + nondimensional_helmholtz_free_energy_legendre = + model.isotensional.nondimensional_gibbs_free_energy( + nondimensional_force, + temperature, + ) + nondimensional_force * nondimensional_end_to_end_length + nondimensional_helmholtz_free_energy_legendre_out = + model.isometric.legendre.nondimensional_helmholtz_free_energy( + nondimensional_end_to_end_length_per_link, + temperature, + ) + residual_abs = + nondimensional_helmholtz_free_energy_legendre - + nondimensional_helmholtz_free_energy_legendre_out + log( + 8.0 * pi^2 * hinge_mass * link_length^2 * BOLTZMANN_CONSTANT * temperature / + PLANCK_CONSTANT^2, + ) + residual_rel = residual_abs / nondimensional_helmholtz_free_energy_legendre + @test abs(residual_abs) <= parameters.abs_tol && + abs(residual_rel) <= parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::test::legendre::nondimensional_helmholtz_free_energy_per_link" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_force = + parameters.nondimensional_force_reference + + parameters.nondimensional_force_scale * (0.5 - rand()) + temperature = + parameters.temperature_reference + parameters.temperature_scale * (0.5 - rand()) + nondimensional_end_to_end_length_per_link = + model.isotensional.nondimensional_end_to_end_length_per_link( + nondimensional_force, + ) + nondimensional_helmholtz_free_energy_per_link_legendre = + model.isotensional.nondimensional_gibbs_free_energy_per_link( + nondimensional_force, + temperature, + ) + nondimensional_force * nondimensional_end_to_end_length_per_link + nondimensional_helmholtz_free_energy_per_link_legendre_out = + model.isometric.legendre.nondimensional_helmholtz_free_energy_per_link( + nondimensional_end_to_end_length_per_link, + temperature, + ) + residual_abs = + nondimensional_helmholtz_free_energy_per_link_legendre - + nondimensional_helmholtz_free_energy_per_link_legendre_out + + log( + 8.0 * pi^2 * hinge_mass * link_length^2 * BOLTZMANN_CONSTANT * temperature / + PLANCK_CONSTANT^2, + ) / number_of_links + residual_rel = residual_abs / nondimensional_helmholtz_free_energy_per_link_legendre + @test abs(residual_abs) <= parameters.abs_tol && + abs(residual_rel) <= parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::test::legendre::nondimensional_relative_helmholtz_free_energy" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_force = + parameters.nondimensional_force_reference + + parameters.nondimensional_force_scale * (0.5 - rand()) + nondimensional_end_to_end_length = + model.isotensional.nondimensional_end_to_end_length(nondimensional_force) + nondimensional_end_to_end_length_per_link = + model.isotensional.nondimensional_end_to_end_length_per_link( + nondimensional_force, + ) + nondimensional_relative_helmholtz_free_energy_legendre = + model.isotensional.nondimensional_relative_gibbs_free_energy( + nondimensional_force, + ) + nondimensional_force * nondimensional_end_to_end_length + nondimensional_relative_helmholtz_free_energy_legendre_out = + model.isometric.legendre.nondimensional_relative_helmholtz_free_energy( + nondimensional_end_to_end_length_per_link, + ) + residual_abs = + nondimensional_relative_helmholtz_free_energy_legendre - + nondimensional_relative_helmholtz_free_energy_legendre_out + residual_rel = residual_abs / nondimensional_relative_helmholtz_free_energy_legendre + @test abs(residual_rel) <= 3e1 * parameters.rel_tol + end +end + +@testset "physics::single_chain::swfjc::thermodynamics::test::legendre::nondimensional_relative_helmholtz_free_energy_per_link" begin + for _ = 1:parameters.number_of_loops + number_of_links = + rand(parameters.number_of_links_minimum:parameters.number_of_links_maximum) + link_length = + parameters.link_length_reference + parameters.link_length_scale * (0.5 - rand()) + hinge_mass = + parameters.hinge_mass_reference + parameters.hinge_mass_scale * (0.5 - rand()) + well_width = + parameters.well_width_reference + parameters.well_width_scale * (0.5 - rand()) + model = SWFJC(number_of_links, link_length, hinge_mass, well_width) + nondimensional_force = + parameters.nondimensional_force_reference + + parameters.nondimensional_force_scale * (0.5 - rand()) + nondimensional_end_to_end_length_per_link = + model.isotensional.nondimensional_end_to_end_length_per_link( + nondimensional_force, + ) + nondimensional_relative_helmholtz_free_energy_per_link_legendre = + model.isotensional.nondimensional_relative_gibbs_free_energy_per_link( + nondimensional_force, + ) + nondimensional_force * nondimensional_end_to_end_length_per_link + nondimensional_relative_helmholtz_free_energy_per_link_legendre_out = + model.isometric.legendre.nondimensional_relative_helmholtz_free_energy_per_link( + nondimensional_end_to_end_length_per_link, + ) + residual_abs = + nondimensional_relative_helmholtz_free_energy_per_link_legendre - + nondimensional_relative_helmholtz_free_energy_per_link_legendre_out + residual_rel = + residual_abs / nondimensional_relative_helmholtz_free_energy_per_link_legendre + @test abs(residual_rel) <= 3e1 * parameters.rel_tol + end +end + end