@@ -26,9 +26,10 @@ use self::formula::{formula_svg_bytes, formula_svg_uri};
2626use self :: i18n:: {
2727 center_origin_icon_image, clear_icon_image, family_label, fit_icon_image,
2828 fit_to_content_icon_image, github_mark_image, language_flag_image, model_choice_label,
29- param_init_method_disabled_label, param_init_method_label, param_init_method_name_en,
30- redo_icon_image, reset_icon_image, spline_extrapolation_label, spline_knot_strategy_label,
31- spray_brush_label, stop_icon_image, tool_icon_image, tool_label, tr, undo_icon_image,
29+ optimization_loss_metric_label, param_init_method_disabled_label, param_init_method_label,
30+ param_init_method_name_en, redo_icon_image, reset_icon_image, spline_extrapolation_label,
31+ spline_knot_strategy_label, spray_brush_label, stop_icon_image, tool_icon_image, tool_label,
32+ tr, undo_icon_image,
3233} ;
3334use self :: optimizer:: {
3435 LbfgsInputState , NelderMeadInputState , OptimizerPreset , OptimizerUiMode ,
@@ -47,15 +48,16 @@ use crate::domain::{
4748} ;
4849use crate :: fit:: {
4950 FitError , SplineConfig , SplineDuplicateXPolicy , SplineExtrapolation , SplineFamilyKind ,
50- SplineKnotStrategy , SplineResult , calculate_metrics , default_spline_initial_knot_y,
51- fit_curve_with_progress_and_optimizer_config , sample_curve,
51+ SplineKnotStrategy , SplineResult , calculate_iteration_metrics , default_spline_initial_knot_y,
52+ fit_curve_with_progress_and_optimizer_config_and_loss_metric , sample_curve,
5253} ;
5354#[ cfg( target_arch = "wasm32" ) ]
5455use crate :: fit:: {
5556 IncrementalFitRunner , IncrementalFitStep , IncrementalSplineFitRunner , IncrementalSplineFitStep ,
5657} ;
5758#[ cfg( not( target_arch = "wasm32" ) ) ]
5859use crate :: fit:: { IncrementalSplineFitRunner , IncrementalSplineFitStep } ;
60+ use crate :: fit:: { IterationMetricSnapshot , OptimizationLossMetric } ;
5961
6062#[ cfg( not( target_arch = "wasm32" ) ) ]
6163use std:: sync:: atomic:: { AtomicBool , Ordering } ;
@@ -76,6 +78,10 @@ const SPLINE_AUTO_SAMPLES_PER_KNOT: usize = 30;
7678const SPLINE_AUTO_SAMPLES_PER_POINT : usize = 3 ;
7779const DIAGNOSTICS_PANEL_DEFAULT_HEIGHT : f32 = 230.0 ;
7880const DIAGNOSTICS_PANEL_MIN_HEIGHT : f32 = 120.0 ;
81+ const LEFT_PANEL_DEFAULT_WIDTH : f32 = 350.0 ;
82+ const LEFT_PANEL_MIN_WIDTH : f32 = 350.0 ;
83+ const RIGHT_PANEL_DEFAULT_WIDTH : f32 = 300.0 ;
84+ const RIGHT_PANEL_MIN_WIDTH : f32 = 300.0 ;
7985const POINTS_PARSE_DEBOUNCE_MS : u64 = 180 ;
8086const POINTS_HISTORY_LIMIT : usize = 256 ;
8187const POINTS_PARSE_ERROR_PREFIX : & str = "Points parse error: " ;
@@ -435,12 +441,12 @@ impl StatusMessage {
435441enum FitWorkerMessage {
436442 Iteration {
437443 iteration : u64 ,
438- mse : f64 ,
444+ metrics : IterationMetricSnapshot ,
439445 params : CurveParams ,
440446 } ,
441447 SplineIteration {
442448 iteration : u64 ,
443- mse : f64 ,
449+ metrics : IterationMetricSnapshot ,
444450 knot_y : Vec < f64 > ,
445451 curve : Vec < [ f64 ; 2 ] > ,
446452 } ,
@@ -469,6 +475,7 @@ pub struct CurveFitApp {
469475 parameter_inputs : Vec < String > ,
470476 optimizer_method : OptimizerMethod ,
471477 optimizer_mode : OptimizerUiMode ,
478+ optimization_loss_metric : OptimizationLossMetric ,
472479 lbfgs_inputs : LbfgsInputState ,
473480 lbfgs_preset : OptimizerPreset ,
474481 nelder_mead_inputs : NelderMeadInputState ,
@@ -490,6 +497,7 @@ pub struct CurveFitApp {
490497 show_left_panel : bool ,
491498 show_right_panel : bool ,
492499 show_diagnostics_panel : bool ,
500+ diagnostics_hide_non_loss_by_default_pending : bool ,
493501 diagnostics_shared_axis_width : f32 ,
494502 iteration_delay_seconds : f64 ,
495503 spline_knots : usize ,
@@ -498,6 +506,7 @@ pub struct CurveFitApp {
498506 spline_duplicate_x_policy : SplineDuplicateXPolicy ,
499507 spline_initial_knot_y_inputs : Vec < String > ,
500508 fit_in_progress : bool ,
509+ fit_loss_metric : OptimizationLossMetric ,
501510 fit_preview_params : Option < CurveParams > ,
502511 fit_preview_iteration : Option < u64 > ,
503512 fit_result : Option < FitResult > ,
@@ -833,6 +842,7 @@ impl CurveFitApp {
833842 self . spline_plot_curve = None ;
834843 self . sampled_curve_cache = None ;
835844 self . iteration_diagnostics . clear ( ) ;
845+ self . diagnostics_hide_non_loss_by_default_pending = true ;
836846 self . clear_fit_preview ( ) ;
837847 }
838848
@@ -938,6 +948,51 @@ impl CurveFitApp {
938948 CurveParams :: try_from_values ( family, values) . map_err ( |error| error. to_string ( ) )
939949 }
940950
951+ fn has_fitted_params_for_family ( & self , family : CurveFamily ) -> bool {
952+ self . fit_result
953+ . as_ref ( )
954+ . is_some_and ( |result| result. family == family)
955+ }
956+
957+ fn build_fitted_initial_params ( & self , family : CurveFamily ) -> Result < CurveParams , String > {
958+ let Some ( result) = & self . fit_result else {
959+ return Err ( "No fitted model parameters are available for initialization" . to_string ( ) ) ;
960+ } ;
961+
962+ if result. family != family {
963+ return Err ( format ! (
964+ "Fitted model family mismatch: expected {family}, got {}" ,
965+ result. family
966+ ) ) ;
967+ }
968+
969+ Ok ( result. params . clone ( ) )
970+ }
971+
972+ fn apply_fitted_param_init ( & mut self ) {
973+ let Some ( family) = self . resolved_model ( ) . parametric_family ( ) else {
974+ self . status = Some ( StatusMessage :: Error (
975+ "Current model is non-parametric and has no initial parameters" . to_string ( ) ,
976+ ) ) ;
977+ return ;
978+ } ;
979+
980+ match self . build_fitted_initial_params ( family) {
981+ Ok ( params) => {
982+ self . parameter_inputs = params
983+ . values ( )
984+ . into_iter ( )
985+ . map ( |value| value. to_string ( ) )
986+ . collect ( ) ;
987+ self . clear_fit_outputs ( ) ;
988+ self . status = Some ( StatusMessage :: Ready ) ;
989+ }
990+ Err ( error) => {
991+ self . status = Some ( StatusMessage :: Error ( error) ) ;
992+ }
993+ }
994+ }
995+
941996 fn apply_param_init_method ( & mut self , method : ParamInitMethod ) {
942997 let Some ( family) = self . resolved_model ( ) . parametric_family ( ) else {
943998 self . status = Some ( StatusMessage :: Error (
@@ -1229,6 +1284,7 @@ impl Default for CurveFitApp {
12291284 . collect ( ) ,
12301285 optimizer_method : OptimizerMethod :: Lbfgs ,
12311286 optimizer_mode : OptimizerUiMode :: Basic ,
1287+ optimization_loss_metric : OptimizationLossMetric :: default ( ) ,
12321288 lbfgs_inputs : LbfgsInputState :: from_config ( & default_lbfgs) ,
12331289 lbfgs_preset : infer_lbfgs_preset ( & default_lbfgs) ,
12341290 nelder_mead_inputs : NelderMeadInputState :: from_config ( & default_nelder_mead) ,
@@ -1257,9 +1313,11 @@ impl Default for CurveFitApp {
12571313 spline_initial_knot_y_inputs : Vec :: new ( ) ,
12581314 show_right_panel : true ,
12591315 show_diagnostics_panel : true ,
1316+ diagnostics_hide_non_loss_by_default_pending : true ,
12601317 diagnostics_shared_axis_width : 0.0 ,
12611318 iteration_delay_seconds : 0.25 ,
12621319 fit_in_progress : false ,
1320+ fit_loss_metric : OptimizationLossMetric :: default ( ) ,
12631321 fit_preview_params : None ,
12641322 fit_preview_iteration : None ,
12651323 fit_result : None ,
@@ -1324,26 +1382,54 @@ impl eframe::App for CurveFitApp {
13241382
13251383 if self . show_left_panel {
13261384 egui:: SidePanel :: left ( "points_panel" )
1327- . default_width ( 340.0 )
1385+ . default_width ( LEFT_PANEL_DEFAULT_WIDTH )
1386+ . min_width ( LEFT_PANEL_MIN_WIDTH )
13281387 . resizable ( true )
13291388 . frame ( Self :: side_panel_frame ( panel_style) )
13301389 . show ( ctx, |ui| {
13311390 ui. spacing_mut ( ) . item_spacing = egui:: vec2 ( 10.0 , 8.0 ) ;
1332- Self :: panel_card_frame ( ui) . show ( ui, |ui| self . ui_tools ( ui) ) ;
1333- Self :: panel_card_frame ( ui) . show ( ui, |ui| self . ui_points_editor ( ui) ) ;
1391+ ui. set_width ( ui. available_width ( ) ) ;
1392+ Self :: panel_card_frame ( ui) . show ( ui, |ui| {
1393+ ui. set_min_width ( ui. available_width ( ) ) ;
1394+ self . ui_tools ( ui) ;
1395+ } ) ;
1396+ // ui.set_width(ui.available_width());
1397+ Self :: panel_card_frame ( ui) . show ( ui, |ui| {
1398+ // ui.set_min_width(ui.available_width());
1399+ self . ui_points_editor ( ui) ;
1400+ } ) ;
13341401 } ) ;
13351402 }
13361403
13371404 if self . show_right_panel {
13381405 egui:: SidePanel :: right ( "settings_panel" )
1339- . default_width ( 320.0 )
1406+ . default_width ( RIGHT_PANEL_DEFAULT_WIDTH )
1407+ . min_width ( RIGHT_PANEL_MIN_WIDTH )
13401408 . resizable ( true )
13411409 . frame ( Self :: side_panel_frame ( panel_style) )
13421410 . show ( ctx, |ui| {
13431411 ui. spacing_mut ( ) . item_spacing = egui:: vec2 ( 10.0 , 8.0 ) ;
1344- Self :: panel_card_frame ( ui) . show ( ui, |ui| self . ui_family_and_params ( ui) ) ;
1345- Self :: panel_card_frame ( ui) . show ( ui, |ui| self . ui_optimizer ( ui) ) ;
1346- Self :: panel_card_frame ( ui) . show ( ui, |ui| self . ui_result ( ui) ) ;
1412+ ui. set_width ( ui. available_width ( ) ) ;
1413+ Self :: panel_card_frame ( ui) . show ( ui, |ui| {
1414+ ui. set_min_width ( ui. available_width ( ) ) ;
1415+ // ui.set_width(ui.available_width());
1416+ self . ui_family_and_params ( ui) ;
1417+ } ) ;
1418+ Self :: panel_card_frame ( ui) . show ( ui, |ui| {
1419+ ui. set_min_width ( ui. available_width ( ) ) ;
1420+ // ui.set_width(ui.available_width());
1421+ self . ui_optimization_metric ( ui) ;
1422+ } ) ;
1423+ Self :: panel_card_frame ( ui) . show ( ui, |ui| {
1424+ ui. set_min_width ( ui. available_width ( ) ) ;
1425+ // ui.set_width(ui.available_width());
1426+ self . ui_optimizer ( ui) ;
1427+ } ) ;
1428+ Self :: panel_card_frame ( ui) . show ( ui, |ui| {
1429+ ui. set_min_width ( ui. available_width ( ) ) ;
1430+ // ui.set_width(ui.available_width());
1431+ self . ui_result ( ui) ;
1432+ } ) ;
13471433 } ) ;
13481434 }
13491435
0 commit comments