diff --git a/jaguars/src/collision_detection/cd_engine.rs b/jaguars/src/collision_detection/cd_engine.rs index 8dbd9f4..89a34bf 100644 --- a/jaguars/src/collision_detection/cd_engine.rs +++ b/jaguars/src/collision_detection/cd_engine.rs @@ -244,7 +244,7 @@ impl CDEngine { return true; } } - for pier in base_surrogate.piers() { + for pier in base_surrogate.ff_piers() { let t_pier = pier.transform_clone(transform); if self.quadtree.collides(&t_pier, ignored_entities).is_some() { return true; diff --git a/jaguars/src/collision_detection/haz_prox_grid/hpg_cell.rs b/jaguars/src/collision_detection/haz_prox_grid/hpg_cell.rs index a68430d..ebc7c48 100644 --- a/jaguars/src/collision_detection/haz_prox_grid/hpg_cell.rs +++ b/jaguars/src/collision_detection/haz_prox_grid/hpg_cell.rs @@ -77,7 +77,7 @@ impl HPGCell { match haz.entity.presence() { GeoPosition::Exterior => (haz, None), //bounding poles only applicable for hazard inside the shape GeoPosition::Interior => { - let pole_bounding_circle = haz.shape.surrogate().poles_bounding_circle(); + let pole_bounding_circle = &haz.shape.surrogate().poles_bounding_circle; let proximity = pole_bounding_circle.distance_from_border(&self.centroid); let proximity = Proximity::new(proximity.0, proximity.1.abs()); (haz, Some(proximity)) @@ -117,7 +117,7 @@ impl HPGCell { //For dynamic hazard_filters, the surrogate poles are used to calculate the distance to the hazard (overestimation, but fast) let haz_prox = match to_register.entity.presence() { - GeoPosition::Interior => distance_to_surrogate_poles_border(self, to_register.shape.surrogate().poles()), + GeoPosition::Interior => distance_to_surrogate_poles_border(self, &to_register.shape.surrogate().poles), GeoPosition::Exterior => unreachable!("No implementation yet for dynamic exterior hazards") }; diff --git a/jaguars/src/collision_detection/quadtree/qt_hazard_vec.rs b/jaguars/src/collision_detection/quadtree/qt_hazard_vec.rs index 12de0e6..9e63da9 100644 --- a/jaguars/src/collision_detection/quadtree/qt_hazard_vec.rs +++ b/jaguars/src/collision_detection/quadtree/qt_hazard_vec.rs @@ -23,7 +23,7 @@ impl QTHazardVec { pub fn add(&mut self, haz: QTHazard) { debug_assert!(self.hazards.iter().filter(|other| other.entity == haz.entity && matches!(haz.entity, HazardEntity::PlacedItem(_))).count() == 0, "More than one hazard from same item entity in the vector! (This should never happen!)"); - match self.hazards.binary_search_by(|other| QTHazardVec::order_stronger(&haz, other)) { + match self.hazards.binary_search_by(|probe| QTHazardVec::order_stronger(probe, &haz)) { Ok(pos) | Err(pos) => { self.n_active += haz.active as usize; self.hazards.insert(pos, haz); @@ -39,24 +39,20 @@ impl QTHazardVec { let haz = self.hazards.remove(pos); self.n_active -= haz.active as usize; Some(haz) - }, + } None => None, }; removed_hazard } - fn remove_index(&mut self, index: usize) -> QTHazard { - let haz = self.hazards.remove(index); - self.n_active -= haz.active as usize; - haz - } - #[inline(always)] pub fn strongest(&self, ignored_entities: &[HazardEntity]) -> Option<&QTHazard> { + debug_assert!(self.hazards.iter().filter(|hz| hz.active).count() == self.n_active, "Active hazards count is not correct!"); debug_assert!(self.hazards.windows(2).all(|w| QTHazardVec::order_stronger(&w[0], &w[1]) != Ordering::Greater), "Hazards are not sorted correctly!"); - match ignored_entities { - [] => self.hazards.get(0), - _ => self.hazards[0..self.n_active].iter().find(|hz| !ignored_entities.contains(&hz.entity)) + match (self.n_active, ignored_entities) { + (0, _) => None, //no active hazards + (_, []) => Some(&self.hazards[0]), //no ignored entities and at least one active hazard + (_, _) => self.hazards[0..self.n_active].iter().find(|hz| !ignored_entities.contains(&hz.entity)), //at least one active hazard and some ignored entities } } @@ -69,7 +65,7 @@ impl QTHazardVec { pub fn activate_hazard(&mut self, entity: &HazardEntity) -> bool { match self.hazards.iter_mut().position(|hz| &hz.entity == entity) { Some(index) => { - let mut hazard = self.remove_index(index); + let mut hazard = self.hazards.remove(index); debug_assert!(!hazard.active); hazard.active = true; self.add(hazard); @@ -82,9 +78,10 @@ impl QTHazardVec { pub fn deactivate_hazard(&mut self, entity: &HazardEntity) -> bool { match self.hazards.iter_mut().position(|hz| &hz.entity == entity) { Some(index) => { - let mut hazard = self.remove_index(index); + let mut hazard = self.hazards.remove(index); debug_assert!(hazard.active); hazard.active = false; + self.n_active -= 1; self.add(hazard); true } @@ -92,7 +89,7 @@ impl QTHazardVec { } } - pub fn active_hazards(&self) -> &[QTHazard]{ + pub fn active_hazards(&self) -> &[QTHazard] { &self.hazards[0..self.n_active] } @@ -113,8 +110,8 @@ impl QTHazardVec { } fn order_stronger(qth1: &QTHazard, qth2: &QTHazard) -> Ordering { - //sort by active, then by presence - qth1.active.cmp(&qth2.active) - .then(qth1.presence.cmp(&qth2.presence)) + //sort by active, then by presence, so that the active hazards are always in front of inactive hazards, and Entire hazards are always in front of Partial hazards + (qth1.active.cmp(&qth2.active).reverse()) + .then(qth1.presence.cmp(&qth2.presence).reverse()) } } \ No newline at end of file diff --git a/jaguars/src/geometry/fail_fast/sp_surrogate.rs b/jaguars/src/geometry/fail_fast/sp_surrogate.rs index 2ba34c4..30fb96d 100644 --- a/jaguars/src/geometry/fail_fast/sp_surrogate.rs +++ b/jaguars/src/geometry/fail_fast/sp_surrogate.rs @@ -1,3 +1,4 @@ +use std::ops::Range; use crate::geometry::convex_hull; use crate::geometry::fail_fast::{piers, poi}; use crate::geometry::geo_traits::{Transformable, TransformableFrom}; @@ -9,11 +10,11 @@ use crate::util::config::SPSurrogateConfig; #[derive(Clone, Debug)] pub struct SPSurrogate { - config: SPSurrogateConfig, - convex_hull_indices: Vec, - poles: Vec, - poles_bounding_circle: Circle, - piers: Vec, + pub convex_hull_indices: Vec, + pub poles: Vec, + pub poles_bounding_circle: Circle, + pub piers: Vec, + pub ff_pole_range: Range, } impl SPSurrogate { @@ -25,49 +26,32 @@ impl SPSurrogate { let relevant_poles_for_piers = &poles[0..config.n_ff_poles+1]; //poi + all poles that will be checked during fail fast are relevant for piers let piers = piers::generate(simple_poly, config.n_ff_piers, relevant_poles_for_piers); + let ff_poles = 1..config.n_ff_poles+1; Self { - config, convex_hull_indices, poles, piers, poles_bounding_circle, + ff_pole_range: ff_poles } } - pub fn convex_hull_indices(&self) -> &Vec { - &self.convex_hull_indices - } - - pub fn piers(&self) -> &[Edge] { - &self.piers - } - - pub fn poles(&self) -> &[Circle] { - &self.poles - } - pub fn ff_poles(&self) -> &[Circle] { - if self.poles.is_empty() { - &[] - } else { - &self.poles[1..self.config.n_ff_poles + 1] - } + let range = self.ff_pole_range.clone(); + &self.poles[range] } - pub fn poles_bounding_circle(&self) -> &Circle { - &self.poles_bounding_circle + pub fn ff_piers(&self) -> &[Edge] { + &self.piers } - pub fn config(&self) -> SPSurrogateConfig { - self.config - } } impl Transformable for SPSurrogate { fn transform(&mut self, t: &Transformation) -> &mut Self { //destructuring pattern used to ensure that the code is updated when the struct changes - let Self { config: _, convex_hull_indices: _, poles, piers, poles_bounding_circle } = self; + let Self {convex_hull_indices: _, poles, poles_bounding_circle, piers, ff_pole_range: _} = self; //transform poles poles.iter_mut().for_each(|c| { @@ -87,12 +71,11 @@ impl Transformable for SPSurrogate { impl TransformableFrom for SPSurrogate { fn transform_from(&mut self, reference: &Self, t: &Transformation) -> &mut Self { - debug_assert!(self.config == reference.config); - debug_assert!(self.poles().len() == reference.poles().len()); - debug_assert!(self.piers().len() == reference.piers().len()); + debug_assert!(self.poles.len() == reference.poles.len()); + debug_assert!(self.piers.len() == reference.piers.len()); //destructuring pattern used to ensure that the code is updated when the struct changes - let Self { config: _, convex_hull_indices: _, poles, piers, poles_bounding_circle } = self; + let Self {convex_hull_indices: _, poles, poles_bounding_circle, piers, ff_pole_range: _} = self; for (pole, ref_pole) in poles.iter_mut().zip(reference.poles.iter()) { pole.transform_from(ref_pole, t); diff --git a/jaguars/src/geometry/primitives/simple_polygon.rs b/jaguars/src/geometry/primitives/simple_polygon.rs index 0aa8763..311bf40 100644 --- a/jaguars/src/geometry/primitives/simple_polygon.rs +++ b/jaguars/src/geometry/primitives/simple_polygon.rs @@ -69,7 +69,7 @@ impl SimplePolygon { self.surrogate = Some(SPSurrogate::new(self, config)); } - pub fn clone_without_surrogate(&self) -> Self { + pub fn clone_and_strip_surrogate(&self) -> Self { SimplePolygon { surrogate: None, ..self.clone() diff --git a/lbf/Cargo.toml b/lbf/Cargo.toml index 38111e3..c63fd50 100644 --- a/lbf/Cargo.toml +++ b/lbf/Cargo.toml @@ -18,6 +18,7 @@ svg = "0.14.0" ordered-float = "4.2.0" clap = { version = "4.4.18", features = ["derive"] } mimalloc = "0.1.39" +tribool = "0.3.0" [dev-dependencies] criterion = "0.5.1" @@ -26,6 +27,10 @@ criterion = "0.5.1" name = "quadtree_bench" harness = false +[[bench]] +name = "fast_fail_bench" +harness = false + [profile.release] opt-level = 3 diff --git a/lbf/benches/fast_fail_bench.rs b/lbf/benches/fast_fail_bench.rs index e69de29..ceddc22 100644 --- a/lbf/benches/fast_fail_bench.rs +++ b/lbf/benches/fast_fail_bench.rs @@ -0,0 +1,141 @@ +use std::fs::File; +use std::io::BufReader; +use std::ops::RangeInclusive; +use std::path::Path; +use std::sync::Arc; +use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; +use itertools::Itertools; +use log::debug; +use rand::prelude::{IteratorRandom, SmallRng}; +use rand::SeedableRng; +use tribool::Tribool; +use jaguars::entities::instance::{Instance, PackingType}; +use jaguars::entities::placed_item::PlacedItemUID; +use jaguars::entities::placing_option::PlacingOption; +use jaguars::entities::problems::problem::{LayoutIndex, Problem, ProblemEnum}; +use jaguars::entities::problems::sp_problem::SPProblem; +use jaguars::geometry::fail_fast::sp_surrogate::SPSurrogate; +use jaguars::geometry::geo_traits::TransformableFrom; +use jaguars::io::json_instance::JsonInstance; +use jaguars::io::parser::Parser; +use jaguars::util::config::{CDEConfig, HazProxConfig, QuadTreeConfig, SPSurrogateConfig}; +use jaguars::util::polygon_simplification::PolySimplConfig; +use lbf::config::Config; +use lbf::io; +use lbf::io::layout_to_svg::layout_to_svg; +use lbf::io::svg_util::SvgDrawOptions; +use lbf::lbf_optimizer::LBFOptimizer; +use lbf::samplers::uniform_rect_sampler::UniformAARectSampler; +use crate::util::SWIM_PATH; + +criterion_main!(benches); +criterion_group!(benches, fast_fail_query_bench); + +mod util; + +const N_ITEMS_REMOVED: usize = 8; + +const FF_POLE_RANGE: RangeInclusive = 0..=0; +const FF_PIER_RANGE: RangeInclusive = 0..=0; + +/// Benchmark the query operation of the quadtree for different depths +/// We validate 1000 sampled transformations for each of the 5 removed items +fn fast_fail_query_bench(c: &mut Criterion) { + let json_instance: JsonInstance = serde_json::from_reader(BufReader::new(File::open(SWIM_PATH).unwrap())).unwrap(); + + let mut group = c.benchmark_group("fast_fail_query_bench"); + + let config_combos= FF_POLE_RANGE.map(|n_ff_poles| FF_PIER_RANGE.map(|n_ff_piers| (n_ff_poles, n_ff_piers)).collect_vec()).flatten().collect_vec(); + + for ff_surr_config in config_combos { + let config = create_config(&ff_surr_config); + let instance = util::create_instance(&json_instance, config.cde_config, config.poly_simpl_config); + let mut problem = util::create_blf_problem(instance.clone(), config); + + let layout_index = LayoutIndex::Existing(0); + let mut rng = SmallRng::seed_from_u64(0); + let mut selected_pi_uids: [Option; N_ITEMS_REMOVED] = <[_; N_ITEMS_REMOVED]>::default(); + + { + // Remove 5 items from the layout + problem.get_layout(&layout_index).placed_items().iter() + .map(|p_i| Some(p_i.uid().clone())) + .choose_multiple_fill(&mut rng, &mut selected_pi_uids); + + for pi_uid in selected_pi_uids.iter().flatten() { + problem.remove_item(layout_index, pi_uid); + println!("Removed item: {} with {} edges", pi_uid.item_id, instance.item(pi_uid.item_id).shape().number_of_points()); + } + problem.flush_changes(); + } + + { + let draw_options = SvgDrawOptions{ + quadtree: true, + ..SvgDrawOptions::default() + }; + let svg = io::layout_to_svg::layout_to_svg(problem.get_layout(&layout_index), &instance, draw_options); + io::write_svg(&svg, Path::new("removed_items.svg")); + } + + + let (n_ff_poles, n_ff_piers) = ff_surr_config; + + let custom_surrogates = selected_pi_uids.clone().map(|pi_uid| { + let item = instance.item(pi_uid.as_ref().unwrap().item_id); + let mut surrogate = item.shape().surrogate().clone(); + surrogate.ff_pole_range = 0..n_ff_poles; //also check PoI, which is not standard + surrogate + }); + + let mut n_invalid: i64 = 0; + let mut n_valid : i64 = 0; + + group.bench_function(BenchmarkId::from_parameter(format!("{n_ff_poles}_poles_{n_ff_piers}_piers")), |b| { + b.iter(|| { + for i in 0..N_ITEMS_REMOVED { + let pi_uid = selected_pi_uids[i].as_ref().unwrap(); + let layout = problem.get_layout(&layout_index); + let item = instance.item(pi_uid.item_id); + let surrogate = &custom_surrogates[i]; + let mut buffer_shape = item.shape().clone(); + let sampler = UniformAARectSampler::new(layout.bin().bbox(), item); + for _ in 0..1000 { + let d_transf = sampler.sample(&mut rng); + let transf = d_transf.compose(); + let collides = match layout.cde().surrogate_collides(surrogate, &transf, &[]) { + true => true, + false => { + buffer_shape.transform_from(item.shape(), &transf); + layout.cde().poly_collides(&buffer_shape, &[]) + } + }; + match collides { + true => n_invalid += 1, + false => n_valid += 1 + } + } + } + }) + }); + println!("{:.3}% valid", n_valid as f64 / (n_invalid + n_valid) as f64 * 100.0); + } + group.finish(); +} + +fn create_config(ff_surrogate: &(usize, usize)) -> Config { + Config { + cde_config: CDEConfig { + quadtree: QuadTreeConfig::FixedDepth(10), + haz_prox: HazProxConfig::Enabled { n_cells: 1 }, + item_surrogate_config: SPSurrogateConfig { + pole_coverage_goal: 0.9, + max_poles: 10, + n_ff_poles: ff_surrogate.0, + n_ff_piers: ff_surrogate.1, + }, + }, + poly_simpl_config: PolySimplConfig::Disabled, + ..Config::default() + } +} \ No newline at end of file diff --git a/lbf/benches/quadtree_bench.rs b/lbf/benches/quadtree_bench.rs index c91ffc1..ef6d977 100644 --- a/lbf/benches/quadtree_bench.rs +++ b/lbf/benches/quadtree_bench.rs @@ -20,11 +20,13 @@ use jaguars::util::polygon_simplification::PolySimplConfig; use lbf::config::Config; use lbf::lbf_optimizer::LBFOptimizer; use lbf::samplers::uniform_rect_sampler::UniformAARectSampler; +use crate::util::SWIM_PATH; criterion_main!(benches); criterion_group!(benches, quadtree_query_update_1000_1, quadtree_query_bench, quadtree_update_bench); -const SWIM_PATH: &str = "../assets/swim.json"; +mod util; + const N_ITEMS_REMOVED: usize = 5; const QT_DEPTHS: [u8; 9] = [0, 1, 2, 3, 4, 5, 6, 7, 8]; @@ -108,8 +110,8 @@ fn quadtree_query_bench(c: &mut Criterion) { let mut group = c.benchmark_group("quadtree_query"); for depth in QT_DEPTHS { config.cde_config.quadtree = QuadTreeConfig::FixedDepth(depth); - let instance = create_instance(&json_instance, config.cde_config, config.poly_simpl_config); - let mut problem = create_blf_problem(instance.clone(), config); + let instance = util::create_instance(&json_instance, config.cde_config, config.poly_simpl_config); + let mut problem = util::create_blf_problem(instance.clone(), config); let layout_index = LayoutIndex::Existing(0); let mut rng = SmallRng::seed_from_u64(0); @@ -165,8 +167,8 @@ fn quadtree_query_update_1000_1(c: &mut Criterion) { let mut group = c.benchmark_group("quadtree_query_update_1000_1"); for depth in QT_DEPTHS { config.cde_config.quadtree = QuadTreeConfig::FixedDepth(depth); - let instance = create_instance(&json_instance, config.cde_config, config.poly_simpl_config); - let mut problem = create_blf_problem(instance.clone(), config); + let instance = util::create_instance(&json_instance, config.cde_config, config.poly_simpl_config); + let mut problem = util::create_blf_problem(instance.clone(), config); let layout_index = LayoutIndex::Existing(0); let mut rng = SmallRng::seed_from_u64(0); @@ -224,21 +226,4 @@ fn quadtree_query_update_1000_1(c: &mut Criterion) { }); } group.finish(); -} - -fn create_instance(json_instance: &JsonInstance, cde_config: CDEConfig, poly_simpl_config: PolySimplConfig) -> Arc { - let parser = Parser::new(poly_simpl_config, cde_config, true); - Arc::new(parser.parse(json_instance)) -} - -fn create_blf_problem(instance: Arc, config: Config) -> SPProblem { - assert!(matches!(instance.packing_type(), PackingType::StripPacking {..})); - let rng = SmallRng::seed_from_u64(0); - let mut lbf_optimizer = LBFOptimizer::new(instance, config, rng); - lbf_optimizer.solve(); - - match lbf_optimizer.problem().clone() { - ProblemEnum::SPProblem(sp_problem) => sp_problem, - _ => panic!("Expected SPProblem") - } -} +} \ No newline at end of file diff --git a/lbf/benches/util.rs b/lbf/benches/util.rs new file mode 100644 index 0000000..27b3cfb --- /dev/null +++ b/lbf/benches/util.rs @@ -0,0 +1,30 @@ +use std::sync::Arc; +use rand::prelude::SmallRng; +use rand::SeedableRng; +use jaguars::entities::instance::{Instance, PackingType}; +use jaguars::entities::problems::problem::ProblemEnum; +use jaguars::entities::problems::sp_problem::SPProblem; +use jaguars::io::json_instance::JsonInstance; +use jaguars::io::parser::Parser; +use jaguars::util::config::CDEConfig; +use jaguars::util::polygon_simplification::PolySimplConfig; +use lbf::config::Config; +use lbf::lbf_optimizer::LBFOptimizer; + +pub const SWIM_PATH: &str = "../assets/swim.json"; +pub fn create_instance(json_instance: &JsonInstance, cde_config: CDEConfig, poly_simpl_config: PolySimplConfig) -> Arc { + let parser = Parser::new(poly_simpl_config, cde_config, true); + Arc::new(parser.parse(json_instance)) +} + +pub fn create_blf_problem(instance: Arc, config: Config) -> SPProblem { + assert!(matches!(instance.packing_type(), PackingType::StripPacking {..})); + let rng = SmallRng::seed_from_u64(0); + let mut lbf_optimizer = LBFOptimizer::new(instance, config, rng); + lbf_optimizer.solve(); + + match lbf_optimizer.problem().clone() { + ProblemEnum::SPProblem(sp_problem) => sp_problem, + _ => panic!("Expected SPProblem") + } +} diff --git a/lbf/src/io/layout_to_svg.rs b/lbf/src/io/layout_to_svg.rs index 2ef4e66..a060577 100644 --- a/lbf/src/io/layout_to_svg.rs +++ b/lbf/src/io/layout_to_svg.rs @@ -9,8 +9,12 @@ use jaguars::geometry::primitives::circle::Circle; use crate::io::{svg_data_export, svg_util}; use crate::io::svg_util::{SvgDrawOptions}; -pub fn layout_to_svg(s_layout: &LayoutSnapshot, instance: &Instance, options: SvgDrawOptions) -> Document { +pub fn s_layout_to_svg(s_layout: &LayoutSnapshot, instance: &Instance, options: SvgDrawOptions) -> Document { let layout = Layout::new_from_stored(s_layout.id(), s_layout, &instance); + layout_to_svg(&layout, instance, options) +} + +pub fn layout_to_svg(layout: &Layout, instance: &Instance, options: SvgDrawOptions) -> Document { let bin = layout.bin(); let vbox = bin.bbox().clone().scale(1.05); @@ -134,11 +138,11 @@ pub fn layout_to_svg(s_layout: &LayoutSnapshot, instance: &Instance, options: Sv ]; let transformed_surrogate = item.shape().surrogate().transform_clone(&pi.d_transformation().compose()); - let poi = &transformed_surrogate.poles()[0]; + let poi = &transformed_surrogate.poles[0]; let ff_poles = transformed_surrogate.ff_poles(); - for i in 0..transformed_surrogate.poles().len() { - let pole = &transformed_surrogate.poles()[i]; + for i in 0..transformed_surrogate.poles.len() { + let pole = &transformed_surrogate.poles[i]; if pole == poi { group = group.add(svg_data_export::circle(pole, &poi_style)); } @@ -148,7 +152,7 @@ pub fn layout_to_svg(s_layout: &LayoutSnapshot, instance: &Instance, options: Sv group = group.add(svg_data_export::circle(pole, &no_ff_style)); } } - for pier in transformed_surrogate.piers() { + for pier in &transformed_surrogate.piers { group = group.add(svg_data_export::data_to_path(svg_data_export::edge_data(pier), &ff_style)); } } diff --git a/lbf/src/lbf_optimizer.rs b/lbf/src/lbf_optimizer.rs index e8c4103..311f099 100644 --- a/lbf/src/lbf_optimizer.rs +++ b/lbf/src/lbf_optimizer.rs @@ -149,7 +149,7 @@ pub fn sample_layout(problem: &ProblemEnum, layout_index: LayoutIndex, item: &It let shape = item.shape(); let surrogate = item.shape().surrogate(); - let mut buffer_shape = shape.clone_without_surrogate(); + let mut buffer_shape = shape.clone_and_strip_surrogate(); let mut best = None; diff --git a/lbf/src/main.rs b/lbf/src/main.rs index 6381b52..7ea3f1c 100644 --- a/lbf/src/main.rs +++ b/lbf/src/main.rs @@ -17,7 +17,7 @@ use lbf::{EPOCH, io}; use lbf::lbf_optimizer::LBFOptimizer; use lbf::io::cli::Cli; use lbf::io::json_output::JsonOutput; -use lbf::io::layout_to_svg::layout_to_svg; +use lbf::io::layout_to_svg::{s_layout_to_svg}; fn main() { io::init_logger(Some(LevelFilter::Info)); @@ -64,6 +64,6 @@ fn main() { for (i,s_layout) in solution.layout_snapshots().iter().enumerate(){ let svg_path = args.solution_folder.join(format!("sol_{}_{}.svg", input_file_stem, i)); - io::write_svg(&layout_to_svg(s_layout, &instance, config.svg_draw_options), Path::new(&svg_path)); + io::write_svg(&s_layout_to_svg(s_layout, &instance, config.svg_draw_options), Path::new(&svg_path)); } } \ No newline at end of file