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}