conspire/physics/molecular/single_chain/ufjc/
mod.rs1#[cfg(test)]
2mod test;
3
4use crate::{
5 math::{
6 Scalar,
7 special::{langevin, langevin_derivative},
8 },
9 physics::molecular::{
10 potential::{Harmonic, Potential},
11 single_chain::{
12 Ensemble, Extensible, Isometric, Isotensional, IsotensionalExtensible, Legendre,
13 SingleChain, SingleChainError, Thermodynamics, ThermodynamicsExtensible,
14 },
15 },
16};
17use std::f64::consts::TAU;
18
19#[derive(Clone, Debug)]
22pub struct ArbitraryPotentialFreelyJointedChain<T>
23where
24 T: Potential,
25{
26 pub link_potential: T,
28 pub number_of_links: u8,
30 pub ensemble: Ensemble,
32}
33
34impl<T> ArbitraryPotentialFreelyJointedChain<T>
35where
36 T: Potential,
37{
38 fn correction(&self) -> Scalar {
39 1.0 / (1.0
40 - 0.5
41 * self
42 .link_potential
43 .nondimensional_anharmonicity(1.0, self.temperature())
44 / self
45 .link_potential
46 .nondimensional_stiffness(1.0, self.temperature()))
47 }
48 fn nondimensional_link_stiffness(&self) -> Scalar {
49 self.link_potential
50 .nondimensional_stiffness(1.0, self.temperature())
51 }
52}
53
54impl<T> SingleChain for ArbitraryPotentialFreelyJointedChain<T>
55where
56 T: Potential,
57{
58 fn link_length(&self) -> Scalar {
59 self.link_potential.rest_length()
60 }
61 fn number_of_links(&self) -> u8 {
62 self.number_of_links
63 }
64}
65
66impl<T> Extensible for ArbitraryPotentialFreelyJointedChain<T> where T: Potential {}
67
68impl<T> Thermodynamics for ArbitraryPotentialFreelyJointedChain<T>
69where
70 T: Potential,
71{
72 fn ensemble(&self) -> Ensemble {
73 self.ensemble
74 }
75}
76
77impl ThermodynamicsExtensible for ArbitraryPotentialFreelyJointedChain<Harmonic> {}
78
79impl<T> Isometric for ArbitraryPotentialFreelyJointedChain<T>
80where
81 T: Potential,
82{
83 fn nondimensional_helmholtz_free_energy(
84 &self,
85 _nondimensional_extension: Scalar,
86 ) -> Result<Scalar, SingleChainError> {
87 unimplemented!()
88 }
89 fn nondimensional_force(
90 &self,
91 _nondimensional_extension: Scalar,
92 ) -> Result<Scalar, SingleChainError> {
93 unimplemented!()
94 }
95 fn nondimensional_stiffness(
96 &self,
97 _nondimensional_extension: Scalar,
98 ) -> Result<Scalar, SingleChainError> {
99 unimplemented!()
100 }
101 fn nondimensional_spherical_distribution(
102 &self,
103 _nondimensional_extension: Scalar,
104 ) -> Result<Scalar, SingleChainError> {
105 unimplemented!()
106 }
107}
108
109impl<T> Isotensional for ArbitraryPotentialFreelyJointedChain<T>
110where
111 T: Potential,
112{
113 fn nondimensional_gibbs_free_energy_per_link(
117 &self,
118 nondimensional_force: Scalar,
119 ) -> Result<Scalar, SingleChainError> {
120 nondimensional_gibbs_free_energy_per_link(
121 nondimensional_force,
122 self.nondimensional_link_stiffness(),
123 self.link_potential
124 .nondimensional_legendre(nondimensional_force, self.temperature()),
125 self.correction(),
126 )
127 }
128 fn nondimensional_extension(
132 &self,
133 nondimensional_force: Scalar,
134 ) -> Result<Scalar, SingleChainError> {
135 nondimensional_extension(
136 nondimensional_force,
137 self.nondimensional_link_stiffness(),
138 self.link_potential
139 .nondimensional_extension(nondimensional_force, self.temperature()),
140 self.correction(),
141 )
142 }
143 fn nondimensional_compliance(
147 &self,
148 nondimensional_force: Scalar,
149 ) -> Result<Scalar, SingleChainError> {
150 nondimensional_compliance(
151 nondimensional_force,
152 self.nondimensional_link_stiffness(),
153 self.link_potential
154 .nondimensional_compliance(nondimensional_force, self.temperature()),
155 self.correction(),
156 )
157 }
158}
159
160impl IsotensionalExtensible for ArbitraryPotentialFreelyJointedChain<Harmonic> {
161 fn nondimensional_link_energy_average(
165 &self,
166 nondimensional_force: Scalar,
167 ) -> Result<Scalar, SingleChainError> {
168 Ok(0.5
169 + helper(
170 nondimensional_force,
171 self.nondimensional_link_stiffness(),
172 self.correction(),
173 )
174 + self
175 .link_potential
176 .nondimensional_energy_at_nondimensional_force(
177 nondimensional_force,
178 self.temperature(),
179 ))
180 }
181 fn nondimensional_link_energy_variance(
185 &self,
186 nondimensional_force: Scalar,
187 ) -> Result<Scalar, SingleChainError> {
188 let hlpr = helper(
189 nondimensional_force,
190 self.nondimensional_link_stiffness(),
191 self.correction(),
192 );
193 Ok(0.5
194 + hlpr * (2.0 - hlpr)
195 + 2.0
196 * self
197 .link_potential
198 .nondimensional_energy_at_nondimensional_force(
199 nondimensional_force,
200 self.temperature(),
201 ))
202 }
203 fn nondimensional_link_energy_probability(
207 &self,
208 nondimensional_energy: Scalar,
209 nondimensional_force: Scalar,
210 ) -> Result<Scalar, SingleChainError> {
211 self.link_potential
212 .nondimensional_forces_at_nondimensional_energy(
213 nondimensional_energy,
214 self.temperature(),
215 )
216 .into_iter()
217 .zip(
218 self.link_potential
219 .nondimensional_lengths_at_nondimensional_energy(
220 nondimensional_energy,
221 self.temperature(),
222 ),
223 )
224 .map(|(eta, nondimensional_length)| {
225 Ok(
226 IsotensionalExtensible::nondimensional_link_length_probability(
227 self,
228 nondimensional_length,
229 nondimensional_force,
230 )? / eta.abs(),
231 )
232 })
233 .sum()
234 }
235 fn nondimensional_link_length_average(
239 &self,
240 nondimensional_force: Scalar,
241 ) -> Result<Scalar, SingleChainError> {
242 let eta = nondimensional_force;
243 let kappa = self.nondimensional_link_stiffness();
244 if eta == 0.0 {
245 Ok(1.0 + 2.0 / (1.0 * kappa + 1.0))
246 } else {
247 let eta_coth = 1.0 / eta.tanh();
248 let eta_over_kappa = eta / kappa;
249 Ok(1.0
250 + (1.0 / kappa + eta_over_kappa * (1.0 - eta_over_kappa) * (eta_coth - 1.0))
251 / (1.0 + eta_over_kappa * eta_coth)
252 + eta_over_kappa)
253 }
254 }
255 fn nondimensional_link_length_variance(
259 &self,
260 nondimensional_force: Scalar,
261 ) -> Result<Scalar, SingleChainError> {
262 let eta = nondimensional_force;
263 let kappa = self.nondimensional_link_stiffness();
264 let mean_squared =
265 ThermodynamicsExtensible::nondimensional_link_length_average(self, eta)?.powi(2);
266 if eta == 0.0 {
267 Ok(1.0 + 3.0 / kappa + 2.0 / (kappa + 1.0) - mean_squared)
268 } else {
269 let eta_coth = 1.0 / eta.tanh();
270 let eta_over_kappa = eta / kappa;
271 let eta_over_kappa_coth = eta_over_kappa * eta_coth;
272 Ok(1.0
273 + (3.0 / kappa
274 + 2.0 * eta_over_kappa.powi(2)
275 + (3.0 / kappa + 2.0) * eta_over_kappa_coth)
276 / (1.0 + eta_over_kappa_coth)
277 + eta_over_kappa.powi(2)
278 - mean_squared)
279 }
280 }
281 fn nondimensional_link_length_probability(
285 &self,
286 nondimensional_length: Scalar,
287 nondimensional_force: Scalar,
288 ) -> Result<Scalar, SingleChainError> {
289 let eta = nondimensional_force;
290 let lambda = nondimensional_length;
291 let kappa = self.nondimensional_link_stiffness();
292 let upsilon_twice = self
293 .link_potential
294 .nondimensional_energy(nondimensional_length, self.temperature())
295 + eta.powi(2) / 2.0 / kappa;
296 Ok((kappa / TAU).sqrt()
297 * lambda
298 * ((eta * (lambda - 1.0) - upsilon_twice).exp()
299 - (-eta * (lambda + 1.0) - upsilon_twice).exp())
300 / (1.0 - (-2.0 * eta).exp())
301 / (1.0 + eta / kappa / self.correction() / eta.tanh()))
302 }
303}
304
305pub fn nondimensional_gibbs_free_energy_per_link(
306 eta: Scalar,
307 kappa: Scalar,
308 nu: Scalar,
309 c: Scalar,
310) -> Result<Scalar, SingleChainError> {
311 Ok(nu
312 - eta
313 - (0.5 - 0.5 * (-2.0 * eta).exp()).ln()
314 - (1.0 / eta + 1.0 / c / kappa / eta.tanh()).ln())
315}
316
317pub fn nondimensional_extension(
318 eta: Scalar,
319 kappa: Scalar,
320 delta_lambda: Scalar,
321 c: Scalar,
322) -> Result<Scalar, SingleChainError> {
323 if eta == 0.0 {
324 Ok(0.0)
325 } else {
326 let eta_coth = 1.0 / eta.tanh();
327 let gamma_0 = langevin(eta);
328 let eta_over_kappa = eta / kappa;
329 Ok(gamma_0
330 + eta_over_kappa * (1.0 - gamma_0 * eta_coth) / (c + eta_over_kappa * eta_coth)
331 + delta_lambda)
332 }
333}
334
335pub fn nondimensional_compliance(
336 eta: Scalar,
337 kappa: Scalar,
338 zeta: Scalar,
339 c: Scalar,
340) -> Result<Scalar, SingleChainError> {
341 if eta == 0.0 {
342 Ok(1.0 / 3.0 + 2.0 / 3.0 / c / kappa + zeta)
343 } else {
344 let eta_tanh = eta.tanh();
345 let eta_coth = 1.0 / eta_tanh;
346 let gamma_0 = langevin(eta);
347 let eta_over_kappa = eta / kappa;
348 let c_0 = langevin_derivative(eta);
349 let g = 1.0 - gamma_0 * eta_coth;
350 let h = c + eta_over_kappa * eta_coth;
351 let dcth = 1.0 - 1.0 / (eta_tanh * eta_tanh);
352 let dg = -(c_0 * eta_coth + gamma_0 * dcth);
353 let dh = eta_coth / kappa + eta_over_kappa * dcth;
354 Ok(c_0 + (g / h) / kappa + eta_over_kappa * (dg * h - g * dh) / (h * h) + zeta)
355 }
356}
357
358fn helper(
359 nondimensional_force: Scalar,
360 nondimensional_stiffness: Scalar,
361 correction: Scalar,
362) -> Scalar {
363 let eta_over_kappa = nondimensional_force / nondimensional_stiffness;
364 eta_over_kappa / (eta_over_kappa + correction * nondimensional_force.tanh())
365}
366
367impl<T> Legendre for ArbitraryPotentialFreelyJointedChain<T>
368where
369 T: Potential,
370{
371 fn nondimensional_spherical_distribution(
372 &self,
373 _nondimensional_extension: Scalar,
374 ) -> Result<Scalar, SingleChainError> {
375 unimplemented!()
376 }
377}