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