xtalforge_core/
supercell.rs1use crate::limits::{Limits, check_supercell};
4use crate::spatial::{FractionalGrid, perpendicular_widths};
5use crate::{Error, Lattice, Site, Structure, StructureBuilder};
6
7impl Structure {
8 pub fn supercell(&self, na: u32, nb: u32, nc: u32) -> Result<Self, Error> {
15 self.supercell_with_limits(na, nb, nc, &Limits::default())
16 }
17
18 pub fn supercell_with_limits(
26 &self,
27 na: u32,
28 nb: u32,
29 nc: u32,
30 limits: &Limits,
31 ) -> Result<Self, Error> {
32 if na == 0 || nb == 0 || nc == 0 {
33 return Err(Error::InvalidInput(
34 "supercell repeats must be positive".to_owned(),
35 ));
36 }
37 self.supercell_matrix_with_limits(
38 [
39 [f64::from(na), 0.0, 0.0],
40 [0.0, f64::from(nb), 0.0],
41 [0.0, 0.0, f64::from(nc)],
42 ],
43 limits,
44 )
45 }
46
47 pub fn supercell_matrix(&self, p: [[f64; 3]; 3]) -> Result<Self, Error> {
56 self.supercell_matrix_with_limits(p, &Limits::default())
57 }
58
59 pub fn supercell_matrix_with_limits(
66 &self,
67 p: [[f64; 3]; 3],
68 limits: &Limits,
69 ) -> Result<Self, Error> {
70 for value in p.iter().flatten() {
71 if !value.is_finite() || (value - value.round()).abs() > 1e-9 {
72 return Err(Error::InvalidInput(
73 "supercell matrix must contain integers".to_owned(),
74 ));
75 }
76 }
77 let expanded = if self.is_p1() {
78 None
79 } else {
80 Some(self.as_p1()?)
81 };
82 let source = expanded.as_ref().unwrap_or(self);
83 let verdict = check_supercell(limits, source.sites().len() as u64, p)?;
84 if verdict.refused() {
85 return Err(Error::LimitExceeded(verdict));
86 }
87 let lattice = source.lattice().transform(p)?;
88 let inverse = Lattice::new(p)?.inverse();
89 let max_inverse = inverse
90 .iter()
91 .flatten()
92 .map(|value| value.abs())
93 .fold(0.0, f64::max);
94 let range = (max_inverse * 4.0).ceil() as i64 + 2;
95 let widths = perpendicular_widths(&lattice)?;
96 let mut grid = FractionalGrid::new(widths, 1e-6);
97 let mut sites: Vec<Site> = Vec::new();
98 for site in source.sites() {
99 for i in -range..=range {
100 for j in -range..=range {
101 for k in -range..=range {
102 let candidate = [
103 site.frac()[0] + i as f64,
104 site.frac()[1] + j as f64,
105 site.frac()[2] + k as f64,
106 ];
107 let frac = multiply_row(candidate, inverse);
108 if !frac.iter().all(|value| (-1e-9..1.0).contains(value)) {
109 continue;
110 }
111 let mut duplicate = false;
112 grid.for_each_candidate(site.element(), frac, |existing_index| {
113 let Some(existing) = sites.get(existing_index) else {
114 return true;
115 };
116 if lattice.distance(existing.frac(), frac) <= 1e-6 {
117 duplicate = true;
118 return false;
119 }
120 true
121 });
122 if duplicate {
123 continue;
124 }
125 let index = sites.len();
126 sites.push(site.clone().with_frac(frac)?);
127 grid.insert(site.element(), frac, index);
128 }
129 }
130 }
131 }
132 let mut builder = StructureBuilder::new(source.name(), lattice);
133 for site in sites {
134 builder = builder.site(site);
135 }
136 for (key, value) in source.metadata() {
137 builder = builder.metadata(key.clone(), value.clone());
138 }
139 builder.build()
140 }
141
142 pub fn origin_shift(&self, delta: [f64; 3]) -> Result<Self, Error> {
148 if !delta.iter().all(|value| value.is_finite()) {
149 return Err(Error::InvalidInput(
150 "origin shift must be finite".to_owned(),
151 ));
152 }
153 let mut builder = StructureBuilder::new(self.name(), *self.lattice());
154 for site in self.sites() {
155 let frac = [
156 site.frac()[0] + delta[0],
157 site.frac()[1] + delta[1],
158 site.frac()[2] + delta[2],
159 ];
160 builder = builder.site(site.clone().with_frac(Lattice::wrap(frac))?);
161 }
162 for (key, value) in self.metadata() {
163 builder = builder.metadata(key.clone(), value.clone());
164 }
165 builder.build()
166 }
167}
168
169fn multiply_row(vector: [f64; 3], matrix: [[f64; 3]; 3]) -> [f64; 3] {
170 let [x, y, z] = vector;
171 let [[a, b, c], [d, e, f], [g, h, i]] = matrix;
172 [
173 x * a + y * d + z * g,
174 x * b + y * e + z * h,
175 x * c + y * f + z * i,
176 ]
177}