From 44fb6d70bfd637b5b1e4caba54a4cdf7a40f3a44 Mon Sep 17 00:00:00 2001 From: Hugo Gimbert Date: Thu, 29 May 2025 05:32:40 +0200 Subject: [PATCH 1/6] added trace log in downset computation --- Cargo.toml | 1 + src/cli.rs | 6 +++--- src/downset.rs | 43 ++++++++++++++++++++++--------------------- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7e12c97..0a76f71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ name = "shepherd" version = "0.1.0" edition = "2021" +default-run = "shepherd" [dependencies] log = "0.4.27" diff --git a/src/cli.rs b/src/cli.rs index 935968b..958c2d5 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,9 +1,9 @@ //! This module defines the command line interface (CLI) for the application. +use crate::nfa; +use crate::solver; use clap::{Parser, ValueEnum}; use std::path::PathBuf; -use crate::solver; -use crate::nfa; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] pub enum OutputFormat { @@ -31,7 +31,7 @@ pub struct Args { short = 'v', long = "verbose", action = clap::ArgAction::Count, - help = "Increase verbosity level" + help = "Increase verbosity level. Default is warn only, -v is info, -vv is debug, -vvv is trace" )] pub verbosity: u8, diff --git a/src/downset.rs b/src/downset.rs index c811c46..b8d093e 100644 --- a/src/downset.rs +++ b/src/downset.rs @@ -4,7 +4,7 @@ use crate::memoizer::Memoizer; use crate::partitions; use cached::proc_macro::cached; use itertools::Itertools; -use log::debug; +use log::{debug, trace}; use once_cell::sync::Lazy; use rayon::prelude::*; use std::collections::VecDeque; @@ -281,7 +281,7 @@ impl DownSet { }) .collect::>(); - //println!("preimage of\n{}\n by\n{}\n", self, edges); + trace!("preimage of\n{}\n by\n{}\n", self, edges); let possible_coefs = (0..dim) .map(|i| { @@ -296,9 +296,9 @@ impl DownSet { } }) .collect::>(); - //println!("max_finite_coords: {:?}\n", max_finite_coordsi); - //println!("is_omega_possible: {:?}\n", is_omega_possible); - //println!("possible_coefs: {:?}\n", possible_coefs); + trace!("max_finite_coords: {:?}\n", max_finite_coordsi); + trace!("is_omega_possible: {:?}\n", is_omega_possible); + trace!("possible_coefs: {:?}\n", possible_coefs); let mut result = DownSet::new(); let candidates = POSSIBLE_COEFS_CACHE.lock().unwrap().get(possible_coefs); @@ -313,7 +313,7 @@ impl DownSet { result.insert(c); }); result.minimize(); - //println!("result {}\n", result); + trace!("result {}\n", result); result } @@ -324,36 +324,37 @@ impl DownSet { safe: &DownSet, maximal_finite_value: coef, ) -> DownSet { - /* - println!( + trace!( "get_intersection_with_safe_ideal\nideal: {}\nsafe_target\n{}\nedges\n{}", - ideal, safe_target, edges - ); */ + ideal, + safe, + edges + ); let mut result = DownSet::new(); let mut to_process: VecDeque = vec![ideal.clone()].into_iter().collect(); let mut processed = HashSet::::new(); while !to_process.is_empty() { let flow = to_process.pop_front().unwrap(); - //print!("Processing {}...", flow); + trace!("Processing {}...", flow); if result.contains(&flow) { - //println!("...already included"); + trace!("...already included"); continue; } if processed.contains(&flow) { - //println!("...already processed"); + trace!("...already processed"); continue; } processed.insert(flow.clone()); if Self::is_safe(ideal, edges, safe, ideal.dimension(), maximal_finite_value) { - //println!("...safe"); + trace!("...safe"); result.insert(ideal); } else { - //println!("...unsafe"); + trace!("...unsafe"); flow.iter().enumerate().for_each(|(i, &ci)| { if ci != C0 { let smaller = flow.clone_and_decrease(i, maximal_finite_value); if !processed.contains(&smaller) { - //println!("adding smaller {} to queue", smaller); + trace!("adding smaller {} to queue", smaller); to_process.push_back(smaller); } } @@ -374,15 +375,15 @@ impl DownSet { maximal_finite_coordinate: coef, ) { if accumulator.contains(candidate) { - //println!("{} already in ideal", candidate); + trace!("{} already in ideal", candidate); return; } if self.is_safe_with_roundup(candidate, edges, maximal_finite_coordinate) { - //println!("{} inserted", candidate); + trace!("{} inserted", candidate); accumulator.insert(candidate); return; } - //println!("{} refined", candidate); + trace!("{} refined", candidate); let mut candidate_copy = candidate.clone(); for i in 0..candidate.dimension() { let ci = candidate.get(i); @@ -453,7 +454,7 @@ impl DownSet { } let image: DownSet = Self::get_image(dim, candidate, edges, maximal_finite_coordinate); - //println!("image\n{}", &image); + trace!("image\n{}", &image); let answer = image.ideals().all(|x| self.contains(x)); answer } @@ -551,7 +552,7 @@ impl DownSet { #[cached] fn get_choices(dim: usize, value: Coef, successors: Vec) -> Vec { - //println!("get_choices({}, {:?}, {:?})", dim, value, successors); + trace!("get_choices({}, {:?}, {:?})", dim, value, successors); //assert!(value == OMEGA || value <= Coef::Value(dim as coef)); match value { C0 => vec![Ideal::new(dim, C0)], From 71abdcf2ba777abaa0268567defb2bb5fc03084d Mon Sep 17 00:00:00 2001 From: Hugo Gimbert Date: Thu, 29 May 2025 05:33:43 +0200 Subject: [PATCH 2/6] added trace log in downset computation --- src/downset.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/downset.rs b/src/downset.rs index b8d093e..6ac098a 100644 --- a/src/downset.rs +++ b/src/downset.rs @@ -47,7 +47,8 @@ static POSSIBLE_COEFS_CACHE: Lazy> = Lazy::new(|| }); fn compute_possible_coefs(possible_coefs: &CoefsCollection) -> impl Iterator> { - possible_coefs + trace!("compute_possible_coefs({:?})", possible_coefs); + let result = possible_coefs .iter() .map(|v| { let coef = v @@ -67,7 +68,9 @@ fn compute_possible_coefs(possible_coefs: &CoefsCollection) -> impl Iterator Date: Thu, 29 May 2025 06:07:45 +0200 Subject: [PATCH 3/6] avoids storing large cartesian products in cache --- src/downset.rs | 71 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 55 insertions(+), 16 deletions(-) diff --git a/src/downset.rs b/src/downset.rs index 6ac098a..9a132cb 100644 --- a/src/downset.rs +++ b/src/downset.rs @@ -4,7 +4,7 @@ use crate::memoizer::Memoizer; use crate::partitions; use cached::proc_macro::cached; use itertools::Itertools; -use log::{debug, trace}; +use log::{debug, trace, warn}; use once_cell::sync::Lazy; use rayon::prelude::*; use std::collections::VecDeque; @@ -38,6 +38,7 @@ impl PartialEq for DownSet { type CoefsCollection = Vec>; type Herd = Vec; type CoefsCollectionMemoizer = Memoizer Herd>; +static MAX_CACHED_OBJECT_SIZE: i64 = 1_000; static POSSIBLE_COEFS_CACHE: Lazy> = Lazy::new(|| { Mutex::new(Memoizer::new(|possible_coefs| { compute_possible_coefs(possible_coefs) @@ -48,7 +49,7 @@ static POSSIBLE_COEFS_CACHE: Lazy> = Lazy::new(|| fn compute_possible_coefs(possible_coefs: &CoefsCollection) -> impl Iterator> { trace!("compute_possible_coefs({:?})", possible_coefs); - let result = possible_coefs + possible_coefs .iter() .map(|v| { let coef = v @@ -68,9 +69,7 @@ fn compute_possible_coefs(possible_coefs: &CoefsCollection) -> impl Iterator 1, + OMEGA => 2, + Coef::Value(c) => c as i64 + 1, + }) + .reduce(std::cmp::max) + .unwrap_or(1) + }) + .reduce(|a, b| a * b) + .unwrap_or(0); + trace!("approximate_size: {:?}\n", approximate_size); let mut result = DownSet::new(); - let candidates = POSSIBLE_COEFS_CACHE.lock().unwrap().get(possible_coefs); - candidates - .par_iter() - .filter(|&candidate| { - self.is_safe_with_roundup(candidate, edges, maximal_finite_coordinate) - }) - .collect::>() - .iter() - .for_each(|c| { - result.insert(c); - }); + if approximate_size < MAX_CACHED_OBJECT_SIZE { + trace!( + "iterating over {} possible ideals using cached value", + approximate_size + ); + let candidates = POSSIBLE_COEFS_CACHE.lock().unwrap().get(possible_coefs); + candidates + .par_iter() + .filter(|&candidate| { + self.is_safe_with_roundup(candidate, edges, maximal_finite_coordinate) + }) + .collect::>() + .iter() + .for_each(|c| { + result.insert(c); + }); + } else { + if approximate_size > 10_000_000 { + warn!("iterating over a very large number of possible ideals ({}), will probably never terminate", approximate_size); + } else { + debug!("iterating over {} possible ideals", approximate_size); + } + let candidates = compute_possible_coefs(&possible_coefs); + candidates + .map(Ideal::from_vec) + .filter(|candidate| { + self.is_safe_with_roundup(candidate, edges, maximal_finite_coordinate) + }) + .collect::>() + .iter() + .for_each(|c| { + result.insert(c); + }); + }; + + trace!("minimizing result"); result.minimize(); trace!("result {}\n", result); result From 34ed1d25ae966b8de23c182ea5f19dd939b2f116 Mon Sep 17 00:00:00 2001 From: Hugo Gimbert Date: Thu, 29 May 2025 08:33:41 +0200 Subject: [PATCH 4/6] heuristic for faster anumeration of candidate ideals in safe preimage computation --- latex/examples.tex | 6 +++ src/downset.rs | 121 ++++++++++++++++++++++++++++++++------------- src/strategy.rs | 11 +++++ 3 files changed, 103 insertions(+), 35 deletions(-) diff --git a/latex/examples.tex b/latex/examples.tex index 10c2837..f223d77 100644 --- a/latex/examples.tex +++ b/latex/examples.tex @@ -53,4 +53,10 @@ \subsection{Bottleneck 2.} File {\tt bottleneck-2.tikz} \input{../examples/bottleneck-2.tikz} + +\subsection{Bug 12} + +File {\tt bug12.tikz} + +\input{../examples/bug12.tikz} \end{document} diff --git a/src/downset.rs b/src/downset.rs index 9a132cb..a7f3346 100644 --- a/src/downset.rs +++ b/src/downset.rs @@ -41,35 +41,35 @@ type CoefsCollectionMemoizer = Memoizer> = Lazy::new(|| { Mutex::new(Memoizer::new(|possible_coefs| { - compute_possible_coefs(possible_coefs) + expand_finite_downward_closure(possible_coefs) + .multi_cartesian_product() .map(Ideal::from_vec) .collect() })) }); -fn compute_possible_coefs(possible_coefs: &CoefsCollection) -> impl Iterator> { +fn expand_finite_downward_closure<'a>( + possible_coefs: &'a CoefsCollection, +) -> impl Iterator> + 'a { trace!("compute_possible_coefs({:?})", possible_coefs); - possible_coefs - .iter() - .map(|v| { - let coef = v - .iter() - .filter_map(|&x| match x { - OMEGA => None, - Coef::Value(c) => Some(c), - }) - .next(); - let is_omega = v.contains(&OMEGA); - match (is_omega, coef) { - (false, None) => vec![C0], - (true, None) => vec![OMEGA], - (false, Some(c)) => (0..c + 1).map(Coef::Value).collect(), - (true, Some(c)) => std::iter::once(OMEGA) - .chain((0..c + 1).map(Coef::Value)) - .collect(), - } - }) - .multi_cartesian_product() + possible_coefs.iter().map(|v| { + let coef = v + .iter() + .filter_map(|&x| match x { + OMEGA => None, + Coef::Value(c) => Some(c), + }) + .next(); + let is_omega = v.contains(&OMEGA); + match (is_omega, coef) { + (false, None) => vec![C0], + (true, None) => vec![OMEGA], + (false, Some(c)) => (0..c + 1).map(Coef::Value).rev().collect(), + (true, Some(c)) => std::iter::once(OMEGA) + .chain((0..c + 1).map(Coef::Value).rev()) + .collect(), + } + }) } impl DownSet { @@ -336,21 +336,36 @@ impl DownSet { }); } else { if approximate_size > 10_000_000 { - warn!("iterating over a very large number of possible ideals ({}), will probably never terminate", approximate_size); + warn!("iterating over a potentially very large number of possible ideals (up to {}), will possibly never terminate", approximate_size); } else { debug!("iterating over {} possible ideals", approximate_size); } - let candidates = compute_possible_coefs(&possible_coefs); - candidates - .map(Ideal::from_vec) - .filter(|candidate| { - self.is_safe_with_roundup(candidate, edges, maximal_finite_coordinate) - }) - .collect::>() - .iter() - .for_each(|c| { - result.insert(c); - }); + let all_possible_coefs: CoefsCollection = + expand_finite_downward_closure(&possible_coefs).collect(); + let mut iterator: Vec = DownSet::get_initial_iterator(&all_possible_coefs); + let mut is_not_over = true; + while is_not_over { + let candidate = Ideal::from_vec( + iterator + .iter() + .enumerate() + .map(|(i, &j)| all_possible_coefs[i][j]) + .collect(), + ); + trace!("checking candidate {} for safe preimage", candidate); + let is_safe = + self.is_safe_with_roundup(&candidate, edges, maximal_finite_coordinate); + if is_safe { + trace!("{} is safe", candidate); + result.insert(&candidate); + //we can jump to the next incomparable ideal + is_not_over = + DownSet::get_next_incomparable_iterator(&mut iterator, &all_possible_coefs); + } else { + trace!("{} is unsafe", candidate); + is_not_over = DownSet::get_next_iterator(&mut iterator, &all_possible_coefs); + } + } }; trace!("minimizing result"); @@ -359,6 +374,42 @@ impl DownSet { result } + fn get_initial_iterator(all_possible_coefs: &CoefsCollection) -> Vec { + assert!(all_possible_coefs.iter().all(|l| !l.is_empty())); + all_possible_coefs.iter().map(|_l| 0).collect() + } + fn get_next_iterator(iterator: &mut [usize], all_possible_coefs: &CoefsCollection) -> bool { + assert!(iterator.len() == all_possible_coefs.len()); + for i in (0..iterator.len()).rev() { + if iterator[i] < all_possible_coefs[i].len() - 1 { + iterator[i] += 1; + return true; + } else { + iterator[i] = 0; + } + } + false + } + fn get_next_incomparable_iterator( + iterator: &mut [usize], + all_possible_coefs: &CoefsCollection, + ) -> bool { + assert!(iterator.len() == all_possible_coefs.len()); + for i in (0..iterator.len()).rev() { + if iterator[i] > 0 { + iterator[i] = 0; + for j in (0..i).rev() { + if iterator[j] < all_possible_coefs[j].len() - 1 { + iterator[j] += 1; + return true; + } else { + iterator[j] = 0; + } + } + } + } + false + } /* naive exponential impl of get_intersection_with_safe_ideal*/ fn safe_post( ideal: &Ideal, diff --git a/src/strategy.rs b/src/strategy.rs index 3b0e7f0..520af5a 100644 --- a/src/strategy.rs +++ b/src/strategy.rs @@ -3,6 +3,7 @@ use crate::downset::DownSet; use crate::graph::Graph; use crate::ideal::Ideal; use crate::nfa; +use log::trace; use std::collections::HashMap; use std::fmt; @@ -40,8 +41,18 @@ impl Strategy { // print!("."); //io::stdout().flush().unwrap(); let edges = edges_per_letter.get(a).unwrap(); + trace!( + "Restricting strategy for letter '{}' with downset {}", + a, + downset + ); let safe_pre_image = safe.safe_pre_image(edges, maximal_finite_value); result |= downset.restrict_to(&safe_pre_image); + trace!( + "After restriction, downset for letter '{}' is {}", + a, + downset + ); } result } From 1a1c5044fac28dcdf2ebd73d058d7155c20efb82 Mon Sep 17 00:00:00 2001 From: Hugo Gimbert Date: Thu, 29 May 2025 15:16:09 +0200 Subject: [PATCH 5/6] pass tests --- Cargo.toml | 4 +- src/downset.rs | 286 ++++++++++++++++++++------------------ src/main.rs | 4 +- tests/integration_test.rs | 6 +- 4 files changed, 160 insertions(+), 140 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0a76f71..9345f0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,6 @@ edition = "2021" default-run = "shepherd" [dependencies] -log = "0.4.27" env_logger = "0.11.8" regex = "1" clap = { version = "4.5.35", features = ["derive"] } @@ -27,3 +26,6 @@ itertools = "0.14.0" name = "schaeppert" path = "src/iterative.rs" +[dependencies.log] +version = "0.4.27" +features = ["release_max_level_info"] diff --git a/src/downset.rs b/src/downset.rs index a7f3346..08cb306 100644 --- a/src/downset.rs +++ b/src/downset.rs @@ -1,15 +1,11 @@ use crate::coef::{coef, Coef, C0, OMEGA}; use crate::ideal::Ideal; -use crate::memoizer::Memoizer; use crate::partitions; use cached::proc_macro::cached; use itertools::Itertools; use log::{debug, trace, warn}; -use once_cell::sync::Lazy; -use rayon::prelude::*; use std::collections::VecDeque; use std::fmt; -use std::sync::Mutex; use std::{collections::HashSet, vec::Vec}; /* @@ -36,40 +32,38 @@ impl PartialEq for DownSet { } type CoefsCollection = Vec>; -type Herd = Vec; -type CoefsCollectionMemoizer = Memoizer Herd>; -static MAX_CACHED_OBJECT_SIZE: i64 = 1_000; -static POSSIBLE_COEFS_CACHE: Lazy> = Lazy::new(|| { - Mutex::new(Memoizer::new(|possible_coefs| { - expand_finite_downward_closure(possible_coefs) - .multi_cartesian_product() - .map(Ideal::from_vec) - .collect() - })) -}); - -fn expand_finite_downward_closure<'a>( - possible_coefs: &'a CoefsCollection, -) -> impl Iterator> + 'a { - trace!("compute_possible_coefs({:?})", possible_coefs); - possible_coefs.iter().map(|v| { - let coef = v - .iter() - .filter_map(|&x| match x { - OMEGA => None, - Coef::Value(c) => Some(c), - }) - .next(); - let is_omega = v.contains(&OMEGA); - match (is_omega, coef) { - (false, None) => vec![C0], - (true, None) => vec![OMEGA], - (false, Some(c)) => (0..c + 1).map(Coef::Value).rev().collect(), - (true, Some(c)) => std::iter::once(OMEGA) - .chain((0..c + 1).map(Coef::Value).rev()) - .collect(), - } - }) + +/** + * every vector comes in order omega / 0 / c+1 / 2 / 1 + */ +fn expand_finite_downward_closure( + maximal_finite_coef: &Vec, + is_omega_sometimes_possible: &Vec, + is_omega_always_possible: &Vec, +) -> CoefsCollection { + trace!( + "expand_finite_downward_closure maximal_finite_coef {:?} is_omega_sometimes_possible {:?} is_omega_always_possible {:?})", + maximal_finite_coef, + is_omega_sometimes_possible, + is_omega_always_possible + ); + assert!(maximal_finite_coef.len() == is_omega_sometimes_possible.len()); + assert!(maximal_finite_coef.len() == is_omega_always_possible.len()); + maximal_finite_coef + .iter() + .enumerate() + .map(|(i, &coef)| { + let is_omega_sometimes = is_omega_sometimes_possible[i]; + let is_omega_always = is_omega_always_possible[i]; + match (is_omega_always, is_omega_sometimes, coef) { + (true, _, _) => vec![OMEGA], + (false, true, _) => vec![C0, OMEGA], + (false, false, c) => std::iter::once(C0) + .chain((1..c + 1).map(Coef::Value).rev()) + .collect(), + } + }) + .collect() } impl DownSet { @@ -247,16 +241,22 @@ impl DownSet { //compute for every i whether omega should be allowed at i, //this is the case iff there exists a ideal in the downward-closed set such that //on that coordinate the non-empty set of successors all lead to omega - let is_omega_possible = (0..dim) + let is_omega_sometimes_possible = (0..dim) .map(|i| { let succ = edges.get_successors(i); !succ.is_empty() && self.0.iter().any(|ideal| ideal.all_omega(&succ)) }) .collect::>(); + let is_omega_always_possible = (0..dim) + .map(|i| { + let succ = edges.get_successors(i); + !succ.is_empty() && self.0.iter().all(|ideal| ideal.all_omega(&succ)) + }) + .collect::>(); //compute for every j the maximal finite coef appearing at index j, if exists //omega are turned to 1 - let max_finite_coordsj: Vec = (0..dim) + let max_finite_coords_post: Vec = (0..dim) .map(|j: usize| { self.0 .iter() @@ -270,103 +270,63 @@ impl DownSet { }) .collect::>(); - let max_finite_coordsi = (0..dim) + let max_finite_coords_pre = (0..dim) .map(|i| { { edges .get_successors(i) .iter() - .map(|&j| std::cmp::min(maximal_finite_coordinate, max_finite_coordsj[j])) - .max() + .map(|&j| { + std::cmp::min(maximal_finite_coordinate, max_finite_coords_post[j]) + }) + .min() .unwrap_or(0) } }) .collect::>(); trace!("preimage of\n{}\n by\n{}\n", self, edges); + trace!("max_finite_coords: {:?}\n", max_finite_coords_pre); + trace!( + "is_omega_sometimes_possible: {:?}\n", + is_omega_sometimes_possible + ); + trace!("is_omega_always_possible: {:?}\n", is_omega_always_possible); - let possible_coefs = (0..dim) - .map(|i| { - match ( - max_finite_coordsi.get(i).unwrap(), - is_omega_possible.get(i).unwrap(), - ) { - (0, false) => vec![Coef::Value(0)], - (0, true) => vec![OMEGA], - (&c, false) => vec![Coef::Value(c)], - (&c, true) => vec![OMEGA, Coef::Value(c)], - } - }) - .collect::>(); - trace!("max_finite_coords: {:?}\n", max_finite_coordsi); - trace!("is_omega_possible: {:?}\n", is_omega_possible); - trace!("possible_coefs: {:?}\n", possible_coefs); - let approximate_size = possible_coefs - .iter() - .map(|v| { - v.iter() - .map(|&x| match x { - C0 => 1, - OMEGA => 2, - Coef::Value(c) => c as i64 + 1, - }) - .reduce(std::cmp::max) - .unwrap_or(1) - }) - .reduce(|a, b| a * b) - .unwrap_or(0); - trace!("approximate_size: {:?}\n", approximate_size); + let all_possible_coefs: CoefsCollection = expand_finite_downward_closure( + &max_finite_coords_pre, + &is_omega_sometimes_possible, + &is_omega_always_possible, + ); + + let max_iteration_nb = all_possible_coefs.iter().fold(1, |acc, l| acc * l.len()); + trace!("max_iteration_nb: {:?}\n", max_iteration_nb); + if max_iteration_nb > 10_000_000 { + warn!("iterating over a potentially very large number of possible ideals (up to {}), will possibly never terminate", max_iteration_nb); + } else { + debug!("iterating over up to {} possible ideals", max_iteration_nb); + } let mut result = DownSet::new(); - if approximate_size < MAX_CACHED_OBJECT_SIZE { - trace!( - "iterating over {} possible ideals using cached value", - approximate_size - ); - let candidates = POSSIBLE_COEFS_CACHE.lock().unwrap().get(possible_coefs); - candidates - .par_iter() - .filter(|&candidate| { - self.is_safe_with_roundup(candidate, edges, maximal_finite_coordinate) - }) - .collect::>() + let mut iterator: Vec = DownSet::get_initial_iterator(&all_possible_coefs); + let mut is_not_over = true; + while is_not_over { + let coordinates = iterator .iter() - .for_each(|c| { - result.insert(c); - }); - } else { - if approximate_size > 10_000_000 { - warn!("iterating over a potentially very large number of possible ideals (up to {}), will possibly never terminate", approximate_size); + .enumerate() + .map(|(i, &j)| all_possible_coefs[i][j]) + .collect(); + let candidate = Ideal::from_vec(coordinates); + trace!("checking candidate {} for safe preimage", candidate); + let is_safe = self.is_safe_with_roundup(&candidate, edges, maximal_finite_coordinate); + if is_safe { + trace!("{} is safe", candidate); + result.insert(&candidate); } else { - debug!("iterating over {} possible ideals", approximate_size); - } - let all_possible_coefs: CoefsCollection = - expand_finite_downward_closure(&possible_coefs).collect(); - let mut iterator: Vec = DownSet::get_initial_iterator(&all_possible_coefs); - let mut is_not_over = true; - while is_not_over { - let candidate = Ideal::from_vec( - iterator - .iter() - .enumerate() - .map(|(i, &j)| all_possible_coefs[i][j]) - .collect(), - ); - trace!("checking candidate {} for safe preimage", candidate); - let is_safe = - self.is_safe_with_roundup(&candidate, edges, maximal_finite_coordinate); - if is_safe { - trace!("{} is safe", candidate); - result.insert(&candidate); - //we can jump to the next incomparable ideal - is_not_over = - DownSet::get_next_incomparable_iterator(&mut iterator, &all_possible_coefs); - } else { - trace!("{} is unsafe", candidate); - is_not_over = DownSet::get_next_iterator(&mut iterator, &all_possible_coefs); - } + trace!("{} is unsafe", candidate); } - }; + is_not_over = DownSet::get_next_iterator(&mut iterator, &all_possible_coefs, is_safe); + } trace!("minimizing result"); result.minimize(); @@ -378,32 +338,69 @@ impl DownSet { assert!(all_possible_coefs.iter().all(|l| !l.is_empty())); all_possible_coefs.iter().map(|_l| 0).collect() } - fn get_next_iterator(iterator: &mut [usize], all_possible_coefs: &CoefsCollection) -> bool { + fn get_next_iterator( + iterator: &mut [usize], + all_possible_coefs: &CoefsCollection, + all_below_current_are_safe: bool, + ) -> bool { assert!(iterator.len() == all_possible_coefs.len()); - for i in (0..iterator.len()).rev() { - if iterator[i] < all_possible_coefs[i].len() - 1 { - iterator[i] += 1; - return true; - } else { + if all_below_current_are_safe { + //l'idéal actuel est gagant donc tous les idéaux plus petits également + //on va chercherun itérateur qui n'est aps plus petit + DownSet::get_next_not_below_current(iterator, all_possible_coefs) + } else { + let mut non_zero = false; + for i in (0..iterator.len()).rev() { + if all_possible_coefs[i].len() == 1 { + continue; //only one coef at this index + } + non_zero |= iterator[i] > 0; + if (iterator[i] == 0 && non_zero) + || (0 < iterator[i] && iterator[i] < all_possible_coefs[i].len() - 1) + { + iterator[i] += 1; + return true; + } iterator[i] = 0; } + false } - false } - fn get_next_incomparable_iterator( + fn get_next_not_below_current( iterator: &mut [usize], all_possible_coefs: &CoefsCollection, ) -> bool { assert!(iterator.len() == all_possible_coefs.len()); for i in (0..iterator.len()).rev() { - if iterator[i] > 0 { - iterator[i] = 0; - for j in (0..i).rev() { - if iterator[j] < all_possible_coefs[j].len() - 1 { - iterator[j] += 1; + if all_possible_coefs[i].len() == 1 { + continue; //only one coef at this index + } + if iterator[i] == 1 { + //on a déjà mis le 1, on ne peut pas incrémenter + continue; + } + if iterator[i] == 0 { + //il suffit de passer à 1 et resetter à droite + iterator[i] = 1; + iterator.iter_mut().skip(i + 1).for_each(|x| *x = 0); + return true; + } + iterator[i] -= 1; + iterator.iter_mut().skip(i + 1).for_each(|x| *x = 0); + //on va incrémenter à gauche de i, dès que possible + let mut j = i; + while j > 0 { + j -= 1; + let itj1 = iterator[j]; + let len1 = all_possible_coefs[j].len() - 1; + if len1 >= 1 && itj1 < len1 { + if itj1 == 0 { + iterator[j] = 1; //on met celui-là au max + iterator.iter_mut().skip(j + 1).for_each(|x| *x = 0); return true; } else { - iterator[j] = 0; + iterator[j] += 1; + return true; } } } @@ -687,7 +684,7 @@ impl fmt::Display for DownSet { mod test { use super::*; - use crate::coef::{C0, C1, C2, OMEGA}; + use crate::coef::{C0, C1, C2, C3, OMEGA}; #[test] fn is_in_ideal() { @@ -963,4 +960,23 @@ mod test { let pre_image0 = downset0.safe_pre_image(&edges, dim as coef); assert_eq!(pre_image0, DownSet::from_vecs(&[&[C2, C0, C0, C0, C0]])); } + + #[test] + fn expand_finite_downward_closure() { + use crate::downset::expand_finite_downward_closure; + let expanded = expand_finite_downward_closure( + &vec![0, 3, 1, 0], + &vec![true, false, false, true], + &vec![true, false, false, false], + ); + assert_eq!( + expanded, + vec![ + vec![OMEGA], + vec![C0, C3, C2, C1], + vec![C0, C1], + vec![C0, OMEGA], + ] + ); + } } diff --git a/src/main.rs b/src/main.rs index c0bd08f..dc33c94 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,11 @@ use clap::Parser; +use log::info; use std::fs::File; use std::io; use std::io::Write; -use log::info; -use shepherd::solver; use shepherd::nfa; +use shepherd::solver; mod cli; mod logging; diff --git a/tests/integration_test.rs b/tests/integration_test.rs index f243259..4d1ff74 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -1,8 +1,8 @@ -use shepherd::nfa; -use shepherd::solver; use shepherd::coef::{C0, C1, C2, OMEGA}; use shepherd::downset::DownSet; use shepherd::ideal::Ideal; +use shepherd::nfa; +use shepherd::solver; const EXAMPLE1: &str = include_str!("../examples/bottleneck-1-ab.tikz"); const EXAMPLE1_COMPLETE: &str = include_str!("../examples/bottleneck-1-ab-complete.tikz"); @@ -138,4 +138,6 @@ fn test_bug12() { .unwrap(); println!("{}", downsetb); assert!(downsetb.contains(&Ideal::from_vec(vec![C2, C0, C0, C0, C0, C0, C0, C0]))); + assert!(downsetb.contains(&Ideal::from_vec(vec![C0, C1, C1, C0, C0, C0, C0, C0]))); + assert!(downsetb.contains(&Ideal::from_vec(vec![C0, C0, C0, C0, C0, C0, C0, OMEGA]))); } From be67696ff9bf00fdc634264e8440e68f60b6e260 Mon Sep 17 00:00:00 2001 From: Hugo Gimbert Date: Thu, 29 May 2025 15:28:45 +0200 Subject: [PATCH 6/6] compute iterated majority n = 3 in less than two minutes --- src/cli.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli.rs b/src/cli.rs index 958c2d5..6f7c960 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -73,7 +73,7 @@ pub struct Args { long, value_enum, default_value = "strategy", - help = "Solver output specification." + help = "Solver output specification. Strategy will always compute the maximal winning strategy. Yes-no may be faster when the answer is positive, it uses a heuristic trying to find a simple strategy, which is winning but might not be maximal." )] pub solver_output: solver::SolverOutput, }