bentopy 0.2.0a10__cp313-cp313-manylinux_2_34_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bentopy-0.2.0a10.data/scripts/bentopy-init +0 -0
- bentopy-0.2.0a10.data/scripts/bentopy-pack +0 -0
- bentopy-0.2.0a10.data/scripts/bentopy-render +0 -0
- bentopy-0.2.0a10.data/scripts/bentopy-solvate +0 -0
- bentopy-0.2.0a10.dist-info/METADATA +358 -0
- bentopy-0.2.0a10.dist-info/RECORD +58 -0
- bentopy-0.2.0a10.dist-info/WHEEL +5 -0
- bentopy-0.2.0a10.dist-info/entry_points.txt +4 -0
- bentopy-0.2.0a10.dist-info/licenses/LICENSE.txt +13 -0
- bentopy-0.2.0a10.dist-info/top_level.txt +8 -0
- check/check.py +128 -0
- core/config/bent/lexer.rs +338 -0
- core/config/bent/parser.rs +1180 -0
- core/config/bent/writer.rs +205 -0
- core/config/bent.rs +149 -0
- core/config/compartment_combinations.rs +300 -0
- core/config/legacy.rs +768 -0
- core/config.rs +362 -0
- core/mod.rs +4 -0
- core/placement.rs +100 -0
- core/utilities.rs +1 -0
- core/version.rs +32 -0
- init/example.bent +74 -0
- init/main.rs +235 -0
- mask/config.py +153 -0
- mask/mask.py +308 -0
- mask/utilities.py +38 -0
- merge/merge.py +175 -0
- pack/args.rs +77 -0
- pack/main.rs +121 -0
- pack/mask.rs +940 -0
- pack/session.rs +176 -0
- pack/state/combinations.rs +31 -0
- pack/state/compartment.rs +44 -0
- pack/state/mask.rs +196 -0
- pack/state/pack.rs +187 -0
- pack/state/segment.rs +72 -0
- pack/state/space.rs +98 -0
- pack/state.rs +440 -0
- pack/structure.rs +185 -0
- pack/voxelize.rs +85 -0
- render/args.rs +109 -0
- render/limits.rs +73 -0
- render/main.rs +12 -0
- render/render.rs +393 -0
- render/structure.rs +264 -0
- solvate/args.rs +324 -0
- solvate/convert.rs +25 -0
- solvate/cookies.rs +185 -0
- solvate/main.rs +177 -0
- solvate/placement.rs +380 -0
- solvate/solvate.rs +244 -0
- solvate/structure.rs +160 -0
- solvate/substitute.rs +113 -0
- solvate/water/martini.rs +409 -0
- solvate/water/models.rs +150 -0
- solvate/water/tip3p.rs +658 -0
- solvate/water.rs +115 -0
solvate/args.rs
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
use std::path::PathBuf;
|
|
2
|
+
use std::str::FromStr;
|
|
3
|
+
|
|
4
|
+
use clap::{Parser, ValueEnum};
|
|
5
|
+
use eightyseven::structure::ResName;
|
|
6
|
+
|
|
7
|
+
/// Solvate.
|
|
8
|
+
#[derive(Debug, Parser)]
|
|
9
|
+
#[command(about, version = bentopy::core::version::VERSION)]
|
|
10
|
+
pub struct Args {
|
|
11
|
+
/// Structure input path.
|
|
12
|
+
#[arg(short, long)]
|
|
13
|
+
pub input: PathBuf,
|
|
14
|
+
/// Output path.
|
|
15
|
+
#[arg(short, long)]
|
|
16
|
+
pub output: PathBuf,
|
|
17
|
+
|
|
18
|
+
/// Lowest allowable distance between solvent and structure beads (nm).
|
|
19
|
+
///
|
|
20
|
+
/// This is the minimum allowable center-to-center distance when checking for a collision
|
|
21
|
+
/// between a solvent bead and a structure bead.
|
|
22
|
+
///
|
|
23
|
+
/// For the solvent-solvent cutoff distance, see `--solvent-cutoff`.
|
|
24
|
+
///
|
|
25
|
+
/// For Martini solvation, the default cutoff is 0.43 nm. For atomistic solvation, the default
|
|
26
|
+
/// cutoff is 0.28.
|
|
27
|
+
#[arg(long)]
|
|
28
|
+
pub cutoff: Option<f32>,
|
|
29
|
+
|
|
30
|
+
/// Lowest allowable distance between solvent beads (nm).
|
|
31
|
+
///
|
|
32
|
+
/// This is the minimum allowable center-to-center distance between solvent beads when cutting
|
|
33
|
+
/// the sides to fit in the specified output structure box.
|
|
34
|
+
///
|
|
35
|
+
/// For Martini solvation, the default cutoff is 0.21 nm. For atomistic solvation, the default
|
|
36
|
+
/// cutoff is 0.21.
|
|
37
|
+
#[arg(long)]
|
|
38
|
+
pub solvent_cutoff: Option<f32>,
|
|
39
|
+
|
|
40
|
+
/// List of resnames to ignore when checking against structure-solvent collisions.
|
|
41
|
+
#[arg(long, value_delimiter = ',')]
|
|
42
|
+
pub ignore: Vec<ResName>,
|
|
43
|
+
|
|
44
|
+
/// The type of water written to the output file.
|
|
45
|
+
#[arg(long, value_enum, default_value_t)]
|
|
46
|
+
pub water_type: WaterType,
|
|
47
|
+
|
|
48
|
+
/// Center the structure in the new box.
|
|
49
|
+
///
|
|
50
|
+
/// Note that this option only has an effect if the boundary mode is set to `grow`.
|
|
51
|
+
#[arg(short, long)]
|
|
52
|
+
pub center: bool,
|
|
53
|
+
|
|
54
|
+
/// Set how the boundaries of the box will be treated.
|
|
55
|
+
#[arg(short, long, value_enum, default_value_t)]
|
|
56
|
+
pub boundary_mode: BoundaryMode,
|
|
57
|
+
|
|
58
|
+
/// Set how periodic images of the input structure are considered.
|
|
59
|
+
///
|
|
60
|
+
/// Note that only orthorhombic (cuboid) periodicity is considered, currently.
|
|
61
|
+
// TODO: Consider whether supporting other forms of periodicity is worthwhile.
|
|
62
|
+
#[arg(short, long, value_enum, default_value_t)]
|
|
63
|
+
pub periodic_mode: PeriodicMode,
|
|
64
|
+
|
|
65
|
+
/// Define substitutes for solvent residues, such as ions.
|
|
66
|
+
#[arg(short, long = "substitute")]
|
|
67
|
+
pub substitutes: Vec<SubstituteConfig>,
|
|
68
|
+
|
|
69
|
+
/// Set the charge to neutralize with additional ions.
|
|
70
|
+
///
|
|
71
|
+
/// In essence, this is a shorthand for explicitly providing ions to compensate the charge as
|
|
72
|
+
/// substitutes. This can be helpful for automation purposes.
|
|
73
|
+
///
|
|
74
|
+
/// By default, NA (positive) and CL (negative) ions are used, but a different
|
|
75
|
+
/// positive-negative substitution pair can be specified by prepending a colon followed by the
|
|
76
|
+
/// positive and negative substitute name separated by one comma.
|
|
77
|
+
///
|
|
78
|
+
/// <charge>:<positive ion name>,<negative ion name>
|
|
79
|
+
///
|
|
80
|
+
/// So, by default, some `<charge>` is interpreted as
|
|
81
|
+
///
|
|
82
|
+
/// <charge>:NA,CL
|
|
83
|
+
#[arg(long, allow_hyphen_values = true)]
|
|
84
|
+
pub charge: Option<ChargeConfig>,
|
|
85
|
+
|
|
86
|
+
/// Combine substitutes with identical names into one block.
|
|
87
|
+
#[arg(long, default_value_t)]
|
|
88
|
+
pub no_combine_substitutes: bool,
|
|
89
|
+
|
|
90
|
+
/// Set whether and in what way substitutes are sorted.
|
|
91
|
+
#[arg(long, value_enum, default_value_t)]
|
|
92
|
+
pub sort_substitutes: SortBehavior,
|
|
93
|
+
|
|
94
|
+
/// Random number generator seed for solvent substitution, such as ion placement.
|
|
95
|
+
#[arg(long)]
|
|
96
|
+
pub seed: Option<u64>,
|
|
97
|
+
|
|
98
|
+
/// If the solvent template contains velocity information, write these velocities to the output
|
|
99
|
+
/// file.
|
|
100
|
+
#[arg(long, default_value_t)]
|
|
101
|
+
pub write_velocities: bool,
|
|
102
|
+
|
|
103
|
+
/// Append solvation topology lines to a path.
|
|
104
|
+
#[arg(short = 't', long)]
|
|
105
|
+
pub append_topol: Option<PathBuf>,
|
|
106
|
+
|
|
107
|
+
#[arg(long)]
|
|
108
|
+
pub no_write_parallel: bool,
|
|
109
|
+
/// The suggested number of atoms to format at once.
|
|
110
|
+
///
|
|
111
|
+
/// Setting this to a larger value will allow more atoms to be formatted at once, at the cost
|
|
112
|
+
/// of higher memory consumption. Setting this to a smaller value will lower the memory
|
|
113
|
+
/// footprint at a possible cost of efficiency.
|
|
114
|
+
///
|
|
115
|
+
/// Note that depending on the number of residues in the template box, the provided value may
|
|
116
|
+
/// be overshot by at most that number.
|
|
117
|
+
#[arg(long, default_value_t = 10000000)]
|
|
118
|
+
pub buffer_size: usize,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
#[derive(Debug, Default, Clone, Copy, ValueEnum, PartialEq, Eq)]
|
|
122
|
+
#[clap(rename_all = "lowercase")]
|
|
123
|
+
pub enum WaterType {
|
|
124
|
+
#[default]
|
|
125
|
+
Martini,
|
|
126
|
+
Tip3P,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
impl WaterType {
|
|
130
|
+
pub const fn default_cutoff(&self) -> f32 {
|
|
131
|
+
match self {
|
|
132
|
+
WaterType::Martini => 0.43,
|
|
133
|
+
WaterType::Tip3P => 0.28,
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
pub const fn default_solvent_cutoff(&self) -> f32 {
|
|
138
|
+
match self {
|
|
139
|
+
WaterType::Martini => 0.21,
|
|
140
|
+
WaterType::Tip3P => 0.21,
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
#[derive(Debug, Default, Clone, ValueEnum, PartialEq, Eq)]
|
|
146
|
+
pub enum BoundaryMode {
|
|
147
|
+
/// Cut at the structure box size and remove solvent residues that overlap with the periodic
|
|
148
|
+
/// neighbors.
|
|
149
|
+
#[default]
|
|
150
|
+
Cut,
|
|
151
|
+
/// If necessary, grow the box of the output structure to fit a whole number of template boxes.
|
|
152
|
+
Grow,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
#[derive(Debug, Default, Clone, ValueEnum, PartialEq, Eq)]
|
|
156
|
+
pub enum PeriodicMode {
|
|
157
|
+
/// Include the periodic images of the structure when checking whether a solvent spot is
|
|
158
|
+
/// occupied.
|
|
159
|
+
#[default]
|
|
160
|
+
Periodic,
|
|
161
|
+
/// Ignore the periodic images of the structure for the solvent placement check.
|
|
162
|
+
Ignore,
|
|
163
|
+
/// Treat any structure atoms outside of the output box as an error and exit.
|
|
164
|
+
Deny,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#[derive(Debug, Clone)]
|
|
168
|
+
pub struct SubstituteConfig {
|
|
169
|
+
name: String,
|
|
170
|
+
quantity: Quantity,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
impl SubstituteConfig {
|
|
174
|
+
/// Bake into a [`Substitute`] according to a final volume in nm³ and some number of valid
|
|
175
|
+
/// solvent beads.
|
|
176
|
+
pub fn bake(self, volume: f64, solvent_beads: u64) -> Substitute {
|
|
177
|
+
Substitute {
|
|
178
|
+
name: self.name,
|
|
179
|
+
number: self.quantity.number(volume, solvent_beads),
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
impl FromStr for SubstituteConfig {
|
|
185
|
+
type Err = String;
|
|
186
|
+
|
|
187
|
+
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
188
|
+
let mut words = s.split(':');
|
|
189
|
+
let name = words
|
|
190
|
+
.next()
|
|
191
|
+
.ok_or("expected a name, then a colon, followed by a quantity".to_string())?
|
|
192
|
+
.to_string();
|
|
193
|
+
let quantity = words
|
|
194
|
+
.next()
|
|
195
|
+
.ok_or("expected a quantifier after the colon".to_string())?
|
|
196
|
+
.parse()?;
|
|
197
|
+
Ok(Self { name, quantity })
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#[derive(Debug, Clone)]
|
|
202
|
+
pub struct ChargeConfig {
|
|
203
|
+
charge: i64,
|
|
204
|
+
positive: String,
|
|
205
|
+
negative: String,
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
impl FromStr for ChargeConfig {
|
|
209
|
+
type Err = String;
|
|
210
|
+
|
|
211
|
+
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
212
|
+
let mut words = s.split(':');
|
|
213
|
+
let charge = words
|
|
214
|
+
.next()
|
|
215
|
+
.ok_or("expected a charge")?
|
|
216
|
+
.parse::<i64>()
|
|
217
|
+
.map_err(|err| err.to_string())?;
|
|
218
|
+
let (positive, negative) = if let Some(names) = words.next() {
|
|
219
|
+
names
|
|
220
|
+
.split_once(',')
|
|
221
|
+
.ok_or("expected two substitute names separated by a comma")?
|
|
222
|
+
} else {
|
|
223
|
+
("NA", "CL")
|
|
224
|
+
};
|
|
225
|
+
Ok(Self {
|
|
226
|
+
charge,
|
|
227
|
+
positive: positive.to_string(),
|
|
228
|
+
negative: negative.to_string(),
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
impl ChargeConfig {
|
|
234
|
+
/// Bake the [`ChargeConfig`] into a [`Substitute`] if applicable.
|
|
235
|
+
///
|
|
236
|
+
/// If the charge is 0, no `Substitute` is returned since there is no charge to neutralize.
|
|
237
|
+
pub fn bake(self) -> Option<Substitute> {
|
|
238
|
+
// Choose the name of the ion to neutralize with. If the charge to compensate is negative,
|
|
239
|
+
// we compensate with the positive ion substitute, and vice versa.
|
|
240
|
+
let name = match self.charge {
|
|
241
|
+
..0 => self.positive,
|
|
242
|
+
0 => return None,
|
|
243
|
+
1.. => self.negative,
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
Some(Substitute {
|
|
247
|
+
name,
|
|
248
|
+
number: self.charge.unsigned_abs(),
|
|
249
|
+
})
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
#[derive(Debug, Clone)]
|
|
254
|
+
enum Quantity {
|
|
255
|
+
/// A fixed number.
|
|
256
|
+
Number(u64),
|
|
257
|
+
/// A fraction of the valid solvent beads.
|
|
258
|
+
Ratio(f64),
|
|
259
|
+
/// Molarity in mol/L.
|
|
260
|
+
Molarity(f64),
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
impl FromStr for Quantity {
|
|
264
|
+
type Err = String;
|
|
265
|
+
|
|
266
|
+
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
267
|
+
if let Some(molarity) = s.strip_suffix('M') {
|
|
268
|
+
// Molarity.
|
|
269
|
+
return Ok(Self::Molarity(
|
|
270
|
+
molarity.parse::<f64>().map_err(|err| err.to_string())?,
|
|
271
|
+
));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if let Ok(number) = s.parse::<u64>() {
|
|
275
|
+
return Ok(Self::Number(number));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if let Ok(ratio) = s.parse::<f64>() {
|
|
279
|
+
if !(0.0..=1.0).contains(&ratio) {
|
|
280
|
+
return Err(format!(
|
|
281
|
+
"ratio value must satisfy `1.0 >= r >= 0.0`, found {ratio}"
|
|
282
|
+
));
|
|
283
|
+
}
|
|
284
|
+
return Ok(Self::Ratio(ratio));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
Err(format!("could not parse quantity {s:?}"))
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
impl Quantity {
|
|
292
|
+
/// Given some volume in nm³ and some number of initial valid solvent beads, determine the
|
|
293
|
+
/// number of beads according to this [`Quantity`].
|
|
294
|
+
fn number(self, volume: f64, solvent_beads: u64) -> u64 {
|
|
295
|
+
const N_AVOGADRO: f64 = 6.0221415e23; // per mol.
|
|
296
|
+
let volume_liter = volume * 1e-24;
|
|
297
|
+
let number = match self {
|
|
298
|
+
Quantity::Number(number) => number,
|
|
299
|
+
Quantity::Ratio(ratio) => (solvent_beads as f64 * ratio).round() as u64,
|
|
300
|
+
Quantity::Molarity(molarity) => (volume_liter * molarity * N_AVOGADRO).round() as u64,
|
|
301
|
+
};
|
|
302
|
+
assert!(
|
|
303
|
+
number <= solvent_beads,
|
|
304
|
+
"the number of substitutions that are specified ({number}) exceeds the number of \
|
|
305
|
+
solvent positions that can be substituted ({solvent_beads})"
|
|
306
|
+
);
|
|
307
|
+
number
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
pub struct Substitute {
|
|
312
|
+
pub name: String,
|
|
313
|
+
pub number: u64,
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
#[derive(ValueEnum, Default, Clone, Debug)]
|
|
317
|
+
pub enum SortBehavior {
|
|
318
|
+
#[default]
|
|
319
|
+
Size,
|
|
320
|
+
RevSize,
|
|
321
|
+
Alphabetical,
|
|
322
|
+
RevAlphabetical,
|
|
323
|
+
No,
|
|
324
|
+
}
|
solvate/convert.rs
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/// Convert between `Vec3` types.
|
|
2
|
+
///
|
|
3
|
+
/// In this program we use both [`glam::Vec3`] and the [`eightyseven::structure::Vec3`] type, which
|
|
4
|
+
/// is an export of some version of `glam::Vec3` used in `eightyseven`.
|
|
5
|
+
///
|
|
6
|
+
/// This is very annoying, because we can't just use them interchangeably despite the types being
|
|
7
|
+
/// otherwise identical.
|
|
8
|
+
pub trait Convert<V> {
|
|
9
|
+
/// Convert between `Vec3` types.
|
|
10
|
+
fn convert(&self) -> V;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
impl Convert<glam::Vec3> for eightyseven::structure::Vec3 {
|
|
14
|
+
#[inline(always)]
|
|
15
|
+
fn convert(&self) -> glam::Vec3 {
|
|
16
|
+
glam::Vec3::from_array(self.to_array())
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
impl Convert<eightyseven::structure::Vec3> for glam::Vec3 {
|
|
21
|
+
#[inline(always)]
|
|
22
|
+
fn convert(&self) -> eightyseven::structure::Vec3 {
|
|
23
|
+
eightyseven::structure::Vec3::from_array(self.to_array())
|
|
24
|
+
}
|
|
25
|
+
}
|
solvate/cookies.rs
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
use eightyseven::structure::ResName;
|
|
2
|
+
use glam::{BVec3, IVec3, UVec3, Vec3, Vec3A};
|
|
3
|
+
|
|
4
|
+
use crate::PeriodicMode;
|
|
5
|
+
use crate::convert::Convert;
|
|
6
|
+
use crate::placement::{index_3d, iter_3d};
|
|
7
|
+
use crate::structure::{BoxVecsExtension, Structure};
|
|
8
|
+
|
|
9
|
+
/// A simple spatial data structure that is used to accelerate solvent-structure collision checks.
|
|
10
|
+
///
|
|
11
|
+
/// Each _cookie_ represents one solvent box, and is constructed in such a manner that all
|
|
12
|
+
/// positions that are in or sufficiently close to the solvent beads represented by a cookie are
|
|
13
|
+
/// stored in the cookie.
|
|
14
|
+
pub struct Cookies {
|
|
15
|
+
dimensions: UVec3,
|
|
16
|
+
cookie_size: Vec3,
|
|
17
|
+
cookies: Box<[Box<[Vec3]>]>,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
impl Cookies {
|
|
21
|
+
/// Creates a new [`Cookies`].
|
|
22
|
+
///
|
|
23
|
+
/// Note that the box size for the `structure` are treated as the dimensions when considering
|
|
24
|
+
/// the treatment of atoms according to the [`PeriodicMode`].
|
|
25
|
+
pub fn new(
|
|
26
|
+
structure: &Structure,
|
|
27
|
+
ignore: &[ResName],
|
|
28
|
+
cookie_size: Vec3,
|
|
29
|
+
dimensions: UVec3,
|
|
30
|
+
cutoff: f32,
|
|
31
|
+
mode: PeriodicMode,
|
|
32
|
+
) -> Self {
|
|
33
|
+
let n_cells = dimensions.x as usize * dimensions.y as usize * dimensions.z as usize;
|
|
34
|
+
let mut cookies = vec![Vec::new(); n_cells];
|
|
35
|
+
|
|
36
|
+
// Place the bead's position in the appropriate cookie.
|
|
37
|
+
// Like raisins or pieces of chocolate. But in our case the cookies aren't round but
|
|
38
|
+
// cuboid. Look, I don't even remember why I picked this name.
|
|
39
|
+
let box_dimensions = structure.boxvecs.as_vec3();
|
|
40
|
+
for (i, bead) in structure.atoms.iter().enumerate() {
|
|
41
|
+
let mut pos = bead.position.convert();
|
|
42
|
+
if ignore.contains(&bead.resname) {
|
|
43
|
+
// We don't even consider this particle in setting up our Cookies because we want
|
|
44
|
+
// to ignore it in our solvent-water check.
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
match mode {
|
|
48
|
+
PeriodicMode::Periodic => pos = pos.rem_euclid(box_dimensions),
|
|
49
|
+
PeriodicMode::Ignore => {
|
|
50
|
+
if pos.cmplt(Vec3::ZERO).any() || pos.cmpgt(box_dimensions).any() {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
PeriodicMode::Deny => {
|
|
55
|
+
if pos.cmplt(Vec3::ZERO).any() || pos.cmpgt(box_dimensions).any() {
|
|
56
|
+
eprintln!(
|
|
57
|
+
"ERROR: Atom {i} with position {pos} lies outside the box {box_dimensions}."
|
|
58
|
+
);
|
|
59
|
+
// FIXME: It's rather gross to me to exit from inside this function. A nice
|
|
60
|
+
// improvement could be to bubble it up as an Err to the caller.
|
|
61
|
+
std::process::exit(2);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Note that rem_euclid or our PeriodicMode::Deny check may return negative zeros,
|
|
66
|
+
// which may subtly break our assumptions for determining cookie membership.
|
|
67
|
+
// Perhaps overly strict but better be safe.
|
|
68
|
+
let pos = pos.abs();
|
|
69
|
+
let cell_pos = (pos / cookie_size).floor().as_uvec3();
|
|
70
|
+
let idx = index_3d(cell_pos, dimensions);
|
|
71
|
+
cookies[idx].push(pos);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Now, we have to convolve the contents of the cookies to their neighbors. Otherwise, a
|
|
75
|
+
// neighboring bead may be too close to some solvent bead but go unnoticed in the collision
|
|
76
|
+
// check.
|
|
77
|
+
let idimensions = dimensions.as_ivec3();
|
|
78
|
+
let da = Vec3A::from(box_dimensions);
|
|
79
|
+
// NOTE: If we are ever _really_ pressed for memory, we can win quite a bit here. Cloning
|
|
80
|
+
// the cookies here saves some time gluing the neighbors onto the original positions. But,
|
|
81
|
+
// there are ways of removing this clone.
|
|
82
|
+
let mut convolved_cookies = cookies.clone();
|
|
83
|
+
for cell_pos in iter_3d(dimensions) {
|
|
84
|
+
// This closure returns true if the provided position is in cutoff-range of the present
|
|
85
|
+
// cookie.
|
|
86
|
+
let is_in_range = {
|
|
87
|
+
let start = Vec3A::from(cookie_size * cell_pos.as_vec3() - cutoff);
|
|
88
|
+
let end = Vec3A::from(cookie_size * (cell_pos + 1).as_vec3() + cutoff);
|
|
89
|
+
move |v: &Vec3A| v.cmpge(start).all() && v.cmple(end).all()
|
|
90
|
+
};
|
|
91
|
+
let idx = index_3d(cell_pos, dimensions);
|
|
92
|
+
|
|
93
|
+
for offset in NEIGHBORS {
|
|
94
|
+
let neighbor_pos = cell_pos.as_ivec3() + offset;
|
|
95
|
+
// Note that these jumps are mutually exclusive.
|
|
96
|
+
let backward_jumps = neighbor_pos.cmplt(IVec3::ZERO);
|
|
97
|
+
let forward_jumps = neighbor_pos.cmpge(idimensions);
|
|
98
|
+
// Sanity check of their mutual exclusivity.
|
|
99
|
+
assert_eq!(backward_jumps & forward_jumps, BVec3::FALSE);
|
|
100
|
+
|
|
101
|
+
if !(backward_jumps | forward_jumps).any() {
|
|
102
|
+
// Default case, no complicated stuff with periodicity to consider.
|
|
103
|
+
// TODO: Take only the positions that are closer than `cutoff` to the
|
|
104
|
+
// central cell.
|
|
105
|
+
let neighbor_pos = neighbor_pos.as_uvec3();
|
|
106
|
+
let neighbor_idx = index_3d(neighbor_pos, dimensions);
|
|
107
|
+
let neighbor_content = cookies[neighbor_idx]
|
|
108
|
+
.iter()
|
|
109
|
+
.map(|&v| Vec3A::from(v))
|
|
110
|
+
.filter(is_in_range)
|
|
111
|
+
.map(Vec3::from);
|
|
112
|
+
convolved_cookies[idx].extend(neighbor_content);
|
|
113
|
+
} else {
|
|
114
|
+
fn apply(b: BVec3, v: Vec3A) -> Vec3A {
|
|
115
|
+
UVec3::new(b.x as u32, b.y as u32, b.z as u32).as_vec3a() * v
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let wrapped_neighbor_pos = neighbor_pos.rem_euclid(idimensions).as_uvec3();
|
|
119
|
+
let wrapped_neighbor_idx = index_3d(wrapped_neighbor_pos, dimensions);
|
|
120
|
+
let wrapped_neighbor_content = cookies[wrapped_neighbor_idx]
|
|
121
|
+
.iter()
|
|
122
|
+
.map(|&v| {
|
|
123
|
+
let translation = apply(backward_jumps, -da) + apply(forward_jumps, da);
|
|
124
|
+
Vec3A::from(v) + translation
|
|
125
|
+
})
|
|
126
|
+
.filter(is_in_range)
|
|
127
|
+
.map(Vec3::from);
|
|
128
|
+
convolved_cookies[idx].extend(wrapped_neighbor_content);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
Self {
|
|
134
|
+
dimensions,
|
|
135
|
+
cookie_size,
|
|
136
|
+
cookies: convolved_cookies
|
|
137
|
+
.into_iter()
|
|
138
|
+
.map(|cookie| cookie.into_boxed_slice())
|
|
139
|
+
.collect(),
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
pub fn get(&self, cell_pos: UVec3) -> Option<&[Vec3]> {
|
|
144
|
+
let idx = index_3d(cell_pos, self.dimensions);
|
|
145
|
+
self.cookies.get(idx).map(|cookie| &cookie[..])
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/// Calculate the offset of a cookie at some position from the system origin.
|
|
149
|
+
pub fn offset(&self, cell_pos: UVec3) -> Vec3 {
|
|
150
|
+
cell_pos.as_vec3() * self.cookie_size()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
pub fn cookie_size(&self) -> Vec3 {
|
|
154
|
+
self.cookie_size
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const NEIGHBORS: [IVec3; 26] = [
|
|
159
|
+
IVec3::new(-1, -1, -1),
|
|
160
|
+
IVec3::new(0, -1, -1),
|
|
161
|
+
IVec3::new(1, -1, -1),
|
|
162
|
+
IVec3::new(-1, 0, -1),
|
|
163
|
+
IVec3::new(0, 0, -1),
|
|
164
|
+
IVec3::new(1, 0, -1),
|
|
165
|
+
IVec3::new(-1, 1, -1),
|
|
166
|
+
IVec3::new(0, 1, -1),
|
|
167
|
+
IVec3::new(1, 1, -1),
|
|
168
|
+
IVec3::new(-1, -1, 0),
|
|
169
|
+
IVec3::new(0, -1, 0),
|
|
170
|
+
IVec3::new(1, -1, 0),
|
|
171
|
+
IVec3::new(-1, 0, 0),
|
|
172
|
+
IVec3::new(1, 0, 0),
|
|
173
|
+
IVec3::new(-1, 1, 0),
|
|
174
|
+
IVec3::new(0, 1, 0),
|
|
175
|
+
IVec3::new(1, 1, 0),
|
|
176
|
+
IVec3::new(-1, -1, 1),
|
|
177
|
+
IVec3::new(0, -1, 1),
|
|
178
|
+
IVec3::new(1, -1, 1),
|
|
179
|
+
IVec3::new(-1, 0, 1),
|
|
180
|
+
IVec3::new(0, 0, 1),
|
|
181
|
+
IVec3::new(1, 0, 1),
|
|
182
|
+
IVec3::new(-1, 1, 1),
|
|
183
|
+
IVec3::new(0, 1, 1),
|
|
184
|
+
IVec3::new(1, 1, 1),
|
|
185
|
+
];
|