1use molrs::types::F;
16use std::time::Instant;
17
18use crate::cell::{cell_ind, index_cell, setcell};
19use crate::constraints::EvalMode;
20use crate::context::{NONE_IDX, PackContext};
21use crate::euler::{compcart, eulerrmat};
22use crate::gencan::{GencanParams, GencanWorkspace, pgencan};
23use crate::movebad::{MoveBadConfig, movebad};
24use crate::random::uniform01;
25
26use rand::Rng;
27
28const TWO_PI: F = std::f64::consts::TAU as F;
29const MAX_GUESS_TRY: usize = 20;
31
32pub struct SwapState {
36 xfull: Vec<F>,
38 ntotmol_full: usize,
40}
41
42impl SwapState {
43 pub fn init(x: &[F], sys: &PackContext) -> Self {
45 SwapState {
46 xfull: x.to_vec(),
47 ntotmol_full: sys.ntotmol,
48 }
49 }
50
51 pub fn set_type(&self, itype: usize, sys: &mut PackContext) -> Vec<F> {
56 let ilubar_start: usize = sys.nmols[0..itype].iter().sum::<usize>() * 3;
59 let ilugan_start: usize = self.ntotmol_full * 3 + ilubar_start;
60 let nm = sys.nmols[itype];
61
62 let mut xtype = vec![0.0 as F; nm * 6];
63 xtype[..nm * 3].copy_from_slice(&self.xfull[ilubar_start..ilubar_start + nm * 3]);
64 xtype[nm * 3..nm * 6].copy_from_slice(&self.xfull[ilugan_start..ilugan_start + nm * 3]);
65
66 sys.ntotmol = nm;
68 for i in 0..sys.ntype_with_fixed {
69 sys.comptype[i] = i >= sys.ntype || i == itype;
70 }
71
72 xtype
73 }
74
75 pub fn save_type(&mut self, itype: usize, xtype: &[F], sys: &PackContext) {
77 let ilubar_start: usize = sys.nmols[0..itype].iter().sum::<usize>() * 3;
78 let ilugan_start: usize = self.ntotmol_full * 3 + ilubar_start;
79 let nm = sys.nmols[itype];
80
81 self.xfull[ilubar_start..ilubar_start + nm * 3].copy_from_slice(&xtype[..nm * 3]);
82 self.xfull[ilugan_start..ilugan_start + nm * 3].copy_from_slice(&xtype[nm * 3..nm * 6]);
83 }
84
85 pub fn restore(&self, x: &mut [F], sys: &mut PackContext) {
87 debug_assert_eq!(x.len(), self.xfull.len());
88 x.copy_from_slice(&self.xfull);
89 sys.ntotmol = self.ntotmol_full;
90 for i in 0..sys.ntype_with_fixed {
91 sys.comptype[i] = true;
92 }
93 }
94}
95
96pub fn compute_dmax(sys: &mut PackContext) {
100 sys.dmax = vec![0.0 as F; sys.ntype];
101 for itype in 0..sys.ntype {
102 let idatom_base = sys.idfirst[itype];
103 let na = sys.natoms[itype];
104 for ia in 0..na {
105 for ib in (ia + 1)..na {
106 let a = sys.coor[idatom_base + ia];
107 let b = sys.coor[idatom_base + ib];
108 let d2 = (a[0] - b[0]).powi(2) + (a[1] - b[1]).powi(2) + (a[2] - b[2]).powi(2);
109 if d2 > sys.dmax[itype] {
110 sys.dmax[itype] = d2;
111 }
112 }
113 }
114 sys.dmax[itype] = sys.dmax[itype].sqrt();
115 if sys.dmax[itype] == 0.0 {
116 sys.dmax[itype] = 1.0;
117 }
118 log::debug!(" dmax type {itype}: {:.4}", sys.dmax[itype]);
119 }
120}
121
122struct RestmolScope<'a> {
126 sys: &'a mut PackContext,
127 itype: usize,
128 ntotmol: usize,
129 nmols_itype: usize,
130 comptype: Vec<bool>,
131 init1: bool,
132}
133
134impl<'a> RestmolScope<'a> {
135 fn enter(sys: &'a mut PackContext, itype: usize) -> Self {
136 let saved = Self {
137 ntotmol: sys.ntotmol,
138 nmols_itype: sys.nmols[itype],
139 comptype: sys.comptype.clone(),
140 init1: sys.init1,
141 itype,
142 sys,
143 };
144
145 saved.sys.ntotmol = 1;
146 saved.sys.nmols[itype] = 1;
151 for i in 0..saved.sys.ntype_with_fixed {
152 saved.sys.comptype[i] = i == itype;
153 }
154 saved.sys.init1 = true; saved
157 }
158
159 fn ctx_mut(&mut self) -> &mut PackContext {
160 self.sys
161 }
162}
163
164impl Drop for RestmolScope<'_> {
165 fn drop(&mut self) {
166 self.sys.ntotmol = self.ntotmol;
167 self.sys.nmols[self.itype] = self.nmols_itype;
168 self.sys.comptype.clone_from(&self.comptype);
169 self.sys.init1 = self.init1;
170 }
171}
172
173#[allow(clippy::too_many_arguments)]
184pub fn restmol(
185 itype: usize,
186 ilubar: usize,
187 x: &mut [F],
188 sys: &mut PackContext,
189 precision: F,
190 gencan_maxit: usize,
191 solve: bool,
192 workspace: &mut GencanWorkspace,
193) {
194 let ilugan_offset = sys.ntotmol * 3;
195 let mut xmol = vec![0.0 as F; 6];
196 xmol[0] = x[ilubar];
197 xmol[1] = x[ilubar + 1];
198 xmol[2] = x[ilubar + 2];
199 xmol[3] = x[ilubar + ilugan_offset];
200 xmol[4] = x[ilubar + ilugan_offset + 1];
201 xmol[5] = x[ilubar + ilugan_offset + 2];
202
203 {
204 let mut scope = RestmolScope::enter(sys, itype);
205 let sys = scope.ctx_mut();
206 if !solve {
207 sys.evaluate(&xmol, EvalMode::FOnly, None);
208 } else {
209 let params = GencanParams {
210 maxit: gencan_maxit,
211 maxfc: gencan_maxit * 10,
212 iprint: 0,
213 ..Default::default()
214 };
215 pgencan(&mut xmol, sys, ¶ms, precision, workspace);
216 }
217 }
218
219 x[ilubar] = xmol[0];
220 x[ilubar + 1] = xmol[1];
221 x[ilubar + 2] = xmol[2];
222 x[ilubar + ilugan_offset] = xmol[3];
223 x[ilubar + ilugan_offset + 1] = xmol[4];
224 x[ilubar + ilugan_offset + 2] = xmol[5];
225 }
227
228#[allow(clippy::too_many_arguments)]
239fn init_loop_one_type(
240 itype: usize,
241 nloop0: usize,
242 xtype: &mut [F],
243 sys: &mut PackContext,
244 precision: F,
245 gencan_maxit: usize,
246 movebad_cfg: &MoveBadConfig<'_>,
247 rng: &mut impl Rng,
248 t0: &Instant,
249 workspace: &mut GencanWorkspace,
250) {
251 let params = GencanParams {
254 maxit: gencan_maxit,
255 maxfc: gencan_maxit * 10,
256 iprint: 0,
257 ..Default::default()
258 };
259
260 let mut iter = 0usize;
261 sys.evaluate(xtype, EvalMode::FOnly, None);
262 log::debug!(
263 "[{:.3}s] initial frest={:.4e}",
264 t0.elapsed().as_secs_f64(),
265 sys.frest
266 );
267
268 while sys.frest > precision && iter < nloop0 {
269 iter += 1;
270 pgencan(xtype, sys, ¶ms, precision, workspace);
271 sys.evaluate(xtype, EvalMode::FOnly, None);
272 log::debug!(
273 "[{:.3}s] post-gencan frest={:.4e}",
274 t0.elapsed().as_secs_f64(),
275 sys.frest
276 );
277 if sys.frest > precision {
278 log::debug!(
279 "[{:.3}s] movebad iter {iter}",
280 t0.elapsed().as_secs_f64()
281 );
282 movebad(xtype, sys, precision, movebad_cfg, rng, workspace);
283 }
284 }
285 log::debug!(
286 "[{:.3}s] type {itype} done (nloop0={nloop0}): frest={:.4e}",
287 t0.elapsed().as_secs_f64(),
288 sys.frest
289 );
290}
291
292#[allow(clippy::too_many_arguments)]
297pub fn initial(
298 x: &mut [F],
299 sys: &mut PackContext,
300 precision: F,
301 discale: F,
302 sidemax: F,
303 nloop0: usize,
304 pbc: Option<([F; 3], [F; 3], [bool; 3])>,
305 avoid_overlap: bool,
306 movebad_cfg: &MoveBadConfig<'_>,
307 rng: &mut impl Rng,
308) {
309 let t0 = Instant::now();
310 let mut workspace = GencanWorkspace::new();
311
312 sys.move_flag = false;
313 sys.init1 = false;
314 sys.lcellfirst = NONE_IDX;
315
316 for i in 0..sys.ntype_with_fixed {
317 sys.comptype[i] = true;
318 }
319
320 sys.scale = 1.0;
322 sys.scale2 = 0.01;
323
324 log::debug!("[{:.3}s] computing dmax", t0.elapsed().as_secs_f64());
326 compute_dmax(sys);
327
328 x.fill(0.0);
331 log::debug!(
332 "[{:.3}s] initial restmol → sizemin/sizemax",
333 t0.elapsed().as_secs_f64()
334 );
335 restmol(
336 0,
337 0,
338 x,
339 sys,
340 precision,
341 movebad_cfg.gencan_maxit,
342 true,
343 &mut workspace,
344 );
345 let cm0 = [x[0], x[1], x[2]];
346 sys.sizemin = [cm0[0] - sidemax, cm0[1] - sidemax, cm0[2] - sidemax];
347 sys.sizemax = [cm0[0] + sidemax, cm0[1] + sidemax, cm0[2] + sidemax];
348 log::debug!(
349 "[{:.3}s] sizemin={:?} sizemax={:?}",
350 t0.elapsed().as_secs_f64(),
351 sys.sizemin,
352 sys.sizemax
353 );
354
355 log::debug!(
358 "[{:.3}s] generating first random guess",
359 t0.elapsed().as_secs_f64()
360 );
361 {
362 let mut ilubar = 0usize;
363 let mut ilugan = sys.ntotmol * 3;
364 for itype in 0..sys.ntype {
365 for _imol in 0..sys.nmols[itype] {
366 x[ilubar] = sys.sizemin[0] + uniform01(rng) * (sys.sizemax[0] - sys.sizemin[0]);
367 x[ilubar + 1] = sys.sizemin[1] + uniform01(rng) * (sys.sizemax[1] - sys.sizemin[1]);
368 x[ilubar + 2] = sys.sizemin[2] + uniform01(rng) * (sys.sizemax[2] - sys.sizemin[2]);
369 x[ilugan] = random_angle_for_type(itype, 0, sys, rng);
370 x[ilugan + 1] = random_angle_for_type(itype, 1, sys, rng);
371 x[ilugan + 2] = random_angle_for_type(itype, 2, sys, rng);
372 ilubar += 3;
373 ilugan += 3;
374 }
375 }
376 }
377
378 init_xcart_from_x(x, sys);
380
381 let free_atoms = sys.ntotat - sys.nfixedat;
382 debug_assert!((free_atoms..sys.ntotat).all(|icart| sys.fixedatom[icart]));
389
390 log::debug!(
393 "[{:.3}s] Phase 1: constraint-only GENCAN ({} types, nloop0={})",
394 t0.elapsed().as_secs_f64(),
395 sys.ntype,
396 nloop0,
397 );
398 sys.init1 = true;
399 {
400 let mut swap = SwapState::init(x, sys);
401 for itype in 0..sys.ntype {
402 let nm = sys.nmols[itype];
403 log::debug!(
404 "[{:.3}s] type {itype}: {nm} mols × {} atoms (n={})",
405 t0.elapsed().as_secs_f64(),
406 sys.natoms[itype],
407 nm * 6
408 );
409 let mut xtype = swap.set_type(itype, sys);
410 init_loop_one_type(
411 itype,
412 nloop0,
413 &mut xtype,
414 sys,
415 precision,
416 movebad_cfg.gencan_maxit,
417 movebad_cfg,
418 rng,
419 &t0,
420 &mut workspace,
421 );
422 swap.save_type(itype, &xtype, sys);
423 }
424 swap.restore(x, sys);
425 }
426 sys.init1 = false;
427
428 log::debug!(
431 "[{:.3}s] rescaling bounds + computing cm_min/cm_max",
432 t0.elapsed().as_secs_f64()
433 );
434
435 init_xcart_from_x(x, sys);
437
438 let radmax = sys
441 .radius_ini
442 .iter()
443 .copied()
444 .map(|r| 2.0 * r)
445 .fold(0.0 as F, F::max);
446
447 let mut smin = [1.0e20 as F; 3];
448 let mut smax = [-1.0e20 as F; 3];
449
450 for icart in free_atoms..sys.ntotat {
452 let pos = sys.xcart[icart];
453 for k in 0..3 {
454 smin[k] = smin[k].min(pos[k]);
455 smax[k] = smax[k].max(pos[k]);
456 }
457 }
458
459 let mut cm_min_per_type = vec![[1.0e20 as F; 3]; sys.ntype];
461 let mut cm_max_per_type = vec![[-1.0e20 as F; 3]; sys.ntype];
462 {
463 let mut icart = 0usize;
464 for itype in 0..sys.ntype {
465 for _imol in 0..sys.nmols[itype] {
466 let mut xcm = [0.0 as F; 3];
467 for _iatom in 0..sys.natoms[itype] {
468 let pos = sys.xcart[icart];
469 for k in 0..3 {
470 smin[k] = smin[k].min(pos[k]);
471 smax[k] = smax[k].max(pos[k]);
472 xcm[k] += pos[k];
473 }
474 icart += 1;
475 }
476 let na = sys.natoms[itype] as F;
477 for k in 0..3 {
478 xcm[k] /= na;
479 cm_min_per_type[itype][k] = cm_min_per_type[itype][k].min(xcm[k]);
480 cm_max_per_type[itype][k] = cm_max_per_type[itype][k].max(xcm[k]);
481 }
482 }
483 }
484 }
485
486 for k in 0..3 {
488 if smin[k] > 1.0e19 {
489 smin[k] = sys.sizemin[k];
490 smax[k] = sys.sizemax[k];
491 }
492 }
493
494 for k in 0..3 {
496 sys.sizemin[k] = smin[k] - 1.1 * radmax;
497 sys.sizemax[k] = smax[k] + 1.1 * radmax;
498 }
499
500 log::debug!(
501 "[{:.3}s] sizemin={:?} sizemax={:?} radmax={:.4}",
502 t0.elapsed().as_secs_f64(),
503 sys.sizemin,
504 sys.sizemax,
505 radmax
506 );
507 for itype in 0..sys.ntype {
508 log::debug!(
509 "[{:.3}s] type {itype} cm range: min={:?} max={:?}",
510 t0.elapsed().as_secs_f64(),
511 cm_min_per_type[itype],
512 cm_max_per_type[itype]
513 );
514 }
515
516 if let Some((pbc_min, pbc_max, pbc_periodic)) = pbc {
519 sys.pbc_min = pbc_min;
520 sys.pbc_length = [
521 pbc_max[0] - pbc_min[0],
522 pbc_max[1] - pbc_min[1],
523 pbc_max[2] - pbc_min[2],
524 ];
525 sys.pbc_periodic = pbc_periodic;
526 } else {
527 sys.pbc_min = sys.sizemin;
528 sys.pbc_length = [
529 sys.sizemax[0] - sys.sizemin[0],
530 sys.sizemax[1] - sys.sizemin[1],
531 sys.sizemax[2] - sys.sizemin[2],
532 ];
533 sys.pbc_periodic = [false; 3];
534 }
535
536 let cell_side = if radmax > 0.0 {
537 discale * 1.01 * radmax
538 } else {
539 1.0
540 };
541 log::debug!(
542 "[{:.3}s] setting up cell grid (cell_side={:.4})",
543 t0.elapsed().as_secs_f64(),
544 cell_side
545 );
546 let raw = [
548 ((sys.pbc_length[0] / cell_side).floor() as usize).max(1),
549 ((sys.pbc_length[1] / cell_side).floor() as usize).max(1),
550 ((sys.pbc_length[2] / cell_side).floor() as usize).max(1),
551 ];
552 let max_total_cells = sys.ntotat.max(1).saturating_mul(64).clamp(1 << 16, 1 << 22);
559 let raw_total = raw[0].saturating_mul(raw[1]).saturating_mul(raw[2]);
560 let shrink = if raw_total > max_total_cells {
561 (raw_total as f64 / max_total_cells as f64).cbrt()
562 } else {
563 1.0
564 };
565 for (k, &raw_k) in raw.iter().enumerate() {
566 sys.ncells[k] = ((raw_k as f64 / shrink).floor() as usize).max(1);
567 sys.cell_length[k] = sys.pbc_length[k] / sys.ncells[k] as F;
568 }
569 log::debug!(
570 "[{:.3}s] ncells={:?} cell_length={:?}",
571 t0.elapsed().as_secs_f64(),
572 sys.ncells,
573 sys.cell_length
574 );
575
576 sys.resize_cell_arrays();
577
578 for icart in free_atoms..sys.ntotat {
580 let pos = sys.xcart[icart];
581 let cell = setcell(
582 &pos,
583 &sys.pbc_min,
584 &sys.pbc_length,
585 &sys.cell_length,
586 &sys.ncells,
587 &sys.pbc_periodic,
588 );
589 let icell = index_cell(&cell, &sys.ncells);
590 if sys.latomfix[icell] == NONE_IDX {
591 sys.fixed_cells.push(icell);
592 }
593 sys.latomnext[icart] = sys.latomfix[icell];
594 sys.latomfix[icell] = icart as u32;
595 }
596
597 log::debug!(
603 "[{:.3}s] setting random initial point ({} types, {} total mols)",
604 t0.elapsed().as_secs_f64(),
605 sys.ntype,
606 sys.ntotmol
607 );
608 let has_fixed = avoid_overlap && sys.nfixedat > 0;
611 {
612 let mut ilubar = 0usize;
613 for itype in 0..sys.ntype {
614 let cm_lo = cm_min_per_type[itype];
615 let cm_hi = cm_max_per_type[itype];
616 let nmols = sys.nmols[itype];
617 log::debug!(
618 "[{:.3}s] type {itype}: {nmols} mols, \
619 cm_x=[{:.2},{:.2}] cm_y=[{:.2},{:.2}] cm_z=[{:.2},{:.2}]",
620 t0.elapsed().as_secs_f64(),
621 cm_lo[0],
622 cm_hi[0],
623 cm_lo[1],
624 cm_hi[1],
625 cm_lo[2],
626 cm_hi[2]
627 );
628 for _imol in 0..nmols {
629 let mut ntry = 0usize;
635 let mut fmol = 1.0 as F;
636 let mut overlap = false;
637 while (overlap || fmol > precision) && ntry < MAX_GUESS_TRY {
638 overlap = false;
639 ntry += 1;
640 let rx: F = uniform01(rng);
641 let ry: F = uniform01(rng);
642 let rz: F = uniform01(rng);
643 x[ilubar] = cm_lo[0] + rx * (cm_hi[0] - cm_lo[0]);
644 x[ilubar + 1] = cm_lo[1] + ry * (cm_hi[1] - cm_lo[1]);
645 x[ilubar + 2] = cm_lo[2] + rz * (cm_hi[2] - cm_lo[2]);
646 if has_fixed {
647 let pos = [x[ilubar], x[ilubar + 1], x[ilubar + 2]];
648 let cell = setcell(
649 &pos,
650 &sys.pbc_min,
651 &sys.pbc_length,
652 &sys.cell_length,
653 &sys.ncells,
654 &sys.pbc_periodic,
655 );
656 'scan: for ic in -1isize..=1 {
657 for jc in -1isize..=1 {
658 for kc in -1isize..=1 {
659 let nc = [
660 cell_ind(cell[0] as isize + ic, sys.ncells[0]),
661 cell_ind(cell[1] as isize + jc, sys.ncells[1]),
662 cell_ind(cell[2] as isize + kc, sys.ncells[2]),
663 ];
664 if sys.latomfix[index_cell(&nc, &sys.ncells)] != NONE_IDX {
665 overlap = true;
666 break 'scan;
667 }
668 }
669 }
670 }
671 }
672 if !overlap {
673 restmol(
674 itype,
675 ilubar,
676 x,
677 sys,
678 precision,
679 movebad_cfg.gencan_maxit,
680 false,
681 &mut workspace,
682 );
683 fmol = sys.frest;
684 }
685 }
686 ilubar += 3;
687 }
688 log::debug!(
689 "[{:.3}s] type {itype} placement done",
690 t0.elapsed().as_secs_f64()
691 );
692 }
693 }
694
695 log::debug!("[{:.3}s] setting random angles", t0.elapsed().as_secs_f64());
698 {
699 let mut ilugan = sys.ntotmol * 3;
700 for itype in 0..sys.ntype {
701 for _imol in 0..sys.nmols[itype] {
702 x[ilugan] = random_angle_for_type(itype, 0, sys, rng);
703 x[ilugan + 1] = random_angle_for_type(itype, 1, sys, rng);
704 x[ilugan + 2] = random_angle_for_type(itype, 2, sys, rng);
705 ilugan += 3;
706 }
707 }
708 }
709
710 log::debug!(
713 "[{:.3}s] Phase 2: constraint-only GENCAN ({} types, nloop0={})",
714 t0.elapsed().as_secs_f64(),
715 sys.ntype,
716 nloop0,
717 );
718 sys.init1 = true;
719 {
720 let mut swap = SwapState::init(x, sys);
721 for itype in 0..sys.ntype {
722 let nm = sys.nmols[itype];
723 log::debug!(
724 "[{:.3}s] type {itype}: {nm} mols × {} atoms (n={})",
725 t0.elapsed().as_secs_f64(),
726 sys.natoms[itype],
727 nm * 6
728 );
729 let mut xtype = swap.set_type(itype, sys);
730 init_loop_one_type(
731 itype,
732 nloop0,
733 &mut xtype,
734 sys,
735 precision,
736 movebad_cfg.gencan_maxit,
737 movebad_cfg,
738 rng,
739 &t0,
740 &mut workspace,
741 );
742 swap.save_type(itype, &xtype, sys);
743 }
744 swap.restore(x, sys);
745 }
746 sys.init1 = false;
747
748 log::debug!("[{:.3}s] initial() complete", t0.elapsed().as_secs_f64());
749}
750
751fn random_angle_for_type(itype: usize, axis: usize, sys: &PackContext, rng: &mut impl Rng) -> F {
752 if sys.constrain_rot[itype][axis] {
753 let center = sys.rot_bound[itype][axis][0];
754 let half_width = sys.rot_bound[itype][axis][1].abs();
755 (center - half_width) + 2.0 * uniform01(rng) * half_width
756 } else {
757 TWO_PI * uniform01(rng)
758 }
759}
760
761pub fn init_xcart_from_x(x: &[F], sys: &mut PackContext) {
765 let mut ilubar = 0usize;
766 let mut ilugan = sys.ntotmol * 3;
767 let mut icart = 0usize;
768
769 for itype in 0..sys.ntype {
770 for _imol in 0..sys.nmols[itype] {
771 let xcm = [x[ilubar], x[ilubar + 1], x[ilubar + 2]];
772 let beta = x[ilugan];
773 let gama = x[ilugan + 1];
774 let teta = x[ilugan + 2];
775 let (v1, v2, v3) = eulerrmat(beta, gama, teta);
776
777 let idatom_base = sys.idfirst[itype];
778 for iatom in 0..sys.natoms[itype] {
779 let pos = compcart(&xcm, &sys.coor[idatom_base + iatom], &v1, &v2, &v3);
780 sys.xcart[icart] = pos;
781 debug_assert!(!sys.fixedatom[icart]);
786 icart += 1;
787 }
788
789 ilugan += 3;
790 ilubar += 3;
791 }
792 }
793}