conspire/physics/molecular/single_chain/efrc/
mod.rs1#[cfg(test)]
2mod test;
3
4use crate::{
5 math::{
6 CrossProduct, Scalar, Tensor,
7 random::{random_uniform, random_x2_normal},
8 },
9 mechanics::CurrentCoordinate,
10 physics::{
11 BOLTZMANN_CONSTANT,
12 molecular::single_chain::{
13 Configuration, Ensemble, Extensible, Isometric, Isotensional, Legendre, MonteCarlo,
14 SingleChain, SingleChainError, Thermodynamics,
15 },
16 },
17};
18use std::f64::consts::TAU;
19
20#[derive(Clone, Debug)]
22pub struct ExtensibleFreelyRotatingChain {
23 pub link_angle: Scalar,
25 pub link_length: Scalar,
27 pub link_stiffness: Scalar,
29 pub number_of_links: u8,
31 pub ensemble: Ensemble,
33}
34
35impl ExtensibleFreelyRotatingChain {
36 fn nondimensional_link_stiffness(&self) -> Scalar {
37 self.link_stiffness * self.link_length().powi(2) / BOLTZMANN_CONSTANT / self.temperature()
38 }
39}
40
41impl SingleChain for ExtensibleFreelyRotatingChain {
42 fn link_length(&self) -> Scalar {
43 self.link_length
44 }
45 fn number_of_links(&self) -> u8 {
46 self.number_of_links
47 }
48}
49
50impl Extensible for ExtensibleFreelyRotatingChain {}
51
52impl Thermodynamics for ExtensibleFreelyRotatingChain {
53 fn ensemble(&self) -> Ensemble {
54 self.ensemble
55 }
56}
57
58impl Isometric for ExtensibleFreelyRotatingChain {
59 fn nondimensional_helmholtz_free_energy(
60 &self,
61 _nondimensional_extension: Scalar,
62 ) -> Result<Scalar, SingleChainError> {
63 unimplemented!()
64 }
65 fn nondimensional_force(
66 &self,
67 _nondimensional_extension: Scalar,
68 ) -> Result<Scalar, SingleChainError> {
69 unimplemented!()
70 }
71 fn nondimensional_stiffness(
72 &self,
73 _nondimensional_extension: Scalar,
74 ) -> Result<Scalar, SingleChainError> {
75 unimplemented!()
76 }
77 fn nondimensional_spherical_distribution(
78 &self,
79 _nondimensional_extension: Scalar,
80 ) -> Result<Scalar, SingleChainError> {
81 unimplemented!()
82 }
83}
84
85impl Isotensional for ExtensibleFreelyRotatingChain {
86 fn nondimensional_gibbs_free_energy_per_link(
87 &self,
88 _nondimensional_force: Scalar,
89 ) -> Result<Scalar, SingleChainError> {
90 unimplemented!()
91 }
92 fn nondimensional_extension(
93 &self,
94 _nondimensional_force: Scalar,
95 ) -> Result<Scalar, SingleChainError> {
96 unimplemented!()
97 }
98 fn nondimensional_compliance(
99 &self,
100 _nondimensional_force: Scalar,
101 ) -> Result<Scalar, SingleChainError> {
102 unimplemented!()
103 }
104}
105
106impl Legendre for ExtensibleFreelyRotatingChain {
107 fn nondimensional_spherical_distribution(
108 &self,
109 _nondimensional_extension: Scalar,
110 ) -> Result<Scalar, SingleChainError> {
111 unimplemented!()
112 }
113}
114
115impl MonteCarlo for ExtensibleFreelyRotatingChain {
116 fn nondimensional_longitudinal_extension(
117 &self,
118 nondimensional_force: Scalar,
119 number_of_samples: usize,
120 number_of_threads: usize,
121 ) -> Scalar {
122 nondimensional_extension_reweighted_biased_stretch(
123 self,
124 nondimensional_force,
125 nondimensional_force,
126 number_of_samples,
127 number_of_threads,
128 )
129 }
130 fn random_nondimensional_link_vectors(&self, nondimensional_force: Scalar) -> Configuration {
131 if nondimensional_force != 0.0 {
132 unimplemented!()
133 }
134 let std = 1.0 / self.nondimensional_link_stiffness().sqrt();
135 let cos_theta = 2.0 * random_uniform() - 1.0;
136 let sin_theta = (1.0 - cos_theta * cos_theta).sqrt();
137 let phi = TAU * random_uniform();
138 let (sin_phi, cos_phi) = phi.sin_cos();
139 const AY: CurrentCoordinate = CurrentCoordinate::const_from([0.0, 1.0, 0.0]);
140 const AZ: CurrentCoordinate = CurrentCoordinate::const_from([0.0, 0.0, 1.0]);
141 let mut a = AY;
142 let mut b =
143 CurrentCoordinate::const_from([sin_theta * cos_phi, sin_theta * sin_phi, cos_theta]);
144 let (sin_theta, cos_theta) = self.link_angle.sin_cos();
145 (0..self.number_of_links())
146 .map(|link| {
147 if link > 0 {
148 a = if b[1].abs() < 0.9 { AY } else { AZ };
149 let u = a.cross(&b).normalized();
150 let v = b.cross(&u);
151 let phi = TAU * random_uniform();
152 let (sin_phi, cos_phi) = phi.sin_cos();
153 b = &b * cos_theta + (&u * cos_phi + &v * sin_phi) * sin_theta;
154 }
155 &b * random_x2_normal(1.0, std)
156 })
157 .collect()
158 }
159}
160
161fn random_nondimensional_link_vectors_biased_stretch(
162 model: &ExtensibleFreelyRotatingChain,
163 nondimensional_stretch_bias: Scalar,
164) -> Configuration {
165 let kappa = model.nondimensional_link_stiffness();
166 let std = 1.0 / kappa.sqrt();
167 let mean = 1.0 + nondimensional_stretch_bias / kappa;
168
169 let cos_theta = 2.0 * random_uniform() - 1.0;
170 let sin_theta = (1.0 - cos_theta * cos_theta).sqrt();
171 let phi = TAU * random_uniform();
172 let (sin_phi, cos_phi) = phi.sin_cos();
173
174 const AY: CurrentCoordinate = CurrentCoordinate::const_from([0.0, 1.0, 0.0]);
175 const AZ: CurrentCoordinate = CurrentCoordinate::const_from([0.0, 0.0, 1.0]);
176
177 let mut a = AY;
178 let mut b =
179 CurrentCoordinate::const_from([sin_theta * cos_phi, sin_theta * sin_phi, cos_theta]);
180
181 let (sin_theta, cos_theta) = model.link_angle.sin_cos();
182
183 (0..model.number_of_links())
184 .map(|link| {
185 if link > 0 {
186 a = if b[1].abs() < 0.9 { AY } else { AZ };
187 let u = a.cross(&b).normalized();
188 let v = b.cross(&u);
189 let phi = TAU * random_uniform();
190 let (sin_phi, cos_phi) = phi.sin_cos();
191 b = &b * cos_theta + (&u * cos_phi + &v * sin_phi) * sin_theta;
192 }
193 &b * random_x2_normal(mean, std)
194 })
195 .collect()
196}
197
198use std::thread::scope;
199
200fn nondimensional_extension_reweighted_biased_stretch(
201 model: &ExtensibleFreelyRotatingChain,
202 nondimensional_force: Scalar,
203 nondimensional_stretch_bias: Scalar,
204 number_of_samples: usize,
205 number_of_threads: usize,
206) -> Scalar {
207 let base = number_of_samples / number_of_threads;
208 let remainder = number_of_samples % number_of_threads;
209
210 scope(|s| {
211 (0..number_of_threads)
212 .map(|t| {
213 s.spawn(move || {
214 nondimensional_extension_reweighted_biased_stretch_inner(
215 model,
216 nondimensional_force,
217 nondimensional_stretch_bias,
218 base + usize::from(t < remainder),
219 )
220 })
221 })
222 .collect::<Vec<_>>()
223 .into_iter()
224 .map(|handle| handle.join().unwrap())
225 .reduce(|mut acc, (x_max, z_scaled, ext_scaled)| {
226 let x_max_new = acc.0.max(x_max);
227 let scale_acc = (acc.0 - x_max_new).exp();
228 let scale_new = (x_max - x_max_new).exp();
229
230 acc.1 = acc.1 * scale_acc + z_scaled * scale_new;
231 acc.2 = acc.2 * scale_acc + ext_scaled * scale_new;
232 acc.0 = x_max_new;
233 acc
234 })
235 .map(|(_x_max, z_scaled, ext_scaled)| {
236 ext_scaled / z_scaled / model.number_of_links() as Scalar
237 })
238 .unwrap()
239 })
240}
241
242fn nondimensional_extension_reweighted_biased_stretch_inner(
243 model: &ExtensibleFreelyRotatingChain,
244 nondimensional_force: Scalar,
245 nondimensional_stretch_bias: Scalar,
246 number_of_samples: usize,
247) -> (Scalar, Scalar, Scalar) {
248 let mut x_max = Scalar::NEG_INFINITY;
249 let mut z_scaled = 0.0;
250 let mut ext_scaled = 0.0;
251
252 for _ in 0..number_of_samples {
253 let links =
254 random_nondimensional_link_vectors_biased_stretch(model, nondimensional_stretch_bias);
255
256 let extension_sum: Scalar = links.iter().map(|link| link[2]).sum();
257 let stretch_sum: Scalar = links.iter().map(|link| link.norm()).sum();
258
259 let x = nondimensional_force * extension_sum - nondimensional_stretch_bias * stretch_sum;
260
261 if x > x_max {
262 let scale = if x_max.is_finite() {
263 (x_max - x).exp()
264 } else {
265 0.0
266 };
267 z_scaled *= scale;
268 ext_scaled *= scale;
269 x_max = x;
270 }
271
272 let w = (x - x_max).exp();
273 z_scaled += w;
274 ext_scaled += extension_sum * w;
275 }
276
277 (x_max, z_scaled, ext_scaled)
278}