11import datetime
22import json
3+ import logging
34import os
45from pathlib import Path
56from typing import Dict
1011import numpy as np
1112from easyscience import global_object
1213from easyscience .fitting import AvailableMinimizers
13- from easyscience .fitting .fitter import DEFAULT_MINIMIZER
1414from easyscience .variable import Parameter
15+ from easyscience .variable .parameter_dependency_resolver import resolve_all_parameter_dependencies
1516from scipp import DataGroup
1617
1718from easyreflectometry .calculators import CalculatorFactory
2021from easyreflectometry .data .measurement import extract_orso_title
2122from easyreflectometry .data .measurement import load_data_from_orso_file
2223from easyreflectometry .fitting import MultiFitter
23- from easyreflectometry .model import LinearSpline
2424from easyreflectometry .model import Model
2525from easyreflectometry .model import ModelCollection
2626from easyreflectometry .model import PercentageFwhm
27- from easyreflectometry .model import Pointwise
2827from easyreflectometry .sample import Layer
2928from easyreflectometry .sample import Material
3029from easyreflectometry .sample import MaterialCollection
3130from easyreflectometry .sample import Multilayer
3231from easyreflectometry .sample import Sample
3332from easyreflectometry .sample .collections .base_collection import BaseCollection
3433
34+ logger = logging .getLogger (__name__ )
35+
3536Q_MIN = 0.001
3637Q_MAX = 0.3
3738Q_RESOLUTION = 500
3839
39- DEFAULT_MINIZER = AvailableMinimizers .LMFit_leastsq
40+ DEFAULT_MINIMIZER = AvailableMinimizers .LMFit_leastsq
4041
4142
4243class Project :
@@ -48,6 +49,7 @@ def __init__(self):
4849 self ._calculator = CalculatorFactory ()
4950 self ._experiments : Dict [DataGroup ] = {}
5051 self ._fitter : MultiFitter = None
52+ self ._minimizer_selection : AvailableMinimizers = DEFAULT_MINIMIZER
5153 self ._colors : list [str ] = None
5254 self ._report = None
5355 self ._q_min : float = None
@@ -207,9 +209,8 @@ def models(self, models: ModelCollection) -> None:
207209 def fitter (self ) -> MultiFitter :
208210 if len (self ._models ):
209211 if (self ._fitter is None ) or (self ._fitter_model_index != self ._current_model_index ):
210- minimizer = self .minimizer
211212 self ._fitter = MultiFitter (self ._models [self ._current_model_index ])
212- self .minimizer = minimizer
213+ self ._fitter . easy_science_multi_fitter . switch_minimizer ( self . _minimizer_selection )
213214 self ._fitter_model_index = self ._current_model_index
214215 return self ._fitter
215216
@@ -225,10 +226,14 @@ def calculator(self, calculator: str) -> None:
225226 def minimizer (self ) -> AvailableMinimizers :
226227 if self ._fitter is not None :
227228 return self ._fitter .easy_science_multi_fitter .minimizer .enum
228- return DEFAULT_MINIMIZER
229+ return self . _minimizer_selection
229230
230231 @minimizer .setter
231232 def minimizer (self , minimizer : AvailableMinimizers ) -> None :
233+ old_name = getattr (self ._minimizer_selection , 'name' , str (self ._minimizer_selection ))
234+ new_name = getattr (minimizer , 'name' , str (minimizer ))
235+ logger .info ('Minimizer changed from %s to %s (fitter active: %s)' , old_name , new_name , self ._fitter is not None )
236+ self ._minimizer_selection = minimizer
232237 if self ._fitter is not None :
233238 self ._fitter .easy_science_multi_fitter .switch_minimizer (minimizer )
234239
@@ -386,21 +391,10 @@ def _apply_resolution_function(
386391 ) -> None :
387392 """Set the resolution function on *model* based on variance data in *experiment*.
388393
389- Prefers Pointwise when q-resolution (xe) data is present, otherwise falls
390- back to LinearSpline when reflectivity error (ye) data is present.
391-
392394 :param experiment: The experiment whose variance data drives the choice.
393395 :param model: The model whose resolution function is set.
394396 """
395- if sum (experiment .xe ) != 0 :
396- resolution_function = Pointwise (q_data_points = [experiment .x , experiment .y , experiment .xe ])
397- model .resolution_function = resolution_function
398- elif sum (experiment .ye ) != 0 :
399- resolution_function = LinearSpline (
400- q_data_points = experiment .x ,
401- fwhm_values = np .sqrt (experiment .ye ),
402- )
403- model .resolution_function = resolution_function
397+ model .resolution_function = PercentageFwhm (5.0 )
404398
405399 def load_new_experiment (self , path : Union [Path , str ]) -> None :
406400 new_experiment = load_as_dataset (str (path ))
@@ -603,6 +597,8 @@ def as_dict(self, include_materials_not_in_model=False):
603597 self ._as_dict_add_experiments (project_dict )
604598 if self .fitter is not None :
605599 project_dict ['fitter_minimizer' ] = self .fitter .easy_science_multi_fitter .minimizer .name
600+ elif self ._minimizer_selection is not None :
601+ project_dict ['fitter_minimizer' ] = self ._minimizer_selection .name
606602 if self ._calculator is not None :
607603 project_dict ['calculator' ] = self ._calculator .current_interface_name
608604 if self ._colors is not None :
@@ -641,14 +637,17 @@ def from_dict(self, project_dict: dict):
641637 if 'materials_not_in_model' in keys :
642638 self ._materials .extend (MaterialCollection .from_dict (project_dict ['materials_not_in_model' ]))
643639 if 'fitter_minimizer' in keys :
644- self .fitter . easy_science_multi_fitter . switch_minimizer ( AvailableMinimizers [project_dict ['fitter_minimizer' ]])
640+ self .minimizer = AvailableMinimizers [project_dict ['fitter_minimizer' ]]
645641 else :
646642 self ._fitter = None
647643 if 'experiments' in keys :
648644 self ._experiments = self ._from_dict_extract_experiments (project_dict )
649645 else :
650646 self ._experiments = {}
651647
648+ # Resolve any pending parameter dependencies (constraints) after all objects are loaded
649+ resolve_all_parameter_dependencies (self )
650+
652651 def _from_dict_extract_experiments (self , project_dict : dict ) -> Dict [int , DataSet1D ]:
653652 experiments = {}
654653 for key in project_dict ['experiments' ].keys ():
0 commit comments