Skip to main content

polysim_core/distribution/
log_normal.rs

1use rand::RngCore;
2use rand_distr::{Distribution, LogNormal as LN};
3
4use super::ChainLengthDistribution;
5
6/// Log-normal chain length distribution.
7///
8/// Flexible distribution parameterized by Mn and PDI:
9///   σ² = ln(PDI),  μ = ln(Xn) − σ²/2
10///
11/// Note: the relation PDI = exp(σ²) is exact for a continuous mass
12/// distribution. When end-group mass (m_end) is non-negligible compared
13/// to Mn, the actual PDI of the generated ensemble will deviate slightly
14/// because MW(n) = n·m0 + m_end introduces a constant offset. The
15/// approximation is excellent when m_end << Mn (typical for high-MW polymers).
16///
17/// Suitable for most synthetic polymers (radical, anionic, etc.).
18pub struct LogNormal;
19
20impl ChainLengthDistribution for LogNormal {
21    fn sample(
22        &self,
23        mn: f64,
24        pdi: f64,
25        m0: f64,
26        num_chains: usize,
27        rng: &mut dyn RngCore,
28    ) -> Vec<usize> {
29        let sigma_sq = pdi.max(1.0).ln();
30        let sigma = sigma_sq.sqrt();
31        let xn = (mn / m0).max(1.0);
32        let mu = xn.ln() - sigma_sq / 2.0;
33
34        let dist = LN::new(mu, sigma).expect("valid log-normal parameters");
35        (0..num_chains)
36            .map(|_| {
37                let x: f64 = dist.sample(rng);
38                (x.round() as usize).max(1)
39            })
40            .collect()
41    }
42
43    fn name(&self) -> &'static str {
44        "log_normal"
45    }
46}