1use crate::{
2 math::{
3 Current, Quantity, Scalar, SquareMatrix, Tensor, TensorArray, TensorRank1, Vector,
4 optimize::{
5 Direct, EqualityConstraint, LineSearch, NewtonRaphson, SecondOrderOptimization,
6 Tolerances,
7 },
8 },
9 mechanics::Vectors,
10 physics::molecular::single_chain::{Extensible, Inextensible, SingleChain, SingleChainError},
11 units::Temperature,
12};
13use std::{f64::consts::PI, thread::scope};
14
15pub type Configuration = Vectors<Current>;
16
17#[derive(Clone, Copy, Debug)]
18pub enum Ensemble {
19 Isometric(Scalar),
20 Isotensional(Scalar),
21}
22
23pub trait Thermodynamics
24where
25 Self: Isometric + Isotensional + Legendre + SingleChain,
26{
27 fn ensemble(&self) -> Ensemble;
28 fn temperature(&self) -> Quantity<Temperature> {
29 match self.ensemble() {
30 Ensemble::Isometric(temperature) => Quantity::new(temperature),
31 Ensemble::Isotensional(temperature) => Quantity::new(temperature),
32 }
33 }
34 fn nondimensional_helmholtz_free_energy(
35 &self,
36 nondimensional_extension: Scalar,
37 ) -> Result<Scalar, SingleChainError> {
38 match self.ensemble() {
39 Ensemble::Isometric(_) => {
40 Isometric::nondimensional_helmholtz_free_energy(self, nondimensional_extension)
41 }
42 Ensemble::Isotensional(_) => {
43 Legendre::nondimensional_helmholtz_free_energy(self, nondimensional_extension)
44 }
45 }
46 }
47 fn nondimensional_helmholtz_free_energy_per_link(
48 &self,
49 nondimensional_extension: Scalar,
50 ) -> Result<Scalar, SingleChainError> {
51 match self.ensemble() {
52 Ensemble::Isometric(_) => Isometric::nondimensional_helmholtz_free_energy_per_link(
53 self,
54 nondimensional_extension,
55 ),
56 Ensemble::Isotensional(_) => Legendre::nondimensional_helmholtz_free_energy_per_link(
57 self,
58 nondimensional_extension,
59 ),
60 }
61 }
62 fn nondimensional_force(
63 &self,
64 nondimensional_extension: Scalar,
65 ) -> Result<Scalar, SingleChainError> {
66 match self.ensemble() {
67 Ensemble::Isometric(_) => {
68 Isometric::nondimensional_force(self, nondimensional_extension)
69 }
70 Ensemble::Isotensional(_) => {
71 Legendre::nondimensional_force(self, nondimensional_extension)
72 }
73 }
74 }
75 fn nondimensional_stiffness(
76 &self,
77 nondimensional_extension: Scalar,
78 ) -> Result<Scalar, SingleChainError> {
79 match self.ensemble() {
80 Ensemble::Isometric(_) => {
81 Isometric::nondimensional_stiffness(self, nondimensional_extension)
82 }
83 Ensemble::Isotensional(_) => {
84 Legendre::nondimensional_stiffness(self, nondimensional_extension)
85 }
86 }
87 }
88 fn nondimensional_radial_distribution(
89 &self,
90 nondimensional_extension: Scalar,
91 ) -> Result<Scalar, SingleChainError> {
92 match self.ensemble() {
93 Ensemble::Isometric(_) => {
94 Isometric::nondimensional_radial_distribution(self, nondimensional_extension)
95 }
96 Ensemble::Isotensional(_) => {
97 Legendre::nondimensional_radial_distribution(self, nondimensional_extension)
98 }
99 }
100 }
101 fn nondimensional_spherical_distribution(
102 &self,
103 nondimensional_extension: Scalar,
104 ) -> Result<Scalar, SingleChainError> {
105 match self.ensemble() {
106 Ensemble::Isometric(_) => {
107 Isometric::nondimensional_spherical_distribution(self, nondimensional_extension)
108 }
109 Ensemble::Isotensional(_) => {
110 Legendre::nondimensional_spherical_distribution(self, nondimensional_extension)
111 }
112 }
113 }
114 fn nondimensional_gibbs_free_energy(
115 &self,
116 nondimensional_force: Scalar,
117 ) -> Result<Scalar, SingleChainError> {
118 match self.ensemble() {
119 Ensemble::Isometric(_) => {
120 Legendre::nondimensional_gibbs_free_energy(self, nondimensional_force)
121 }
122 Ensemble::Isotensional(_) => {
123 Isotensional::nondimensional_gibbs_free_energy(self, nondimensional_force)
124 }
125 }
126 }
127 fn nondimensional_gibbs_free_energy_per_link(
128 &self,
129 nondimensional_force: Scalar,
130 ) -> Result<Scalar, SingleChainError> {
131 match self.ensemble() {
132 Ensemble::Isometric(_) => {
133 Legendre::nondimensional_gibbs_free_energy_per_link(self, nondimensional_force)
134 }
135 Ensemble::Isotensional(_) => {
136 Isotensional::nondimensional_gibbs_free_energy_per_link(self, nondimensional_force)
137 }
138 }
139 }
140 fn nondimensional_extension(
141 &self,
142 nondimensional_force: Scalar,
143 ) -> Result<Scalar, SingleChainError> {
144 match self.ensemble() {
145 Ensemble::Isometric(_) => {
146 Legendre::nondimensional_extension(self, nondimensional_force)
147 }
148 Ensemble::Isotensional(_) => {
149 Isotensional::nondimensional_extension(self, nondimensional_force)
150 }
151 }
152 }
153 fn nondimensional_compliance(
154 &self,
155 nondimensional_force: Scalar,
156 ) -> Result<Scalar, SingleChainError> {
157 match self.ensemble() {
158 Ensemble::Isometric(_) => {
159 Legendre::nondimensional_compliance(self, nondimensional_force)
160 }
161 Ensemble::Isotensional(_) => {
162 Isotensional::nondimensional_compliance(self, nondimensional_force)
163 }
164 }
165 }
166}
167
168pub trait ThermodynamicsExtensible
169where
170 Self: IsotensionalExtensible + Thermodynamics,
171{
172 fn nondimensional_link_energy_average(
173 &self,
174 nondimensional_force: Scalar,
175 ) -> Result<Scalar, SingleChainError> {
176 match self.ensemble() {
177 Ensemble::Isometric(_) => {
178 unimplemented!()
179 }
180 Ensemble::Isotensional(_) => {
181 IsotensionalExtensible::nondimensional_link_energy_average(
182 self,
183 nondimensional_force,
184 )
185 }
186 }
187 }
188 fn nondimensional_link_energy_variance(
189 &self,
190 nondimensional_force: Scalar,
191 ) -> Result<Scalar, SingleChainError> {
192 match self.ensemble() {
193 Ensemble::Isometric(_) => {
194 unimplemented!()
195 }
196 Ensemble::Isotensional(_) => {
197 IsotensionalExtensible::nondimensional_link_energy_variance(
198 self,
199 nondimensional_force,
200 )
201 }
202 }
203 }
204 fn nondimensional_link_energy_probability(
205 &self,
206 nondimensional_energy: Scalar,
207 nondimensional_force: Scalar,
208 ) -> Result<Scalar, SingleChainError> {
209 match self.ensemble() {
210 Ensemble::Isometric(_) => {
211 unimplemented!()
212 }
213 Ensemble::Isotensional(_) => {
214 IsotensionalExtensible::nondimensional_link_energy_probability(
215 self,
216 nondimensional_energy,
217 nondimensional_force,
218 )
219 }
220 }
221 }
222 fn nondimensional_link_length_average(
223 &self,
224 nondimensional_force: Scalar,
225 ) -> Result<Scalar, SingleChainError> {
226 match self.ensemble() {
227 Ensemble::Isometric(_) => {
228 unimplemented!()
229 }
230 Ensemble::Isotensional(_) => {
231 IsotensionalExtensible::nondimensional_link_length_average(
232 self,
233 nondimensional_force,
234 )
235 }
236 }
237 }
238 fn nondimensional_link_length_variance(
239 &self,
240 nondimensional_force: Scalar,
241 ) -> Result<Scalar, SingleChainError> {
242 match self.ensemble() {
243 Ensemble::Isometric(_) => {
244 unimplemented!()
245 }
246 Ensemble::Isotensional(_) => {
247 IsotensionalExtensible::nondimensional_link_length_variance(
248 self,
249 nondimensional_force,
250 )
251 }
252 }
253 }
254 fn nondimensional_link_length_probability(
255 &self,
256 nondimensional_length: Scalar,
257 nondimensional_force: Scalar,
258 ) -> Result<Scalar, SingleChainError> {
259 match self.ensemble() {
260 Ensemble::Isometric(_) => {
261 unimplemented!()
262 }
263 Ensemble::Isotensional(_) => {
264 IsotensionalExtensible::nondimensional_link_length_probability(
265 self,
266 nondimensional_length,
267 nondimensional_force,
268 )
269 }
270 }
271 }
272}
273
274pub trait Isometric
275where
276 Self: SingleChain,
277{
278 fn nondimensional_helmholtz_free_energy(
282 &self,
283 nondimensional_extension: Scalar,
284 ) -> Result<Scalar, SingleChainError> {
285 Ok(
286 self.nondimensional_helmholtz_free_energy_per_link(nondimensional_extension)?
287 * (self.number_of_links() as Scalar),
288 )
289 }
290 fn nondimensional_helmholtz_free_energy_per_link(
294 &self,
295 nondimensional_extension: Scalar,
296 ) -> Result<Scalar, SingleChainError> {
297 Ok(
298 self.nondimensional_helmholtz_free_energy(nondimensional_extension)?
299 / (self.number_of_links() as Scalar),
300 )
301 }
302 fn nondimensional_force(
306 &self,
307 nondimensional_extension: Scalar,
308 ) -> Result<Scalar, SingleChainError>;
309 fn nondimensional_stiffness(
313 &self,
314 nondimensional_extension: Scalar,
315 ) -> Result<Scalar, SingleChainError>;
316 fn nondimensional_radial_distribution(
320 &self,
321 nondimensional_extension: Scalar,
322 ) -> Result<Scalar, SingleChainError> {
323 Ok(
324 self.nondimensional_spherical_distribution(nondimensional_extension)?
325 * (4.0 * PI * nondimensional_extension.powi(2)),
326 )
327 }
328 fn nondimensional_spherical_distribution(
332 &self,
333 nondimensional_extension: Scalar,
334 ) -> Result<Scalar, SingleChainError>;
335}
336
337pub trait Isotensional
338where
339 Self: SingleChain,
340{
341 fn nondimensional_gibbs_free_energy(
345 &self,
346 nondimensional_force: Scalar,
347 ) -> Result<Scalar, SingleChainError> {
348 Ok(
349 self.nondimensional_gibbs_free_energy_per_link(nondimensional_force)?
350 * (self.number_of_links() as Scalar),
351 )
352 }
353 fn nondimensional_gibbs_free_energy_per_link(
357 &self,
358 nondimensional_force: Scalar,
359 ) -> Result<Scalar, SingleChainError> {
360 Ok(self.nondimensional_gibbs_free_energy(nondimensional_force)?
361 / (self.number_of_links() as Scalar))
362 }
363 fn nondimensional_extension(
367 &self,
368 nondimensional_force: Scalar,
369 ) -> Result<Scalar, SingleChainError>;
370 fn nondimensional_compliance(
374 &self,
375 nondimensional_force: Scalar,
376 ) -> Result<Scalar, SingleChainError>;
377}
378
379pub trait IsotensionalExtensible
380where
381 Self: Extensible + Isotensional,
382{
383 fn nondimensional_link_energy_average(
387 &self,
388 nondimensional_force: Scalar,
389 ) -> Result<Scalar, SingleChainError>;
390 fn nondimensional_link_energy_variance(
394 &self,
395 nondimensional_force: Scalar,
396 ) -> Result<Scalar, SingleChainError>;
397 fn nondimensional_link_energy_probability(
401 &self,
402 nondimensional_energy: Scalar,
403 nondimensional_force: Scalar,
404 ) -> Result<Scalar, SingleChainError>;
405 fn nondimensional_link_length_average(
409 &self,
410 nondimensional_force: Scalar,
411 ) -> Result<Scalar, SingleChainError>;
412 fn nondimensional_link_length_variance(
416 &self,
417 nondimensional_force: Scalar,
418 ) -> Result<Scalar, SingleChainError>;
419 fn nondimensional_link_length_probability(
423 &self,
424 nondimensional_length: Scalar,
425 nondimensional_force: Scalar,
426 ) -> Result<Scalar, SingleChainError>;
427}
428
429pub trait Legendre
430where
431 Self: Isometric + Isotensional + SingleChain,
432{
433 fn nondimensional_helmholtz_free_energy(
437 &self,
438 nondimensional_extension: Scalar,
439 ) -> Result<Scalar, SingleChainError> {
440 let nondimensional_force = Legendre::nondimensional_force(self, nondimensional_extension)?;
441 Ok(
442 Isotensional::nondimensional_gibbs_free_energy(self, nondimensional_force)?
443 + self.number_of_links() as Scalar
444 * nondimensional_force
445 * nondimensional_extension,
446 )
447 }
448 fn nondimensional_helmholtz_free_energy_per_link(
452 &self,
453 nondimensional_extension: Scalar,
454 ) -> Result<Scalar, SingleChainError> {
455 Ok(
456 Legendre::nondimensional_helmholtz_free_energy(self, nondimensional_extension)?
457 / (self.number_of_links() as Scalar),
458 )
459 }
460 fn nondimensional_force(
464 &self,
465 nondimensional_extension: Scalar,
466 ) -> Result<Scalar, SingleChainError> {
467 (NewtonRaphson {
468 abs_tol: Tolerances {
469 constraint: 1e-10,
470 residual: 1e-10,
471 },
472 line_search: LineSearch::Error {
473 cut_back: 5e-1,
474 max_steps: 10,
475 },
476 linear_solver: Direct,
477 ..Default::default()
478 }
479 .minimize(
480 |&nondimensional_force| {
481 Ok(Isotensional::nondimensional_gibbs_free_energy_per_link(
482 self,
483 nondimensional_force,
484 )? - nondimensional_force * nondimensional_extension)
485 },
486 |&nondimensional_force| {
487 Ok(
488 Isotensional::nondimensional_extension(self, nondimensional_force)?
489 - nondimensional_extension,
490 )
491 },
492 |&nondimensional_force| {
493 Ok(Isotensional::nondimensional_compliance(
494 self,
495 nondimensional_force,
496 )?)
497 },
498 nondimensional_extension,
499 EqualityConstraint::None,
500 None,
501 ))
502 .map_err(|error| SingleChainError::upstream(error, self))
503 }
504 fn nondimensional_stiffness(
508 &self,
509 nondimensional_extension: Scalar,
510 ) -> Result<Scalar, SingleChainError> {
511 let nondimensional_force = Legendre::nondimensional_force(self, nondimensional_extension)?;
512 Ok(1.0 / Isotensional::nondimensional_compliance(self, nondimensional_force)?)
513 }
514 fn nondimensional_radial_distribution(
518 &self,
519 nondimensional_extension: Scalar,
520 ) -> Result<Scalar, SingleChainError> {
521 Ok(
522 Legendre::nondimensional_spherical_distribution(self, nondimensional_extension)?
523 * (4.0 * PI * nondimensional_extension.powi(2)),
524 )
525 }
526 fn nondimensional_spherical_distribution(
530 &self,
531 nondimensional_extension: Scalar,
532 ) -> Result<Scalar, SingleChainError> {
533 Ok(
534 Legendre::nondimensional_radial_distribution(self, nondimensional_extension)?
535 / (4.0 * PI * nondimensional_extension.powi(2)),
536 )
537 }
538 fn nondimensional_gibbs_free_energy(
542 &self,
543 nondimensional_force: Scalar,
544 ) -> Result<Scalar, SingleChainError> {
545 let nondimensional_extension =
546 Legendre::nondimensional_extension(self, nondimensional_force)?;
547 Ok(
548 Isometric::nondimensional_helmholtz_free_energy(self, nondimensional_extension)?
549 - self.number_of_links() as Scalar
550 * nondimensional_force
551 * nondimensional_extension,
552 )
553 }
554 fn nondimensional_gibbs_free_energy_per_link(
558 &self,
559 nondimensional_force: Scalar,
560 ) -> Result<Scalar, SingleChainError> {
561 Ok(
562 Legendre::nondimensional_gibbs_free_energy(self, nondimensional_force)?
563 / (self.number_of_links() as Scalar),
564 )
565 }
566 fn nondimensional_extension(
570 &self,
571 nondimensional_force: Scalar,
572 ) -> Result<Scalar, SingleChainError> {
573 (NewtonRaphson {
574 abs_tol: Tolerances {
575 constraint: 1e-10,
576 residual: 1e-10,
577 },
578 line_search: LineSearch::Error {
579 cut_back: 5e-1,
580 max_steps: 10,
581 },
582 linear_solver: Direct,
583 ..Default::default()
584 }
585 .minimize(
586 |&nondimensional_extension| {
587 Ok(Isometric::nondimensional_helmholtz_free_energy_per_link(
588 self,
589 nondimensional_extension,
590 )? - nondimensional_force * nondimensional_extension)
591 },
592 |&nondimensional_extension| {
593 Ok(
594 Isometric::nondimensional_force(self, nondimensional_extension)?
595 - nondimensional_force,
596 )
597 },
598 |&nondimensional_extension| {
599 Ok(Isometric::nondimensional_stiffness(
600 self,
601 nondimensional_extension,
602 )?)
603 },
604 nondimensional_force,
605 EqualityConstraint::None,
606 None,
607 ))
608 .map_err(|error| SingleChainError::upstream(error, self))
609 }
610 fn nondimensional_compliance(
614 &self,
615 nondimensional_force: Scalar,
616 ) -> Result<Scalar, SingleChainError> {
617 let nondimensional_extension =
618 Legendre::nondimensional_extension(self, nondimensional_force)?;
619 Ok(1.0 / Isometric::nondimensional_stiffness(self, nondimensional_extension)?)
620 }
621}
622
623pub trait MonteCarlo
624where
625 Self: SingleChain + Sync,
626{
627 fn cosine_moments(
628 &self,
629 nondimensional_force: Scalar,
630 number_of_samples: usize,
631 number_of_threads: usize,
632 ) -> (Vector, SquareMatrix, Vector, SquareMatrix) {
633 cosine_moments_reweighted(
634 self,
635 nondimensional_force,
636 number_of_samples,
637 number_of_threads,
638 )
639 }
640 fn nondimensional_longitudinal_extension(
641 &self,
642 nondimensional_force: Scalar,
643 number_of_samples: usize,
644 number_of_threads: usize,
645 ) -> Scalar {
646 nondimensional_longitudinal_extension_reweighted(
653 self,
654 nondimensional_force,
655 number_of_samples,
656 number_of_threads,
657 )
658 }
659 fn random_nondimensional_link_vectors(&self, nondimensional_force: Scalar) -> Configuration;
660 fn random_configuration(&self, nondimensional_force: Scalar) -> Configuration {
661 let mut position = TensorRank1::<3, Current>::zero();
662 self.random_nondimensional_link_vectors(nondimensional_force)
663 .into_iter()
664 .map(|displacement| {
665 position += displacement;
666 position.clone()
667 })
668 .collect()
669 }
670}
671
672pub trait MonteCarloExtensible
673where
674 Self: Extensible + MonteCarlo,
675{
676 fn nondimensional_lateral_distribution(
677 &self,
678 nondimensional_force: Scalar,
679 num_bins: usize,
680 number_of_samples: usize,
681 number_of_threads: usize,
682 maximum_nondimensional_extension: Scalar,
683 ) -> (Vector, Vector) {
684 nondimensional_lateral_distribution(
685 self,
686 nondimensional_force,
687 num_bins,
688 number_of_samples,
689 number_of_threads,
690 maximum_nondimensional_extension,
691 )
692 }
693 fn nondimensional_longitudinal_distribution(
694 &self,
695 nondimensional_force: Scalar,
696 num_bins: usize,
697 number_of_samples: usize,
698 number_of_threads: usize,
699 maximum_nondimensional_extension: Scalar,
700 ) -> (Vector, Vector) {
701 nondimensional_longitudinal_distribution(
702 self,
703 nondimensional_force,
704 num_bins,
705 number_of_samples,
706 number_of_threads,
707 maximum_nondimensional_extension,
708 )
709 }
710 fn nondimensional_radial_distribution(
711 &self,
712 nondimensional_force: Scalar,
713 num_bins: usize,
714 number_of_samples: usize,
715 number_of_threads: usize,
716 maximum_nondimensional_extension: Scalar,
717 ) -> (Vector, Vector) {
718 nondimensional_radial_distribution(
719 self,
720 nondimensional_force,
721 num_bins,
722 number_of_samples,
723 number_of_threads,
724 maximum_nondimensional_extension,
725 )
726 }
727 fn nondimensional_transverse_distribution(
728 &self,
729 nondimensional_force: Scalar,
730 num_bins: usize,
731 number_of_samples: usize,
732 number_of_threads: usize,
733 maximum_nondimensional_extension: Scalar,
734 ) -> (Vector, Vector) {
735 nondimensional_transverse_distribution(
736 self,
737 nondimensional_force,
738 num_bins,
739 number_of_samples,
740 number_of_threads,
741 maximum_nondimensional_extension,
742 )
743 }
744}
745
746impl<T> MonteCarloExtensible for T where T: Extensible + MonteCarlo {}
747
748pub trait MonteCarloInextensible
749where
750 Self: Inextensible + MonteCarlo,
751{
752 fn nondimensional_angular_distribution(
753 &self,
754 nondimensional_force: Scalar,
755 num_bins: usize,
756 number_of_samples: usize,
757 number_of_threads: usize,
758 ) -> (Vector, Vector) {
759 nondimensional_angular_distribution(
760 self,
761 nondimensional_force,
762 num_bins,
763 number_of_samples,
764 number_of_threads,
765 self.maximum_nondimensional_extension(),
766 )
767 }
768 fn nondimensional_lateral_distribution(
769 &self,
770 nondimensional_force: Scalar,
771 num_bins: usize,
772 number_of_samples: usize,
773 number_of_threads: usize,
774 ) -> (Vector, Vector) {
775 nondimensional_lateral_distribution(
776 self,
777 nondimensional_force,
778 num_bins,
779 number_of_samples,
780 number_of_threads,
781 self.maximum_nondimensional_extension(),
782 )
783 }
784 fn nondimensional_longitudinal_distribution(
785 &self,
786 nondimensional_force: Scalar,
787 num_bins: usize,
788 number_of_samples: usize,
789 number_of_threads: usize,
790 ) -> (Vector, Vector) {
791 nondimensional_longitudinal_distribution(
792 self,
793 nondimensional_force,
794 num_bins,
795 number_of_samples,
796 number_of_threads,
797 self.maximum_nondimensional_extension(),
798 )
799 }
800 fn nondimensional_radial_distribution(
801 &self,
802 nondimensional_force: Scalar,
803 num_bins: usize,
804 number_of_samples: usize,
805 number_of_threads: usize,
806 ) -> (Vector, Vector) {
807 nondimensional_radial_distribution(
808 self,
809 nondimensional_force,
810 num_bins,
811 number_of_samples,
812 number_of_threads,
813 self.maximum_nondimensional_extension(),
814 )
815 }
816 fn nondimensional_transverse_distribution(
817 &self,
818 nondimensional_force: Scalar,
819 num_bins: usize,
820 number_of_samples: usize,
821 number_of_threads: usize,
822 ) -> (Vector, Vector) {
823 nondimensional_transverse_distribution(
824 self,
825 nondimensional_force,
826 num_bins,
827 number_of_samples,
828 number_of_threads,
829 self.maximum_nondimensional_extension(),
830 )
831 }
832}
833
834impl<T> MonteCarloInextensible for T where T: Inextensible + MonteCarlo {}
835
836pub(super) fn cosine_moments_reweighted<T: MonteCarlo>(
837 model: &T,
838 nondimensional_force: Scalar,
839 number_of_samples: usize,
840 number_of_threads: usize,
841) -> (Vector, SquareMatrix, Vector, SquareMatrix) {
842 let base = number_of_samples / number_of_threads;
843 let remainder = number_of_samples % number_of_threads;
844 scope(|s| {
845 (0..number_of_threads)
846 .map(|t| {
847 s.spawn(move || {
848 cosine_moments_reweighted_inner(
849 model,
850 nondimensional_force,
851 base + usize::from(t < remainder),
852 )
853 })
854 })
855 .collect::<Vec<_>>()
856 .into_iter()
857 .map(|handle| handle.join().unwrap())
858 .reduce(|mut acc, (x_max, z_scaled, cos1_scaled, cos1cos1_scaled, cos2_scaled, cos2cos1_scaled)| {
859 let x_max_new = acc.0.max(x_max);
860 let scale_acc = (acc.0 - x_max_new).exp();
861 let scale_new = (x_max - x_max_new).exp();
862 acc.1 = acc.1 * scale_acc + z_scaled * scale_new;
863 acc.2 = acc.2 * scale_acc + cos1_scaled * scale_new;
864 acc.3 = acc.3 * scale_acc + cos1cos1_scaled * scale_new;
865 acc.4 = acc.4 * scale_acc + cos2_scaled * scale_new;
866 acc.5 = acc.5 * scale_acc + cos2cos1_scaled * scale_new;
867 acc.0 = x_max_new;
868 acc
869 })
870 .map(|(_x_max, z_scaled, cos1_scaled, cos1cos1_scaled, cos2_scaled, cos2cos1_scaled)| {
871 let z_inv = 1.0 / z_scaled;
872 (
873 cos1_scaled * z_inv,
874 cos1cos1_scaled * z_inv,
875 cos2_scaled * z_inv,
876 cos2cos1_scaled * z_inv,
877 )
878 })
879 .unwrap()
880 })
881}
882
883fn cosine_moments_reweighted_inner<T: MonteCarlo>(
884 model: &T,
885 nondimensional_force: Scalar,
886 number_of_samples: usize,
887) -> (Scalar, Scalar, Vector, SquareMatrix, Vector, SquareMatrix) {
888 let num_links = model.number_of_links() as usize;
889
890 let mut x_max = Scalar::NEG_INFINITY;
891 let mut z_scaled = 0.0;
892 let mut cos1_scaled = Vector::zero(num_links);
893 let mut cos1cos1_scaled = SquareMatrix::zero(num_links);
894 let mut cos2_scaled = Vector::zero(num_links);
895 let mut cos2cos1_scaled = SquareMatrix::zero(num_links);
896
897 for _ in 0..number_of_samples {
898 let links = model.random_nondimensional_link_vectors(0.0);
899
900 let cosines: Vec<Scalar> = links
901 .iter()
902 .map(|link| link[2].value() / link.norm().value())
903 .collect();
904
905 let sum_cos: Scalar = cosines.iter().sum();
906 let x = nondimensional_force * sum_cos;
907
908 if x > x_max {
909 let scale = if x_max.is_finite() {
910 (x_max - x).exp()
911 } else {
912 0.0
913 };
914 z_scaled *= scale;
915 cos1_scaled *= scale;
916 cos1cos1_scaled *= scale;
917 cos2_scaled *= scale;
918 cos2cos1_scaled *= scale;
919 x_max = x;
920 }
921
922 let w = (x - x_max).exp();
923
924 z_scaled += w;
925
926 for i in 0..num_links {
927 let ci = cosines[i];
928 let ci2 = ci * ci;
929
930 cos1_scaled[i] += ci * w;
931 cos2_scaled[i] += ci2 * w;
932
933 for j in 0..num_links {
934 let cj = cosines[j];
935 cos1cos1_scaled[i][j] += ci * cj * w;
936 cos2cos1_scaled[i][j] += ci2 * cj * w;
937 }
938 }
939 }
940 (
941 x_max,
942 z_scaled,
943 cos1_scaled,
944 cos1cos1_scaled,
945 cos2_scaled,
946 cos2cos1_scaled,
947 )
948}
949
950fn nondimensional_longitudinal_extension_reweighted<T: MonteCarlo>(
951 model: &T,
952 nondimensional_force: Scalar,
953 number_of_samples: usize,
954 number_of_threads: usize,
955) -> Scalar {
956 let base = number_of_samples / number_of_threads;
957 let remainder = number_of_samples % number_of_threads;
958
959 scope(|s| {
960 (0..number_of_threads)
961 .map(|t| {
962 s.spawn(move || {
963 nondimensional_longitudinal_extension_reweighted_inner(
964 model,
965 nondimensional_force,
966 base + usize::from(t < remainder),
967 )
968 })
969 })
970 .collect::<Vec<_>>()
971 .into_iter()
972 .map(|handle| handle.join().unwrap())
973 .reduce(|mut acc, (x_max, z_scaled, ext_scaled)| {
974 let x_max_new = acc.0.max(x_max);
975 let scale_acc = (acc.0 - x_max_new).exp();
976 let scale_new = (x_max - x_max_new).exp();
977
978 acc.1 = acc.1 * scale_acc + z_scaled * scale_new;
979 acc.2 = acc.2 * scale_acc + ext_scaled * scale_new;
980 acc.0 = x_max_new;
981 acc
982 })
983 .map(|(_x_max, z_scaled, ext_scaled)| {
984 ext_scaled / z_scaled / model.number_of_links() as Scalar
985 })
986 .unwrap()
987 })
988}
989
990fn nondimensional_longitudinal_extension_reweighted_inner<T: MonteCarlo>(
991 model: &T,
992 nondimensional_force: Scalar,
993 number_of_samples: usize,
994) -> (Scalar, Scalar, Scalar) {
995 let mut x_max = Scalar::NEG_INFINITY;
996 let mut z_scaled = 0.0;
997 let mut ext_scaled = 0.0;
998
999 for _ in 0..number_of_samples {
1000 let links = model.random_nondimensional_link_vectors(0.0);
1001
1002 let extension_sum: Scalar = links.iter().map(|link| link[2].value()).sum();
1003 let x = nondimensional_force * extension_sum;
1004
1005 if x > x_max {
1006 let scale = if x_max.is_finite() {
1007 (x_max - x).exp()
1008 } else {
1009 0.0
1010 };
1011 z_scaled *= scale;
1012 ext_scaled *= scale;
1013 x_max = x;
1014 }
1015
1016 let w = (x - x_max).exp();
1017 z_scaled += w;
1018 ext_scaled += extension_sum * w;
1019 }
1020
1021 (x_max, z_scaled, ext_scaled)
1022}
1023
1024fn nondimensional_angular_distribution<T: MonteCarlo>(
1025 model: &T,
1026 nondimensional_force: Scalar,
1027 number_of_bins: usize,
1028 number_of_samples: usize,
1029 number_of_threads: usize,
1030 maximum_nondimensional_extension: Scalar,
1031) -> (Vector, Vector) {
1032 let base = number_of_samples / number_of_threads;
1033 let remainder = number_of_samples % number_of_threads;
1034 scope(|s| {
1035 let mut total_counts = vec![0; number_of_bins];
1036 (0..number_of_threads)
1037 .map(|t| {
1038 s.spawn(move || {
1039 nondimensional_angular_distribution_inner(
1040 model,
1041 nondimensional_force,
1042 number_of_bins,
1043 base + usize::from(t < remainder),
1044 maximum_nondimensional_extension,
1045 )
1046 })
1047 })
1048 .collect::<Vec<_>>()
1049 .into_iter()
1050 .for_each(|handle| {
1051 total_counts
1052 .iter_mut()
1053 .zip(handle.join().unwrap())
1054 .for_each(|(tot, c)| *tot += c)
1055 });
1056 let bin_width = 2.0 * maximum_nondimensional_extension / (number_of_bins as Scalar);
1057 let bin_centers = (0..number_of_bins)
1058 .map(|i| -maximum_nondimensional_extension + (i as Scalar + 0.5) * bin_width)
1059 .collect();
1060 let total_samples = number_of_samples as Scalar;
1061 let bin_values = total_counts
1062 .into_iter()
1063 .map(|count| count as Scalar / total_samples / bin_width)
1064 .collect();
1065 (bin_centers, bin_values)
1066 })
1067}
1068
1069fn nondimensional_lateral_distribution<T: MonteCarlo>(
1070 model: &T,
1071 nondimensional_force: Scalar,
1072 number_of_bins: usize,
1073 number_of_samples: usize,
1074 number_of_threads: usize,
1075 maximum_nondimensional_extension: Scalar,
1076) -> (Vector, Vector) {
1077 let base = number_of_samples / number_of_threads;
1078 let remainder = number_of_samples % number_of_threads;
1079 scope(|s| {
1080 let mut total_counts = vec![0; number_of_bins];
1081 (0..number_of_threads)
1082 .map(|t| {
1083 s.spawn(move || {
1084 nondimensional_lateral_distribution_inner(
1085 model,
1086 nondimensional_force,
1087 number_of_bins,
1088 base + usize::from(t < remainder),
1089 maximum_nondimensional_extension,
1090 )
1091 })
1092 })
1093 .collect::<Vec<_>>()
1094 .into_iter()
1095 .for_each(|handle| {
1096 total_counts
1097 .iter_mut()
1098 .zip(handle.join().unwrap())
1099 .for_each(|(tot, c)| *tot += c)
1100 });
1101 let bin_width = 2.0 * maximum_nondimensional_extension / (number_of_bins as Scalar);
1102 let bin_centers = (0..number_of_bins)
1103 .map(|i| -maximum_nondimensional_extension + (i as Scalar + 0.5) * bin_width)
1104 .collect();
1105 let total_samples = number_of_samples as Scalar;
1106 let bin_values = total_counts
1107 .into_iter()
1108 .map(|count| count as Scalar / total_samples / bin_width)
1109 .collect();
1110 (bin_centers, bin_values)
1111 })
1112}
1113
1114fn nondimensional_longitudinal_distribution<T: MonteCarlo>(
1115 model: &T,
1116 nondimensional_force: Scalar,
1117 number_of_bins: usize,
1118 number_of_samples: usize,
1119 number_of_threads: usize,
1120 maximum_nondimensional_extension: Scalar,
1121) -> (Vector, Vector) {
1122 let base = number_of_samples / number_of_threads;
1123 let remainder = number_of_samples % number_of_threads;
1124 scope(|s| {
1125 let mut total_counts = vec![0; number_of_bins];
1126 (0..number_of_threads)
1127 .map(|t| {
1128 s.spawn(move || {
1129 nondimensional_longitudinal_distribution_inner(
1130 model,
1131 nondimensional_force,
1132 number_of_bins,
1133 base + usize::from(t < remainder),
1134 maximum_nondimensional_extension,
1135 )
1136 })
1137 })
1138 .collect::<Vec<_>>()
1139 .into_iter()
1140 .for_each(|handle| {
1141 total_counts
1142 .iter_mut()
1143 .zip(handle.join().unwrap())
1144 .for_each(|(tot, c)| *tot += c)
1145 });
1146 let bin_width = 2.0 * maximum_nondimensional_extension / (number_of_bins as Scalar);
1147 let bin_centers = (0..number_of_bins)
1148 .map(|i| -maximum_nondimensional_extension + (i as Scalar + 0.5) * bin_width)
1149 .collect();
1150 let total_samples = number_of_samples as Scalar;
1151 let bin_values = total_counts
1152 .into_iter()
1153 .map(|count| count as Scalar / total_samples / bin_width)
1154 .collect();
1155 (bin_centers, bin_values)
1156 })
1157}
1158
1159fn nondimensional_radial_distribution<T: MonteCarlo>(
1160 model: &T,
1161 nondimensional_force: Scalar,
1162 number_of_bins: usize,
1163 number_of_samples: usize,
1164 number_of_threads: usize,
1165 maximum_nondimensional_extension: Scalar,
1166) -> (Vector, Vector) {
1167 let base = number_of_samples / number_of_threads;
1168 let remainder = number_of_samples % number_of_threads;
1169 scope(|s| {
1170 let mut total_counts = vec![0; number_of_bins];
1171 (0..number_of_threads)
1172 .map(|t| {
1173 s.spawn(move || {
1174 nondimensional_radial_distribution_inner(
1175 model,
1176 nondimensional_force,
1177 number_of_bins,
1178 base + usize::from(t < remainder),
1179 maximum_nondimensional_extension,
1180 )
1181 })
1182 })
1183 .collect::<Vec<_>>()
1184 .into_iter()
1185 .for_each(|handle| {
1186 total_counts
1187 .iter_mut()
1188 .zip(handle.join().unwrap())
1189 .for_each(|(tot, c)| *tot += c)
1190 });
1191 let bin_width = maximum_nondimensional_extension / (number_of_bins as Scalar);
1192 let bin_centers = (0..number_of_bins)
1193 .map(|i| (i as Scalar + 0.5) * bin_width)
1194 .collect();
1195 let total_samples = number_of_samples as Scalar;
1196 let bin_values = total_counts
1197 .into_iter()
1198 .map(|count| count as Scalar / total_samples / bin_width)
1199 .collect();
1200 (bin_centers, bin_values)
1201 })
1202}
1203
1204fn nondimensional_transverse_distribution<T: MonteCarlo>(
1205 model: &T,
1206 nondimensional_force: Scalar,
1207 number_of_bins: usize,
1208 number_of_samples: usize,
1209 number_of_threads: usize,
1210 maximum_nondimensional_extension: Scalar,
1211) -> (Vector, Vector) {
1212 let base = number_of_samples / number_of_threads;
1213 let remainder = number_of_samples % number_of_threads;
1214 scope(|s| {
1215 let mut total_counts = vec![0; number_of_bins];
1216 (0..number_of_threads)
1217 .map(|t| {
1218 s.spawn(move || {
1219 nondimensional_transverse_distribution_inner(
1220 model,
1221 nondimensional_force,
1222 number_of_bins,
1223 base + usize::from(t < remainder),
1224 maximum_nondimensional_extension,
1225 )
1226 })
1227 })
1228 .collect::<Vec<_>>()
1229 .into_iter()
1230 .for_each(|handle| {
1231 total_counts
1232 .iter_mut()
1233 .zip(handle.join().unwrap())
1234 .for_each(|(tot, c)| *tot += c)
1235 });
1236 let bin_width = maximum_nondimensional_extension / (number_of_bins as Scalar);
1237 let bin_centers = (0..number_of_bins)
1238 .map(|i| (i as Scalar + 0.5) * bin_width)
1239 .collect();
1240 let total_samples = number_of_samples as Scalar;
1241 let bin_values = total_counts
1242 .into_iter()
1243 .map(|count| count as Scalar / total_samples / bin_width)
1244 .collect();
1245 (bin_centers, bin_values)
1246 })
1247}
1248
1249fn nondimensional_angular_distribution_inner<T: MonteCarlo>(
1250 model: &T,
1251 nondimensional_force: Scalar,
1252 num_bins: usize,
1253 number_of_samples: usize,
1254 maximum_nondimensional_extension: Scalar,
1255) -> Vec<usize> {
1256 let mut bin_counts = vec![0; num_bins];
1257 let end_index = model.number_of_links() as usize - 1;
1258 for _ in 0..number_of_samples {
1259 let configuration = model.random_configuration(nondimensional_force);
1260 let gamma = configuration[end_index].norm().value();
1261 let nondimensional_extension = if gamma == 0.0 {
1262 0.0
1263 } else {
1264 configuration[end_index][2].value() / gamma
1265 };
1266 if nondimensional_extension.abs() > maximum_nondimensional_extension {
1267 panic!(
1268 "Sample {nondimensional_extension} outside [-{maximum_nondimensional_extension}, {maximum_nondimensional_extension}]"
1269 )
1270 }
1271 let bin_index = ((nondimensional_extension + maximum_nondimensional_extension)
1272 / (2.0 * maximum_nondimensional_extension)
1273 * num_bins as Scalar) as usize;
1274 bin_counts[bin_index] += 1;
1275 }
1276 bin_counts
1277}
1278
1279fn nondimensional_lateral_distribution_inner<T: MonteCarlo>(
1280 model: &T,
1281 nondimensional_force: Scalar,
1282 num_bins: usize,
1283 number_of_samples: usize,
1284 maximum_nondimensional_extension: Scalar,
1285) -> Vec<usize> {
1286 let mut bin_counts = vec![0; num_bins];
1287 let num_links = model.number_of_links() as Scalar;
1288 let end_index = model.number_of_links() as usize - 1;
1289 for _ in 0..number_of_samples {
1290 let configuration = model.random_configuration(nondimensional_force);
1291 let nondimensional_extension = configuration[end_index][1].value() / num_links;
1292 if nondimensional_extension.abs() > maximum_nondimensional_extension {
1293 panic!(
1294 "Sample {nondimensional_extension} outside [-{maximum_nondimensional_extension}, {maximum_nondimensional_extension}]"
1295 )
1296 }
1297 let bin_index = ((nondimensional_extension + maximum_nondimensional_extension)
1298 / (2.0 * maximum_nondimensional_extension)
1299 * num_bins as Scalar) as usize;
1300 bin_counts[bin_index] += 1;
1301 }
1302 bin_counts
1303}
1304
1305fn nondimensional_longitudinal_distribution_inner<T: MonteCarlo>(
1306 model: &T,
1307 nondimensional_force: Scalar,
1308 num_bins: usize,
1309 number_of_samples: usize,
1310 maximum_nondimensional_extension: Scalar,
1311) -> Vec<usize> {
1312 let mut bin_counts = vec![0; num_bins];
1313 let num_links = model.number_of_links() as Scalar;
1314 let end_index = model.number_of_links() as usize - 1;
1315 for _ in 0..number_of_samples {
1316 let configuration = model.random_configuration(nondimensional_force);
1317 let nondimensional_extension = configuration[end_index][2].value() / num_links;
1318 if nondimensional_extension.abs() > maximum_nondimensional_extension {
1319 panic!(
1320 "Sample {nondimensional_extension} outside [-{maximum_nondimensional_extension}, {maximum_nondimensional_extension}]"
1321 )
1322 }
1323 let bin_index = ((nondimensional_extension + maximum_nondimensional_extension)
1324 / (2.0 * maximum_nondimensional_extension)
1325 * num_bins as Scalar) as usize;
1326 bin_counts[bin_index] += 1;
1327 }
1328 bin_counts
1329}
1330
1331fn nondimensional_radial_distribution_inner<T: MonteCarlo>(
1332 model: &T,
1333 nondimensional_force: Scalar,
1334 num_bins: usize,
1335 number_of_samples: usize,
1336 maximum_nondimensional_extension: Scalar,
1337) -> Vec<usize> {
1338 let mut bin_counts = vec![0; num_bins];
1339 let num_links = model.number_of_links() as Scalar;
1340 let end_index = model.number_of_links() as usize - 1;
1341 for _ in 0..number_of_samples {
1342 let configuration = model.random_configuration(nondimensional_force);
1343 let nondimensional_extension = configuration[end_index].norm().value() / num_links;
1344 if nondimensional_extension > maximum_nondimensional_extension {
1345 panic!(
1346 "Sample {nondimensional_extension} above maximum {maximum_nondimensional_extension}"
1347 )
1348 }
1349 let bin_index = (nondimensional_extension / maximum_nondimensional_extension
1350 * num_bins as Scalar) as usize;
1351 bin_counts[bin_index] += 1;
1352 }
1353 bin_counts
1354}
1355
1356fn nondimensional_transverse_distribution_inner<T: MonteCarlo>(
1357 model: &T,
1358 nondimensional_force: Scalar,
1359 num_bins: usize,
1360 number_of_samples: usize,
1361 maximum_nondimensional_extension: Scalar,
1362) -> Vec<usize> {
1363 let mut bin_counts = vec![0; num_bins];
1364 let num_links = model.number_of_links() as Scalar;
1365 let end_index = model.number_of_links() as usize - 1;
1366 for _ in 0..number_of_samples {
1367 let configuration = model.random_configuration(nondimensional_force);
1368 let nondimensional_extension = (configuration[end_index][0].value().powi(2)
1369 + configuration[end_index][1].value().powi(2))
1370 .sqrt()
1371 / num_links;
1372 if nondimensional_extension.abs() > maximum_nondimensional_extension {
1373 panic!(
1374 "Sample {nondimensional_extension} outside [-{maximum_nondimensional_extension}, {maximum_nondimensional_extension}]"
1375 )
1376 }
1377 let bin_index = (nondimensional_extension / maximum_nondimensional_extension
1378 * num_bins as Scalar) as usize;
1379 bin_counts[bin_index] += 1;
1380 }
1381 bin_counts
1382}