1use crate::{
2 math::{
3 Matrix, Scalar, Tensor, TensorArray, Vector,
4 optimize::{EqualityConstraint, LineSearch, NewtonRaphson, SecondOrderOptimization},
5 },
6 mechanics::{Coordinates, CurrentCoordinate},
7 physics::molecular::single_chain::{Extensible, Inextensible, SingleChain, SingleChainError},
8};
9use std::{f64::consts::PI, thread::scope};
10
11pub type Configuration = Coordinates<1>;
12
13#[derive(Clone, Copy, Debug)]
14pub enum Ensemble {
15 Isometric(Scalar),
16 Isotensional(Scalar),
17}
18
19pub trait Thermodynamics
20where
21 Self: Isometric + Isotensional + Legendre + SingleChain,
22{
23 fn ensemble(&self) -> Ensemble;
24 fn temperature(&self) -> Scalar {
25 match self.ensemble() {
26 Ensemble::Isometric(temperature) => temperature,
27 Ensemble::Isotensional(temperature) => temperature,
28 }
29 }
30 fn nondimensional_helmholtz_free_energy(
31 &self,
32 nondimensional_extension: Scalar,
33 ) -> Result<Scalar, SingleChainError> {
34 match self.ensemble() {
35 Ensemble::Isometric(_) => {
36 Isometric::nondimensional_helmholtz_free_energy(self, nondimensional_extension)
37 }
38 Ensemble::Isotensional(_) => {
39 Legendre::nondimensional_helmholtz_free_energy(self, nondimensional_extension)
40 }
41 }
42 }
43 fn nondimensional_helmholtz_free_energy_per_link(
44 &self,
45 nondimensional_extension: Scalar,
46 ) -> Result<Scalar, SingleChainError> {
47 match self.ensemble() {
48 Ensemble::Isometric(_) => Isometric::nondimensional_helmholtz_free_energy_per_link(
49 self,
50 nondimensional_extension,
51 ),
52 Ensemble::Isotensional(_) => Legendre::nondimensional_helmholtz_free_energy_per_link(
53 self,
54 nondimensional_extension,
55 ),
56 }
57 }
58 fn nondimensional_force(
59 &self,
60 nondimensional_extension: Scalar,
61 ) -> Result<Scalar, SingleChainError> {
62 match self.ensemble() {
63 Ensemble::Isometric(_) => {
64 Isometric::nondimensional_force(self, nondimensional_extension)
65 }
66 Ensemble::Isotensional(_) => {
67 Legendre::nondimensional_force(self, nondimensional_extension)
68 }
69 }
70 }
71 fn nondimensional_stiffness(
72 &self,
73 nondimensional_extension: Scalar,
74 ) -> Result<Scalar, SingleChainError> {
75 match self.ensemble() {
76 Ensemble::Isometric(_) => {
77 Isometric::nondimensional_stiffness(self, nondimensional_extension)
78 }
79 Ensemble::Isotensional(_) => {
80 Legendre::nondimensional_stiffness(self, nondimensional_extension)
81 }
82 }
83 }
84 fn nondimensional_radial_distribution(
85 &self,
86 nondimensional_extension: Scalar,
87 ) -> Result<Scalar, SingleChainError> {
88 match self.ensemble() {
89 Ensemble::Isometric(_) => {
90 Isometric::nondimensional_radial_distribution(self, nondimensional_extension)
91 }
92 Ensemble::Isotensional(_) => {
93 Legendre::nondimensional_radial_distribution(self, nondimensional_extension)
94 }
95 }
96 }
97 fn nondimensional_spherical_distribution(
98 &self,
99 nondimensional_extension: Scalar,
100 ) -> Result<Scalar, SingleChainError> {
101 match self.ensemble() {
102 Ensemble::Isometric(_) => {
103 Isometric::nondimensional_spherical_distribution(self, nondimensional_extension)
104 }
105 Ensemble::Isotensional(_) => {
106 Legendre::nondimensional_spherical_distribution(self, nondimensional_extension)
107 }
108 }
109 }
110 fn nondimensional_gibbs_free_energy(
111 &self,
112 nondimensional_force: Scalar,
113 ) -> Result<Scalar, SingleChainError> {
114 match self.ensemble() {
115 Ensemble::Isometric(_) => {
116 Legendre::nondimensional_gibbs_free_energy(self, nondimensional_force)
117 }
118 Ensemble::Isotensional(_) => {
119 Isotensional::nondimensional_gibbs_free_energy(self, nondimensional_force)
120 }
121 }
122 }
123 fn nondimensional_gibbs_free_energy_per_link(
124 &self,
125 nondimensional_force: Scalar,
126 ) -> Result<Scalar, SingleChainError> {
127 match self.ensemble() {
128 Ensemble::Isometric(_) => {
129 Legendre::nondimensional_gibbs_free_energy_per_link(self, nondimensional_force)
130 }
131 Ensemble::Isotensional(_) => {
132 Isotensional::nondimensional_gibbs_free_energy_per_link(self, nondimensional_force)
133 }
134 }
135 }
136 fn nondimensional_extension(
137 &self,
138 nondimensional_force: Scalar,
139 ) -> Result<Scalar, SingleChainError> {
140 match self.ensemble() {
141 Ensemble::Isometric(_) => {
142 Legendre::nondimensional_extension(self, nondimensional_force)
143 }
144 Ensemble::Isotensional(_) => {
145 Isotensional::nondimensional_extension(self, nondimensional_force)
146 }
147 }
148 }
149 fn nondimensional_compliance(
150 &self,
151 nondimensional_force: Scalar,
152 ) -> Result<Scalar, SingleChainError> {
153 match self.ensemble() {
154 Ensemble::Isometric(_) => {
155 Legendre::nondimensional_compliance(self, nondimensional_force)
156 }
157 Ensemble::Isotensional(_) => {
158 Isotensional::nondimensional_compliance(self, nondimensional_force)
159 }
160 }
161 }
162}
163
164pub trait ThermodynamicsExtensible
165where
166 Self: IsotensionalExtensible + Thermodynamics,
167{
168 fn nondimensional_link_energy_average(
169 &self,
170 nondimensional_force: Scalar,
171 ) -> Result<Scalar, SingleChainError> {
172 match self.ensemble() {
173 Ensemble::Isometric(_) => {
174 unimplemented!()
175 }
176 Ensemble::Isotensional(_) => {
177 IsotensionalExtensible::nondimensional_link_energy_average(
178 self,
179 nondimensional_force,
180 )
181 }
182 }
183 }
184 fn nondimensional_link_energy_variance(
185 &self,
186 nondimensional_force: Scalar,
187 ) -> Result<Scalar, SingleChainError> {
188 match self.ensemble() {
189 Ensemble::Isometric(_) => {
190 unimplemented!()
191 }
192 Ensemble::Isotensional(_) => {
193 IsotensionalExtensible::nondimensional_link_energy_variance(
194 self,
195 nondimensional_force,
196 )
197 }
198 }
199 }
200 fn nondimensional_link_energy_probability(
201 &self,
202 nondimensional_energy: Scalar,
203 nondimensional_force: Scalar,
204 ) -> Result<Scalar, SingleChainError> {
205 match self.ensemble() {
206 Ensemble::Isometric(_) => {
207 unimplemented!()
208 }
209 Ensemble::Isotensional(_) => {
210 IsotensionalExtensible::nondimensional_link_energy_probability(
211 self,
212 nondimensional_energy,
213 nondimensional_force,
214 )
215 }
216 }
217 }
218 fn nondimensional_link_length_average(
219 &self,
220 nondimensional_force: Scalar,
221 ) -> Result<Scalar, SingleChainError> {
222 match self.ensemble() {
223 Ensemble::Isometric(_) => {
224 unimplemented!()
225 }
226 Ensemble::Isotensional(_) => {
227 IsotensionalExtensible::nondimensional_link_length_average(
228 self,
229 nondimensional_force,
230 )
231 }
232 }
233 }
234 fn nondimensional_link_length_variance(
235 &self,
236 nondimensional_force: Scalar,
237 ) -> Result<Scalar, SingleChainError> {
238 match self.ensemble() {
239 Ensemble::Isometric(_) => {
240 unimplemented!()
241 }
242 Ensemble::Isotensional(_) => {
243 IsotensionalExtensible::nondimensional_link_length_variance(
244 self,
245 nondimensional_force,
246 )
247 }
248 }
249 }
250 fn nondimensional_link_length_probability(
251 &self,
252 nondimensional_length: Scalar,
253 nondimensional_force: Scalar,
254 ) -> Result<Scalar, SingleChainError> {
255 match self.ensemble() {
256 Ensemble::Isometric(_) => {
257 unimplemented!()
258 }
259 Ensemble::Isotensional(_) => {
260 IsotensionalExtensible::nondimensional_link_length_probability(
261 self,
262 nondimensional_length,
263 nondimensional_force,
264 )
265 }
266 }
267 }
268}
269
270pub trait Isometric
271where
272 Self: SingleChain,
273{
274 fn nondimensional_helmholtz_free_energy(
278 &self,
279 nondimensional_extension: Scalar,
280 ) -> Result<Scalar, SingleChainError> {
281 Ok(
282 self.nondimensional_helmholtz_free_energy_per_link(nondimensional_extension)?
283 * (self.number_of_links() as Scalar),
284 )
285 }
286 fn nondimensional_helmholtz_free_energy_per_link(
290 &self,
291 nondimensional_extension: Scalar,
292 ) -> Result<Scalar, SingleChainError> {
293 Ok(
294 self.nondimensional_helmholtz_free_energy(nondimensional_extension)?
295 / (self.number_of_links() as Scalar),
296 )
297 }
298 fn nondimensional_force(
302 &self,
303 nondimensional_extension: Scalar,
304 ) -> Result<Scalar, SingleChainError>;
305 fn nondimensional_stiffness(
309 &self,
310 nondimensional_extension: Scalar,
311 ) -> Result<Scalar, SingleChainError>;
312 fn nondimensional_radial_distribution(
316 &self,
317 nondimensional_extension: Scalar,
318 ) -> Result<Scalar, SingleChainError> {
319 Ok(
320 self.nondimensional_spherical_distribution(nondimensional_extension)?
321 * (4.0 * PI * nondimensional_extension.powi(2)),
322 )
323 }
324 fn nondimensional_spherical_distribution(
328 &self,
329 nondimensional_extension: Scalar,
330 ) -> Result<Scalar, SingleChainError>;
331}
332
333pub trait Isotensional
334where
335 Self: SingleChain,
336{
337 fn nondimensional_gibbs_free_energy(
341 &self,
342 nondimensional_force: Scalar,
343 ) -> Result<Scalar, SingleChainError> {
344 Ok(
345 self.nondimensional_gibbs_free_energy_per_link(nondimensional_force)?
346 * (self.number_of_links() as Scalar),
347 )
348 }
349 fn nondimensional_gibbs_free_energy_per_link(
353 &self,
354 nondimensional_force: Scalar,
355 ) -> Result<Scalar, SingleChainError> {
356 Ok(self.nondimensional_gibbs_free_energy(nondimensional_force)?
357 / (self.number_of_links() as Scalar))
358 }
359 fn nondimensional_extension(
363 &self,
364 nondimensional_force: Scalar,
365 ) -> Result<Scalar, SingleChainError>;
366 fn nondimensional_compliance(
370 &self,
371 nondimensional_force: Scalar,
372 ) -> Result<Scalar, SingleChainError>;
373}
374
375pub trait IsotensionalExtensible
376where
377 Self: Extensible + Isotensional,
378{
379 fn nondimensional_link_energy_average(
383 &self,
384 nondimensional_force: Scalar,
385 ) -> Result<Scalar, SingleChainError>;
386 fn nondimensional_link_energy_variance(
390 &self,
391 nondimensional_force: Scalar,
392 ) -> Result<Scalar, SingleChainError>;
393 fn nondimensional_link_energy_probability(
397 &self,
398 nondimensional_energy: Scalar,
399 nondimensional_force: Scalar,
400 ) -> Result<Scalar, SingleChainError>;
401 fn nondimensional_link_length_average(
405 &self,
406 nondimensional_force: Scalar,
407 ) -> Result<Scalar, SingleChainError>;
408 fn nondimensional_link_length_variance(
412 &self,
413 nondimensional_force: Scalar,
414 ) -> Result<Scalar, SingleChainError>;
415 fn nondimensional_link_length_probability(
419 &self,
420 nondimensional_length: Scalar,
421 nondimensional_force: Scalar,
422 ) -> Result<Scalar, SingleChainError>;
423}
424
425pub trait Legendre
426where
427 Self: Isometric + Isotensional + SingleChain,
428{
429 fn nondimensional_helmholtz_free_energy(
433 &self,
434 nondimensional_extension: Scalar,
435 ) -> Result<Scalar, SingleChainError> {
436 let nondimensional_force = Legendre::nondimensional_force(self, nondimensional_extension)?;
437 Ok(
438 Isotensional::nondimensional_gibbs_free_energy(self, nondimensional_force)?
439 + self.number_of_links() as Scalar
440 * nondimensional_force
441 * nondimensional_extension,
442 )
443 }
444 fn nondimensional_helmholtz_free_energy_per_link(
448 &self,
449 nondimensional_extension: Scalar,
450 ) -> Result<Scalar, SingleChainError> {
451 Ok(
452 Legendre::nondimensional_helmholtz_free_energy(self, nondimensional_extension)?
453 / (self.number_of_links() as Scalar),
454 )
455 }
456 fn nondimensional_force(
460 &self,
461 nondimensional_extension: Scalar,
462 ) -> Result<Scalar, SingleChainError> {
463 match (NewtonRaphson {
464 abs_tol: 1e-10,
465 line_search: LineSearch::Error {
466 cut_back: 5e-1,
467 max_steps: 10,
468 },
469 ..Default::default()
470 }
471 .minimize(
472 |&nondimensional_force| {
473 Ok(Isotensional::nondimensional_gibbs_free_energy_per_link(
474 self,
475 nondimensional_force,
476 )? - nondimensional_force * nondimensional_extension)
477 },
478 |&nondimensional_force| {
479 Ok(
480 Isotensional::nondimensional_extension(self, nondimensional_force)?
481 - nondimensional_extension,
482 )
483 },
484 |&nondimensional_force| {
485 Ok(Isotensional::nondimensional_compliance(
486 self,
487 nondimensional_force,
488 )?)
489 },
490 nondimensional_extension,
491 EqualityConstraint::None,
492 None,
493 )) {
494 Ok(nondimensional_force) => Ok(nondimensional_force),
495 Err(error) => Err(SingleChainError::Upstream(
496 format!("{error}"),
497 format!("{self:?}"),
498 )),
499 }
500 }
501 fn nondimensional_stiffness(
505 &self,
506 nondimensional_extension: Scalar,
507 ) -> Result<Scalar, SingleChainError> {
508 let nondimensional_force = Legendre::nondimensional_force(self, nondimensional_extension)?;
509 Ok(1.0 / Isotensional::nondimensional_compliance(self, nondimensional_force)?)
510 }
511 fn nondimensional_radial_distribution(
515 &self,
516 nondimensional_extension: Scalar,
517 ) -> Result<Scalar, SingleChainError> {
518 Ok(
519 Legendre::nondimensional_spherical_distribution(self, nondimensional_extension)?
520 * (4.0 * PI * nondimensional_extension.powi(2)),
521 )
522 }
523 fn nondimensional_spherical_distribution(
527 &self,
528 nondimensional_extension: Scalar,
529 ) -> Result<Scalar, SingleChainError>;
530 fn nondimensional_gibbs_free_energy(
534 &self,
535 nondimensional_force: Scalar,
536 ) -> Result<Scalar, SingleChainError> {
537 let nondimensional_extension =
538 Legendre::nondimensional_extension(self, nondimensional_force)?;
539 Ok(
540 Isometric::nondimensional_helmholtz_free_energy(self, nondimensional_extension)?
541 - self.number_of_links() as Scalar
542 * nondimensional_force
543 * nondimensional_extension,
544 )
545 }
546 fn nondimensional_gibbs_free_energy_per_link(
550 &self,
551 nondimensional_force: Scalar,
552 ) -> Result<Scalar, SingleChainError> {
553 Ok(
554 Legendre::nondimensional_gibbs_free_energy(self, nondimensional_force)?
555 / (self.number_of_links() as Scalar),
556 )
557 }
558 fn nondimensional_extension(
562 &self,
563 nondimensional_force: Scalar,
564 ) -> Result<Scalar, SingleChainError> {
565 match (NewtonRaphson {
566 abs_tol: 1e-10,
567 line_search: LineSearch::Error {
568 cut_back: 5e-1,
569 max_steps: 10,
570 },
571 ..Default::default()
572 }
573 .minimize(
574 |&nondimensional_extension| {
575 Ok(Isometric::nondimensional_helmholtz_free_energy_per_link(
576 self,
577 nondimensional_extension,
578 )? - nondimensional_force * nondimensional_extension)
579 },
580 |&nondimensional_extension| {
581 Ok(
582 Isometric::nondimensional_force(self, nondimensional_extension)?
583 - nondimensional_force,
584 )
585 },
586 |&nondimensional_extension| {
587 Ok(Isometric::nondimensional_stiffness(
588 self,
589 nondimensional_extension,
590 )?)
591 },
592 nondimensional_force,
593 EqualityConstraint::None,
594 None,
595 )) {
596 Ok(nondimensional_extension) => Ok(nondimensional_extension),
597 Err(error) => Err(SingleChainError::Upstream(
598 format!("{error}"),
599 format!("{self:?}"),
600 )),
601 }
602 }
603 fn nondimensional_compliance(
607 &self,
608 nondimensional_force: Scalar,
609 ) -> Result<Scalar, SingleChainError> {
610 let nondimensional_extension =
611 Legendre::nondimensional_extension(self, nondimensional_force)?;
612 Ok(1.0 / Isometric::nondimensional_stiffness(self, nondimensional_extension)?)
613 }
614}
615
616pub trait MonteCarlo
617where
618 Self: SingleChain + Sync,
619{
620 fn random_nondimensional_link_vectors(&self, nondimensional_force: Scalar) -> Configuration;
621 fn random_configuration(&self, nondimensional_force: Scalar) -> Configuration {
622 let mut position = CurrentCoordinate::zero();
623 self.random_nondimensional_link_vectors(nondimensional_force)
624 .into_iter()
625 .map(|displacement| {
626 position += displacement;
627 position.clone()
628 })
629 .collect()
630 }
631}
632
633pub trait MonteCarloExtensible
634where
635 Self: Extensible + MonteCarlo,
636{
637 fn cosine_powers(
638 &self,
639 nondimensional_force: Scalar,
640 number_of_powers: usize,
641 number_of_samples: usize,
642 number_of_threads: usize,
643 ) -> Matrix {
644 cosine_powers(
645 self,
646 nondimensional_force,
647 number_of_powers,
648 number_of_samples,
649 number_of_threads,
650 true,
651 )
652 }
653 fn nondimensional_extension(
654 &self,
655 nondimensional_force: Scalar,
656 num_samples: usize,
657 num_threads: usize,
658 ) -> Scalar {
659 self.cosine_powers(nondimensional_force, 0, num_samples, num_threads)
660 .into_iter()
661 .flatten()
662 .sum::<Scalar>()
663 / self.number_of_links() as Scalar
664 }
665 fn nondimensional_lateral_distribution(
666 &self,
667 nondimensional_force: Scalar,
668 num_bins: usize,
669 num_samples: usize,
670 num_threads: usize,
671 maximum_nondimensional_extension: Scalar,
672 ) -> (Vector, Vector) {
673 nondimensional_lateral_distribution(
674 self,
675 nondimensional_force,
676 num_bins,
677 num_samples,
678 num_threads,
679 maximum_nondimensional_extension,
680 )
681 }
682 fn nondimensional_longitudinal_distribution(
683 &self,
684 nondimensional_force: Scalar,
685 num_bins: usize,
686 num_samples: usize,
687 num_threads: usize,
688 maximum_nondimensional_extension: Scalar,
689 ) -> (Vector, Vector) {
690 nondimensional_longitudinal_distribution(
691 self,
692 nondimensional_force,
693 num_bins,
694 num_samples,
695 num_threads,
696 maximum_nondimensional_extension,
697 )
698 }
699 fn nondimensional_radial_distribution(
700 &self,
701 nondimensional_force: Scalar,
702 num_bins: usize,
703 num_samples: usize,
704 num_threads: usize,
705 maximum_nondimensional_extension: Scalar,
706 ) -> (Vector, Vector) {
707 nondimensional_radial_distribution(
708 self,
709 nondimensional_force,
710 num_bins,
711 num_samples,
712 num_threads,
713 maximum_nondimensional_extension,
714 )
715 }
716 fn nondimensional_transverse_distribution(
717 &self,
718 nondimensional_force: Scalar,
719 num_bins: usize,
720 num_samples: usize,
721 num_threads: usize,
722 maximum_nondimensional_extension: Scalar,
723 ) -> (Vector, Vector) {
724 nondimensional_transverse_distribution(
725 self,
726 nondimensional_force,
727 num_bins,
728 num_samples,
729 num_threads,
730 maximum_nondimensional_extension,
731 )
732 }
733}
734
735impl<T> MonteCarloExtensible for T where T: Extensible + MonteCarlo {}
736
737pub trait MonteCarloInextensible
738where
739 Self: Inextensible + MonteCarlo,
740{
741 fn cosine_powers(
742 &self,
743 nondimensional_force: Scalar,
744 number_of_powers: usize,
745 number_of_samples: usize,
746 number_of_threads: usize,
747 ) -> Matrix {
748 cosine_powers(
749 self,
750 nondimensional_force,
751 number_of_powers,
752 number_of_samples,
753 number_of_threads,
754 false,
755 )
756 }
757 fn nondimensional_extension(
758 &self,
759 nondimensional_force: Scalar,
760 num_samples: usize,
761 num_threads: usize,
762 ) -> Scalar {
763 self.cosine_powers(nondimensional_force, 1, num_samples, num_threads)
764 .into_iter()
765 .flatten()
766 .sum::<Scalar>()
767 / self.number_of_links() as Scalar
768 }
769 fn nondimensional_angular_distribution(
770 &self,
771 nondimensional_force: Scalar,
772 num_bins: usize,
773 num_samples: usize,
774 num_threads: usize,
775 ) -> (Vector, Vector) {
776 nondimensional_angular_distribution(
777 self,
778 nondimensional_force,
779 num_bins,
780 num_samples,
781 num_threads,
782 self.maximum_nondimensional_extension(),
783 )
784 }
785 fn nondimensional_lateral_distribution(
786 &self,
787 nondimensional_force: Scalar,
788 num_bins: usize,
789 num_samples: usize,
790 num_threads: usize,
791 ) -> (Vector, Vector) {
792 nondimensional_lateral_distribution(
793 self,
794 nondimensional_force,
795 num_bins,
796 num_samples,
797 num_threads,
798 self.maximum_nondimensional_extension(),
799 )
800 }
801 fn nondimensional_longitudinal_distribution(
802 &self,
803 nondimensional_force: Scalar,
804 num_bins: usize,
805 num_samples: usize,
806 num_threads: usize,
807 ) -> (Vector, Vector) {
808 nondimensional_longitudinal_distribution(
809 self,
810 nondimensional_force,
811 num_bins,
812 num_samples,
813 num_threads,
814 self.maximum_nondimensional_extension(),
815 )
816 }
817 fn nondimensional_radial_distribution(
818 &self,
819 nondimensional_force: Scalar,
820 num_bins: usize,
821 num_samples: usize,
822 num_threads: usize,
823 ) -> (Vector, Vector) {
824 nondimensional_radial_distribution(
825 self,
826 nondimensional_force,
827 num_bins,
828 num_samples,
829 num_threads,
830 self.maximum_nondimensional_extension(),
831 )
832 }
833 fn nondimensional_transverse_distribution(
834 &self,
835 nondimensional_force: Scalar,
836 num_bins: usize,
837 num_samples: usize,
838 num_threads: usize,
839 ) -> (Vector, Vector) {
840 nondimensional_transverse_distribution(
841 self,
842 nondimensional_force,
843 num_bins,
844 num_samples,
845 num_threads,
846 self.maximum_nondimensional_extension(),
847 )
848 }
849}
850
851impl<T> MonteCarloInextensible for T where T: Inextensible + MonteCarlo {}
852
853fn cosine_powers<T: MonteCarlo>(
854 model: &T,
855 nondimensional_force: Scalar,
856 number_of_powers: usize,
857 number_of_samples: usize,
858 number_of_threads: usize,
859 is_extensible: bool,
860) -> Matrix {
861 let base = number_of_samples / number_of_threads;
862 let remainder = number_of_samples % number_of_threads;
863 scope(|s| {
864 (0..number_of_threads)
865 .map(|t| {
866 s.spawn(move || {
867 cosine_powers_inner(
868 model,
869 nondimensional_force,
870 number_of_powers,
871 base + usize::from(t < remainder),
872 is_extensible,
873 )
874 })
875 })
876 .collect::<Vec<_>>()
877 .into_iter()
878 .map(|handle| handle.join().unwrap() / number_of_samples as Scalar)
879 .sum()
880 })
881}
882
883fn cosine_powers_inner<T: MonteCarlo>(
884 model: &T,
885 nondimensional_force: Scalar,
886 number_of_powers: usize,
887 number_of_samples: usize,
888 is_extensible: bool,
889) -> Matrix {
890 let mut cosines = Matrix::zero(
891 model.number_of_links() as usize,
892 number_of_powers + is_extensible as usize,
893 );
894 for _ in 0..number_of_samples {
895 cosines
896 .iter_mut()
897 .zip(model.random_nondimensional_link_vectors(nondimensional_force))
898 .for_each(|(powers, link)| {
899 if is_extensible {
900 powers[0] += link[2];
901 let unit = link.normalized();
902 powers
903 .iter_mut()
904 .skip(1)
905 .enumerate()
906 .for_each(|(power, entry)| *entry += unit[2].powi(power as i32 + 1))
907 } else {
908 powers
909 .iter_mut()
910 .enumerate()
911 .for_each(|(power, entry)| *entry += link[2].powi(power as i32 + 1))
912 }
913 })
914 }
915 cosines
916}
917
918fn nondimensional_angular_distribution<T: MonteCarlo>(
919 model: &T,
920 nondimensional_force: Scalar,
921 number_of_bins: usize,
922 number_of_samples: usize,
923 number_of_threads: usize,
924 maximum_nondimensional_extension: Scalar,
925) -> (Vector, Vector) {
926 let base = number_of_samples / number_of_threads;
927 let remainder = number_of_samples % number_of_threads;
928 scope(|s| {
929 let mut total_counts = vec![0; number_of_bins];
930 (0..number_of_threads)
931 .map(|t| {
932 s.spawn(move || {
933 nondimensional_angular_distribution_inner(
934 model,
935 nondimensional_force,
936 number_of_bins,
937 base + usize::from(t < remainder),
938 maximum_nondimensional_extension,
939 )
940 })
941 })
942 .collect::<Vec<_>>()
943 .into_iter()
944 .for_each(|handle| {
945 total_counts
946 .iter_mut()
947 .zip(handle.join().unwrap())
948 .for_each(|(tot, c)| *tot += c)
949 });
950 let bin_width = 2.0 * maximum_nondimensional_extension / (number_of_bins as Scalar);
951 let bin_centers = (0..number_of_bins)
952 .map(|i| -maximum_nondimensional_extension + (i as Scalar + 0.5) * bin_width)
953 .collect();
954 let total_samples = number_of_samples as Scalar;
955 let bin_values = total_counts
956 .into_iter()
957 .map(|count| count as Scalar / total_samples / bin_width)
958 .collect();
959 (bin_centers, bin_values)
960 })
961}
962
963fn nondimensional_lateral_distribution<T: MonteCarlo>(
964 model: &T,
965 nondimensional_force: Scalar,
966 number_of_bins: usize,
967 number_of_samples: usize,
968 number_of_threads: usize,
969 maximum_nondimensional_extension: Scalar,
970) -> (Vector, Vector) {
971 let base = number_of_samples / number_of_threads;
972 let remainder = number_of_samples % number_of_threads;
973 scope(|s| {
974 let mut total_counts = vec![0; number_of_bins];
975 (0..number_of_threads)
976 .map(|t| {
977 s.spawn(move || {
978 nondimensional_lateral_distribution_inner(
979 model,
980 nondimensional_force,
981 number_of_bins,
982 base + usize::from(t < remainder),
983 maximum_nondimensional_extension,
984 )
985 })
986 })
987 .collect::<Vec<_>>()
988 .into_iter()
989 .for_each(|handle| {
990 total_counts
991 .iter_mut()
992 .zip(handle.join().unwrap())
993 .for_each(|(tot, c)| *tot += c)
994 });
995 let bin_width = 2.0 * maximum_nondimensional_extension / (number_of_bins as Scalar);
996 let bin_centers = (0..number_of_bins)
997 .map(|i| -maximum_nondimensional_extension + (i as Scalar + 0.5) * bin_width)
998 .collect();
999 let total_samples = number_of_samples as Scalar;
1000 let bin_values = total_counts
1001 .into_iter()
1002 .map(|count| count as Scalar / total_samples / bin_width)
1003 .collect();
1004 (bin_centers, bin_values)
1005 })
1006}
1007
1008fn nondimensional_longitudinal_distribution<T: MonteCarlo>(
1009 model: &T,
1010 nondimensional_force: Scalar,
1011 number_of_bins: usize,
1012 number_of_samples: usize,
1013 number_of_threads: usize,
1014 maximum_nondimensional_extension: Scalar,
1015) -> (Vector, Vector) {
1016 let base = number_of_samples / number_of_threads;
1017 let remainder = number_of_samples % number_of_threads;
1018 scope(|s| {
1019 let mut total_counts = vec![0; number_of_bins];
1020 (0..number_of_threads)
1021 .map(|t| {
1022 s.spawn(move || {
1023 nondimensional_longitudinal_distribution_inner(
1024 model,
1025 nondimensional_force,
1026 number_of_bins,
1027 base + usize::from(t < remainder),
1028 maximum_nondimensional_extension,
1029 )
1030 })
1031 })
1032 .collect::<Vec<_>>()
1033 .into_iter()
1034 .for_each(|handle| {
1035 total_counts
1036 .iter_mut()
1037 .zip(handle.join().unwrap())
1038 .for_each(|(tot, c)| *tot += c)
1039 });
1040 let bin_width = 2.0 * maximum_nondimensional_extension / (number_of_bins as Scalar);
1041 let bin_centers = (0..number_of_bins)
1042 .map(|i| -maximum_nondimensional_extension + (i as Scalar + 0.5) * bin_width)
1043 .collect();
1044 let total_samples = number_of_samples as Scalar;
1045 let bin_values = total_counts
1046 .into_iter()
1047 .map(|count| count as Scalar / total_samples / bin_width)
1048 .collect();
1049 (bin_centers, bin_values)
1050 })
1051}
1052
1053fn nondimensional_radial_distribution<T: MonteCarlo>(
1054 model: &T,
1055 nondimensional_force: Scalar,
1056 number_of_bins: usize,
1057 number_of_samples: usize,
1058 number_of_threads: usize,
1059 maximum_nondimensional_extension: Scalar,
1060) -> (Vector, Vector) {
1061 let base = number_of_samples / number_of_threads;
1062 let remainder = number_of_samples % number_of_threads;
1063 scope(|s| {
1064 let mut total_counts = vec![0; number_of_bins];
1065 (0..number_of_threads)
1066 .map(|t| {
1067 s.spawn(move || {
1068 nondimensional_radial_distribution_inner(
1069 model,
1070 nondimensional_force,
1071 number_of_bins,
1072 base + usize::from(t < remainder),
1073 maximum_nondimensional_extension,
1074 )
1075 })
1076 })
1077 .collect::<Vec<_>>()
1078 .into_iter()
1079 .for_each(|handle| {
1080 total_counts
1081 .iter_mut()
1082 .zip(handle.join().unwrap())
1083 .for_each(|(tot, c)| *tot += c)
1084 });
1085 let bin_width = maximum_nondimensional_extension / (number_of_bins as Scalar);
1086 let bin_centers = (0..number_of_bins)
1087 .map(|i| (i as Scalar + 0.5) * bin_width)
1088 .collect();
1089 let total_samples = number_of_samples as Scalar;
1090 let bin_values = total_counts
1091 .into_iter()
1092 .map(|count| count as Scalar / total_samples / bin_width)
1093 .collect();
1094 (bin_centers, bin_values)
1095 })
1096}
1097
1098fn nondimensional_transverse_distribution<T: MonteCarlo>(
1099 model: &T,
1100 nondimensional_force: Scalar,
1101 number_of_bins: usize,
1102 number_of_samples: usize,
1103 number_of_threads: usize,
1104 maximum_nondimensional_extension: Scalar,
1105) -> (Vector, Vector) {
1106 let base = number_of_samples / number_of_threads;
1107 let remainder = number_of_samples % number_of_threads;
1108 scope(|s| {
1109 let mut total_counts = vec![0; number_of_bins];
1110 (0..number_of_threads)
1111 .map(|t| {
1112 s.spawn(move || {
1113 nondimensional_transverse_distribution_inner(
1114 model,
1115 nondimensional_force,
1116 number_of_bins,
1117 base + usize::from(t < remainder),
1118 maximum_nondimensional_extension,
1119 )
1120 })
1121 })
1122 .collect::<Vec<_>>()
1123 .into_iter()
1124 .for_each(|handle| {
1125 total_counts
1126 .iter_mut()
1127 .zip(handle.join().unwrap())
1128 .for_each(|(tot, c)| *tot += c)
1129 });
1130 let bin_width = maximum_nondimensional_extension / (number_of_bins as Scalar);
1131 let bin_centers = (0..number_of_bins)
1132 .map(|i| (i as Scalar + 0.5) * bin_width)
1133 .collect();
1134 let total_samples = number_of_samples as Scalar;
1135 let bin_values = total_counts
1136 .into_iter()
1137 .map(|count| count as Scalar / total_samples / bin_width)
1138 .collect();
1139 (bin_centers, bin_values)
1140 })
1141}
1142
1143fn nondimensional_angular_distribution_inner<T: MonteCarlo>(
1144 model: &T,
1145 nondimensional_force: Scalar,
1146 num_bins: usize,
1147 num_samples: usize,
1148 maximum_nondimensional_extension: Scalar,
1149) -> Vec<usize> {
1150 let mut bin_counts = vec![0; num_bins];
1151 let end_index = model.number_of_links() as usize - 1;
1152 for _ in 0..num_samples {
1153 let configuration = model.random_configuration(nondimensional_force);
1154 let gamma = configuration[end_index].norm();
1155 let nondimensional_extension = if gamma == 0.0 {
1156 0.0
1157 } else {
1158 configuration[end_index][2] / gamma
1159 };
1160 if nondimensional_extension.abs() > maximum_nondimensional_extension {
1161 panic!(
1162 "Sample {nondimensional_extension} outside [-{maximum_nondimensional_extension}, {maximum_nondimensional_extension}]"
1163 )
1164 }
1165 let bin_index = ((nondimensional_extension + maximum_nondimensional_extension)
1166 / (2.0 * maximum_nondimensional_extension)
1167 * num_bins as Scalar) as usize;
1168 bin_counts[bin_index] += 1;
1169 }
1170 bin_counts
1171}
1172
1173fn nondimensional_lateral_distribution_inner<T: MonteCarlo>(
1174 model: &T,
1175 nondimensional_force: Scalar,
1176 num_bins: usize,
1177 num_samples: usize,
1178 maximum_nondimensional_extension: Scalar,
1179) -> Vec<usize> {
1180 let mut bin_counts = vec![0; num_bins];
1181 let num_links = model.number_of_links() as Scalar;
1182 let end_index = model.number_of_links() as usize - 1;
1183 for _ in 0..num_samples {
1184 let configuration = model.random_configuration(nondimensional_force);
1185 let nondimensional_extension = configuration[end_index][1] / num_links;
1186 if nondimensional_extension.abs() > maximum_nondimensional_extension {
1187 panic!(
1188 "Sample {nondimensional_extension} outside [-{maximum_nondimensional_extension}, {maximum_nondimensional_extension}]"
1189 )
1190 }
1191 let bin_index = ((nondimensional_extension + maximum_nondimensional_extension)
1192 / (2.0 * maximum_nondimensional_extension)
1193 * num_bins as Scalar) as usize;
1194 bin_counts[bin_index] += 1;
1195 }
1196 bin_counts
1197}
1198
1199fn nondimensional_longitudinal_distribution_inner<T: MonteCarlo>(
1200 model: &T,
1201 nondimensional_force: Scalar,
1202 num_bins: usize,
1203 num_samples: usize,
1204 maximum_nondimensional_extension: Scalar,
1205) -> Vec<usize> {
1206 let mut bin_counts = vec![0; num_bins];
1207 let num_links = model.number_of_links() as Scalar;
1208 let end_index = model.number_of_links() as usize - 1;
1209 for _ in 0..num_samples {
1210 let configuration = model.random_configuration(nondimensional_force);
1211 let nondimensional_extension = configuration[end_index][2] / num_links;
1212 if nondimensional_extension.abs() > maximum_nondimensional_extension {
1213 panic!(
1214 "Sample {nondimensional_extension} outside [-{maximum_nondimensional_extension}, {maximum_nondimensional_extension}]"
1215 )
1216 }
1217 let bin_index = ((nondimensional_extension + maximum_nondimensional_extension)
1218 / (2.0 * maximum_nondimensional_extension)
1219 * num_bins as Scalar) as usize;
1220 bin_counts[bin_index] += 1;
1221 }
1222 bin_counts
1223}
1224
1225fn nondimensional_radial_distribution_inner<T: MonteCarlo>(
1226 model: &T,
1227 nondimensional_force: Scalar,
1228 num_bins: usize,
1229 num_samples: usize,
1230 maximum_nondimensional_extension: Scalar,
1231) -> Vec<usize> {
1232 let mut bin_counts = vec![0; num_bins];
1233 let num_links = model.number_of_links() as Scalar;
1234 let end_index = model.number_of_links() as usize - 1;
1235 for _ in 0..num_samples {
1236 let configuration = model.random_configuration(nondimensional_force);
1237 let nondimensional_extension = configuration[end_index].norm() / num_links;
1238 if nondimensional_extension > maximum_nondimensional_extension {
1239 panic!(
1240 "Sample {nondimensional_extension} above maximum {maximum_nondimensional_extension}"
1241 )
1242 }
1243 let bin_index = (nondimensional_extension / maximum_nondimensional_extension
1244 * num_bins as Scalar) as usize;
1245 bin_counts[bin_index] += 1;
1246 }
1247 bin_counts
1248}
1249
1250fn nondimensional_transverse_distribution_inner<T: MonteCarlo>(
1251 model: &T,
1252 nondimensional_force: Scalar,
1253 num_bins: usize,
1254 num_samples: usize,
1255 maximum_nondimensional_extension: Scalar,
1256) -> Vec<usize> {
1257 let mut bin_counts = vec![0; num_bins];
1258 let num_links = model.number_of_links() as Scalar;
1259 let end_index = model.number_of_links() as usize - 1;
1260 for _ in 0..num_samples {
1261 let configuration = model.random_configuration(nondimensional_force);
1262 let nondimensional_extension =
1263 (configuration[end_index][0].powi(2) + configuration[end_index][1].powi(2)).sqrt()
1264 / num_links;
1265 if nondimensional_extension.abs() > maximum_nondimensional_extension {
1266 panic!(
1267 "Sample {nondimensional_extension} outside [-{maximum_nondimensional_extension}, {maximum_nondimensional_extension}]"
1268 )
1269 }
1270 let bin_index = (nondimensional_extension / maximum_nondimensional_extension
1271 * num_bins as Scalar) as usize;
1272 bin_counts[bin_index] += 1;
1273 }
1274 bin_counts
1275}