@@ -669,6 +669,32 @@ impl ActiveOptimizerViewMut<'_> {
669669 }
670670}
671671
672+ #[ derive( Debug , Clone , PartialEq ) ]
673+ enum ActiveOptimizerSnapshot {
674+ Lbfgs ( LbfgsInputState ) ,
675+ NelderMead ( NelderMeadInputState ) ,
676+ SteepestDescent ( SteepestDescentInputState ) ,
677+ NewtonCg ( NewtonCgInputState ) ,
678+ Sgd ( SgdInputState ) ,
679+ Adam ( AdamInputState ) ,
680+ }
681+
682+ #[ derive( Debug , Clone , PartialEq ) ]
683+ struct RightPanelFitSnapshot {
684+ selected_model : ModelChoice ,
685+ polynomial_degree : usize ,
686+ parameter_inputs : Vec < String > ,
687+ spline_knots : usize ,
688+ spline_knot_strategy : SplineKnotStrategy ,
689+ spline_extrapolation : SplineExtrapolation ,
690+ spline_duplicate_x_policy : SplineDuplicateXPolicy ,
691+ spline_initial_knot_y_inputs : Vec < String > ,
692+ optimization_loss_metric : OptimizationLossMetric ,
693+ metric_quantization_enabled : bool ,
694+ metric_quantization_decimal_places : u8 ,
695+ optimizer : ActiveOptimizerSnapshot ,
696+ }
697+
672698#[ cfg( not( target_arch = "wasm32" ) ) ]
673699#[ derive( Debug ) ]
674700enum FitWorkerMessage {
@@ -760,6 +786,9 @@ pub struct CurveFitApp {
760786 spline_extrapolation : SplineExtrapolation ,
761787 spline_duplicate_x_policy : SplineDuplicateXPolicy ,
762788 spline_initial_knot_y_inputs : Vec < String > ,
789+ auto_refit_enabled : bool ,
790+ auto_refit_pending_rerun : bool ,
791+ last_right_panel_fit_snapshot : Option < RightPanelFitSnapshot > ,
763792 fit_in_progress : bool ,
764793 fit_loss_metric : OptimizationLossMetric ,
765794 fit_metric_quantization : MetricQuantization ,
@@ -876,6 +905,79 @@ impl CurveFitApp {
876905 self . active_optimizer_view ( ) . config ( )
877906 }
878907
908+ fn capture_active_optimizer_snapshot ( & self ) -> ActiveOptimizerSnapshot {
909+ match self . optimizer_method {
910+ OptimizerMethod :: Lbfgs => ActiveOptimizerSnapshot :: Lbfgs ( self . lbfgs_inputs . clone ( ) ) ,
911+ OptimizerMethod :: NelderMead => {
912+ ActiveOptimizerSnapshot :: NelderMead ( self . nelder_mead_inputs . clone ( ) )
913+ }
914+ OptimizerMethod :: SteepestDescent => {
915+ ActiveOptimizerSnapshot :: SteepestDescent ( self . steepest_descent_inputs . clone ( ) )
916+ }
917+ OptimizerMethod :: NewtonCg => {
918+ ActiveOptimizerSnapshot :: NewtonCg ( self . newton_cg_inputs . clone ( ) )
919+ }
920+ OptimizerMethod :: Sgd => ActiveOptimizerSnapshot :: Sgd ( self . sgd_inputs . clone ( ) ) ,
921+ OptimizerMethod :: Adam => ActiveOptimizerSnapshot :: Adam ( self . adam_inputs . clone ( ) ) ,
922+ }
923+ }
924+
925+ fn capture_right_panel_fit_snapshot ( & self ) -> RightPanelFitSnapshot {
926+ RightPanelFitSnapshot {
927+ selected_model : self . selected_model ,
928+ polynomial_degree : self . polynomial_degree ,
929+ parameter_inputs : self . parameter_inputs . clone ( ) ,
930+ spline_knots : self . spline_knots ,
931+ spline_knot_strategy : self . spline_knot_strategy ,
932+ spline_extrapolation : self . spline_extrapolation ,
933+ spline_duplicate_x_policy : self . spline_duplicate_x_policy ,
934+ spline_initial_knot_y_inputs : self . spline_initial_knot_y_inputs . clone ( ) ,
935+ optimization_loss_metric : self . optimization_loss_metric ,
936+ metric_quantization_enabled : self . metric_quantization_enabled ,
937+ metric_quantization_decimal_places : self . metric_quantization_decimal_places ,
938+ optimizer : self . capture_active_optimizer_snapshot ( ) ,
939+ }
940+ }
941+
942+ fn track_right_panel_fit_changes_and_maybe_refit ( & mut self ) {
943+ let snapshot = self . capture_right_panel_fit_snapshot ( ) ;
944+ let Some ( last_snapshot) = self . last_right_panel_fit_snapshot . as_mut ( ) else {
945+ self . last_right_panel_fit_snapshot = Some ( snapshot) ;
946+ return ;
947+ } ;
948+
949+ if * last_snapshot == snapshot {
950+ return ;
951+ }
952+
953+ * last_snapshot = snapshot;
954+
955+ if !self . auto_refit_enabled {
956+ return ;
957+ }
958+
959+ if self . fit_in_progress {
960+ self . auto_refit_pending_rerun = true ;
961+ return ;
962+ }
963+
964+ self . run_fit ( ) ;
965+ }
966+
967+ fn maybe_run_pending_auto_refit ( & mut self ) {
968+ if !self . auto_refit_enabled {
969+ self . auto_refit_pending_rerun = false ;
970+ return ;
971+ }
972+
973+ if !self . auto_refit_pending_rerun || self . fit_in_progress {
974+ return ;
975+ }
976+
977+ self . auto_refit_pending_rerun = false ;
978+ self . run_fit ( ) ;
979+ }
980+
879981 fn auto_spline_samples ( points_len : usize , knots : usize ) -> usize {
880982 // На больших датасетах используем более плотную дискретизацию,
881983 // но ограничиваем верхний порог ради отзывчивости UI.
@@ -1384,6 +1486,9 @@ impl Default for CurveFitApp {
13841486 spline_extrapolation : SplineExtrapolation :: default ( ) ,
13851487 spline_duplicate_x_policy : SplineDuplicateXPolicy :: default ( ) ,
13861488 spline_initial_knot_y_inputs : Vec :: new ( ) ,
1489+ auto_refit_enabled : false ,
1490+ auto_refit_pending_rerun : false ,
1491+ last_right_panel_fit_snapshot : None ,
13871492 replay : ReplayState :: default ( ) ,
13881493 fit_in_progress : false ,
13891494 fit_loss_metric : OptimizationLossMetric :: default ( ) ,
@@ -1419,6 +1524,7 @@ impl eframe::App for CurveFitApp {
14191524 fn logic ( & mut self , ctx : & egui:: Context , _frame : & mut eframe:: Frame ) {
14201525 Self :: apply_visual_style ( ctx) ;
14211526 self . poll_fit_worker ( ctx) ;
1527+ self . maybe_run_pending_auto_refit ( ) ;
14221528 self . tick_replay ( ctx) ;
14231529 self . poll_points_clipboard_import ( ctx) ;
14241530 self . maybe_refresh_points_cache_after_debounce ( ) ;
@@ -1529,6 +1635,7 @@ impl eframe::App for CurveFitApp {
15291635 } ,
15301636 ) ;
15311637 } ) ;
1638+ self . track_right_panel_fit_changes_and_maybe_refit ( ) ;
15321639 }
15331640
15341641 self . ui_formula_window ( & ctx) ;
0 commit comments