diff --git a/plate-tool-lib/src/transfer_region.rs b/plate-tool-lib/src/transfer_region.rs index 587d319..a23c5ba 100644 --- a/plate-tool-lib/src/transfer_region.rs +++ b/plate-tool-lib/src/transfer_region.rs @@ -182,61 +182,98 @@ 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()), - ); - let count = ( - // How many times can we replicate? - if il_dest.0.unsigned_abs() == 0 { - 1 - } else { - (1..) - .position(|n| { - (n * number_used_src_wells.0 * il_dest.0.unsigned_abs()) - .saturating_sub(il_dest.0.unsigned_abs()) - + 1 - > d_dims.0 - }) - .unwrap() as u8 - }, - if il_dest.1.unsigned_abs() == 0 { - 1 - } else { - (1..) - .position(|n| { - (n * number_used_src_wells.1 * il_dest.1.unsigned_abs()) - .saturating_sub(il_dest.1.unsigned_abs()) - + 1 - > d_dims.1 - }) - .unwrap() as u8 - }, + (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), ); + + // 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 * replicate_base_size.0) + .saturating_sub(il_dest_uabs.0) + + 1 + > d_dims.0 + }) + .unwrap() as u8 + }, + if il_dest_uabs.1 == 0 { + 1 + } else { + (1..) + .position(|n| { + (n * replicate_base_size.1) + .saturating_sub(il_dest_uabs.1) + + 1 + > d_dims.1 + }) + .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()); - let checked_il_dest = (u8::max(il_dest.0.unsigned_abs(), 1u8), - u8::max(il_dest.1.unsigned_abs(), 1u8)); + .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 @@ -244,24 +281,22 @@ impl TransferRegion { .filter(|Well { row: x, .. }| { x.checked_sub(d_ul.row).unwrap() % row_modulus // Counter along x - == (il_dest.0.unsigned_abs() *i) - % row_modulus + == residue_class.0 }) .filter(|Well { col: y, .. }| { y.checked_sub(d_ul.col).unwrap() % column_modulus // Counter along u - == (il_dest.1.unsigned_abs() *j) - % column_modulus + == 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( - row_modulus - ) < count.0 - && y.checked_sub(d_ul.col).unwrap().div_euclid( - column_modulus - ) < 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(), ) @@ -319,8 +354,8 @@ impl TransferRegion { 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.") + 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(()), @@ -343,7 +378,8 @@ fn create_dense_rectangle(c1: &Well, c2: &Well) -> Vec { // Creates a vector of every point between two corners let (c1, c2) = standardize_rectangle(c1, c2); - let mut points = Vec::::new(); + let number_wells: usize = ((c2.row - c1.row + 1) as usize) * ((c2.col - c1.col + 1) as usize); + let mut points = Vec::::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 });