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

View File

@ -16,7 +16,7 @@ use crate::{
board::{edit::BoardEdit, AccessMesadata},
layout::via::ViaWeight,
router::ng,
stepper::{Abort, EstimateProgress, GetMaybeReconfigurationTriggerProgress, LinearScale, Step},
stepper::{Abort, EstimateProgress, GetTimeoutProgress, LinearScale, Step},
};
use super::{
@ -199,14 +199,12 @@ impl<M> EstimateProgress 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> {
impl<M> GetTimeoutProgress for ExecutionStepper<M> {
type Subscale = ();
fn reconfiguration_trigger_progress(&self) -> Option<LinearScale<f64>> {
fn timeout_progress(&self) -> Option<LinearScale<f64>> {
match self {
ExecutionStepper::MultilayerAutoroute(autoroute) => {
autoroute.reconfiguration_trigger_progress()
}
ExecutionStepper::MultilayerAutoroute(autoroute) => autoroute.timeout_progress(),
ExecutionStepper::PlanarAutoroute(autoroute) => None,
ExecutionStepper::TopoAutoroute(..) => None,
ExecutionStepper::PlaceVia(..) => None,

View File

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

View File

@ -317,7 +317,7 @@ impl EstimateProgress for PlanarAutorouteExecutionStepper {
.map_or(0.0, |route| *route.estimate_progress().value()),
self.route
.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,
router::{navcord::Navcord, navmesh::Navmesh, thetastar::ThetastarStepper},
stepper::{
Abort, EstimateProgress, LinearScale, ReconfiguratorStatus, Reconfigure,
SmaRateReconfigurationTrigger, Step,
Abort, EstimateProgress, LinearScale, ReconfiguratorStatus, Reconfigure, Step,
TimeVsProgressAccumulatorTimeout,
},
};
@ -35,7 +35,7 @@ pub type PlanarAutorouteReconfiguratorStatus =
pub struct PlanarAutorouteReconfigurator {
stepper: PlanarAutorouteExecutionStepper,
reconfiguration_trigger: SmaRateReconfigurationTrigger,
timeout: TimeVsProgressAccumulatorTimeout,
reconfigurer: PlanarAutorouteReconfigurer,
options: PlanarAutorouteOptions,
}
@ -65,7 +65,7 @@ impl PlanarAutorouteReconfigurator {
Ok(Self {
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.
reconfigurer,
options,
@ -77,7 +77,7 @@ impl PlanarAutorouteReconfigurator {
autorouter: &mut Autorouter<impl AccessMesadata>,
) -> 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 {
let Some(configuration) = self
@ -111,7 +111,7 @@ impl<M: AccessMesadata> Step<Autorouter<M>, Option<BoardEdit>, PlanarAutorouteRe
) -> Result<ControlFlow<Option<BoardEdit>, PlanarAutorouteReconfiguratorStatus>, AutorouterError>
{
if !self
.reconfiguration_trigger
.timeout
.update(*self.estimate_progress().value() as f64)
{
return self.reconfigure(autorouter);

View File

@ -25,9 +25,7 @@ use crate::{
ng,
thetastar::ThetastarStepper,
},
stepper::{
Abort, EstimateProgress, GetMaybeReconfigurationTriggerProgress, LinearScale, OnEvent, Step,
},
stepper::{Abort, EstimateProgress, GetTimeoutProgress, LinearScale, OnEvent, Step},
};
/// 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
// long way by using `match`.
impl<M> GetMaybeReconfigurationTriggerProgress for ActivityStepper<M> {
impl<M> GetTimeoutProgress for ActivityStepper<M> {
type Subscale = ();
fn reconfiguration_trigger_progress(&self) -> Option<LinearScale<f64>> {
fn timeout_progress(&self) -> Option<LinearScale<f64>> {
match self {
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 = ();
fn reconfiguration_trigger_progress(&self) -> Option<LinearScale<f64>> {
self.activity.reconfiguration_trigger_progress()
fn timeout_progress(&self) -> Option<LinearScale<f64>> {
self.activity.timeout_progress()
}
}

View File

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