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/main.rs
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
use std::cmp::Reverse;
|
|
2
|
+
use std::io::{self, Write};
|
|
3
|
+
|
|
4
|
+
use anyhow::Context;
|
|
5
|
+
use args::SortBehavior;
|
|
6
|
+
use clap::Parser;
|
|
7
|
+
use eightyseven::reader::ReadGro;
|
|
8
|
+
use rand::SeedableRng;
|
|
9
|
+
|
|
10
|
+
use crate::args::Args;
|
|
11
|
+
pub(crate) use crate::args::{BoundaryMode, PeriodicMode};
|
|
12
|
+
use crate::solvate::solvate;
|
|
13
|
+
use crate::structure::{BoxVecsExtension, Structure, write_structure};
|
|
14
|
+
use crate::substitute::substitute;
|
|
15
|
+
|
|
16
|
+
mod args;
|
|
17
|
+
mod convert;
|
|
18
|
+
mod cookies;
|
|
19
|
+
mod placement;
|
|
20
|
+
mod solvate;
|
|
21
|
+
mod structure;
|
|
22
|
+
mod substitute;
|
|
23
|
+
mod water;
|
|
24
|
+
|
|
25
|
+
fn main() -> anyhow::Result<()> {
|
|
26
|
+
let config = Args::parse();
|
|
27
|
+
let cutoff = config.cutoff.unwrap_or(config.water_type.default_cutoff());
|
|
28
|
+
let solvent_cutoff = config
|
|
29
|
+
.solvent_cutoff
|
|
30
|
+
.unwrap_or(config.water_type.default_solvent_cutoff());
|
|
31
|
+
eprintln!("Solvent-structure cutoff is set to {cutoff} nm.");
|
|
32
|
+
eprintln!("Solvent-solvent cutoff is set to {solvent_cutoff} nm.");
|
|
33
|
+
|
|
34
|
+
eprint!("Loading structure {:?}... ", config.input);
|
|
35
|
+
let start = std::time::Instant::now();
|
|
36
|
+
let input_path = &config.input;
|
|
37
|
+
let mut structure = Structure::open_gro(input_path)
|
|
38
|
+
.with_context(|| format!("Failed to open structure {input_path:?}"))?;
|
|
39
|
+
eprintln!("Took {:.3} s.", start.elapsed().as_secs_f32());
|
|
40
|
+
|
|
41
|
+
eprintln!("Solvating...");
|
|
42
|
+
let start = std::time::Instant::now();
|
|
43
|
+
let solvent = config.water_type.into();
|
|
44
|
+
let mut placemap = solvate(
|
|
45
|
+
&mut structure,
|
|
46
|
+
solvent,
|
|
47
|
+
cutoff,
|
|
48
|
+
solvent_cutoff,
|
|
49
|
+
&config.ignore,
|
|
50
|
+
config.center,
|
|
51
|
+
config.boundary_mode,
|
|
52
|
+
config.periodic_mode,
|
|
53
|
+
);
|
|
54
|
+
eprintln!("Took {:.3} s.", start.elapsed().as_secs_f32());
|
|
55
|
+
|
|
56
|
+
let volume = structure
|
|
57
|
+
.boxvecs
|
|
58
|
+
.as_vec3()
|
|
59
|
+
.as_dvec3()
|
|
60
|
+
.to_array()
|
|
61
|
+
.iter()
|
|
62
|
+
.product();
|
|
63
|
+
let neutralizing_ions = config.charge.and_then(|c| c.bake());
|
|
64
|
+
let substitutes = config
|
|
65
|
+
.substitutes
|
|
66
|
+
.into_iter()
|
|
67
|
+
.map(|sc| sc.bake(volume, placemap.unoccupied_count() as u64))
|
|
68
|
+
.chain(neutralizing_ions)
|
|
69
|
+
.collect::<Vec<_>>();
|
|
70
|
+
|
|
71
|
+
let substitutions = if !substitutes.is_empty() {
|
|
72
|
+
eprintln!("Making substitutions...");
|
|
73
|
+
let start = std::time::Instant::now();
|
|
74
|
+
let mut rng = match config.seed {
|
|
75
|
+
Some(seed) => rand::rngs::StdRng::seed_from_u64(seed),
|
|
76
|
+
None => rand::rngs::StdRng::from_os_rng(),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
let mut subs = substitute(&mut rng, &mut placemap, &substitutes);
|
|
80
|
+
|
|
81
|
+
// If desired, glue the substitutes with identical names together.
|
|
82
|
+
// NOTE: Sorry for the double negative here ;)
|
|
83
|
+
if !config.no_combine_substitutes {
|
|
84
|
+
let mut piles: Vec<substitute::Substitution<'_>> = Vec::new();
|
|
85
|
+
for substitution in subs {
|
|
86
|
+
if let Some(pile) = piles
|
|
87
|
+
.iter_mut()
|
|
88
|
+
.find(|pile| pile.name() == substitution.name())
|
|
89
|
+
{
|
|
90
|
+
// Tack this substitution's replacements onto the existing pile.
|
|
91
|
+
pile.glue(&substitution)
|
|
92
|
+
} else {
|
|
93
|
+
piles.push(substitution);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
subs = piles;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Order them.
|
|
101
|
+
match config.sort_substitutes {
|
|
102
|
+
SortBehavior::Size => subs.sort_by_cached_key(|s| Reverse(s.natoms())),
|
|
103
|
+
SortBehavior::RevSize => subs.sort_by_cached_key(|s| s.natoms()),
|
|
104
|
+
SortBehavior::Alphabetical => subs.sort_by_key(|s| s.name().to_string()),
|
|
105
|
+
SortBehavior::RevAlphabetical => subs.sort_by_key(|s| Reverse(s.name().to_string())),
|
|
106
|
+
SortBehavior::No => {} // Nothing to do.
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
eprintln!("Took {:.3} s.", start.elapsed().as_secs_f32());
|
|
110
|
+
subs
|
|
111
|
+
} else {
|
|
112
|
+
Vec::new()
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
eprintln!("Writing to {:?}...", config.output);
|
|
116
|
+
let start = std::time::Instant::now();
|
|
117
|
+
let output_path = &config.output;
|
|
118
|
+
let file = std::fs::File::create(output_path)
|
|
119
|
+
.with_context(|| format!("Failed to create output file {output_path:?}"))?;
|
|
120
|
+
let mut writer = io::BufWriter::new(file);
|
|
121
|
+
let buffer_size = config.buffer_size;
|
|
122
|
+
if config.no_write_parallel {
|
|
123
|
+
write_structure::<false>(
|
|
124
|
+
&mut writer,
|
|
125
|
+
&structure,
|
|
126
|
+
&placemap,
|
|
127
|
+
&substitutions,
|
|
128
|
+
buffer_size,
|
|
129
|
+
)
|
|
130
|
+
.with_context(|| {
|
|
131
|
+
format!("Encountered a problem while writing the output structure to {output_path:?}")
|
|
132
|
+
})?;
|
|
133
|
+
} else {
|
|
134
|
+
write_structure::<true>(
|
|
135
|
+
&mut writer,
|
|
136
|
+
&structure,
|
|
137
|
+
&placemap,
|
|
138
|
+
&substitutions,
|
|
139
|
+
buffer_size,
|
|
140
|
+
)
|
|
141
|
+
.with_context(|| {
|
|
142
|
+
format!("Encountered a problem while (in parallel) writing the output structure to {output_path:?}")
|
|
143
|
+
})?;
|
|
144
|
+
}
|
|
145
|
+
eprintln!("Writing took {:.3} s", start.elapsed().as_secs_f32());
|
|
146
|
+
|
|
147
|
+
let solvent_name = solvent.resname();
|
|
148
|
+
let nsolvent = placemap.unoccupied_count() as usize;
|
|
149
|
+
let mut topology = vec![(solvent_name, nsolvent)];
|
|
150
|
+
topology.extend(substitutions.iter().map(|s| (s.name(), s.natoms())));
|
|
151
|
+
let mut stdout = io::stdout();
|
|
152
|
+
match &config.append_topol {
|
|
153
|
+
Some(path) => eprintln!("Appending solvent topology lines to {path:?} and stdout:"),
|
|
154
|
+
None => eprintln!("Printing Solvent topology lines to stdout:"),
|
|
155
|
+
}
|
|
156
|
+
let mut topol = config
|
|
157
|
+
.append_topol
|
|
158
|
+
.map(|path| {
|
|
159
|
+
std::fs::OpenOptions::new()
|
|
160
|
+
.create(true)
|
|
161
|
+
.append(true)
|
|
162
|
+
.open(&path)
|
|
163
|
+
.with_context(|| format!("Failed to open topology file {path:?}"))
|
|
164
|
+
})
|
|
165
|
+
.transpose()?;
|
|
166
|
+
for (name, natoms) in topology {
|
|
167
|
+
let s = format!("{name}\t{natoms}\n");
|
|
168
|
+
stdout.write_all(s.as_bytes())?;
|
|
169
|
+
if let Some(topol) = &mut topol {
|
|
170
|
+
topol
|
|
171
|
+
.write_all(s.as_bytes())
|
|
172
|
+
.with_context(|| format!("Failed to append component '{name}' to topology file"))?;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
Ok(())
|
|
177
|
+
}
|
solvate/placement.rs
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
use eightyseven::structure::Atom;
|
|
2
|
+
use glam::{UVec3, Vec3};
|
|
3
|
+
|
|
4
|
+
use crate::{convert::Convert, water::Water};
|
|
5
|
+
|
|
6
|
+
/// Return an index into a linear array that represents items on a 3-dimensional grid in a z-major
|
|
7
|
+
/// ordering.
|
|
8
|
+
pub fn index_3d(pos: UVec3, dimensions: UVec3) -> usize {
|
|
9
|
+
// Casts to usize here serve to avoid wrapping when indexing _very_ large sizes. You never know!
|
|
10
|
+
pos.x as usize
|
|
11
|
+
+ pos.y as usize * dimensions.x as usize
|
|
12
|
+
+ pos.z as usize * dimensions.x as usize * dimensions.y as usize
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/// Iterate over all positions between `(0, 0, 0)` and `dimensions`.
|
|
16
|
+
pub fn iter_3d(dimensions: UVec3) -> impl Iterator<Item = UVec3> {
|
|
17
|
+
let [dx, dy, dz] = dimensions.to_array();
|
|
18
|
+
(0..dz).flat_map(move |z| (0..dy).flat_map(move |y| (0..dx).map(move |x| UVec3::new(x, y, z))))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
#[derive(Clone)]
|
|
22
|
+
pub struct PlaceMap<'s> {
|
|
23
|
+
pub solvent: Water,
|
|
24
|
+
positions: Box<[Vec3]>,
|
|
25
|
+
dimensions: UVec3,
|
|
26
|
+
// Invariant: length = (solvent.natoms() / 8).ceil() * dimensions.product()
|
|
27
|
+
// Invariant: Any wasted bits are always set to zero.
|
|
28
|
+
// Value is 0 if the place of this solvent bead is not otherwise occupied.
|
|
29
|
+
// Conversely, if the value of a bit is 1, a solvent bead cannot be placed there.
|
|
30
|
+
//
|
|
31
|
+
// To illustrate what these wasted bits are, the figure below shows meaningful bits as `*`, and
|
|
32
|
+
// the wasted bits as `0`. The bytes are stored in first to last order, and the bits in the
|
|
33
|
+
// bytes are just treated according to their significance.
|
|
34
|
+
//
|
|
35
|
+
// ******** ******** 0000****
|
|
36
|
+
// ^^^^-- Wasted bits.
|
|
37
|
+
placements: Box<[u8]>,
|
|
38
|
+
_remove: std::marker::PhantomData<&'s ()>,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
impl<'s> PlaceMap<'s> {
|
|
42
|
+
/// Creates a new [`PlaceMap`].
|
|
43
|
+
pub fn new(solvent: Water, size: UVec3) -> Self {
|
|
44
|
+
let positions = solvent.positions().collect::<Box<[_]>>();
|
|
45
|
+
let n_cells = size.x as usize * size.y as usize * size.z as usize;
|
|
46
|
+
let n_bytes = positions.len().div_ceil(8);
|
|
47
|
+
let placements = vec![0x00; n_bytes * n_cells].into_boxed_slice();
|
|
48
|
+
Self {
|
|
49
|
+
dimensions: size,
|
|
50
|
+
solvent,
|
|
51
|
+
positions,
|
|
52
|
+
placements,
|
|
53
|
+
_remove: std::marker::PhantomData,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// Returns the number of cells that make up this [`PlaceMap`]
|
|
58
|
+
pub fn n_cells(&self) -> usize {
|
|
59
|
+
self.dimensions.x as usize * self.dimensions.y as usize * self.dimensions.z as usize
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// Returns the number of positions to be considered for solvent-structure collisions.
|
|
63
|
+
const fn npositions(&self) -> usize {
|
|
64
|
+
self.positions.len()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// Returns a reference to all solvent positions available in this [`PlaceMap`].
|
|
68
|
+
pub fn all_positions(&self) -> &[Vec3] {
|
|
69
|
+
&self.positions
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// TODO: Revisit, because there's some wasteful go-around, here. We're first creating Atoms
|
|
73
|
+
// from positions which then get turned back into positions.
|
|
74
|
+
/// Returns the positions of the solvent particles in this [`PlaceMap`].
|
|
75
|
+
pub fn iter_positions(&self) -> impl Iterator<Item = eightyseven::structure::Vec3> + '_ {
|
|
76
|
+
// TODO: Consider if these are hard-equivalent or soft-equivalent in terms of performance.
|
|
77
|
+
// self.iter_atoms_chunks().flatten()
|
|
78
|
+
// My worry is that the flatten version will still allocate more than the one below.
|
|
79
|
+
self.iter_placements()
|
|
80
|
+
.flat_map(|(translation, placement)| {
|
|
81
|
+
self.solvent.substitute_positions(placement, translation)
|
|
82
|
+
})
|
|
83
|
+
.map(|pos| pos.convert())
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
pub fn iter_atoms_chunks(&self) -> impl Iterator<Item = Box<[Atom]>> + '_ {
|
|
87
|
+
self.iter_placements()
|
|
88
|
+
.map(|(translation, placement)| self.solvent.spray(placement, translation).collect())
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// Returns iterator over the [`Placement`]s with their associated translations stored in this
|
|
92
|
+
/// [`PlaceMap`].
|
|
93
|
+
pub fn iter_placements(&'_ self) -> impl Iterator<Item = (Vec3, Placement<'_>)> {
|
|
94
|
+
iter_3d(self.dimensions).map(|cell_pos| {
|
|
95
|
+
let translation = cell_pos.as_vec3() * self.solvent.dimensions();
|
|
96
|
+
let placement = self.get(cell_pos).unwrap();
|
|
97
|
+
(translation, placement)
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
pub fn iter_placements_mut(&'_ mut self) -> impl Iterator<Item = PlacementMut<'_>> {
|
|
102
|
+
let n_beads = self.npositions();
|
|
103
|
+
let n_bytes = n_beads.div_ceil(8);
|
|
104
|
+
self.placements
|
|
105
|
+
.chunks_exact_mut(n_bytes)
|
|
106
|
+
.map(move |bytes| PlacementMut::new(bytes, n_beads))
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
pub fn get_mut(&'_ mut self, pos: UVec3) -> Option<PlacementMut<'_>> {
|
|
110
|
+
if pos.x >= self.dimensions.x || pos.y >= self.dimensions.y || pos.z >= self.dimensions.z {
|
|
111
|
+
return None;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// TODO: Is it wasteful to 'compute' this here? Or does it reduce down well?
|
|
115
|
+
let n_beads = self.npositions();
|
|
116
|
+
let n_bytes = n_beads.div_ceil(8);
|
|
117
|
+
let idx = index_3d(pos, self.dimensions);
|
|
118
|
+
// This unwrap should be safe, since we checked at the start of the function.
|
|
119
|
+
let bytes = self.placements.chunks_exact_mut(n_bytes).nth(idx).unwrap();
|
|
120
|
+
|
|
121
|
+
Some(PlacementMut::new(bytes, n_beads))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
pub fn get(&'_ self, pos: UVec3) -> Option<Placement<'_>> {
|
|
125
|
+
if pos.x >= self.dimensions.x || pos.y >= self.dimensions.y || pos.z >= self.dimensions.z {
|
|
126
|
+
return None;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let n_beads = self.npositions();
|
|
130
|
+
let n_bytes = n_beads.div_ceil(8);
|
|
131
|
+
let idx = index_3d(pos, self.dimensions);
|
|
132
|
+
// This unwrap should be safe, since we checked at the start of the function.
|
|
133
|
+
let bytes = self.placements.chunks_exact(n_bytes).nth(idx).unwrap();
|
|
134
|
+
|
|
135
|
+
Some(Placement::new(bytes, n_beads))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/// Return the count of the positions that are marked as occupied.
|
|
139
|
+
pub fn occupied_count(&self) -> u64 {
|
|
140
|
+
// Since a bit is marked as 1 iff the position it represents is occupied, we can just
|
|
141
|
+
// perform a popcount over the whole bit array.
|
|
142
|
+
// Note that we don't have to worry about the wasted bits, since these are always set to
|
|
143
|
+
// zero.
|
|
144
|
+
self.placements.iter().map(|b| b.count_ones() as u64).sum()
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/// Return the count of unoccupied positions.
|
|
148
|
+
pub fn unoccupied_count(&self) -> u64 {
|
|
149
|
+
let total = self.npositions() * self.n_cells();
|
|
150
|
+
total as u64 - self.occupied_count()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/// Repair all wasted bits in the type.
|
|
154
|
+
fn repair(&mut self) {
|
|
155
|
+
let n_beads = self.npositions();
|
|
156
|
+
let n_bytes = n_beads.div_ceil(8);
|
|
157
|
+
self.placements
|
|
158
|
+
.chunks_exact_mut(n_bytes)
|
|
159
|
+
.map(|chunk| PlacementMut::new(chunk, n_beads))
|
|
160
|
+
.for_each(|mut p| p.repair())
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
impl std::ops::BitOrAssign for PlaceMap<'_> {
|
|
165
|
+
// Invariant: The or operation will not break the invariant as long as the two operands are
|
|
166
|
+
// also valid.
|
|
167
|
+
fn bitor_assign(&mut self, rhs: Self) {
|
|
168
|
+
assert_eq!(self.dimensions, rhs.dimensions);
|
|
169
|
+
assert_eq!(self.n_cells(), rhs.n_cells());
|
|
170
|
+
assert_eq!(self.solvent, rhs.solvent);
|
|
171
|
+
|
|
172
|
+
self.placements
|
|
173
|
+
.iter_mut()
|
|
174
|
+
.zip(rhs.placements)
|
|
175
|
+
.for_each(|(s, r)| {
|
|
176
|
+
*s |= r;
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
impl std::ops::BitAndAssign for PlaceMap<'_> {
|
|
182
|
+
// Invariant: The and operation will not break the invariant as long as the two operands are
|
|
183
|
+
// also valid.
|
|
184
|
+
fn bitand_assign(&mut self, rhs: Self) {
|
|
185
|
+
assert_eq!(self.dimensions, rhs.dimensions);
|
|
186
|
+
assert_eq!(self.n_cells(), rhs.n_cells());
|
|
187
|
+
assert_eq!(self.solvent, rhs.solvent);
|
|
188
|
+
|
|
189
|
+
self.placements
|
|
190
|
+
.iter_mut()
|
|
191
|
+
.zip(rhs.placements)
|
|
192
|
+
.for_each(|(s, r)| {
|
|
193
|
+
*s &= r;
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
impl std::ops::BitXorAssign for PlaceMap<'_> {
|
|
199
|
+
// Invariant: The xor operation will not break the invariant as long as the two operands are
|
|
200
|
+
// also valid.
|
|
201
|
+
fn bitxor_assign(&mut self, rhs: Self) {
|
|
202
|
+
assert_eq!(self.dimensions, rhs.dimensions);
|
|
203
|
+
assert_eq!(self.n_cells(), rhs.n_cells());
|
|
204
|
+
assert_eq!(self.solvent, rhs.solvent);
|
|
205
|
+
|
|
206
|
+
self.placements
|
|
207
|
+
.iter_mut()
|
|
208
|
+
.zip(rhs.placements)
|
|
209
|
+
.for_each(|(s, r)| {
|
|
210
|
+
*s ^= r;
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
impl std::ops::Not for PlaceMap<'_> {
|
|
216
|
+
type Output = Self;
|
|
217
|
+
|
|
218
|
+
fn not(mut self) -> Self::Output {
|
|
219
|
+
// In order to preserve the invariant that the wasted bits are always zero, we need to be
|
|
220
|
+
// careful, here.
|
|
221
|
+
// First, we will go over all the placement bytes and apply a not.
|
|
222
|
+
self.placements.iter_mut().for_each(|s| *s = !(*s));
|
|
223
|
+
|
|
224
|
+
// After that, we go and repair the wasted bits, setting them to zero.
|
|
225
|
+
self.repair();
|
|
226
|
+
|
|
227
|
+
self
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
pub struct Placement<'ps> {
|
|
232
|
+
pub(crate) bytes: &'ps [u8],
|
|
233
|
+
pub(crate) len: usize,
|
|
234
|
+
idx: usize,
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
impl<'ps> Placement<'ps> {
|
|
238
|
+
fn new(bytes: &'ps [u8], len: usize) -> Self {
|
|
239
|
+
Self { bytes, len, idx: 0 }
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
impl Iterator for Placement<'_> {
|
|
244
|
+
type Item = bool;
|
|
245
|
+
|
|
246
|
+
fn next(&mut self) -> Option<Self::Item> {
|
|
247
|
+
if self.idx >= self.len {
|
|
248
|
+
return None;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
let byte = self.bytes[self.idx / 8];
|
|
252
|
+
let bit = (byte >> (self.idx % 8)) & 1;
|
|
253
|
+
self.idx += 1;
|
|
254
|
+
Some(bit > 0)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
pub struct PlacementMut<'ps> {
|
|
259
|
+
bytes: &'ps mut [u8],
|
|
260
|
+
len: usize,
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
impl<'ps> PlacementMut<'ps> {
|
|
264
|
+
pub(crate) fn new(bytes: &'ps mut [u8], len: usize) -> Self {
|
|
265
|
+
Self { bytes, len }
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
pub fn len(&self) -> usize {
|
|
269
|
+
self.len
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/// Set the bit value for a solvent bead to represent that its spot is occupied.
|
|
273
|
+
///
|
|
274
|
+
/// This communicates that a solvent particle cannot be rendered at this spot.
|
|
275
|
+
pub fn set_occupied(&mut self, idx: usize) {
|
|
276
|
+
if idx >= self.len {
|
|
277
|
+
panic!(
|
|
278
|
+
"index out of range (length was {} but index was {idx})",
|
|
279
|
+
self.len
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
let byte = &mut self.bytes[idx / 8];
|
|
284
|
+
*byte |= 1 << (idx % 8);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/// Get the value of the bit at `idx`.
|
|
288
|
+
///
|
|
289
|
+
/// # Panics
|
|
290
|
+
///
|
|
291
|
+
/// This function will panic if the provided `idx` exceeds the range represented by this value.
|
|
292
|
+
pub fn get(&self, idx: usize) -> bool {
|
|
293
|
+
if idx >= self.len {
|
|
294
|
+
panic!(
|
|
295
|
+
"index out of range (length was {} but index was {idx})",
|
|
296
|
+
self.len
|
|
297
|
+
)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
let byte = self.bytes[idx / 8];
|
|
301
|
+
byte >> (idx % 8) & 1 > 0
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/// Repair the wasted bits at the end of this [`PlacementMut`] by setting them to zero.
|
|
305
|
+
///
|
|
306
|
+
/// This operation is idempotent.
|
|
307
|
+
fn repair(&mut self) {
|
|
308
|
+
let n_bytes = self.bytes.len();
|
|
309
|
+
let n_bits = n_bytes * u8::BITS as usize;
|
|
310
|
+
if n_bits == self.len {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
let Some(last) = self.bytes.last_mut() else {
|
|
315
|
+
return;
|
|
316
|
+
};
|
|
317
|
+
let n_wasted = n_bits - self.len;
|
|
318
|
+
|
|
319
|
+
let mask = (1u8 << (u8::BITS as usize - n_wasted)) - 1;
|
|
320
|
+
*last &= mask;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
#[cfg(test)]
|
|
325
|
+
mod tests {
|
|
326
|
+
use super::*;
|
|
327
|
+
|
|
328
|
+
#[test]
|
|
329
|
+
fn repair_placement_mut() {
|
|
330
|
+
let mut bytes = [0xff, 0xff, 0xff];
|
|
331
|
+
let mut p = PlacementMut::new(&mut bytes, 20);
|
|
332
|
+
|
|
333
|
+
assert_eq!(*p.bytes.last().unwrap(), 0b11111111);
|
|
334
|
+
p.repair();
|
|
335
|
+
assert_eq!(*p.bytes.last().unwrap(), 0b00001111);
|
|
336
|
+
p.repair();
|
|
337
|
+
assert_eq!(*p.bytes.last().unwrap(), 0b00001111);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/// Verify that the repair that occurs within the not operation works.
|
|
341
|
+
///
|
|
342
|
+
/// If the repair is incorrect, we would expect to see `unoccupied_count > natoms_total` after
|
|
343
|
+
/// the not.
|
|
344
|
+
#[test]
|
|
345
|
+
fn check_not_repair() {
|
|
346
|
+
let solvent = Water::Martini;
|
|
347
|
+
// TODO: Currently, we're testing a trivially easy case.
|
|
348
|
+
// // Make sure we have a structure size that does not neatly fit in some number of bytes.
|
|
349
|
+
// if solvent.positions().count() % 8 == 0 {
|
|
350
|
+
// solvent.atoms.pop();
|
|
351
|
+
// }
|
|
352
|
+
let natoms = solvent.positions().count();
|
|
353
|
+
let size = UVec3::new(3, 5, 7);
|
|
354
|
+
let mut placemap = PlaceMap::new(solvent, size);
|
|
355
|
+
let natoms_total = (natoms * placemap.n_cells()) as u64;
|
|
356
|
+
let n_occupied = 0;
|
|
357
|
+
|
|
358
|
+
// Set some atom to be occupied.
|
|
359
|
+
placemap
|
|
360
|
+
.get_mut(UVec3::new(1, 3, 5))
|
|
361
|
+
.unwrap()
|
|
362
|
+
.set_occupied(161);
|
|
363
|
+
// Update natoms_total and n_occupied to reflect this.
|
|
364
|
+
let natoms_total = natoms_total - 1;
|
|
365
|
+
let n_occupied = n_occupied + 1;
|
|
366
|
+
|
|
367
|
+
assert_eq!(placemap.unoccupied_count(), natoms_total);
|
|
368
|
+
assert_eq!(placemap.occupied_count(), n_occupied);
|
|
369
|
+
|
|
370
|
+
placemap = !placemap;
|
|
371
|
+
|
|
372
|
+
assert_eq!(placemap.unoccupied_count(), n_occupied);
|
|
373
|
+
assert_eq!(placemap.occupied_count(), natoms_total);
|
|
374
|
+
|
|
375
|
+
placemap = !placemap;
|
|
376
|
+
|
|
377
|
+
assert_eq!(placemap.unoccupied_count(), natoms_total);
|
|
378
|
+
assert_eq!(placemap.occupied_count(), n_occupied);
|
|
379
|
+
}
|
|
380
|
+
}
|