Compare commits

...

5 Commits

Author SHA1 Message Date
Emilia Allison f50db96697
Precompute in calculate_map
Gitea Scan/plate-tool/pipeline/head This commit looks good Details
should help performance a wee bit
2024-11-16 22:19:03 -06:00
Emilia Allison 3d8445ec82
Formatting on tests
Gitea Scan/plate-tool/pipeline/head This commit looks good Details
2024-11-15 15:28:34 -06:00
Emilia Allison 04554f634b
fix: Optimize region transfer map 2024-11-15 15:28:01 -06:00
Emilia Allison ad3bbd3649
fix: Typo in transfer validation 2024-11-15 15:27:18 -06:00
Emilia Allison 03d7f08e63
Fix Jenkinsfile for deployment
Gitea Scan/plate-tool/pipeline/head This commit looks good Details
2024-11-13 17:18:21 -06:00
2 changed files with 170 additions and 102 deletions

2
Jenkinsfile vendored
View File

@ -13,7 +13,7 @@ pipeline {
sh '''
. "$HOME/.cargo/env"
cd plate-tool-web
trunk build --release --public-url "cool-stuff/$OUTPUT_DIR/"
trunk build --release --public-url "./"
'''
}
}

View File

@ -131,10 +131,8 @@ impl TransferRegion {
pub fn calculate_map(&self) -> Box<dyn Fn(Well) -> Option<Vec<Well>> + '_> {
// By validating first, we have a stronger guarantee that
// this function will not panic. :)
// log::debug!("Validating: {:?}", self.validate());
if let Err(msg) = self.validate() {
eprintln!("{}", msg);
eprintln!("This transfer will be empty.");
log::error!("{}\nThis transfer will be empty.", msg);
return Box::new(|_| None);
}
@ -184,74 +182,121 @@ impl TransferRegion {
let Well { row: i, col: j } = w;
if source_wells.contains(&w) {
let possible_destination_wells = create_dense_rectangle(c1, c2);
// Destination upper-left, bottom-right
let (d_ul, d_br) = standardize_rectangle(c1, c2);
// Source upper-left, bottom-right
let (s_ul, s_br) =
standardize_rectangle(&source_corners.0, &source_corners.1);
// Get base size of source region before interleave
let s_dims = (
s_br.row.checked_sub(s_ul.row).unwrap() + 1,
s_br.col.checked_sub(s_ul.col).unwrap() + 1,
);
// Get base size of destination region before interleave
let d_dims = (
d_br.row.checked_sub(d_ul.row).unwrap() + 1,
d_br.col.checked_sub(d_ul.col).unwrap() + 1,
);
// Precompute unsigned_abs: this should be cheap but
// this is called constantly in loops, plus prettier this way
let il_dest_uabs = (il_dest.0.unsigned_abs(), il_dest.1.unsigned_abs());
let il_source_uabs = (il_source.0.unsigned_abs(), il_source.1.unsigned_abs());
// Get the actual number of source wells in each dimension
// after considering interleave; essentially squish the air out of the
// region.
let number_used_src_wells = (
// Number of used source wells
(s_dims.0 + il_source.0.unsigned_abs() - 1)
.div_euclid(il_source.0.unsigned_abs()),
(s_dims.1 + il_source.1.unsigned_abs() - 1)
.div_euclid(il_source.1.unsigned_abs()),
(s_dims.0 + il_source_uabs.0 - 1)
.div_euclid(il_source_uabs.0),
(s_dims.1 + il_source_uabs.1 - 1)
.div_euclid(il_source_uabs.1),
);
let count = (
// How many times can we replicate?
// How many times can we replicate in each direction?
// Lazy solution: just increment the number of replicates until it wouldn't
// fit anymore!
let count = {
let replicate_base_size = (
number_used_src_wells.0 * il_dest_uabs.0,
number_used_src_wells.1 * il_dest_uabs.1,
);
(
if il_dest_uabs.0 == 0 {
1
} else {
(1..)
.position(|n| {
n * number_used_src_wells.0 * il_dest.0.unsigned_abs()
- il_dest.0.unsigned_abs()
(n * replicate_base_size.0)
.saturating_sub(il_dest_uabs.0)
+ 1
> d_dims.0
})
.unwrap() as u8,
.unwrap() as u8
},
if il_dest_uabs.1 == 0 {
1
} else {
(1..)
.position(|n| {
n * number_used_src_wells.1 * il_dest.1.unsigned_abs()
- il_dest.1.unsigned_abs()
(n * replicate_base_size.1)
.saturating_sub(il_dest_uabs.1)
+ 1
> d_dims.1
})
.unwrap() as u8,
);
.unwrap() as u8
},
)
};
// Normalize our i,j (source coords) to a dense rectangle consisting only
// of points actually in the transfer.
let i = i
.saturating_sub(s_ul.row)
.saturating_div(il_source.0.unsigned_abs());
.saturating_div(il_source_uabs.0);
let j = j
.saturating_sub(s_ul.col)
.saturating_div(il_source.1.unsigned_abs());
.saturating_div(il_source_uabs.1);
// Should not matter because of validation,
// but a cheap way to prevent div by 0
let checked_il_dest = (
u8::max(il_dest_uabs.0, 1u8),
u8::max(il_dest_uabs.1, 1u8),
);
// Precompute multiplication
let row_modulus = number_used_src_wells.0 * checked_il_dest.0;
let column_modulus = number_used_src_wells.1 * checked_il_dest.1;
let residue_class = (
(il_dest_uabs.0 * i) % row_modulus,
(il_dest_uabs.1 * j) % column_modulus
);
Some(
possible_destination_wells
.into_iter()
.filter(|Well { row: x, .. }| {
x.checked_sub(d_ul.row).unwrap()
% (number_used_src_wells.0 * il_dest.0.unsigned_abs()) // Counter along x
== (il_dest.0.unsigned_abs() *i)
% (number_used_src_wells.0 * il_dest.0.unsigned_abs())
% row_modulus // Counter along x
== residue_class.0
})
.filter(|Well { col: y, .. }| {
y.checked_sub(d_ul.col).unwrap()
% (number_used_src_wells.1 * il_dest.1.unsigned_abs()) // Counter along u
== (il_dest.1.unsigned_abs() *j)
% (number_used_src_wells.1 * il_dest.1.unsigned_abs())
% column_modulus // Counter along u
== residue_class.1
})
.filter(|Well { row: x, col: y }| {
// How many times have we replicated? < How many are we allowed
// to replicate?
x.checked_sub(d_ul.row).unwrap().div_euclid(
number_used_src_wells.0 * il_dest.0.unsigned_abs(),
) < count.0
&& y.checked_sub(d_ul.col).unwrap().div_euclid(
number_used_src_wells.1 * il_dest.1.unsigned_abs(),
) < count.1
x.checked_sub(d_ul.row).unwrap().div_euclid(row_modulus)
< count.0
&& y.checked_sub(d_ul.col)
.unwrap()
.div_euclid(column_modulus)
< count.1
})
.collect(),
)
@ -308,11 +353,15 @@ impl TransferRegion {
// log::debug!("s1.1: {}, max.1: {}", s1.1, source_max.1);
return Err("Source region is out-of-bounds! (Too wide)");
}
if il_dest == (0, 0) {
return Err("Refusing to pool both dimensions in a rectangular transfer!\nPlease select a point in the destination plate.");
}
}
Region::Custom(_) => return Ok(()),
}
if il_source.0 == 0 || il_dest.1 == 0 {
if il_source.0 == 0 || il_source.1 == 0 {
return Err("Source interleave cannot be zero!");
}
@ -329,7 +378,8 @@ fn create_dense_rectangle(c1: &Well, c2: &Well) -> Vec<Well> {
// Creates a vector of every point between two corners
let (c1, c2) = standardize_rectangle(c1, c2);
let mut points = Vec::<Well>::new();
let number_wells: usize = ((c2.row - c1.row + 1) as usize) * ((c2.col - c1.col + 1) as usize);
let mut points = Vec::<Well>::with_capacity(number_wells);
for i in c1.row..=c2.row {
for j in c1.col..=c2.col {
points.push(Well { row: i, col: j });
@ -509,12 +559,16 @@ mod tests {
let transfer1_map = transfer1.calculate_map();
assert_eq!(
transfer1_map(Well { row: 1, col: 1 }),
Some(vec! {Well{ row: 2, col: 2}, Well{ row: 2, col: 8}, Well{ row: 8, col: 2}, Well{ row: 8, col: 8}}),
Some(
vec! {Well{ row: 2, col: 2}, Well{ row: 2, col: 8}, Well{ row: 8, col: 2}, Well{ row: 8, col: 8}}
),
"Failed type replicate 1"
);
assert_eq!(
transfer1_map(Well { row: 2, col: 1 }),
Some(vec! {Well{ row: 5, col: 2}, Well{ row: 5, col: 8}, Well{ row: 11, col: 2}, Well{ row: 11, col: 8}}),
Some(
vec! {Well{ row: 5, col: 2}, Well{ row: 5, col: 8}, Well{ row: 11, col: 2}, Well{ row: 11, col: 8}}
),
"Failed type replicate 1"
);
@ -530,7 +584,14 @@ mod tests {
let transfer2_dest = transfer2.get_destination_wells();
assert_eq!(
transfer2_source,
vec![Well{ row: 1, col: 1}, Well{ row: 1, col: 2}, Well{ row: 1, col: 3}, Well{ row: 2, col: 1}, Well{ row: 2, col: 2}, Well{ row: 2, col: 3}],
vec![
Well { row: 1, col: 1 },
Well { row: 1, col: 2 },
Well { row: 1, col: 3 },
Well { row: 2, col: 1 },
Well { row: 2, col: 2 },
Well { row: 2, col: 3 }
],
"Failed type replicate 2 source"
);
assert_eq!(
@ -582,7 +643,14 @@ mod tests {
// Skipping source check---it's just 12 wells.
assert_eq!(
transfer1_dest,
vec![Well{ row: 1, col: 9}, Well{ row: 1, col: 11}, Well{ row: 1, col: 13}, Well{ row: 1, col: 15}].into_iter().collect(),
vec![
Well { row: 1, col: 9 },
Well { row: 1, col: 11 },
Well { row: 1, col: 13 },
Well { row: 1, col: 15 }
]
.into_iter()
.collect(),
"Failed type pool 1 dest"
);
assert_eq!(