diff --git a/topola/Cargo.toml b/topola/Cargo.toml index 96a6597..3573f7b 100644 --- a/topola/Cargo.toml +++ b/topola/Cargo.toml @@ -16,6 +16,8 @@ derive_more.workspace = true i_triangle = "0.40" num-traits.workspace = true polygon_unionfind = "0.7" +rand = "0.10" +rand_distr = "0.6" rstar = "0.12" serde.workspace = true spade = "2.15" diff --git a/topola/src/autoplacer.rs b/topola/src/autoplacer.rs new file mode 100644 index 0000000..af0a9a2 --- /dev/null +++ b/topola/src/autoplacer.rs @@ -0,0 +1,135 @@ +// SPDX-FileCopyrightText: 2026 Topola contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use rand::RngExt; +use rand_distr::{Distribution, Normal}; +use undoredo::{FlushDelta, ResetDelta}; + +use crate::{ + Board, Vector2, board::BoardDelta, layout::compounds::ComponentId, orientation::Orientation, + selections::ComponentSelection, +}; + +pub struct AutoplacerSchedule { + initial_temperature: f64, + temperature_common_ratio: f64, + initial_std_dev: f64, + std_dev_common_ratio: f64, +} + +#[derive(Clone, Copy)] +pub struct AutoplacerStepParams { + temperature: f64, + std_dev: f64, +} + +pub struct Autoplacer { + components: Vec, + schedule: AutoplacerSchedule, + step_counter: u32, + origin_delta: BoardDelta, //rng: ThreadRng, +} + +impl Autoplacer { + pub fn new( + board: &mut Board, + selection: ComponentSelection, + schedule: AutoplacerSchedule, + ) -> Self { + Self { + components: board.resolve_components(selection).collect(), + schedule, + step_counter: 0, + origin_delta: board.flush_delta(), + } + } + + pub fn step(&mut self, board: &mut Board) -> bool { + self.step_with_params( + board, + AutoplacerStepParams { + temperature: self.schedule.initial_temperature + * self + .schedule + .temperature_common_ratio + .powf(self.step_counter as f64), + std_dev: self.schedule.initial_std_dev + * self + .schedule + .std_dev_common_ratio + .powf(self.step_counter as f64), + }, + ) + } + + // TODO. + /*pub fn reject(&mut self) { + + }*/ + + fn step_with_params(&mut self, board: &mut Board, params: AutoplacerStepParams) -> bool { + for &component in self.components.iter() { + //self.step_component_with_params(component, params); + let last_cost = self.cost(board, params); + + let dx_gaussian = Normal::new(0.0, params.std_dev).unwrap(); + let dy_gaussian = Normal::new(0.0, params.std_dev).unwrap(); + + //let dx = dx_gaussian.sample(&mut self.rng); + //let dy = dy_gaussian.sample(&mut self.rng); + let dx = dx_gaussian.sample(&mut rand::rng()); + let dy = dy_gaussian.sample(&mut rand::rng()); + + board.move_resolved_components_by(&self.components, Vector2::new(dx as i64, dy as i64)); + + let new_cost = self.cost(board, params); + let delta_cost = new_cost - last_cost; + + if delta_cost <= 0.0 + || f64::exp(-delta_cost / params.temperature) <= rand::rng().random() + { + self.origin_delta = self.origin_delta.clone().merge_delta(board.flush_delta()); + } else { + board.reset_delta(); + } + } + + true + } + + fn cost(&self, board: &Board, params: AutoplacerStepParams) -> f64 { + self.components + .iter() + .map(|&component| self.component_cost(board, component, params)) + .sum() + } + + fn component_cost( + &self, + board: &Board, + component: ComponentId, + _params: AutoplacerStepParams, + ) -> f64 { + let layout = board.layout(); + + let repulsion_cost: i64 = layout + .locate_component_repulsions(component, Orientation::Oblique) + .map(|vector| vector.x.abs() + vector.y.abs()) + .sum(); + let attraction_cost: f64 = layout + .component_attractions(component) + .map(|vector| 1.0 / (1.0 + (vector.x.abs() + vector.y.abs()) as f64)) + .sum(); + + repulsion_cost as f64 + attraction_cost + } + + /*fn step_component_with_params( + &mut self, + component: ComponentId, + params: AutoplacerStepParams, + ) -> bool { + // + }*/ +} diff --git a/topola/src/lib.rs b/topola/src/lib.rs index b41dd25..d40dbb2 100644 --- a/topola/src/lib.rs +++ b/topola/src/lib.rs @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +mod autoplacer; mod autorouter; mod board; mod compass;