feat(autorouter/multilayer_reconfigurator): Add tracking of SMA of rate

SMA = Simple Moving Average
This commit is contained in:
Mikolaj Wielgus 2025-11-03 01:26:49 +01:00
parent 0f3f96d4af
commit eff58d99e3
12 changed files with 218 additions and 76 deletions

View File

@ -4,7 +4,10 @@
use std::ops::ControlFlow;
use topola::{interactor::activity::ActivityStepperWithStatus, stepper::EstimateLinearProgress};
use topola::{
interactor::activity::ActivityStepperWithStatus,
stepper::{EstimateProgress, GetMaybeReconfigurationTriggerProgress},
};
use crate::{translator::Translator, viewport::Viewport};
@ -40,21 +43,32 @@ impl StatusBar {
));
if let Some(activity) = maybe_activity {
let linear_progress = activity.estimate_linear_progress();
let value = linear_progress.value();
let maximum = linear_progress.maximum();
let progress = activity.estimate_progress();
let value = progress.value();
let maximum = progress.reference();
let ratio = *value as f32 / *maximum as f32;
ui.add(egui::ProgressBar::new(ratio).text(format!(
"{:.1}% ({:.1}/{:.1})",
ratio * 100.0,
value,
maximum
)));
if let Some(trigger_progress) = activity.reconfiguration_trigger_progress() {
ui.add(egui::ProgressBar::new(ratio).text(format!(
"{:.1}% ({:.1}/{:.1}) (sma: {:.1}, min: {:.1}))",
ratio * 100.0,
value,
maximum,
trigger_progress.value(),
trigger_progress.reference(),
)));
} else {
ui.add(egui::ProgressBar::new(ratio).text(format!(
"{:.1}% ({:.1}/{:.1})",
ratio * 100.0,
value,
maximum
)));
}
let linear_subprogress = linear_progress.subscale();
let linear_subprogress = progress.subscale();
let value = linear_subprogress.value();
let maximum = linear_subprogress.maximum();
let maximum = linear_subprogress.reference();
let ratio = *value as f32 / *maximum as f32;
ui.add(egui::ProgressBar::new(ratio).text(format!(

View File

@ -39,7 +39,7 @@ pub struct Workspace {
Receiver<std::io::Result<Result<History, serde_json::Error>>>,
),
update_counter: f32,
dt_accumulator: f32,
}
impl Workspace {
@ -65,7 +65,7 @@ impl Workspace {
)
})?,
history_channel: channel(),
update_counter: 0.0,
dt_accumulator: 0.0,
})
}
@ -82,12 +82,12 @@ impl Workspace {
let instant = Instant::now();
if maybe_step_rate.is_some() {
self.update_counter += interactive_input.dt;
self.dt_accumulator += interactive_input.dt;
}
while maybe_step_rate.is_none_or(|step_rate| self.update_counter >= 1.0 / step_rate) {
while maybe_step_rate.is_none_or(|step_rate| self.dt_accumulator >= 1.0 / step_rate) {
if let Some(step_rate) = maybe_step_rate {
self.update_counter -= 1.0 / step_rate;
self.dt_accumulator -= 1.0 / step_rate;
}
if let ControlFlow::Break(()) = self.update_state(tr, error_dialog, interactive_input) {

View File

@ -16,7 +16,7 @@ use crate::{
board::{edit::BoardEdit, AccessMesadata},
layout::via::ViaWeight,
router::ng,
stepper::{Abort, EstimateLinearProgress, LinearScale, Step},
stepper::{Abort, EstimateProgress, GetMaybeReconfigurationTriggerProgress, LinearScale, Step},
};
use super::{
@ -173,16 +173,14 @@ impl<M: AccessMesadata + Clone> Abort<Invoker<M>> for ExecutionStepper<M> {
// Since enum_dispatch does not really support generics, we implement this the
// long way by using `match`.
impl<M> EstimateLinearProgress for ExecutionStepper<M> {
impl<M> EstimateProgress for ExecutionStepper<M> {
type Value = usize;
type Subscale = LinearScale<f64>;
fn estimate_linear_progress(&self) -> LinearScale<usize, LinearScale<f64>> {
fn estimate_progress(&self) -> LinearScale<usize, LinearScale<f64>> {
match self {
ExecutionStepper::MultilayerAutoroute(autoroute) => {
autoroute.estimate_linear_progress()
}
ExecutionStepper::PlanarAutoroute(autoroute) => autoroute.estimate_linear_progress(),
ExecutionStepper::MultilayerAutoroute(autoroute) => autoroute.estimate_progress(),
ExecutionStepper::PlanarAutoroute(autoroute) => autoroute.estimate_progress(),
ExecutionStepper::TopoAutoroute(..) => {
LinearScale::new(0, 0, LinearScale::new(0.0, 0.0, ()))
}
@ -198,3 +196,22 @@ impl<M> EstimateLinearProgress for ExecutionStepper<M> {
}
}
}
// Since enum_dispatch does not really support generics, we implement this the
// long way by using `match`.
impl<M> GetMaybeReconfigurationTriggerProgress for ExecutionStepper<M> {
type Subscale = ();
fn reconfiguration_trigger_progress(&self) -> Option<LinearScale<f64>> {
match self {
ExecutionStepper::MultilayerAutoroute(autoroute) => {
autoroute.reconfiguration_trigger_progress()
}
ExecutionStepper::PlanarAutoroute(autoroute) => None,
ExecutionStepper::TopoAutoroute(..) => None,
ExecutionStepper::PlaceVia(..) => None,
ExecutionStepper::RemoveBands(..) => None,
ExecutionStepper::MeasureLength(..) => None,
}
}
}

View File

@ -21,9 +21,7 @@ use crate::{
drawing::graph::PrimitiveIndex,
geometry::{edit::Edit, primitive::PrimitiveShape},
router::{navcord::Navcord, navmesh::Navmesh, thetastar::ThetastarStepper},
stepper::{
Abort, EstimateLinearProgress, LinearScale, ReconfiguratorStatus, Reconfigure, Step,
},
stepper::{Abort, EstimateProgress, LinearScale, ReconfiguratorStatus, Reconfigure, Step},
};
#[derive(Clone, Debug)]
@ -119,12 +117,12 @@ impl<M: AccessMesadata> Reconfigure<Autorouter<M>> for MultilayerAutorouteExecut
}
}
impl EstimateLinearProgress for MultilayerAutorouteExecutionStepper {
impl EstimateProgress for MultilayerAutorouteExecutionStepper {
type Value = usize;
type Subscale = LinearScale<f64>;
fn estimate_linear_progress(&self) -> LinearScale<usize, LinearScale<f64>> {
self.planar.estimate_linear_progress()
fn estimate_progress(&self) -> LinearScale<usize, LinearScale<f64>> {
self.planar.estimate_progress()
}
}

View File

@ -30,7 +30,8 @@ use crate::{
geometry::primitive::PrimitiveShape,
router::{navcord::Navcord, navmesh::Navmesh, thetastar::ThetastarStepper},
stepper::{
Abort, EstimateLinearProgress, LinearScale, ReconfiguratorStatus, Reconfigure, Step,
Abort, EstimateProgress, GetMaybeReconfigurationTriggerProgress, LinearScale,
ReconfiguratorStatus, Reconfigure, SmaRateReconfigurationTrigger, Step,
},
};
@ -39,6 +40,7 @@ pub type MultilayerReconfiguratorStatus =
pub struct MultilayerAutorouteReconfigurator {
stepper: MultilayerAutorouteExecutionStepper,
reconfiguration_trigger: SmaRateReconfigurationTrigger,
reconfigurer: MultilayerAutorouteReconfigurer,
options: MultilayerAutorouteOptions,
}
@ -72,6 +74,7 @@ impl MultilayerAutorouteReconfigurator {
preconfiguration,
options,
)?,
reconfiguration_trigger: SmaRateReconfigurationTrigger::new(10, 0.5, 0.5),
reconfigurer,
options,
})
@ -83,6 +86,9 @@ impl MultilayerAutorouteReconfigurator {
planar_result: Result<PlanarAutorouteConfigurationStatus, AutorouterError>,
) -> Result<ControlFlow<Option<BoardEdit>, MultilayerReconfiguratorStatus>, AutorouterError>
{
// Reset the reconfiguration trigger.
self.reconfiguration_trigger = SmaRateReconfigurationTrigger::new(4, 0.5, 0.5);
loop {
let configuration = match self
.reconfigurer
@ -120,6 +126,9 @@ impl<M: AccessMesadata> Step<Autorouter<M>, Option<BoardEdit>, MultilayerReconfi
autorouter: &mut Autorouter<M>,
) -> Result<ControlFlow<Option<BoardEdit>, MultilayerReconfiguratorStatus>, AutorouterError>
{
self.reconfiguration_trigger
.update(*self.estimate_progress().value() as f64);
match self.stepper.step(autorouter) {
Ok(ControlFlow::Break(maybe_edit)) => Ok(ControlFlow::Break(maybe_edit)),
Ok(ControlFlow::Continue(ReconfiguratorStatus::Running(status))) => {
@ -141,12 +150,24 @@ impl<M: AccessMesadata> Abort<Autorouter<M>> for MultilayerAutorouteReconfigurat
}
}
impl EstimateLinearProgress for MultilayerAutorouteReconfigurator {
impl EstimateProgress for MultilayerAutorouteReconfigurator {
type Value = usize;
type Subscale = LinearScale<f64>;
fn estimate_linear_progress(&self) -> LinearScale<usize, LinearScale<f64>> {
self.stepper.estimate_linear_progress()
fn estimate_progress(&self) -> LinearScale<usize, LinearScale<f64>> {
self.stepper.estimate_progress()
}
}
impl GetMaybeReconfigurationTriggerProgress for MultilayerAutorouteReconfigurator {
type Subscale = ();
fn reconfiguration_trigger_progress(&self) -> Option<LinearScale<f64>> {
Some(LinearScale::new(
(*self.reconfiguration_trigger.maybe_sma_rate_per_sec())?,
*self.reconfiguration_trigger.min_sma_rate_per_sec(),
(),
))
}
}

View File

@ -21,7 +21,7 @@ use crate::{
router::{
navcord::Navcord, navmesh::Navmesh, thetastar::ThetastarStepper, RouteStepper, Router,
},
stepper::{Abort, EstimateLinearProgress, LinearScale, Reconfigure, Step},
stepper::{Abort, EstimateProgress, LinearScale, Reconfigure, Step},
};
use super::{
@ -303,21 +303,21 @@ impl<M: AccessMesadata> Reconfigure<Autorouter<M>> for PlanarAutorouteExecutionS
}
}
impl EstimateLinearProgress for PlanarAutorouteExecutionStepper {
impl EstimateProgress for PlanarAutorouteExecutionStepper {
type Value = usize;
type Subscale = LinearScale<f64>;
fn estimate_linear_progress(&self) -> LinearScale<usize, LinearScale<f64>> {
fn estimate_progress(&self) -> LinearScale<usize, LinearScale<f64>> {
LinearScale::new(
self.curr_ratline_index,
self.configuration().ratlines.len(),
LinearScale::new(
self.route
.as_ref()
.map_or(0.0, |route| *route.estimate_linear_progress().value()),
.map_or(0.0, |route| *route.estimate_progress().value()),
self.route
.as_ref()
.map_or(0.0, |route| *route.estimate_linear_progress().maximum()),
.map_or(0.0, |route| *route.estimate_progress().reference()),
(),
),
)

View File

@ -24,9 +24,7 @@ use crate::{
drawing::graph::PrimitiveIndex,
geometry::primitive::PrimitiveShape,
router::{navcord::Navcord, navmesh::Navmesh, thetastar::ThetastarStepper},
stepper::{
Abort, EstimateLinearProgress, LinearScale, ReconfiguratorStatus, Reconfigure, Step,
},
stepper::{Abort, EstimateProgress, LinearScale, ReconfiguratorStatus, Reconfigure, Step},
};
pub type PlanarAutorouteReconfiguratorStatus =
@ -120,12 +118,12 @@ impl<M: AccessMesadata> Abort<Autorouter<M>> for PlanarAutorouteReconfigurator {
}
}
impl EstimateLinearProgress for PlanarAutorouteReconfigurator {
impl EstimateProgress for PlanarAutorouteReconfigurator {
type Value = usize;
type Subscale = LinearScale<f64>;
fn estimate_linear_progress(&self) -> LinearScale<usize, LinearScale<f64>> {
self.stepper.estimate_linear_progress()
fn estimate_progress(&self) -> LinearScale<usize, LinearScale<f64>> {
self.stepper.estimate_progress()
}
}

View File

@ -25,7 +25,9 @@ use crate::{
ng,
thetastar::ThetastarStepper,
},
stepper::{Abort, EstimateLinearProgress, LinearScale, OnEvent, Step},
stepper::{
Abort, EstimateProgress, GetMaybeReconfigurationTriggerProgress, LinearScale, OnEvent, Step,
},
};
/// Stores the interactive input data from the user.
@ -99,17 +101,30 @@ impl<M: AccessMesadata + Clone> Abort<Invoker<M>> for ActivityStepper<M> {
}
// Since enum_dispatch does not really support generics, we implement this the
// long way.
impl<M> EstimateLinearProgress for ActivityStepper<M> {
// long way by using `match`.
impl<M> EstimateProgress for ActivityStepper<M> {
type Value = usize;
type Subscale = LinearScale<f64>;
fn estimate_linear_progress(&self) -> LinearScale<usize, LinearScale<f64>> {
fn estimate_progress(&self) -> LinearScale<usize, LinearScale<f64>> {
match self {
ActivityStepper::Interaction(..) => {
LinearScale::new(0, 0, LinearScale::new(0.0, 0.0, ()))
}
ActivityStepper::Execution(execution) => execution.estimate_linear_progress(),
ActivityStepper::Execution(execution) => execution.estimate_progress(),
}
}
}
// Since enum_dispatch does not really support generics, we implement this the
// long way by using `match`.
impl<M> GetMaybeReconfigurationTriggerProgress for ActivityStepper<M> {
type Subscale = ();
fn reconfiguration_trigger_progress(&self) -> Option<LinearScale<f64>> {
match self {
ActivityStepper::Interaction(..) => None,
ActivityStepper::Execution(execution) => execution.reconfiguration_trigger_progress(),
}
}
}
@ -195,12 +210,20 @@ impl<M: AccessMesadata + Clone> OnEvent<ActivityContext<'_, M>, InteractiveEvent
}
}
impl<M> EstimateLinearProgress for ActivityStepperWithStatus<M> {
impl<M> EstimateProgress for ActivityStepperWithStatus<M> {
type Value = usize;
type Subscale = LinearScale<f64>;
fn estimate_linear_progress(&self) -> LinearScale<usize, LinearScale<f64>> {
self.activity.estimate_linear_progress()
fn estimate_progress(&self) -> LinearScale<usize, LinearScale<f64>> {
self.activity.estimate_progress()
}
}
impl<M> GetMaybeReconfigurationTriggerProgress for ActivityStepperWithStatus<M> {
type Subscale = ();
fn reconfiguration_trigger_progress(&self) -> Option<LinearScale<f64>> {
self.activity.reconfiguration_trigger_progress()
}
}

View File

@ -16,7 +16,7 @@ use crate::{
geometry::primitive::PrimitiveShape,
graph::GenericIndex,
layout::{poly::PolyWeight, Layout},
stepper::{Abort, EstimateLinearProgress},
stepper::{Abort, EstimateProgress},
};
use super::{

View File

@ -19,7 +19,7 @@ use crate::{
thetastar::{ThetastarError, ThetastarStepper},
Router, RouterThetastarStrategy,
},
stepper::{EstimateLinearProgress, LinearScale, Step},
stepper::{EstimateProgress, LinearScale, Step},
};
#[derive(Getters, Dissolve)]
@ -99,11 +99,11 @@ impl<R: AccessRules> Step<Router<'_, R>, BandTermsegIndex> for RouteStepper {
}
}
impl EstimateLinearProgress for RouteStepper {
impl EstimateProgress for RouteStepper {
type Value = f64;
type Subscale = ();
fn estimate_linear_progress(&self) -> LinearScale<f64> {
self.thetastar.estimate_linear_progress()
fn estimate_progress(&self) -> LinearScale<f64> {
self.thetastar.estimate_progress()
}
}

View File

@ -20,7 +20,7 @@ use thiserror::Error;
use std::cmp::Ordering;
use crate::stepper::{EstimateLinearProgress, LinearScale, Step};
use crate::stepper::{EstimateProgress, LinearScale, Step};
#[derive(Copy, Clone, Debug)]
pub struct MinScored<K, T>(pub K, pub T);
@ -443,7 +443,7 @@ where
}
}
impl<G, K> EstimateLinearProgress for ThetastarStepper<G, K>
impl<G, K> EstimateProgress for ThetastarStepper<G, K>
where
G: GraphBase,
G::NodeId: Eq + Ord,
@ -453,7 +453,7 @@ where
type Value = K;
type Subscale = ();
fn estimate_linear_progress(&self) -> LinearScale<Self::Value> {
fn estimate_progress(&self) -> LinearScale<Self::Value> {
LinearScale::new(
self.progress_estimate_value,
self.progress_estimate_maximum,

View File

@ -3,6 +3,7 @@
// SPDX-License-Identifier: MIT
use core::ops::ControlFlow;
use std::{collections::VecDeque, time::Instant};
use derive_getters::Getters;
@ -86,34 +87,104 @@ pub trait OnEvent<Ctx, Event> {
#[derive(Clone, Copy, Debug, Getters)]
pub struct LinearScale<V, S = ()> {
value: V,
maximum: V,
reference: V,
subscale: S,
}
impl<V, S> LinearScale<V, S> {
pub fn new(value: V, maximum: V, subscale: S) -> Self {
pub fn new(value: V, reference: V, subscale: S) -> Self {
Self {
value,
maximum,
reference,
subscale,
}
}
}
impl<V: Default> Default for LinearScale<V> {
fn default() -> Self {
Self {
value: V::default(),
maximum: V::default(),
subscale: (),
}
}
}
/// Some steppers report estimates of how far they are from completion.
pub trait EstimateLinearProgress {
pub trait EstimateProgress {
type Value;
type Subscale;
fn estimate_linear_progress(&self) -> LinearScale<Self::Value, Self::Subscale>;
fn estimate_progress(&self) -> LinearScale<Self::Value, Self::Subscale>;
}
pub trait GetMaybeReconfigurationTriggerProgress {
type Subscale;
fn reconfiguration_trigger_progress(&self) -> Option<LinearScale<f64, Self::Subscale>>;
}
#[derive(Clone, Debug, Getters)]
pub struct SmaRateReconfigurationTrigger {
#[getter(skip)]
sample_buffer: VecDeque<f64>,
#[getter(skip)]
last_instant: Instant,
#[getter(skip)]
last_value: f64,
maybe_sma_rate_per_sec: Option<f64>,
#[getter(skip)]
sample_buffer_size: usize,
#[getter(skip)]
sampling_interval_secs: f64,
min_sma_rate_per_sec: f64,
}
impl SmaRateReconfigurationTrigger {
pub fn new(
sample_buffer_size: usize,
sampling_interval_secs: f64,
min_sma_rate_per_sec: f64,
) -> Self {
Self {
sample_buffer: VecDeque::new(),
last_instant: Instant::now(),
last_value: 0.0,
maybe_sma_rate_per_sec: None,
sample_buffer_size,
sampling_interval_secs,
min_sma_rate_per_sec,
}
}
pub fn update(&mut self, value: f64) -> bool {
let elapsed = self.last_instant.elapsed();
let delta = value - self.last_value;
if elapsed.as_secs_f64() >= self.sampling_interval_secs {
let count = (elapsed.as_secs_f64() / self.sampling_interval_secs) as usize;
let mut total_pushed = 0.0;
let mut total_popped = 0.0;
for _ in 0..count {
let pushed = delta.max(0.0) / count as f64;
self.sample_buffer.push_back(delta.max(0.0) / count as f64);
total_pushed += pushed;
if self.sample_buffer.len() > self.sample_buffer_size {
total_popped += self.sample_buffer.pop_front().unwrap_or_default();
}
}
if let Some(sma_rate_per_sec) = self.maybe_sma_rate_per_sec {
self.maybe_sma_rate_per_sec = Some(
sma_rate_per_sec
+ (total_pushed - total_popped) / self.sample_buffer_size as f64,
)
} else if self.sample_buffer.len() >= self.sample_buffer_size {
self.maybe_sma_rate_per_sec =
Some(self.sample_buffer.iter().sum::<f64>() / self.sample_buffer_size as f64)
}
self.last_instant = Instant::now();
self.last_value = value;
}
if let Some(sma_rate_per_sec) = self.maybe_sma_rate_per_sec {
sma_rate_per_sec >= self.min_sma_rate_per_sec
} else {
true
}
}
}