feat(stepper): Replace overengineered SMA rate timeout with two accumulators

This commit is contained in:
Mikolaj Wielgus 2025-11-04 16:37:41 +01:00
parent 9345d5de8a
commit 18e8f9812c
7 changed files with 52 additions and 106 deletions

View File

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

View File

@ -16,7 +16,7 @@ use crate::{
board::{edit::BoardEdit, AccessMesadata}, board::{edit::BoardEdit, AccessMesadata},
layout::via::ViaWeight, layout::via::ViaWeight,
router::ng, router::ng,
stepper::{Abort, EstimateProgress, GetMaybeReconfigurationTriggerProgress, LinearScale, Step}, stepper::{Abort, EstimateProgress, GetTimeoutProgress, LinearScale, Step},
}; };
use super::{ use super::{
@ -199,14 +199,12 @@ impl<M> EstimateProgress for ExecutionStepper<M> {
// Since enum_dispatch does not really support generics, we implement this the // Since enum_dispatch does not really support generics, we implement this the
// long way by using `match`. // long way by using `match`.
impl<M> GetMaybeReconfigurationTriggerProgress for ExecutionStepper<M> { impl<M> GetTimeoutProgress for ExecutionStepper<M> {
type Subscale = (); type Subscale = ();
fn reconfiguration_trigger_progress(&self) -> Option<LinearScale<f64>> { fn timeout_progress(&self) -> Option<LinearScale<f64>> {
match self { match self {
ExecutionStepper::MultilayerAutoroute(autoroute) => { ExecutionStepper::MultilayerAutoroute(autoroute) => autoroute.timeout_progress(),
autoroute.reconfiguration_trigger_progress()
}
ExecutionStepper::PlanarAutoroute(autoroute) => None, ExecutionStepper::PlanarAutoroute(autoroute) => None,
ExecutionStepper::TopoAutoroute(..) => None, ExecutionStepper::TopoAutoroute(..) => None,
ExecutionStepper::PlaceVia(..) => None, ExecutionStepper::PlaceVia(..) => None,

View File

@ -20,7 +20,6 @@ use crate::{
IncrementFailedRatlineLayersMultilayerAutorouteReconfigurer, IncrementFailedRatlineLayersMultilayerAutorouteReconfigurer,
MakeNextMultilayerAutorouteConfiguration, MultilayerAutorouteReconfigurer, MakeNextMultilayerAutorouteConfiguration, MultilayerAutorouteReconfigurer,
}, },
planar_autoroute::PlanarAutorouteConfigurationStatus,
planar_preconfigurer::PlanarAutoroutePreconfigurerInput, planar_preconfigurer::PlanarAutoroutePreconfigurerInput,
planar_reconfigurator::PlanarAutorouteReconfiguratorStatus, planar_reconfigurator::PlanarAutorouteReconfiguratorStatus,
Autorouter, AutorouterError, Autorouter, AutorouterError,
@ -30,8 +29,8 @@ use crate::{
geometry::primitive::PrimitiveShape, geometry::primitive::PrimitiveShape,
router::{navcord::Navcord, navmesh::Navmesh, thetastar::ThetastarStepper}, router::{navcord::Navcord, navmesh::Navmesh, thetastar::ThetastarStepper},
stepper::{ stepper::{
Abort, EstimateProgress, GetMaybeReconfigurationTriggerProgress, LinearScale, Abort, EstimateProgress, GetTimeoutProgress, LinearScale, ReconfiguratorStatus,
ReconfiguratorStatus, Reconfigure, SmaRateReconfigurationTrigger, Step, Reconfigure, Step, TimeVsProgressAccumulatorTimeout,
}, },
}; };
@ -40,7 +39,7 @@ pub type MultilayerReconfiguratorStatus =
pub struct MultilayerAutorouteReconfigurator { pub struct MultilayerAutorouteReconfigurator {
stepper: MultilayerAutorouteExecutionStepper, stepper: MultilayerAutorouteExecutionStepper,
reconfiguration_trigger: SmaRateReconfigurationTrigger, timeout: TimeVsProgressAccumulatorTimeout,
reconfigurer: MultilayerAutorouteReconfigurer, reconfigurer: MultilayerAutorouteReconfigurer,
options: MultilayerAutorouteOptions, options: MultilayerAutorouteOptions,
} }
@ -74,7 +73,7 @@ impl MultilayerAutorouteReconfigurator {
preconfiguration, preconfiguration,
options, options,
)?, )?,
reconfiguration_trigger: SmaRateReconfigurationTrigger::new(20, 0.5, 0.1), timeout: TimeVsProgressAccumulatorTimeout::new(10.0, 5.0),
reconfigurer, reconfigurer,
options, options,
}) })
@ -86,7 +85,7 @@ impl MultilayerAutorouteReconfigurator {
) -> Result<ControlFlow<Option<BoardEdit>, MultilayerReconfiguratorStatus>, AutorouterError> ) -> Result<ControlFlow<Option<BoardEdit>, MultilayerReconfiguratorStatus>, AutorouterError>
{ {
// Reset the reconfiguration trigger. // Reset the reconfiguration trigger.
self.reconfiguration_trigger = SmaRateReconfigurationTrigger::new(20, 1.0, 0.1); self.timeout = TimeVsProgressAccumulatorTimeout::new(10.0, 5.0);
loop { loop {
let Some(configuration) = self.reconfigurer.next_configuration(autorouter) else { let Some(configuration) = self.reconfigurer.next_configuration(autorouter) else {
@ -117,7 +116,7 @@ impl<M: AccessMesadata> Step<Autorouter<M>, Option<BoardEdit>, MultilayerReconfi
) -> Result<ControlFlow<Option<BoardEdit>, MultilayerReconfiguratorStatus>, AutorouterError> ) -> Result<ControlFlow<Option<BoardEdit>, MultilayerReconfiguratorStatus>, AutorouterError>
{ {
if !self if !self
.reconfiguration_trigger .timeout
.update(*self.estimate_progress().value() as f64) .update(*self.estimate_progress().value() as f64)
{ {
return self.reconfigure(autorouter); return self.reconfigure(autorouter);
@ -161,13 +160,13 @@ impl EstimateProgress for MultilayerAutorouteReconfigurator {
} }
} }
impl GetMaybeReconfigurationTriggerProgress for MultilayerAutorouteReconfigurator { impl GetTimeoutProgress for MultilayerAutorouteReconfigurator {
type Subscale = (); type Subscale = ();
fn reconfiguration_trigger_progress(&self) -> Option<LinearScale<f64>> { fn timeout_progress(&self) -> Option<LinearScale<f64>> {
Some(LinearScale::new( Some(LinearScale::new(
(*self.reconfiguration_trigger.maybe_sma_rate_per_sec())?, self.timeout.start_instant().elapsed().as_secs_f64(),
*self.reconfiguration_trigger.min_sma_rate_per_sec(), *self.timeout.progress_accumulator(),
(), (),
)) ))
} }

View File

@ -317,7 +317,7 @@ impl EstimateProgress for PlanarAutorouteExecutionStepper {
.map_or(0.0, |route| *route.estimate_progress().value()), .map_or(0.0, |route| *route.estimate_progress().value()),
self.route self.route
.as_ref() .as_ref()
.map_or(0.0, |route| *route.estimate_progress().reference()), .map_or(0.0, |route| *route.estimate_progress().maximum()),
(), (),
), ),
) )

View File

@ -25,8 +25,8 @@ use crate::{
geometry::primitive::PrimitiveShape, geometry::primitive::PrimitiveShape,
router::{navcord::Navcord, navmesh::Navmesh, thetastar::ThetastarStepper}, router::{navcord::Navcord, navmesh::Navmesh, thetastar::ThetastarStepper},
stepper::{ stepper::{
Abort, EstimateProgress, LinearScale, ReconfiguratorStatus, Reconfigure, Abort, EstimateProgress, LinearScale, ReconfiguratorStatus, Reconfigure, Step,
SmaRateReconfigurationTrigger, Step, TimeVsProgressAccumulatorTimeout,
}, },
}; };
@ -35,7 +35,7 @@ pub type PlanarAutorouteReconfiguratorStatus =
pub struct PlanarAutorouteReconfigurator { pub struct PlanarAutorouteReconfigurator {
stepper: PlanarAutorouteExecutionStepper, stepper: PlanarAutorouteExecutionStepper,
reconfiguration_trigger: SmaRateReconfigurationTrigger, timeout: TimeVsProgressAccumulatorTimeout,
reconfigurer: PlanarAutorouteReconfigurer, reconfigurer: PlanarAutorouteReconfigurer,
options: PlanarAutorouteOptions, options: PlanarAutorouteOptions,
} }
@ -65,7 +65,7 @@ impl PlanarAutorouteReconfigurator {
Ok(Self { Ok(Self {
stepper: PlanarAutorouteExecutionStepper::new(autorouter, preconfiguration, options)?, stepper: PlanarAutorouteExecutionStepper::new(autorouter, preconfiguration, options)?,
reconfiguration_trigger: SmaRateReconfigurationTrigger::new(5, 0.5, 0.1), timeout: TimeVsProgressAccumulatorTimeout::new(3.0, 1.0),
// Note: I assume here that the first permutation is the same as the original order. // Note: I assume here that the first permutation is the same as the original order.
reconfigurer, reconfigurer,
options, options,
@ -77,7 +77,7 @@ impl PlanarAutorouteReconfigurator {
autorouter: &mut Autorouter<impl AccessMesadata>, autorouter: &mut Autorouter<impl AccessMesadata>,
) -> Result<ControlFlow<Option<BoardEdit>, PlanarAutorouteReconfiguratorStatus>, AutorouterError> ) -> Result<ControlFlow<Option<BoardEdit>, PlanarAutorouteReconfiguratorStatus>, AutorouterError>
{ {
self.reconfiguration_trigger = SmaRateReconfigurationTrigger::new(5, 0.5, 0.1); self.timeout = TimeVsProgressAccumulatorTimeout::new(3.0, 1.0);
loop { loop {
let Some(configuration) = self let Some(configuration) = self
@ -111,7 +111,7 @@ impl<M: AccessMesadata> Step<Autorouter<M>, Option<BoardEdit>, PlanarAutorouteRe
) -> Result<ControlFlow<Option<BoardEdit>, PlanarAutorouteReconfiguratorStatus>, AutorouterError> ) -> Result<ControlFlow<Option<BoardEdit>, PlanarAutorouteReconfiguratorStatus>, AutorouterError>
{ {
if !self if !self
.reconfiguration_trigger .timeout
.update(*self.estimate_progress().value() as f64) .update(*self.estimate_progress().value() as f64)
{ {
return self.reconfigure(autorouter); return self.reconfigure(autorouter);

View File

@ -25,9 +25,7 @@ use crate::{
ng, ng,
thetastar::ThetastarStepper, thetastar::ThetastarStepper,
}, },
stepper::{ stepper::{Abort, EstimateProgress, GetTimeoutProgress, LinearScale, OnEvent, Step},
Abort, EstimateProgress, GetMaybeReconfigurationTriggerProgress, LinearScale, OnEvent, Step,
},
}; };
/// Stores the interactive input data from the user. /// Stores the interactive input data from the user.
@ -118,13 +116,13 @@ impl<M> EstimateProgress for ActivityStepper<M> {
// Since enum_dispatch does not really support generics, we implement this the // Since enum_dispatch does not really support generics, we implement this the
// long way by using `match`. // long way by using `match`.
impl<M> GetMaybeReconfigurationTriggerProgress for ActivityStepper<M> { impl<M> GetTimeoutProgress for ActivityStepper<M> {
type Subscale = (); type Subscale = ();
fn reconfiguration_trigger_progress(&self) -> Option<LinearScale<f64>> { fn timeout_progress(&self) -> Option<LinearScale<f64>> {
match self { match self {
ActivityStepper::Interaction(..) => None, ActivityStepper::Interaction(..) => None,
ActivityStepper::Execution(execution) => execution.reconfiguration_trigger_progress(), ActivityStepper::Execution(execution) => execution.timeout_progress(),
} }
} }
} }
@ -219,11 +217,11 @@ impl<M> EstimateProgress for ActivityStepperWithStatus<M> {
} }
} }
impl<M> GetMaybeReconfigurationTriggerProgress for ActivityStepperWithStatus<M> { impl<M> GetTimeoutProgress for ActivityStepperWithStatus<M> {
type Subscale = (); type Subscale = ();
fn reconfiguration_trigger_progress(&self) -> Option<LinearScale<f64>> { fn timeout_progress(&self) -> Option<LinearScale<f64>> {
self.activity.reconfiguration_trigger_progress() self.activity.timeout_progress()
} }
} }

View File

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
use core::ops::ControlFlow; use core::ops::ControlFlow;
use std::{collections::VecDeque, time::Instant}; use std::time::Instant;
use derive_getters::Getters; use derive_getters::Getters;
@ -87,7 +87,7 @@ pub trait OnEvent<Ctx, Event> {
#[derive(Clone, Copy, Debug, Getters)] #[derive(Clone, Copy, Debug, Getters)]
pub struct LinearScale<V, S = ()> { pub struct LinearScale<V, S = ()> {
value: V, value: V,
reference: V, maximum: V,
subscale: S, subscale: S,
} }
@ -95,7 +95,7 @@ impl<V, S> LinearScale<V, S> {
pub fn new(value: V, reference: V, subscale: S) -> Self { pub fn new(value: V, reference: V, subscale: S) -> Self {
Self { Self {
value, value,
reference, maximum: reference,
subscale, subscale,
} }
} }
@ -109,87 +109,38 @@ pub trait EstimateProgress {
fn estimate_progress(&self) -> LinearScale<Self::Value, Self::Subscale>; fn estimate_progress(&self) -> LinearScale<Self::Value, Self::Subscale>;
} }
pub trait GetMaybeReconfigurationTriggerProgress { pub trait GetTimeoutProgress {
type Subscale; type Subscale;
fn reconfiguration_trigger_progress(&self) -> Option<LinearScale<f64, Self::Subscale>>; fn timeout_progress(&self) -> Option<LinearScale<f64, Self::Subscale>>;
} }
#[derive(Clone, Debug, Getters)] #[derive(Clone, Debug, Getters)]
pub struct SmaRateReconfigurationTrigger { pub struct TimeVsProgressAccumulatorTimeout {
#[getter(skip)] start_instant: Instant,
sample_buffer: VecDeque<f64>,
#[getter(skip)]
last_instant: Instant,
#[getter(skip)] #[getter(skip)]
last_max_value: f64, last_max_value: f64,
progress_accumulator: f64,
#[getter(skip)] #[getter(skip)]
incoming_max_value: f64, progress_bonus_s: 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 { impl TimeVsProgressAccumulatorTimeout {
pub fn new( pub fn new(initial_timeout_s: f64, progress_bonus_s: f64) -> Self {
sample_buffer_size: usize,
sampling_interval_secs: f64,
min_sma_rate_per_sec: f64,
) -> Self {
Self { Self {
sample_buffer: VecDeque::new(), start_instant: Instant::now(),
last_instant: Instant::now(),
last_max_value: 0.0, last_max_value: 0.0,
incoming_max_value: 0.0, progress_accumulator: initial_timeout_s,
maybe_sma_rate_per_sec: None, progress_bonus_s,
sample_buffer_size,
sampling_interval_secs,
min_sma_rate_per_sec,
} }
} }
pub fn update(&mut self, value: f64) -> bool { pub fn update(&mut self, value: f64) -> bool {
self.incoming_max_value = self.incoming_max_value.max(value); if value > self.last_max_value {
self.progress_accumulator += (value - self.last_max_value) * self.progress_bonus_s;
let elapsed = self.last_instant.elapsed(); self.last_max_value = value;
if elapsed.as_secs_f64() >= self.sampling_interval_secs {
let delta = self.incoming_max_value - self.last_max_value;
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 / count as f64) / self.sampling_interval_secs;
self.sample_buffer.push_back(delta / 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.start_instant.elapsed().as_secs_f64() < self.progress_accumulator
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_max_value = self.incoming_max_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
}
} }
} }