diff --git a/ext/src/chain_complex/chain_homotopy.rs b/ext/src/chain_complex/chain_homotopy.rs index 687f7064a..60eb153c6 100644 --- a/ext/src/chain_complex/chain_homotopy.rs +++ b/ext/src/chain_complex/chain_homotopy.rs @@ -1,6 +1,9 @@ -use crate::chain_complex::{ChainComplex, FreeChainComplex}; use crate::resolution_homomorphism::ResolutionHomomorphism; use crate::save::SaveKind; +use crate::{ + chain_complex::{ChainComplex, FreeChainComplex}, + save::SaveOption, +}; use algebra::module::homomorphism::{FreeModuleHomomorphism, ModuleHomomorphism}; use algebra::module::Module; use fp::prime::ValidPrime; @@ -31,6 +34,7 @@ pub struct ChainHomotopy< /// Homotopies, indexed by the filtration of the target of f - g. homotopies: OnceBiVec>>, save_dir: Option, + save_option: SaveOption, } impl< @@ -43,14 +47,29 @@ impl< left: Arc>, right: Arc>, ) -> Self { + Self::new_with_save_option(left, right, Default::default()) + } + + pub fn new_with_save_option( + left: Arc>, + right: Arc>, + save_option: SaveOption, + ) -> Self { + if save_option.required() { + assert!( + left.source.save_dir().is_some(), + "Saving is required but no path was provided" + ); + } let save_dir = if left.source.save_dir().is_some() && !left.name().is_empty() && !right.name().is_empty() { let mut path = left.source.save_dir().unwrap().to_owned(); - path.push(format!("massey/{},{}/", left.name(), right.name(),)); - - SaveKind::ChainHomotopy.create_dir(&path).unwrap(); + if save_option.write() { + path.push(format!("massey/{},{}/", left.name(), right.name(),)); + SaveKind::ChainHomotopy.create_dir(&path).unwrap(); + } Some(path) } else { @@ -64,6 +83,7 @@ impl< right, lock: Mutex::new(()), save_dir, + save_option, } } @@ -282,7 +302,8 @@ impl< &scratches, )); - if let Some(dir) = &self.save_dir { + if self.save_option.write() && self.save_dir.is_some() { + let dir = self.save_dir.as_ref().unwrap(); let mut f = self .left .source diff --git a/ext/src/resolution_homomorphism.rs b/ext/src/resolution_homomorphism.rs index 3c3153599..aaf13afb8 100644 --- a/ext/src/resolution_homomorphism.rs +++ b/ext/src/resolution_homomorphism.rs @@ -4,10 +4,11 @@ use std::ops::Range; use std::path::PathBuf; use std::sync::Arc; -use crate::chain_complex::{ - AugmentedChainComplex, BoundedChainComplex, ChainComplex, FreeChainComplex, -}; use crate::save::SaveKind; +use crate::{ + chain_complex::{AugmentedChainComplex, BoundedChainComplex, ChainComplex, FreeChainComplex}, + save::SaveOption, +}; use algebra::module::homomorphism::{ModuleHomomorphism, MuFreeModuleHomomorphism}; use algebra::module::Module; use algebra::MuAlgebra; @@ -38,6 +39,7 @@ where pub shift_s: u32, pub shift_t: i32, save_dir: Option, + save_option: SaveOption, } impl MuResolutionHomomorphism @@ -53,10 +55,30 @@ where shift_s: u32, shift_t: i32, ) -> Self { + Self::new_with_save_option(name, source, target, shift_s, shift_t, Default::default()) + } + + pub fn new_with_save_option( + name: String, + source: Arc, + target: Arc, + shift_s: u32, + shift_t: i32, + save_option: SaveOption, + ) -> Self { + if save_option.required() { + assert!( + source.save_dir().is_some(), + "Saving is required but no path was provided" + ); + } + let save_dir = if source.save_dir().is_some() && !name.is_empty() { let mut path = source.save_dir().unwrap().to_owned(); - path.push(format!("products/{name}")); - SaveKind::ChainMap.create_dir(&path).unwrap(); + if save_option.write() { + path.push(format!("products/{name}")); + SaveKind::ChainMap.create_dir(&path).unwrap(); + } Some(path) } else { None @@ -70,6 +92,7 @@ where shift_s, shift_t, save_dir, + save_option, } } @@ -257,14 +280,16 @@ where let outputs = extra_images.unwrap_or_else(|| vec![FpVector::new(p, fx_dimension); num_gens]); - if let Some(dir) = &self.save_dir { - let mut f = self - .source - .save_file(SaveKind::ChainMap, input_s, input_t) - .create_file(dir.clone(), false); - f.write_u64::(fx_dimension as u64).unwrap(); - for row in &outputs { - row.to_bytes(&mut f).unwrap(); + if self.save_option.write() { + if let Some(dir) = &self.save_dir { + let mut f = self + .source + .save_file(SaveKind::ChainMap, input_s, input_t) + .create_file(dir.clone(), false); + f.write_u64::(fx_dimension as u64).unwrap(); + for row in &outputs { + row.to_bytes(&mut f).unwrap(); + } } } @@ -335,14 +360,16 @@ where )); } - if let Some(dir) = &self.save_dir { - let mut f = self - .source - .save_file(SaveKind::ChainMap, input_s, input_t) - .create_file(dir.clone(), false); - f.write_u64::(fx_dimension as u64).unwrap(); - for row in &outputs { - row.to_bytes(&mut f).unwrap(); + if self.save_option.write() { + if let Some(dir) = &self.save_dir { + let mut f = self + .source + .save_file(SaveKind::ChainMap, input_s, input_t) + .create_file(dir.clone(), false); + f.write_u64::(fx_dimension as u64).unwrap(); + for row in &outputs { + row.to_bytes(&mut f).unwrap(); + } } } f_cur.add_generators_from_rows_ooo(input_t, outputs) @@ -363,7 +390,28 @@ where shift_t: i32, class: &[u32], ) -> Self { - let result = Self::new(name, source, target, shift_s, shift_t); + Self::from_class_with_save_option( + name, + source, + target, + shift_s, + shift_t, + class, + Default::default(), + ) + } + + pub fn from_class_with_save_option( + name: String, + source: Arc, + target: Arc, + shift_s: u32, + shift_t: i32, + class: &[u32], + save_option: SaveOption, + ) -> Self { + let result = + Self::new_with_save_option(name, source, target, shift_s, shift_t, save_option); let num_gens = result .source diff --git a/ext/src/save.rs b/ext/src/save.rs index f5f827087..501f7076f 100644 --- a/ext/src/save.rs +++ b/ext/src/save.rs @@ -418,3 +418,50 @@ impl SaveFile { f } } + +/// Decides whether the structure will be stored to disk. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[non_exhaustive] +pub enum SaveOption { + /// Save to disk if a save path was provided. + #[default] + Yes, + /// Do not save to disk even if a save path was provided. + No, + /// Save to disk. Not providing a save path will cause an error. + Force, + /// Only read from disk if the structure has already been computed, but don't write. Note that + /// the directories will still be created if they don't already exist, but they won't be + /// populated. + ReadOnly, +} + +impl SaveOption { + pub fn required(&self) -> bool { + matches!(self, SaveOption::Force) + } + + pub fn write(&self) -> bool { + match self { + SaveOption::Yes => true, + SaveOption::No => false, + SaveOption::Force => true, + SaveOption::ReadOnly => false, + } + } +} + +impl std::str::FromStr for SaveOption { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s { + "" | "default" => Ok(Default::default()), + "yes" | "y" | "Y" => Ok(SaveOption::Yes), + "no" | "n" | "N" => Ok(SaveOption::No), + "force" | "f" | "F" => Ok(SaveOption::Force), + "readonly" | "r" | "ro" | "R" | "RO" => Ok(SaveOption::ReadOnly), + _ => Err(anyhow::anyhow!("Invalid save option")), + } + } +} diff --git a/ext/tests/save_load_resolution.rs b/ext/tests/save_load_resolution.rs index 58449a819..a499c308b 100644 --- a/ext/tests/save_load_resolution.rs +++ b/ext/tests/save_load_resolution.rs @@ -260,3 +260,86 @@ fn test_checksum() { .unwrap() .compute_through_bidegree(2, 2); } + +mod save_option { + use std::sync::Arc; + + use ext::{ + chain_complex::ChainHomotopy, resolution_homomorphism::ResolutionHomomorphism, + save::SaveOption, utils::construct, + }; + use rstest::rstest; + + use super::lock_tempdir; + + #[rstest] + #[case(SaveOption::No)] + #[case(SaveOption::ReadOnly)] + fn test_morphism_locked(#[case] save_option: SaveOption) { + let tempdir = tempfile::TempDir::new().unwrap(); + + let resolution = Arc::new(construct("S_2", Some(tempdir.path().into())).unwrap()); + resolution.compute_through_stem(1, 0); + + lock_tempdir(tempdir.path()); + ResolutionHomomorphism::from_class_with_save_option( + "h0".into(), + Arc::clone(&resolution), + Arc::clone(&resolution), + 1, + 1, + &[1], + save_option, + ) + .extend_all(); + } + + #[rstest] + #[case(SaveOption::No)] + #[case(SaveOption::ReadOnly)] + fn test_homotopy_locked(#[case] save_option: SaveOption) { + let tempdir = tempfile::TempDir::new().unwrap(); + + let resolution = Arc::new(construct("S_2", Some(tempdir.path().into())).unwrap()); + resolution.compute_through_stem(2, 2); + + let h0 = ResolutionHomomorphism::from_class( + "h0".into(), + Arc::clone(&resolution), + Arc::clone(&resolution), + 1, + 1, + &[1], + ); + h0.extend_all(); + let h1 = ResolutionHomomorphism::from_class( + "h1".into(), + Arc::clone(&resolution), + Arc::clone(&resolution), + 1, + 2, + &[1], + ); + h1.extend_all(); + + lock_tempdir(tempdir.path()); + ChainHomotopy::new_with_save_option(Arc::new(h0), Arc::new(h1), save_option).extend_all(); + } + + #[test] + #[should_panic] + fn test_force() { + let resolution = Arc::new(construct("S_2", None).unwrap()); + resolution.compute_through_stem(1, 0); + + ResolutionHomomorphism::from_class_with_save_option( + "h0".into(), + Arc::clone(&resolution), + Arc::clone(&resolution), + 1, + 1, + &[1], + SaveOption::Force, + ); + } +}