Skip to content

Commit

Permalink
small changes to ls_sampler
Browse files Browse the repository at this point in the history
  • Loading branch information
JeroenGar committed Feb 28, 2024
1 parent f3e8298 commit c907b75
Show file tree
Hide file tree
Showing 14 changed files with 144 additions and 110 deletions.
105 changes: 69 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,51 +1,71 @@
# 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

<img src="assets/jaguars_logo.svg" width="100%" height="300px" alt="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:**
- [x] Define custom C&P problem variants by adding new `Instance` and `Problem` implementations
- [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 -- \
Expand All @@ -56,6 +76,7 @@ cargo run --release -- \
```

Concrete example:

```bash
cd lbf
cargo run --release -- \
Expand All @@ -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

Expand All @@ -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

Expand All @@ -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.*
Expand Down Expand Up @@ -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
Expand All @@ -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/).

<img src="https://upload.wikimedia.org/wikipedia/commons/f/fc/Fonds_Wetenschappelijk_Onderzoek_logo.svg" width="100px" alt="FWO logo">
<img src="https://upload.wikimedia.org/wikipedia/commons/f/fc/Fonds_Wetenschappelijk_Onderzoek_logo.svg" height="50px" alt="FWO logo">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<img src="https://upload.wikimedia.org/wikipedia/commons/4/49/KU_Leuven_logo.svg" height="50px" alt="KU Leuven logo">

## License

Expand Down
2 changes: 1 addition & 1 deletion assets/config_lbf.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"tolerance": 0.001
}
},
"deterministic_mode": true,
"prng_seed": 0,
"n_samples_per_item": 5000,
"ls_samples_fraction": 0.2
}
2 changes: 1 addition & 1 deletion jagua-rs/src/entities/bin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion jagua-rs/src/entities/problems/strip_packing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
12 changes: 6 additions & 6 deletions lbf/benches/edge_sensitivity_bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<WallTime>) {
fn edge_sensitivity_bench(config: LBFConfig, mut g: BenchmarkGroup<WallTime>) {
let json_instance: JsonInstance =
serde_json::from_reader(BufReader::new(File::open(SWIM_PATH).unwrap())).unwrap();

Expand Down Expand Up @@ -142,7 +142,7 @@ fn edge_sensitivity_bench(config: Config, mut g: BenchmarkGroup<WallTime>) {
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()
Expand Down
10 changes: 5 additions & 5 deletions lbf/benches/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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<PlacedItemUID>) {
assert!(matches!(&instance, &Instance::SP(_)));
Expand Down Expand Up @@ -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,
Expand All @@ -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(),
Expand Down
4 changes: 2 additions & 2 deletions lbf/src/io/json_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ 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")]
pub struct JsonOutput {
#[serde(flatten)]
pub instance: JsonInstance,
pub solution: JsonSolution,
pub config: Config,
pub config: LBFConfig,
}
12 changes: 7 additions & 5 deletions lbf/src/config.rs → lbf/src/lbf_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u64>,
/// Total number of samples per item
pub n_samples_per_item: usize,
/// Fraction of the samples used for the local search sampler
Expand All @@ -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 {
Expand All @@ -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(),
Expand Down
10 changes: 5 additions & 5 deletions lbf/src/lbf_cost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// <br>
/// 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<f64>);

impl LBFPlacingCost {
Expand Down
Loading

0 comments on commit c907b75

Please sign in to comment.