From c907b7598d326c9a2dac0c0904b3b2c0e2823281 Mon Sep 17 00:00:00 2001 From: Jeroen Gardeyn Date: Wed, 28 Feb 2024 13:39:04 +0100 Subject: [PATCH] small changes to ls_sampler --- README.md | 105 ++++++++++++------ assets/config_lbf.json | 2 +- jagua-rs/src/entities/bin.rs | 2 +- .../src/entities/problems/strip_packing.rs | 2 +- lbf/benches/edge_sensitivity_bench.rs | 12 +- lbf/benches/util.rs | 10 +- lbf/src/io/json_output.rs | 4 +- lbf/src/{config.rs => lbf_config.rs} | 12 +- lbf/src/lbf_cost.rs | 10 +- lbf/src/lbf_optimizer.rs | 12 +- lbf/src/lib.rs | 2 +- lbf/src/main.rs | 12 +- lbf/src/samplers/ls_sampler.rs | 65 ++++++----- lbf/tests/tests.rs | 4 +- 14 files changed, 144 insertions(+), 110 deletions(-) rename lbf/src/{config.rs => lbf_config.rs} (78%) diff --git a/README.md b/README.md index 280c6d5..d010f8a 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,46 @@ # Jagua-rs [![Rust CI](https://github.com/JeroenGar/jagua-rs/actions/workflows/rust.yml/badge.svg?branch=main)](https://github.com/JeroenGar/jagua-rs/actions/workflows/rust.yml)[![Docs](https://github.com/JeroenGar/jagua-rs/actions/workflows/doc.yml/badge.svg)](https://jeroengar.github.io/jagua-rs-docs/jagua_rs/) + ### A fast and fearless Collision Detection Engine for 2D irregular Cutting and Packing problems jagua-rs logo ## Preamble -2D irregular cutting and packing (C&P) problems are a class of optimization problems that involve placing irregular shaped items into containers in an efficient way. + +2D irregular cutting and packing (C&P) problems are a class of optimization problems that involve placing irregular +shaped items into containers in an efficient way. These problems contain two distinct challenges: - * **Combinatorial**: deciding which items to place in which configuration in order to optimize some objective function. - * **Geometric**: determining the feasibility of a placement. Does the item fit in the container? Does it not collide with other items? -`jagua-rs` decouples these challenges by providing a Collision Detection Engine (CDE) that efficiently deals with the geometric challenge. -By making use of `jagua-rs`, you can confidently focus on the combinatorial aspects without having to worry about sufficiently fast feasibility checks. +* **Combinatorial**: deciding which items to place in which configuration in order to optimize some objective function. +* **Geometric**: determining the feasibility of a placement. Does the item fit in the container? Does it not collide + with other items? + +This project aim to decouples these challenges by providing a Collision Detection Engine (CDE) that deals with the +geometric aspects of the problem. +`jagua-rs` enables you to confidently focus on the combinatorial aspects of the problem, without having to worry about +the geometric feasibility of the placements. +The speed at which these *feasibility checks* can be resolved is of paramount importance, defining the design +constraints of the optimization algorithms that rely on it. -A reference implementation of an optimization algorithm built on top of `jagua-rs` is also provided in the form of the `lbf` crate. +`jagua-rs` can be used as-is (as a library) to build your own optimization algorithm on top of it. +Or it can be used as the starting point for building a custom CDE tailored to your specific problem variant and use case. + +We also provide a reference implementation of an optimization algorithm built on top of `jagua-rs` in the `lbf` crate. ## `jagua-rs` 🐆 -`jagua-rs` incorporates all components required to create an **easily manipulable internal representation** of 2D irregular C&P problems. -It also boasts a powerful **Collision Detection Engine (CDE)** that can efficiently determine whether an item can fit at a specific position without *colliding* with anything. -The speed at which these *feasibility checks* can be resolved is of paramount importance, defining the design constraints of the optimization algorithms that rely on it. + +`jagua-rs` incorporates all components required to create an **easily manipulable internal representation** of 2D +irregular C&P problems. +It also boasts a powerful **Collision Detection Engine (CDE)** which determines whether an item can fit at a specific +position without causing any *collisions*. ### Design Goals -- **Currently supports:** - - [x] Bin- and strip-packing problems - - [x] Both irregular shaped items and bins - - [x] Continuous rotation and translation - - [x] Holes and quality zones in the bin + +- **Performant:** + - [x] Focus on maximum performance, both in terms of query resolution and update speed + - [x] Able to resolve millions of collision queries per second + - [x] Contains preprocessor to simplify polygons - **Robust:** - - [x] Uses the polygon representation of items and bins, and mimics the results of a simple trigonometric approach + - [x] Uses the polygon representation shapes and mimics the results of a basic trigonometric approach - [x] Special care is taken to handle floating-point arithmetic edge cases - [x] Written in pure Rust 🦀 - **Adaptable:** @@ -34,18 +48,24 @@ The speed at which these *feasibility checks* can be resolved is of paramount im - [x] Add extra constraints by creating new `Hazards` and `HazardFilters` - [x] `Hazards`: consolidating all spatial constraints into a single model - [x] `HazardFilters`: excluding specific `Hazards` on a per-query basis -- **Performant:** - - [x] Focus on maximum `query` and `update` performance - - [x] Able to resolve millions of collision queries per second - - [x] Contains preprocessor to simplify polygons +- **Currently supports:** + - [x] Bin- & strip-packing problems + - [x] Irregular shaped items & bins + - [x] Continuous rotation & translation + - [x] Holes and quality zones in the bin ## `lbf` ↙️ + The `lbf` crate contains a reference implementation of an optimization algorithm built on top of `jagua-rs`. -It is a simple left-bottom-fill heuristic, which places the items one-by-one in the bin, each time at the left-bottom most position. -It should provide a good starting point for anyone interested building their own optimization algorithm on top of `jagua-rs`. +It is a simple left-bottom-fill heuristic, which places the items one-by-one in the bin, each time at the left-bottom +most position. +It should provide a good starting point for anyone interested building their own optimization algorithm on top +of `jagua-rs`. ### How to run + General usage: + ```bash cd lbf cargo run --release -- \ @@ -56,6 +76,7 @@ cargo run --release -- \ ``` Concrete example: + ```bash cd lbf cargo run --release -- \ @@ -69,7 +90,9 @@ cargo run --release -- \ The [assets](assets) folder contains a set of input files from the academic literature that were converted to the same JSON structure. -The files are also available in the [OR-Datasets repository](https://github.com/Oscar-Oliveira/OR-Datasets/tree/master/Cutting-and-Packing/2D-Irregular) by Oscar Oliveira. +The files are also available in +the [OR-Datasets repository](https://github.com/Oscar-Oliveira/OR-Datasets/tree/master/Cutting-and-Packing/2D-Irregular) +by Oscar Oliveira. ### Solution @@ -79,7 +102,8 @@ Two types of files are written in the solution folder: #### JSON The solution JSON is similar to the input JSON, but with the addition of the `Solution` key at the top level. -It contains all information required to recreate the solution, including the containers used, the placements of the items and some additional stats. +It contains all information required to recreate the solution, including the containers used, the placements of the +items and some additional stats. #### SVG @@ -88,9 +112,8 @@ By default, just the container and placed inside it are drawn. Optionally the quadtree, hazard proximity grid or fail-fast surrogates can be drawn on top. This can be configured in the config file. -See [docs](https://jeroengar.github.io/jagua-rs-docs/lbf/io/svg_util/struct.SvgDrawOptions.html) for all available options. - - +See [docs](https://jeroengar.github.io/jagua-rs-docs/lbf/io/svg_util/struct.SvgDrawOptions.html) for all available +options. *Note: Unfortunately, the SVG standard does not support strokes drawn purely inside (or outside) shapes. Items might therefore sometimes falsely appear to be (very slightly) colliding in the SVG visualizations.* @@ -120,31 +143,38 @@ The configuration file is a JSON file with the following structure: "tolerance": 0.001 //Polygons will be simplified until they deviate at most 0.1% from their original area. } }, - "deterministic_mode": true, //The heuristic will always produce the same solution for the same input and configuration + "prng_seed": 0, //Seed for the pseudo-random number generator. If not defined the outcome will be non-deterministic "n_samples_per_item": 5000, //5000 placement samples will be queried per item. "ls_samples_fraction": 0.2 //Of those 5000, 80% will be sampled at uniformly at random, 20% will be local search samples } ``` -See [docs](https://jeroengar.github.io/jagua-rs-docs/lbf/config/struct.Config.html) for a detailed description of all available options. + +See [docs](https://jeroengar.github.io/jagua-rs-docs/lbf/config/struct.Config.html) for a detailed description of all +available options. ### Important note -Due to `lbf` being a one-pass constructive heuristic, the final solution quality is extremely *chaotic*. \ -Meaning that minute changes in the flow (sorting of the items, configuration, prng seed...) lead to solutions with drastically different quality. \ -Seemingly superior configurations (such as increased `n_samples_per_item`), for example, can result in worse solutions and vice versa. \ -Testing with `deterministic_mode` set to `false` will demonstrate this spread in solution quality. +Due to `lbf` being a one-pass constructive heuristic, the final solution quality is very *chaotic*. \ +Meaning that minute changes in the flow of the algorithm (sorting of the items, configuration, prng seed...) lead to solutions with +drastically different quality. \ +Seemingly superior configurations (such as increased `n_samples_per_item`), for example, can result in worse solutions +and vice versa. \ +Setting `prng_seed: null` will demonstrate this spread in solution quality. -**Once again, this heuristic should only serve as a reference implementation of how to use `jagua-rs` and not as a reliable optimization algorithm for any real-world problems.** +**Once again, this heuristic should only serve as a reference implementation of how to use `jagua-rs` and not as a +reliable optimization algorithm for any real-world problems.** ## Testing The `jagua-rs` codebase contains a suite of assertion checks which verify the correctness of the engine. -These `debug_asserts` are enabled by default in debug builds and tests but are omitted in release builds to maximize performance. +These `debug_asserts` are enabled by default in debug builds and tests but are omitted in release builds to maximize +performance. Additionally, `lbf` contains some basic integration tests to validate the correctness of the engine on a macro level. It basically runs the heuristic on a set of input files with multiple configurations with assertions enabled. To run the tests, use: + ```bash cd lbf cargo test @@ -161,9 +191,12 @@ Alternatively, you can compile and view the docs locally with `cargo doc --open` ## Acknowledgements -This project was funded by [Research Foundation - Flanders (FWO)](https://www.fwo.be/en/) (grant number: 1S71222N) +This project and funded by [Research Foundation - Flanders (FWO)](https://www.fwo.be/en/) (grant number: 1S71222N) and +developed at [KU Leuven](https://www.kuleuven.be/english/). -FWO logo +FWO logo +      +KU Leuven logo ## License diff --git a/assets/config_lbf.json b/assets/config_lbf.json index c502577..cd87998 100644 --- a/assets/config_lbf.json +++ b/assets/config_lbf.json @@ -15,7 +15,7 @@ "tolerance": 0.001 } }, - "deterministic_mode": true, + "prng_seed": 0, "n_samples_per_item": 5000, "ls_samples_fraction": 0.2 } diff --git a/jagua-rs/src/entities/bin.rs b/jagua-rs/src/entities/bin.rs index 6d1bcd5..d79ebbb 100644 --- a/jagua-rs/src/entities/bin.rs +++ b/jagua-rs/src/entities/bin.rs @@ -5,8 +5,8 @@ use itertools::Itertools; use crate::collision_detection::cd_engine::CDEngine; use crate::collision_detection::hazard::Hazard; use crate::collision_detection::hazard::HazardEntity; -use crate::entities::quality_zone::QualityZone; use crate::entities::quality_zone::N_QUALITIES; +use crate::entities::quality_zone::QualityZone; use crate::geometry::geo_traits::Shape; use crate::geometry::primitives::aa_rectangle::AARectangle; use crate::geometry::primitives::simple_polygon::SimplePolygon; diff --git a/jagua-rs/src/entities/problems/strip_packing.rs b/jagua-rs/src/entities/problems/strip_packing.rs index 13b994a..2e2b58f 100644 --- a/jagua-rs/src/entities/problems/strip_packing.rs +++ b/jagua-rs/src/entities/problems/strip_packing.rs @@ -10,8 +10,8 @@ use crate::entities::instances::strip_packing::SPInstance; use crate::entities::layout::Layout; use crate::entities::placed_item::PlacedItemUID; use crate::entities::placing_option::PlacingOption; -use crate::entities::problems::problem_generic::private::ProblemGenericPrivate; use crate::entities::problems::problem_generic::LayoutIndex; +use crate::entities::problems::problem_generic::private::ProblemGenericPrivate; use crate::entities::problems::problem_generic::ProblemGeneric; use crate::entities::solution::Solution; use crate::geometry::geo_traits::{Shape, Transformable}; diff --git a/lbf/benches/edge_sensitivity_bench.rs b/lbf/benches/edge_sensitivity_bench.rs index 5e7facf..b7c2f58 100644 --- a/lbf/benches/edge_sensitivity_bench.rs +++ b/lbf/benches/edge_sensitivity_bench.rs @@ -5,22 +5,22 @@ use std::path::Path; use criterion::measurement::WallTime; use criterion::{criterion_group, criterion_main, BenchmarkGroup, BenchmarkId, Criterion}; use itertools::Itertools; +use rand::prelude::SmallRng; +use rand::SeedableRng; + use jagua_rs::entities::instances::bin_packing::BPInstance; use jagua_rs::entities::instances::instance::Instance; use jagua_rs::entities::instances::instance_generic::InstanceGeneric; use jagua_rs::entities::instances::strip_packing::SPInstance; -use rand::prelude::SmallRng; -use rand::SeedableRng; - use jagua_rs::entities::item::Item; use jagua_rs::entities::problems::problem_generic::{LayoutIndex, ProblemGeneric}; use jagua_rs::geometry::geo_traits::{Shape, TransformableFrom}; use jagua_rs::geometry::primitives::point::Point; use jagua_rs::geometry::primitives::simple_polygon::SimplePolygon; use jagua_rs::io::json_instance::JsonInstance; -use lbf::config::Config; use lbf::io; use lbf::io::svg_util::SvgDrawOptions; +use lbf::lbf_config::LBFConfig; use lbf::samplers::hpg_sampler::HPGSampler; use crate::util::{N_ITEMS_REMOVED, SWIM_PATH}; @@ -54,7 +54,7 @@ fn edge_sensitivity_bench_with_ff(c: &mut Criterion) { edge_sensitivity_bench(config, group); } -fn edge_sensitivity_bench(config: Config, mut g: BenchmarkGroup) { +fn edge_sensitivity_bench(config: LBFConfig, mut g: BenchmarkGroup) { let json_instance: JsonInstance = serde_json::from_reader(BufReader::new(File::open(SWIM_PATH).unwrap())).unwrap(); @@ -142,7 +142,7 @@ fn edge_sensitivity_bench(config: Config, mut g: BenchmarkGroup) { g.finish(); } -fn modify_instance(instance: &Instance, multiplier: usize, config: Config) -> Instance { +fn modify_instance(instance: &Instance, multiplier: usize, config: LBFConfig) -> Instance { let modified_items = instance .items() .iter() diff --git a/lbf/benches/util.rs b/lbf/benches/util.rs index 4d4b7e1..7f6158c 100644 --- a/lbf/benches/util.rs +++ b/lbf/benches/util.rs @@ -14,9 +14,9 @@ use jagua_rs::io::json_instance::JsonInstance; use jagua_rs::io::parser::Parser; use jagua_rs::util::config::{CDEConfig, SPSurrogateConfig}; use jagua_rs::util::polygon_simplification::PolySimplConfig; -use lbf::config::Config; use lbf::io; use lbf::io::svg_util::SvgDrawOptions; +use lbf::lbf_config::LBFConfig; use lbf::lbf_optimizer::LBFOptimizer; pub const SWIM_PATH: &str = "../assets/swim.json"; @@ -36,7 +36,7 @@ pub fn create_instance( /// Simulates a common scenario in iterative optimization algorithms: dense packing with a few items removed pub fn create_blf_problem( instance: Instance, - config: Config, + config: LBFConfig, n_items_removed: usize, ) -> (SPProblem, Vec) { assert!(matches!(&instance, &Instance::SP(_))); @@ -90,8 +90,8 @@ pub fn create_blf_problem( (problem, removed_pi_uids) } -pub fn create_base_config() -> Config { - Config { +pub fn create_base_config() -> LBFConfig { + LBFConfig { cde_config: CDEConfig { quadtree_depth: 5, hpg_n_cells: 2000, @@ -103,7 +103,7 @@ pub fn create_base_config() -> Config { }, }, poly_simpl_config: PolySimplConfig::Disabled, - deterministic_mode: true, + prng_seed: Some(0), n_samples_per_item: 5000, ls_samples_fraction: 0.2, svg_draw_options: Default::default(), diff --git a/lbf/src/io/json_output.rs b/lbf/src/io/json_output.rs index ed6a102..48443c4 100644 --- a/lbf/src/io/json_output.rs +++ b/lbf/src/io/json_output.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use jagua_rs::io::json_instance::JsonInstance; use jagua_rs::io::json_solution::JsonSolution; -use crate::config::Config; +use crate::lbf_config::LBFConfig; #[derive(Serialize, Deserialize, Clone)] #[serde(rename_all = "PascalCase")] @@ -11,5 +11,5 @@ pub struct JsonOutput { #[serde(flatten)] pub instance: JsonInstance, pub solution: JsonSolution, - pub config: Config, + pub config: LBFConfig, } diff --git a/lbf/src/config.rs b/lbf/src/lbf_config.rs similarity index 78% rename from lbf/src/config.rs rename to lbf/src/lbf_config.rs index 1a48dba..8f732e8 100644 --- a/lbf/src/config.rs +++ b/lbf/src/lbf_config.rs @@ -7,11 +7,13 @@ use crate::io::svg_util::SvgDrawOptions; /// Configuration for the LBF optimizer #[derive(Debug, Serialize, Deserialize, Clone, Copy)] -pub struct Config { +pub struct LBFConfig { + /// Configuration of the Collision Detection Engine pub cde_config: CDEConfig, + /// Configuration of the polygon simplification in preprocessing pub poly_simpl_config: PolySimplConfig, - /// Fixes the seed for the random number generator, resulting in deterministic behavior. - pub deterministic_mode: bool, + /// Seed for the PRNG. If not defined, the algorithm will run in non-deterministic mode using entropy + pub prng_seed: Option, /// Total number of samples per item pub n_samples_per_item: usize, /// Fraction of the samples used for the local search sampler @@ -20,7 +22,7 @@ pub struct Config { pub svg_draw_options: SvgDrawOptions, } -impl Default for Config { +impl Default for LBFConfig { fn default() -> Self { Self { cde_config: CDEConfig { @@ -34,7 +36,7 @@ impl Default for Config { }, }, poly_simpl_config: PolySimplConfig::Enabled { tolerance: 0.001 }, - deterministic_mode: true, + prng_seed: Some(0), n_samples_per_item: 5000, ls_samples_fraction: 0.2, svg_draw_options: SvgDrawOptions::default(), diff --git a/lbf/src/lbf_cost.rs b/lbf/src/lbf_cost.rs index fee89ca..5e5ee26 100644 --- a/lbf/src/lbf_cost.rs +++ b/lbf/src/lbf_cost.rs @@ -5,11 +5,11 @@ use jagua_rs::geometry::primitives::simple_polygon::SimplePolygon; const X_MULTIPLIER: f64 = 10.0; -/// The cost LBF assigns to a placing option. -/// A pure lexicographic comparison would lead to weird results due to continuous values, -// instead we opted for a weighted sum of the x_max and y_max of the shape, -// with the horizontal dimension being more important. -#[derive(PartialEq, PartialOrd, Copy, Clone, Debug)] +/// The cost LBF assigned to a placing option. +/// Weighted sum of the x_max and y_max of the shape, with the horizontal dimension being more important. +///
+/// A pure lexicographic comparison (always prioritizing x-axis) would lead to undesirable results due to the continuous nature of the values. +#[derive(PartialEq, PartialOrd, Copy, Clone, Debug, Eq, Ord)] pub struct LBFPlacingCost(NotNan); impl LBFPlacingCost { diff --git a/lbf/src/lbf_optimizer.rs b/lbf/src/lbf_optimizer.rs index e145245..7debf90 100644 --- a/lbf/src/lbf_optimizer.rs +++ b/lbf/src/lbf_optimizer.rs @@ -22,7 +22,7 @@ use jagua_rs::geometry::convex_hull::convex_hull_from_points; use jagua_rs::geometry::geo_traits::{Shape, TransformableFrom}; use jagua_rs::geometry::primitives::simple_polygon::SimplePolygon; -use crate::config::Config; +use crate::lbf_config::LBFConfig; use crate::lbf_cost::LBFPlacingCost; use crate::samplers::hpg_sampler::HPGSampler; use crate::samplers::ls_sampler::LSSampler; @@ -33,13 +33,13 @@ pub const ITEM_LIMIT: usize = usize::MAX; pub struct LBFOptimizer { pub instance: Instance, pub problem: Problem, - pub config: Config, + pub config: LBFConfig, /// SmallRng is a fast, non-cryptographic PRNG pub rng: SmallRng, } impl LBFOptimizer { - pub fn new(instance: Instance, config: Config, rng: SmallRng) -> Self { + pub fn new(instance: Instance, config: LBFConfig, rng: SmallRng) -> Self { assert!(config.n_samples_per_item > 0); let problem = match instance.clone() { Instance::BP(bpi) => BPProblem::new(bpi.clone()).into(), @@ -127,7 +127,7 @@ impl LBFOptimizer { pub fn find_lbf_placement( problem: &Problem, item: &Item, - config: &Config, + config: &LBFConfig, rng: &mut impl Rng, ) -> Option { //search all existing layouts and template layouts with remaining stock @@ -147,7 +147,7 @@ pub fn sample_layout( problem: &Problem, l_index: LayoutIndex, item: &Item, - config: &Config, + config: &LBFConfig, rng: &mut impl Rng, ) -> Option { let layout: &Layout = problem.get_layout(&l_index); @@ -209,7 +209,7 @@ pub fn sample_layout( */ let mut ls_sampler = - LSSampler::from_default_stddevs(item, &best_opt.d_transform, &layout.bin().bbox()); + LSSampler::from_defaults(item, &best_opt.d_transform, &layout.bin().bbox()); for i in 0..n_ls_samples { let transform = ls_sampler.sample(rng); diff --git a/lbf/src/lib.rs b/lbf/src/lib.rs index e965ce7..ca8fe52 100644 --- a/lbf/src/lib.rs +++ b/lbf/src/lib.rs @@ -3,8 +3,8 @@ use std::time::Instant; use mimalloc::MiMalloc; use once_cell::sync::Lazy; -pub mod config; pub mod io; +pub mod lbf_config; pub mod lbf_cost; pub mod lbf_optimizer; pub mod samplers; diff --git a/lbf/src/main.rs b/lbf/src/main.rs index f243acd..ecc2721 100644 --- a/lbf/src/main.rs +++ b/lbf/src/main.rs @@ -10,10 +10,10 @@ use rand::SeedableRng; use jagua_rs::io::parser; use jagua_rs::io::parser::Parser; -use lbf::config::Config; use lbf::io::cli::Cli; use lbf::io::json_output::JsonOutput; use lbf::io::layout_to_svg::s_layout_to_svg; +use lbf::lbf_config::LBFConfig; use lbf::lbf_optimizer::LBFOptimizer; use lbf::{io, EPOCH}; @@ -26,9 +26,9 @@ fn main() { warn!("No config file provided, use --config-file to provide a custom config"); warn!( "Falling back default config:\n{}", - serde_json::to_string(&Config::default()).unwrap() + serde_json::to_string(&LBFConfig::default()).unwrap() ); - Config::default() + LBFConfig::default() } Some(config_file) => { let file = File::open(config_file).unwrap_or_else(|err| { @@ -47,9 +47,9 @@ fn main() { let parser = Parser::new(config.poly_simpl_config, config.cde_config, true); let instance = parser.parse(&json_instance); - let rng = match config.deterministic_mode { - true => SmallRng::seed_from_u64(0), - false => SmallRng::from_entropy(), + let rng = match config.prng_seed { + Some(seed) => SmallRng::seed_from_u64(seed), + None => SmallRng::from_entropy(), }; let mut optimizer = LBFOptimizer::new(instance.clone(), config, rng); diff --git a/lbf/src/samplers/ls_sampler.rs b/lbf/src/samplers/ls_sampler.rs index bdb00fa..2247aee 100644 --- a/lbf/src/samplers/ls_sampler.rs +++ b/lbf/src/samplers/ls_sampler.rs @@ -11,30 +11,31 @@ use jagua_rs::geometry::transformation::Transformation; use crate::samplers::rotation_distr::NormalRotDistr; -const TRANSL_START_FRAC: f64 = 0.01; -const TRANSL_END_FRAC: f64 = 0.001; -const ROT_START_FRAC: f64 = 2.0 * (PI / 180.0); -const ROT_END_FRAC: f64 = 0.5 * (PI / 180.0); +/// The stddev of translation starts at 1% and ends at 0.05% of the largest dimension of the bounding box. +pub const SD_TRANSL: (f64, f64) = (0.01, 0.0005); + +/// The stddev of rotation starts at 2° and ends at 0.5°. +pub const SD_ROT: (f64, f64) = (2.0 * PI / 180.0, 0.5 * PI / 180.0); pub struct LSSampler { normal_x: Normal, normal_y: Normal, normal_r: NormalRotDistr, - stddev_transl: f64, - stddev_rot: f64, - stddev_transl_range: (f64, f64), - stddev_rot_range: (f64, f64), + sd_transl: f64, + sd_rot: f64, + sd_transl_range: (f64, f64), + sd_rot_range: (f64, f64), } impl LSSampler { pub fn new( item: &Item, ref_transform: &DTransformation, - stddev_transl_range: (f64, f64), - stddev_rot_range: (f64, f64), + sd_transl_range: (f64, f64), + sd_rot_range: (f64, f64), ) -> Self { - let stddev_transl = stddev_transl_range.0; - let stddev_rot = stddev_rot_range.0; + let stddev_transl = sd_transl_range.0; + let stddev_rot = sd_rot_range.0; let normal_x = Normal::new(ref_transform.translation().0, stddev_transl).unwrap(); let normal_y = Normal::new(ref_transform.translation().1, stddev_transl).unwrap(); @@ -44,39 +45,36 @@ impl LSSampler { normal_x, normal_y, normal_r, - stddev_transl, - stddev_rot, - stddev_transl_range, - stddev_rot_range, + sd_transl: stddev_transl, + sd_rot: stddev_rot, + sd_transl_range, + sd_rot_range, } } - pub fn from_default_stddevs( - item: &Item, - ref_transform: &DTransformation, - bbox: &AARectangle, - ) -> Self { + /// Creates a new sampler with default standard deviation ranges: [SD_TRANSL] and [SD_ROT]. + pub fn from_defaults(item: &Item, ref_transform: &DTransformation, bbox: &AARectangle) -> Self { let max_dim = f64::max(bbox.width(), bbox.height()); - let stddev_transl_range = (max_dim * TRANSL_START_FRAC, max_dim * TRANSL_END_FRAC); - let stddev_rot_range = (ROT_START_FRAC, ROT_END_FRAC); - Self::new(item, ref_transform, stddev_transl_range, stddev_rot_range) + let stddev_transl_range = (SD_TRANSL.0 * max_dim, SD_TRANSL.1 * max_dim); + Self::new(item, ref_transform, stddev_transl_range, SD_ROT) } /// Shifts the mean of the normal distributions to the given reference transformation. pub fn shift_mean(&mut self, ref_transform: &DTransformation) { - self.normal_x = Normal::new(ref_transform.translation().0, self.stddev_transl).unwrap(); - self.normal_y = Normal::new(ref_transform.translation().1, self.stddev_transl).unwrap(); + self.normal_x = Normal::new(ref_transform.translation().0, self.sd_transl).unwrap(); + self.normal_y = Normal::new(ref_transform.translation().1, self.sd_transl).unwrap(); self.normal_r.set_mean(ref_transform.rotation()); } + /// Sets the standard deviation of the normal distributions. pub fn set_stddev(&mut self, stddev_transl: f64, stddev_rot: f64) { assert!(stddev_transl >= 0.0 && stddev_rot >= 0.0); - self.stddev_transl = stddev_transl; - self.stddev_rot = stddev_rot; - self.normal_x = Normal::new(self.normal_x.mean(), self.stddev_transl).unwrap(); - self.normal_y = Normal::new(self.normal_y.mean(), self.stddev_transl).unwrap(); - self.normal_r.set_stddev(self.stddev_rot); + self.sd_transl = stddev_transl; + self.sd_rot = stddev_rot; + self.normal_x = Normal::new(self.normal_x.mean(), self.sd_transl).unwrap(); + self.normal_y = Normal::new(self.normal_y.mean(), self.sd_transl).unwrap(); + self.normal_r.set_stddev(self.sd_rot); } /// Adjusts the standard deviation according to the fraction of samples that have passed, @@ -89,11 +87,12 @@ impl LSSampler { pub fn decay_stddev(&mut self, progress_pct: f64) { let calc_stddev = |(init, end): (f64, f64), pct: f64| init * (end / init).powf(pct); self.set_stddev( - calc_stddev(self.stddev_transl_range, progress_pct), - calc_stddev(self.stddev_rot_range, progress_pct), + calc_stddev(self.sd_transl_range, progress_pct), + calc_stddev(self.sd_rot_range, progress_pct), ); } + /// Samples a transformation from the distribution. pub fn sample(&self, rng: &mut impl Rng) -> Transformation { Transformation::from_rotation(self.normal_r.sample(rng)) .translate((self.normal_x.sample(rng), self.normal_y.sample(rng))) diff --git a/lbf/tests/tests.rs b/lbf/tests/tests.rs index cd7559f..a6b1882 100644 --- a/lbf/tests/tests.rs +++ b/lbf/tests/tests.rs @@ -10,8 +10,8 @@ mod tests { use jagua_rs::entities::problems::problem_generic::LayoutIndex; use jagua_rs::entities::problems::problem_generic::ProblemGeneric; use jagua_rs::io::parser::Parser; - use lbf::config::Config; use lbf::io; + use lbf::lbf_config::LBFConfig; use lbf::lbf_optimizer::LBFOptimizer; const N_ITEMS_TO_REMOVE: usize = 5; @@ -29,7 +29,7 @@ mod tests { fn test_instance(instance_path: &str) { let instance = Path::new(instance_path); // parse the instance - let mut config = Config::default(); + let mut config = LBFConfig::default(); config.n_samples_per_item = 100; let json_instance = io::read_json_instance(&instance); let parser = Parser::new(config.poly_simpl_config, config.cde_config, true);