Skip to content

Commit

Permalink
important qt_haz_vec bugfix
Browse files Browse the repository at this point in the history
  • Loading branch information
JeroenGar committed Feb 5, 2024
1 parent dd4639c commit fb52927
Show file tree
Hide file tree
Showing 12 changed files with 230 additions and 85 deletions.
2 changes: 1 addition & 1 deletion jaguars/src/collision_detection/cd_engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions jaguars/src/collision_detection/haz_prox_grid/hpg_cell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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")
};

Expand Down
31 changes: 14 additions & 17 deletions jaguars/src/collision_detection/quadtree/qt_hazard_vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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
}
}

Expand All @@ -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);
Expand All @@ -82,17 +78,18 @@ 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
}
None => false
}
}

pub fn active_hazards(&self) -> &[QTHazard]{
pub fn active_hazards(&self) -> &[QTHazard] {
&self.hazards[0..self.n_active]
}

Expand All @@ -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())
}
}
49 changes: 16 additions & 33 deletions jaguars/src/geometry/fail_fast/sp_surrogate.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -9,11 +10,11 @@ use crate::util::config::SPSurrogateConfig;

#[derive(Clone, Debug)]
pub struct SPSurrogate {
config: SPSurrogateConfig,
convex_hull_indices: Vec<usize>,
poles: Vec<Circle>,
poles_bounding_circle: Circle,
piers: Vec<Edge>,
pub convex_hull_indices: Vec<usize>,
pub poles: Vec<Circle>,
pub poles_bounding_circle: Circle,
pub piers: Vec<Edge>,
pub ff_pole_range: Range<usize>,
}

impl SPSurrogate {
Expand All @@ -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<usize> {
&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| {
Expand All @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion jaguars/src/geometry/primitives/simple_polygon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
5 changes: 5 additions & 0 deletions lbf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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

Expand Down
141 changes: 141 additions & 0 deletions lbf/benches/fast_fail_bench.rs
Original file line number Diff line number Diff line change
@@ -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<usize> = 0..=0;
const FF_PIER_RANGE: RangeInclusive<usize> = 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<PlacedItemUID>; 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()
}
}
Loading

0 comments on commit fb52927

Please sign in to comment.