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/solvate.rs
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
use eightyseven::structure::ResName;
|
|
2
|
+
use glam::{UVec3, Vec3};
|
|
3
|
+
|
|
4
|
+
use crate::convert::Convert;
|
|
5
|
+
use crate::cookies::Cookies;
|
|
6
|
+
use crate::placement::PlaceMap;
|
|
7
|
+
use crate::placement::iter_3d;
|
|
8
|
+
use crate::structure::{BoxVecsExtension, Structure};
|
|
9
|
+
use crate::water::Water;
|
|
10
|
+
use crate::{BoundaryMode, PeriodicMode};
|
|
11
|
+
|
|
12
|
+
/// Solvate a [`Structure`] with a template solvent box.
|
|
13
|
+
#[allow(clippy::too_many_arguments)]
|
|
14
|
+
pub fn solvate<'sol>(
|
|
15
|
+
structure: &mut Structure,
|
|
16
|
+
solvent: Water,
|
|
17
|
+
cutoff: f32,
|
|
18
|
+
solvent_cutoff: f32,
|
|
19
|
+
ignore: &[ResName],
|
|
20
|
+
center: bool,
|
|
21
|
+
boundary_mode: BoundaryMode,
|
|
22
|
+
periodic_mode: PeriodicMode,
|
|
23
|
+
) -> PlaceMap<'sol> {
|
|
24
|
+
// Determine how many copies of the solvent cell are required to fill the input box for each
|
|
25
|
+
// direction.
|
|
26
|
+
let cookie_size = solvent.dimensions();
|
|
27
|
+
let d = structure.boxvecs.as_vec3() / cookie_size;
|
|
28
|
+
let dimensions = d.ceil().as_uvec3();
|
|
29
|
+
|
|
30
|
+
// Depending on the desired behavior, modify the structure.
|
|
31
|
+
match boundary_mode {
|
|
32
|
+
BoundaryMode::Cut => {}
|
|
33
|
+
BoundaryMode::Grow => {
|
|
34
|
+
eprintln!("\tGrowing output structure box to fit solvent precisely.");
|
|
35
|
+
// If the solvent box does not fit precisely (viz., the input box size is not an
|
|
36
|
+
// integer multiple of the solvent box size), the size will be overshot. This means that in
|
|
37
|
+
// many cases, the size of the output box may be greater than that of the input structure.
|
|
38
|
+
let remainder = d.fract() * cookie_size;
|
|
39
|
+
if center {
|
|
40
|
+
// Add half of the overshot to the positions in the structure to place it in the center of
|
|
41
|
+
// the final box.
|
|
42
|
+
let offset = remainder * 0.5;
|
|
43
|
+
for bead in &mut structure.atoms {
|
|
44
|
+
bead.position += offset.convert();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
eprintln!("\tCentered structure in expanded box.");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
match &mut structure.boxvecs {
|
|
51
|
+
// We don't have to cut off the solvent structures at the boundaries, but we have to
|
|
52
|
+
// change the boundaries to fit.
|
|
53
|
+
eightyseven::structure::BoxVecs::Short(three) => {
|
|
54
|
+
*three = (Vec3::from_array(*three) + remainder).to_array()
|
|
55
|
+
}
|
|
56
|
+
eightyseven::structure::BoxVecs::Full(_) => todo!(),
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Cut the input structure into cell-sized cookies that are ordered in the same manner as the
|
|
62
|
+
// solvent placement map is.
|
|
63
|
+
if !ignore.is_empty() {
|
|
64
|
+
let ignored = ignore.join(", ");
|
|
65
|
+
eprintln!("\tParticles with the following resnames will be ignored: {ignored}");
|
|
66
|
+
}
|
|
67
|
+
eprint!("\tCutting cookies... ");
|
|
68
|
+
let start = std::time::Instant::now();
|
|
69
|
+
let cookies = Cookies::new(
|
|
70
|
+
structure,
|
|
71
|
+
ignore,
|
|
72
|
+
cookie_size,
|
|
73
|
+
dimensions,
|
|
74
|
+
cutoff,
|
|
75
|
+
periodic_mode,
|
|
76
|
+
);
|
|
77
|
+
eprintln!("Took {:.3} s.", start.elapsed().as_secs_f32());
|
|
78
|
+
|
|
79
|
+
// For each location of a solvent box, go through each of the solvent bead positions and see
|
|
80
|
+
// whether it collides with a structure atom. If it does, we jot that down so we don't write
|
|
81
|
+
// that solvent bead out later.
|
|
82
|
+
eprint!("\tChecking solvent collisions... ");
|
|
83
|
+
let start = std::time::Instant::now();
|
|
84
|
+
let mut placemap = PlaceMap::new(solvent, dimensions);
|
|
85
|
+
let cutoff2 = cutoff.powi(2); // Square to avoid taking the square root of the distances.
|
|
86
|
+
for cell_pos in iter_3d(dimensions) {
|
|
87
|
+
let cookie = cookies.get(cell_pos).unwrap(); // We are sure there is a cookie here.
|
|
88
|
+
if cookie.is_empty() {
|
|
89
|
+
// If the cookie does not contain any structure beads, we can just skip
|
|
90
|
+
// measuring any distances.
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
let translation = cookies.offset(cell_pos);
|
|
94
|
+
// TODO: This is a bit dirty but necessary for the time being because we are iterating over
|
|
95
|
+
// something that we also need to modify.
|
|
96
|
+
let p = placemap.all_positions().to_vec();
|
|
97
|
+
for (idx, &solvent_bead) in p.iter().enumerate() {
|
|
98
|
+
let solvent_pos = solvent_bead + translation;
|
|
99
|
+
let mut collision = false;
|
|
100
|
+
for &cookie_bead in cookie {
|
|
101
|
+
// TODO: Consider applying this translation as a map before the iter over
|
|
102
|
+
// the solvent beads?
|
|
103
|
+
// Translate the unit cell solvent bead position to the reference frame of
|
|
104
|
+
// the bead from the cookie.
|
|
105
|
+
if solvent_pos.distance_squared(cookie_bead) < cutoff2 {
|
|
106
|
+
collision = true;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if collision {
|
|
111
|
+
// This unwrap is safe, since we are sure there is a cell here.
|
|
112
|
+
let placement = &mut placemap.get_mut(cell_pos).unwrap();
|
|
113
|
+
placement.set_occupied(idx)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
eprintln!("Took {:.3} s.", start.elapsed().as_secs_f32());
|
|
118
|
+
|
|
119
|
+
// Deal with the boundary conditions.
|
|
120
|
+
match boundary_mode {
|
|
121
|
+
BoundaryMode::Cut => {
|
|
122
|
+
let solvent_cutoff2 = solvent_cutoff.powi(2);
|
|
123
|
+
|
|
124
|
+
eprint!("\tCutting solvent to fit box... ");
|
|
125
|
+
let start = std::time::Instant::now();
|
|
126
|
+
for axis in [Axis::X, Axis::Y, Axis::Z] {
|
|
127
|
+
let max = dimensions - 1;
|
|
128
|
+
let box_dimensions = structure.boxvecs.as_vec3();
|
|
129
|
+
let (main, asz, bsz) = match axis {
|
|
130
|
+
Axis::X => (Vec3::X, dimensions.y, dimensions.z),
|
|
131
|
+
Axis::Y => (Vec3::Y, dimensions.x, dimensions.z),
|
|
132
|
+
Axis::Z => (Vec3::Z, dimensions.x, dimensions.y),
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Set up a 3×3 layer of atoms as a crown on the box dimensions at the `this` box.
|
|
136
|
+
let limit = axis.select(box_dimensions);
|
|
137
|
+
let crown: Box<[Vec3]> = axis
|
|
138
|
+
.crown(box_dimensions)
|
|
139
|
+
.map(|translation| {
|
|
140
|
+
placemap
|
|
141
|
+
.all_positions()
|
|
142
|
+
.iter()
|
|
143
|
+
.map(move |&a| a + translation)
|
|
144
|
+
// We are only interested in positions that lie at the periodic
|
|
145
|
+
// interface. Anything beyond the solvent cutoff distance from that
|
|
146
|
+
// interface is out of reach.
|
|
147
|
+
.filter(|&position| axis.select(position) <= limit + solvent_cutoff)
|
|
148
|
+
})
|
|
149
|
+
.into_iter()
|
|
150
|
+
.flatten()
|
|
151
|
+
.collect();
|
|
152
|
+
|
|
153
|
+
let translation = cookies.offset(main.as_uvec3() * max);
|
|
154
|
+
let this = placemap
|
|
155
|
+
.all_positions()
|
|
156
|
+
.iter()
|
|
157
|
+
.map(|&position| position + translation);
|
|
158
|
+
|
|
159
|
+
// We'll make a list of solvent atoms we need to reject by index.
|
|
160
|
+
let rejected: Box<[_]> = this
|
|
161
|
+
.enumerate()
|
|
162
|
+
.filter_map(|(idx, bead)| {
|
|
163
|
+
let outside = axis.select(bead) > limit;
|
|
164
|
+
let too_close = crown
|
|
165
|
+
.iter()
|
|
166
|
+
.any(|&crown_bead| bead.distance_squared(crown_bead) < solvent_cutoff2);
|
|
167
|
+
if outside || too_close {
|
|
168
|
+
Some(idx)
|
|
169
|
+
} else {
|
|
170
|
+
None
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
.collect();
|
|
174
|
+
|
|
175
|
+
// Apply the rejections to all solvent boxes on this face.
|
|
176
|
+
for b in 0..bsz {
|
|
177
|
+
for a in 0..asz {
|
|
178
|
+
let pos = match axis {
|
|
179
|
+
Axis::X => UVec3::new(max.x, a, b),
|
|
180
|
+
Axis::Y => UVec3::new(a, max.y, b),
|
|
181
|
+
Axis::Z => UVec3::new(a, b, max.z),
|
|
182
|
+
};
|
|
183
|
+
let mut pm = placemap.get_mut(pos).unwrap();
|
|
184
|
+
for &idx in &rejected {
|
|
185
|
+
pm.set_occupied(idx)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
eprintln!("Took {:.6} s.", start.elapsed().as_secs_f32());
|
|
191
|
+
}
|
|
192
|
+
// We already set the box size at the top. Nothing to do here.
|
|
193
|
+
BoundaryMode::Grow => {}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
placemap
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
#[derive(Clone, Copy)]
|
|
200
|
+
enum Axis {
|
|
201
|
+
X,
|
|
202
|
+
Y,
|
|
203
|
+
Z,
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
impl Axis {
|
|
207
|
+
const fn select(&self, v: Vec3) -> f32 {
|
|
208
|
+
v.to_array()[*self as usize]
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const fn roll(&self, mut v: Vec3) -> Vec3 {
|
|
212
|
+
let mut n = 0;
|
|
213
|
+
while n < *self as usize {
|
|
214
|
+
v = Vec3 {
|
|
215
|
+
x: v.z,
|
|
216
|
+
y: v.x,
|
|
217
|
+
z: v.y,
|
|
218
|
+
};
|
|
219
|
+
n += 1;
|
|
220
|
+
}
|
|
221
|
+
v
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const fn crown(&self, box_dimensions: Vec3) -> [Vec3; 9] {
|
|
225
|
+
let d = self.select(box_dimensions);
|
|
226
|
+
let mut crown = [
|
|
227
|
+
Vec3::new(d, -1.0, -1.0),
|
|
228
|
+
Vec3::new(d, 0.0, -1.0),
|
|
229
|
+
Vec3::new(d, 1.0, -1.0),
|
|
230
|
+
Vec3::new(d, -1.0, 0.0),
|
|
231
|
+
Vec3::new(d, 0.0, 0.0),
|
|
232
|
+
Vec3::new(d, 1.0, 0.0),
|
|
233
|
+
Vec3::new(d, -1.0, 1.0),
|
|
234
|
+
Vec3::new(d, 0.0, 1.0),
|
|
235
|
+
Vec3::new(d, 1.0, 1.0),
|
|
236
|
+
];
|
|
237
|
+
let mut i = 0;
|
|
238
|
+
while i < crown.len() {
|
|
239
|
+
crown[i] = self.roll(crown[i]);
|
|
240
|
+
i += 1;
|
|
241
|
+
}
|
|
242
|
+
crown
|
|
243
|
+
}
|
|
244
|
+
}
|
solvate/structure.rs
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
use std::io;
|
|
2
|
+
|
|
3
|
+
use eightyseven::structure::BoxVecs;
|
|
4
|
+
use eightyseven::writer::WriteGro;
|
|
5
|
+
use glam::Vec3;
|
|
6
|
+
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
|
7
|
+
|
|
8
|
+
use crate::placement::PlaceMap;
|
|
9
|
+
use crate::substitute::Substitution;
|
|
10
|
+
|
|
11
|
+
pub type Structure = eightyseven::structure::Structure;
|
|
12
|
+
|
|
13
|
+
pub trait BoxVecsExtension {
|
|
14
|
+
fn as_vec3(&self) -> glam::Vec3;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
impl BoxVecsExtension for BoxVecs {
|
|
18
|
+
fn as_vec3(&self) -> Vec3 {
|
|
19
|
+
match self {
|
|
20
|
+
// FIX: When a gro file with cuboid boundaries represented as a nine-valued vector
|
|
21
|
+
// (trailing six values are zero) is loaded, we can canonicalize it as a three-valued
|
|
22
|
+
// cuboid. This should probably be fixed in eightyseven, but I'll want to reconsider
|
|
23
|
+
// this and related issues properly at another moment.
|
|
24
|
+
&BoxVecs::Short(three) | &BoxVecs::Full([three @ .., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) => {
|
|
25
|
+
Vec3::from_array(three)
|
|
26
|
+
}
|
|
27
|
+
BoxVecs::Full(nine) => todo!("nine-value boxvecs ({nine:?}) are currently unsupported"),
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
pub fn write_structure<const PAR: bool>(
|
|
33
|
+
writer: &mut impl io::Write,
|
|
34
|
+
structure: &Structure,
|
|
35
|
+
solvent_placemap: &PlaceMap,
|
|
36
|
+
substitutions: &[Substitution],
|
|
37
|
+
buffer_size: usize,
|
|
38
|
+
) -> io::Result<()> {
|
|
39
|
+
let solvent = solvent_placemap.solvent;
|
|
40
|
+
let natoms_structure = structure.natoms();
|
|
41
|
+
let natoms_per_residue = solvent.residue_points();
|
|
42
|
+
let natoms_solvent = solvent_placemap.unoccupied_count() as usize * natoms_per_residue;
|
|
43
|
+
let natoms_substitutes = substitutions.iter().map(|s| s.natoms()).sum::<usize>();
|
|
44
|
+
let natoms = natoms_structure + natoms_solvent + natoms_substitutes;
|
|
45
|
+
|
|
46
|
+
// Write the gro header.
|
|
47
|
+
writeln!(
|
|
48
|
+
writer,
|
|
49
|
+
"solvated by {} (v{})",
|
|
50
|
+
env!("CARGO_BIN_NAME"),
|
|
51
|
+
bentopy::core::version::VERSION
|
|
52
|
+
)?;
|
|
53
|
+
writeln!(writer, "{natoms}")?;
|
|
54
|
+
|
|
55
|
+
// First, we write the original structure.
|
|
56
|
+
let start = std::time::Instant::now();
|
|
57
|
+
eprint!("Writing original structure ({natoms_structure:>9}/{natoms} total atoms)... ");
|
|
58
|
+
for line in structure.format_atom_lines_iter() {
|
|
59
|
+
writer.write_all(line.as_bytes())?;
|
|
60
|
+
}
|
|
61
|
+
eprintln!("Done, took {:.3} s.", start.elapsed().as_secs_f32());
|
|
62
|
+
|
|
63
|
+
// Now for the solvent. We will go over the cells described by the placemap, and format and
|
|
64
|
+
// write the atom lines per cell. This means that we can avoid creating the whole solvent
|
|
65
|
+
// structure at once, which would be tremendously memory intensive.
|
|
66
|
+
let start = std::time::Instant::now();
|
|
67
|
+
eprintln!("Writing solvent structure ({natoms_solvent:>9}/{natoms} total atoms)...");
|
|
68
|
+
let mut placements = solvent_placemap.iter_atoms_chunks();
|
|
69
|
+
let mut particle_buffer = Vec::new();
|
|
70
|
+
let mut n = 0;
|
|
71
|
+
'write_atoms: loop {
|
|
72
|
+
particle_buffer.clear();
|
|
73
|
+
|
|
74
|
+
// TODO: I think it's scary that buffer_size could feasibly be zero.
|
|
75
|
+
'fill_buffer: while particle_buffer.len() < buffer_size {
|
|
76
|
+
match placements.next() {
|
|
77
|
+
Some(sp) => particle_buffer.extend(sp),
|
|
78
|
+
None if particle_buffer.is_empty() => break 'write_atoms,
|
|
79
|
+
None => break 'fill_buffer,
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if PAR {
|
|
84
|
+
let lines: String = particle_buffer
|
|
85
|
+
.par_iter()
|
|
86
|
+
.map(Structure::format_atom_line)
|
|
87
|
+
.collect();
|
|
88
|
+
writer.write_all(lines.as_bytes())?;
|
|
89
|
+
} else {
|
|
90
|
+
// TODO: Even this could be done with a buffer of lines that fills up for a while,
|
|
91
|
+
// and then we write it. So non-parallel but still buffered. But in a way that is
|
|
92
|
+
// equivalent to making sure we use a BufWriter. Maybe first benchmark if requiring a
|
|
93
|
+
// BufWriter in the function signature (we already use it at the call site) makes a
|
|
94
|
+
// difference (it shouldn't because the impl Write makes it generic over our
|
|
95
|
+
// BufWriter). Only then try to see if rolling our own helps out.
|
|
96
|
+
for atom in &particle_buffer {
|
|
97
|
+
let line = Structure::format_atom_line(atom);
|
|
98
|
+
writer.write_all(line.as_bytes())?;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Report the progress.
|
|
103
|
+
n += particle_buffer.len();
|
|
104
|
+
let progress = n as f32 / natoms_solvent as f32;
|
|
105
|
+
let percentage = progress * 100.0;
|
|
106
|
+
let delta = std::time::Instant::now() - start;
|
|
107
|
+
let time_left = delta.as_secs_f32() * (progress.recip() - 1.0);
|
|
108
|
+
eprint!(
|
|
109
|
+
"\r({percentage:>4.1}%) Wrote {n:>9}/{natoms_solvent} solvent atoms. (ETA {time_left:>4.0} s) "
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
eprintln!();
|
|
113
|
+
eprintln!(
|
|
114
|
+
"Done writing solvent atoms, took {:.3} s.",
|
|
115
|
+
start.elapsed().as_secs_f32()
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
if !substitutions.is_empty() {
|
|
119
|
+
// Write out the substitutes at the end of the structure.
|
|
120
|
+
let start = std::time::Instant::now();
|
|
121
|
+
eprintln!("Writing substitutes ({natoms_substitutes:>9}/{natoms} total atoms)...");
|
|
122
|
+
for substitution in substitutions {
|
|
123
|
+
let name = substitution.name();
|
|
124
|
+
let number = substitution.natoms();
|
|
125
|
+
eprint!("\t{name}, {number} atoms... ");
|
|
126
|
+
let start = std::time::Instant::now();
|
|
127
|
+
let mut placements = substitution.iter_atoms();
|
|
128
|
+
loop {
|
|
129
|
+
particle_buffer.clear();
|
|
130
|
+
particle_buffer.extend(placements.by_ref().take(buffer_size));
|
|
131
|
+
if particle_buffer.is_empty() {
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let beads = &particle_buffer;
|
|
136
|
+
if PAR {
|
|
137
|
+
let lines: String = beads.par_iter().map(Structure::format_atom_line).collect();
|
|
138
|
+
writer.write_all(lines.as_bytes())?;
|
|
139
|
+
} else {
|
|
140
|
+
for atom in beads {
|
|
141
|
+
let line = Structure::format_atom_line(atom);
|
|
142
|
+
writer.write_all(line.as_bytes())?;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
eprintln!("Took {:.3} s.", start.elapsed().as_secs_f32());
|
|
147
|
+
}
|
|
148
|
+
eprintln!(
|
|
149
|
+
"Done writing substitutes, took {:.3} s.",
|
|
150
|
+
start.elapsed().as_secs_f32()
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Finally, we write the box vectors.
|
|
155
|
+
writeln!(writer, "{}", structure.boxvecs())?;
|
|
156
|
+
|
|
157
|
+
writer.flush()?;
|
|
158
|
+
|
|
159
|
+
Ok(())
|
|
160
|
+
}
|
solvate/substitute.rs
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
use eightyseven::structure::{Atom, AtomName};
|
|
2
|
+
|
|
3
|
+
use crate::{args::Substitute, placement::PlaceMap};
|
|
4
|
+
|
|
5
|
+
pub struct Substitution<'sol> {
|
|
6
|
+
name: AtomName,
|
|
7
|
+
placemap: PlaceMap<'sol>,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
impl<'sol> Substitution<'sol> {
|
|
11
|
+
pub fn new(name: impl Into<AtomName>, placemap: PlaceMap<'sol>) -> Self {
|
|
12
|
+
Self {
|
|
13
|
+
name: name.into(),
|
|
14
|
+
placemap,
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
pub fn natoms(&self) -> usize {
|
|
19
|
+
self.placemap.unoccupied_count() as usize
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
pub fn name(&self) -> &str {
|
|
23
|
+
&self.name
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// Iterate over all atoms in this [`Substitution`].
|
|
27
|
+
pub fn iter_atoms(&self) -> impl Iterator<Item = Atom> + '_ {
|
|
28
|
+
self.placemap.iter_positions().enumerate().map(|(i, pos)| {
|
|
29
|
+
let num = i as u32 + 1;
|
|
30
|
+
Atom {
|
|
31
|
+
resnum: num,
|
|
32
|
+
resname: self.name,
|
|
33
|
+
atomname: self.name,
|
|
34
|
+
atomnum: num,
|
|
35
|
+
position: pos,
|
|
36
|
+
velocity: Default::default(),
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// Fuse the contents of two [`Substitution`] maps.
|
|
42
|
+
pub fn glue(&mut self, substitution: &Substitution<'sol>) {
|
|
43
|
+
self.placemap &= substitution.placemap.clone();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
pub fn substitute<'sol>(
|
|
48
|
+
rng: &mut rand::rngs::StdRng,
|
|
49
|
+
placemap: &mut PlaceMap<'sol>,
|
|
50
|
+
substitutes: &[Substitute],
|
|
51
|
+
) -> Vec<Substitution<'sol>> {
|
|
52
|
+
use rand::seq::SliceRandom;
|
|
53
|
+
|
|
54
|
+
// FIXME: Make this into a nicer error. Already explains the situation well, but the assert
|
|
55
|
+
// failure is ugly in the output.
|
|
56
|
+
let n_free = placemap.unoccupied_count();
|
|
57
|
+
let n_subs = substitutes.iter().map(|s| s.number).sum::<u64>();
|
|
58
|
+
assert!(
|
|
59
|
+
n_subs <= n_free,
|
|
60
|
+
"the number of substitutions that are specified ({n_subs}) exceeds the number of \
|
|
61
|
+
solvent positions that can be substituted ({n_free})"
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
let mut substitutions = Vec::new();
|
|
65
|
+
let mut zebra = Vec::new();
|
|
66
|
+
for substitute in substitutes {
|
|
67
|
+
eprint!(
|
|
68
|
+
"\tSubstituting {} solvent locations with {}... ",
|
|
69
|
+
substitute.number, substitute.name
|
|
70
|
+
);
|
|
71
|
+
let start = std::time::Instant::now();
|
|
72
|
+
if substitute.number == 0 {
|
|
73
|
+
eprintln!("Skipping.");
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let n_free = placemap.unoccupied_count();
|
|
78
|
+
if n_free == 0 {
|
|
79
|
+
eprintln!("No free solvent spots left.");
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
zebra.clear();
|
|
84
|
+
zebra.resize(n_free as usize, false);
|
|
85
|
+
zebra[..substitute.number as usize].fill(true);
|
|
86
|
+
zebra.shuffle(rng);
|
|
87
|
+
let mut zebra = zebra.iter();
|
|
88
|
+
|
|
89
|
+
let mut old_placemap = placemap.clone(); // Becomes the map for our substitute.
|
|
90
|
+
for mut placement in placemap.iter_placements_mut() {
|
|
91
|
+
for i in 0..placement.len() {
|
|
92
|
+
let is_occupied = placement.get(i);
|
|
93
|
+
// If this position is not already occupied by a non-solvent bead, and if our
|
|
94
|
+
// shuffled values indicate that we need to place a substitute here, we set this
|
|
95
|
+
// bead to occupied in the solvent placemap.
|
|
96
|
+
if !is_occupied && *zebra.next().unwrap() {
|
|
97
|
+
placement.set_occupied(i)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
assert_eq!(zebra.len(), 0);
|
|
102
|
+
|
|
103
|
+
old_placemap ^= placemap.clone();
|
|
104
|
+
let substitute_placemap = !old_placemap;
|
|
105
|
+
substitutions.push(Substitution::new(
|
|
106
|
+
substitute.name.as_str(),
|
|
107
|
+
substitute_placemap,
|
|
108
|
+
));
|
|
109
|
+
eprintln!("Took {:.3} s.", start.elapsed().as_secs_f32());
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
substitutions
|
|
113
|
+
}
|