From 9a2fd73f03209898076972c00662a03461debb65 Mon Sep 17 00:00:00 2001 From: Marieke Westendorp Date: Thu, 9 Jan 2025 13:01:58 +0100 Subject: [PATCH] WIP Add combination compartments --- src/pack/config.rs | 18 +++++ src/pack/state.rs | 167 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 156 insertions(+), 29 deletions(-) diff --git a/src/pack/config.rs b/src/pack/config.rs index 25ef0a9..a70a246 100644 --- a/src/pack/config.rs +++ b/src/pack/config.rs @@ -26,6 +26,14 @@ impl std::fmt::Display for Shape { } } +#[derive(Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum CombinationExpression { + Not(Box), + Or(Vec), + ID(CompartmentID), +} + #[derive(Deserialize)] #[serde(rename_all = "lowercase")] pub(crate) enum Mask { @@ -38,6 +46,7 @@ pub(crate) enum Mask { Voxels { path: PathBuf, }, + Combination(Vec), } #[derive(Deserialize)] @@ -47,6 +56,15 @@ pub struct Compartment { pub mask: Mask, } +impl Compartment { + pub fn is_predefined(&self) -> bool { + match &self.mask { + Mask::Shape(_) | Mask::Analytical { .. } | Mask::Voxels { .. } => true, + Mask::Combination(_) => false, + } + } +} + pub(crate) fn true_by_default() -> bool { true } diff --git a/src/pack/state.rs b/src/pack/state.rs index 1a21696..3b07d52 100644 --- a/src/pack/state.rs +++ b/src/pack/state.rs @@ -11,8 +11,8 @@ use rand::{Rng as _, SeedableRng}; use crate::args::{Args, RearrangeMethod}; use crate::config::{ - Configuration, Mask as ConfigMask, Quantity, RuleExpression, Shape as ConfigShape, - TopolIncludes, + CombinationExpression, Compartment as ConfigCompartment, Configuration, Mask as ConfigMask, + Quantity, RuleExpression, Shape as ConfigShape, TopolIncludes, }; use crate::mask::{distance_mask, distance_mask_grow, Dimensions, Mask}; use crate::placement::{Batch, Placement}; @@ -377,44 +377,153 @@ impl State { .map(|d| (d / config.space.resolution) as u64); let resolution = config.space.resolution; eprintln!("Setting up compartments..."); - let compartments = config + let (predefined, combinations): (Vec<_>, Vec<_>) = config .space .compartments + .into_iter() + .partition(ConfigCompartment::is_predefined); + let mut compartments: Vec = predefined .into_iter() .map(|comp| -> io::Result<_> { - Ok(Compartment { + let mask = match comp.mask { + ConfigMask::Shape(shape) => { + if verbose { + eprintln!("\tConstructing a {shape} mask..."); + } + Mask::create_from_shape(shape, dimensions, None, None) + } + ConfigMask::Analytical { + shape, + center, + radius, + } => { + if verbose { + eprintln!("\tConstructing a {shape} mask..."); + } + let center = center.map(|c| (Vec3::from_array(c) / resolution).as_uvec3()); + let radius = radius.map(|r| (r / resolution) as u32); + Mask::create_from_shape(shape, dimensions, center, radius) + } + ConfigMask::Voxels { path } => { + if verbose { + eprintln!("\tLoading mask from {path:?}..."); + } + Mask::load_from_path(&path).map_err(|err| report_opening(err, path))? + } + ConfigMask::Combination(_) => { + unreachable!() // We partitioned the list above. + } + }; + let c = Compartment { id: comp.id, - mask: match comp.mask { - ConfigMask::Shape(shape) => { - if verbose { - eprintln!("\tConstructing a {shape} mask..."); + mask, + distance_masks: Default::default(), + }; + Ok(c) + }) + .collect::>()?; + for combination in combinations { + let ConfigMask::Combination(ces) = combination.mask else { + unreachable!() // We partitioned the list above. + }; + + // TODO: Really disliking this code duplication. `or` and `and` are more or less the same + // function, except for their mask bit operation. + fn or(ces: &[CombinationExpression], compartments: &[Compartment]) -> Mask { + // TODO: Think about how we should address this case. It feels like something we + // should just be chill about. But perhaps a warning? + if ces.is_empty() { + panic!("combination expression cannot be empty"); + } + + // TODO: Replace this with a fold or something. + let mut mask = None; + for ce in ces { + match ce { + CombinationExpression::ID(id) => { + let comp = compartments + .iter() + .find(|comp| &comp.id == id) + .expect(&format!("mask with id {id:?} not (yet) defined")); + if let Some(mask) = &mut mask { + *mask |= comp.mask.clone(); // FIXME: We could get rid of this clone, perhaps. + } else { + mask = Some(comp.mask.clone()); } - Mask::create_from_shape(shape, dimensions, None, None) } - ConfigMask::Analytical { - shape, - center, - radius, - } => { - if verbose { - eprintln!("\tConstructing a {shape} mask..."); + CombinationExpression::Or(ces) => { + let res = or(ces, compartments); + if let Some(mask) = &mut mask { + *mask |= res; + } else { + mask = Some(res); } - let center = - center.map(|c| (Vec3::from_array(c) / resolution).as_uvec3()); - let radius = radius.map(|r| (r / resolution) as u32); - Mask::create_from_shape(shape, dimensions, center, radius) } - ConfigMask::Voxels { path } => { - if verbose { - eprintln!("\tLoading mask from {path:?}..."); + CombinationExpression::Not(ces) => { + let res = !or(std::slice::from_ref(ces), compartments); + if let Some(mask) = &mut mask { + *mask &= res; + } else { + mask = Some(res); } - Mask::load_from_path(&path).map_err(|err| report_opening(err, path))? } - }, - distance_masks: Default::default(), - }) - }) - .collect::>()?; + }; + } + + mask.unwrap() // We know ecs is not empty. + } + + fn and(ces: &[CombinationExpression], compartments: &[Compartment]) -> Mask { + // TODO: Think about how we should address this case. It feels like something we + // should just be chill about. But perhaps a warning? + if ces.is_empty() { + panic!("combination expression cannot be empty"); + } + + // TODO: Replace this with a fold or something. + let mut mask = None; + for ce in ces { + match ce { + CombinationExpression::ID(id) => { + let comp = compartments + .iter() + .find(|comp| &comp.id == id) + .expect(&format!("mask with id {id:?} not (yet) defined")); + if let Some(mask) = &mut mask { + *mask &= comp.mask.clone(); // FIXME: We could get rid of this clone, perhaps. + } else { + mask = Some(comp.mask.clone()); + } + } + CombinationExpression::Or(ces) => { + let res = or(ces, compartments); + if let Some(mask) = &mut mask { + *mask &= res; + } else { + mask = Some(res); + } + } + CombinationExpression::Not(ces) => { + let res = !or(std::slice::from_ref(ces), compartments); + if let Some(mask) = &mut mask { + *mask &= res; + } else { + mask = Some(res); + } + } + }; + } + + mask.unwrap() // We know ecs is not empty. + } + + let baked = Compartment { + id: combination.id, + mask: and(&ces, &compartments), + distance_masks: Default::default(), + }; + compartments.push(baked); + } let space = Space { size: config.space.size, dimensions,