1+ use std:: collections:: BTreeSet ;
2+
13use levenberg_marquardt:: { LeastSquaresProblem , LevenbergMarquardt } ;
24use nalgebra:: { DMatrix , DVector , Dyn , Owned } ;
35use rayon:: prelude:: * ;
@@ -9,8 +11,9 @@ use super::transform::{
911} ;
1012use super :: types:: {
1113 DatasetResult , FeffBatchExecutionStrategy , FeffBatchOptions , FeffFitDataset , FeffFitResult ,
12- FitVariables , PathContribution ,
14+ FitVariables , PathContribution , PathParamSpec ,
1315} ;
16+ use super :: variables:: try_extract_symbols;
1417
1518fn feffit (
1619 dataset : & FeffFitDataset ,
@@ -77,7 +80,8 @@ pub fn feffit_joint(
7780 ) ?;
7881 let ds_chi_square = ds_residual. dot ( & ds_residual) ;
7982 let ds_n_data = ds_residual. len ( ) ;
80- let ds_dof = ds_n_data. saturating_sub ( n_vary) . max ( 1 ) ;
83+ let ds_n_vary = dataset_vary_count ( dataset, & solved. variables , & solved. variable_names ) ?;
84+ let ds_dof = ds_n_data. saturating_sub ( ds_n_vary) . max ( 1 ) ;
8185 let ds_reduced_chi_square = ds_chi_square / ds_dof as f64 ;
8286
8387 let data_norm = dataset. chi . iter ( ) . map ( |value| value. abs ( ) ) . sum :: < f64 > ( ) ;
@@ -153,6 +157,75 @@ pub fn feffit_joint(
153157 Ok ( out)
154158}
155159
160+ fn dataset_vary_count (
161+ dataset : & FeffFitDataset ,
162+ variables : & FitVariables ,
163+ varying_names : & [ String ] ,
164+ ) -> Result < usize , FittingError > {
165+ let varying = varying_names
166+ . iter ( )
167+ . map ( String :: as_str)
168+ . collect :: < BTreeSet < _ > > ( ) ;
169+ let mut referenced = BTreeSet :: < String > :: new ( ) ;
170+
171+ for path in dataset. paths . iter ( ) . filter ( |path| path. use_path ) {
172+ for spec in [
173+ & path. degen ,
174+ & path. s02 ,
175+ & path. e0 ,
176+ & path. ei ,
177+ & path. deltar ,
178+ & path. sigma2 ,
179+ & path. third ,
180+ & path. fourth ,
181+ ] {
182+ if let PathParamSpec :: Expression ( expr) = spec {
183+ for symbol in try_extract_symbols ( expr) ? {
184+ referenced. insert ( symbol) ;
185+ }
186+ }
187+ }
188+ }
189+
190+ let mut active = BTreeSet :: < String > :: new ( ) ;
191+ let mut visiting = BTreeSet :: < String > :: new ( ) ;
192+
193+ fn walk_symbol (
194+ symbol : & str ,
195+ varying : & BTreeSet < & str > ,
196+ variables : & FitVariables ,
197+ active : & mut BTreeSet < String > ,
198+ visiting : & mut BTreeSet < String > ,
199+ ) -> Result < ( ) , FittingError > {
200+ if varying. contains ( symbol) {
201+ active. insert ( symbol. to_string ( ) ) ;
202+ return Ok ( ( ) ) ;
203+ }
204+ if !visiting. insert ( symbol. to_string ( ) ) {
205+ return Ok ( ( ) ) ;
206+ }
207+
208+ if let Some ( expr) = variables
209+ . vars
210+ . get ( symbol)
211+ . and_then ( |variable| variable. expr . as_ref ( ) )
212+ {
213+ for dependency in try_extract_symbols ( expr) ? {
214+ walk_symbol ( & dependency, varying, variables, active, visiting) ?;
215+ }
216+ }
217+
218+ visiting. remove ( symbol) ;
219+ Ok ( ( ) )
220+ }
221+
222+ for symbol in referenced {
223+ walk_symbol ( & symbol, & varying, variables, & mut active, & mut visiting) ?;
224+ }
225+
226+ Ok ( active. len ( ) )
227+ }
228+
156229pub fn feffit_independent (
157230 datasets : & [ FeffFitDataset ] ,
158231 variables : & FitVariables ,
@@ -511,6 +584,38 @@ mod tests {
511584 let result = feffit_joint ( & [ ds1, ds2] , & initial) . unwrap ( ) ;
512585 assert ! ( result. chi_square. is_finite( ) ) ;
513586 assert_eq ! ( result. datasets. len( ) , 2 ) ;
587+
588+ // Per-dataset reduced chi-square should use dataset-local varying parameter counts.
589+ let expected0 = result. datasets [ 0 ] . chi_square
590+ / result. datasets [ 0 ] . n_data . saturating_sub ( 4 ) . max ( 1 ) as f64 ;
591+ let expected1 = result. datasets [ 1 ] . chi_square
592+ / result. datasets [ 1 ] . n_data . saturating_sub ( 4 ) . max ( 1 ) as f64 ;
593+ assert ! ( ( result. datasets[ 0 ] . reduced_chi_square - expected0) . abs( ) < 1.0e-10 ) ;
594+ assert ! ( ( result. datasets[ 1 ] . reduced_chi_square - expected1) . abs( ) < 1.0e-10 ) ;
595+ }
596+
597+ #[ test]
598+ fn test_dataset_vary_count_tracks_expression_dependencies ( ) {
599+ let path1 = crate :: xafs:: fitting:: types:: FeffPathModel :: default ( )
600+ . set_s02 ( "amp" )
601+ . set_sigma2 ( "sig2_eff" ) ;
602+ let path2 = crate :: xafs:: fitting:: types:: FeffPathModel :: default ( ) . set_s02 ( "amp2" ) ;
603+
604+ let ds1 = FeffFitDataset :: new ( ) . add_path ( path1) ;
605+ let ds2 = FeffFitDataset :: new ( ) . add_path ( path2) ;
606+
607+ let mut vars = FitVariables :: new ( ) ;
608+ vars. insert ( "amp" , FitVariable :: new ( 1.0 , true ) ) ;
609+ vars. insert ( "amp2" , FitVariable :: new ( 1.0 , true ) ) ;
610+ vars. insert ( "sig2" , FitVariable :: new ( 0.003 , true ) ) ;
611+ vars. insert (
612+ "sig2_eff" ,
613+ FitVariable :: new ( 0.0 , false ) . with_expr ( "sig2 * 2.0" ) ,
614+ ) ;
615+ let varying = vec ! [ "amp" . to_string( ) , "amp2" . to_string( ) , "sig2" . to_string( ) ] ;
616+
617+ assert_eq ! ( dataset_vary_count( & ds1, & vars, & varying) . unwrap( ) , 2 ) ;
618+ assert_eq ! ( dataset_vary_count( & ds2, & vars, & varying) . unwrap( ) , 1 ) ;
514619 }
515620
516621 #[ test]
0 commit comments