1use crate::common::*;
3
4use gchemol::neighbors::{Neighbor, Neighborhood};
5use gchemol::Lattice;
6use vecfx::*;
7fn guess_appropriate_zstep(lat: &Lattice, radius: f64) -> f64 {
11 assert!(radius.is_sign_positive(), "invalid radius: {}", radius);
12 let [_, _, zwidth] = lat.widths();
13 radius / zwidth
14}
15
16fn probe_surface_neighgbors_along_z(
17 probe: &Neighborhood,
18 lat: &gchemol::Lattice,
19 x: f64,
20 y: f64,
21 zhigh: f64,
22 zstep: f64,
23 r_cutoff: f64,
24) -> Result<Vec<Neighbor>> {
25 assert!(zhigh >= 0.0, "invalid zhigh: {}", zhigh);
26
27 let p = lat.to_cart([x, y, zhigh]);
29 let n = probe.search(p.into(), r_cutoff).collect_vec();
30 let mut i = 0;
31 if n.is_empty() {
32 let mut z = zhigh;
34 loop {
35 i += 1;
36 z -= zstep;
37 let p = lat.to_cart([x, y, z]);
38 let n = probe.search(p.into(), r_cutoff).collect_vec();
39 if n.len() > 0 || z <= 0.0 {
40 trace!("probe surface atoms by moving down done in {} iterations", i);
41 break Ok(n);
42 }
43 }
44 } else {
45 let mut z = zhigh;
47 let mut n_pre = n;
48 loop {
49 i += 1;
50 z += zstep;
51 let p = lat.to_cart([x, y, z]);
52 let n = probe.search(p.into(), r_cutoff).collect_vec();
53 if n.len() == 0 || z >= 1.0 {
54 trace!("probe surface atoms by moving up done in {} iterations", i);
55 break Ok(n_pre);
56 }
57 n_pre = n;
58 }
59 }
60}
61pub fn probe_surface_atoms(mol: &Molecule, r_cutoff: f64, n_probes: usize) -> Result<Vec<usize>> {
72 let n_probes = 800;
73 ensure!(mol.lattice.is_some(), "only work for periodic system");
74
75 let zhigh = mol.get_scaled_positions().unwrap().map(|[_, _, fz]| fz).float_max();
76 let probe = mol.create_neighbor_probe();
77 let lat = mol.get_lattice().ok_or(anyhow!("no lattice"))?;
78
79 let zstep = guess_appropriate_zstep(mol.lattice.as_ref().unwrap(), 0.5);
80 debug!("probe step size along z = {}", zstep);
81 let mut surface_nodes = std::collections::HashSet::new();
82 for _ in 0..n_probes {
83 let [x, y] = crate::sample::random_frac_xy();
84 let nn = probe_surface_neighgbors_along_z(&probe, lat, x, y, zhigh, zstep, r_cutoff)?;
85 for n in nn {
86 surface_nodes.insert(n.node);
87 }
88 }
89 let nodes = surface_nodes.into_iter().collect();
90 Ok(nodes)
91}
92#[test]
96fn test_probe_surface_atoms() -> Result<()> {
97 gut::cli::setup_logger_for_test();
98
99 let file = "./tests/files/Ni211.cif";
100 let mol = Molecule::from_file(file)?;
101 let surface_atoms = probe_surface_atoms(&mol, 1.8, 500)?;
102 let s = gut::utils::abbreviate_numbers_human_readable(&surface_atoms)?;
103 assert_eq!(
104 "2,4,8,10,12,16,18,20,24,26,28,32,34,36,40,42,44,48,50,52,56,58,60,64,66,68,72,74,76,80",
105 s
106 );
107
108 Ok(())
109}
110