2020class ParametricModel :
2121 def __init__ (self , name ):
2222 self .name = name
23- self .calc_obj = FitContribution (name )
23+ self ._contribution = FitContribution (name )
24+ self .calc_obj = self ._contribution
2425 self ._graph = nx .DiGraph ()
2526 # all submodels will share the same profile
2627 self ._submodels = []
@@ -58,8 +59,7 @@ def _construct_parameter_graph(
5859 )
5960 self ._graph .add_edge (parent_name , child_name )
6061 self ._construct_parameter_graph (
61- obj ,
62- prefix = f"{ parent_name } ." ,
62+ obj , prefix = f"{ parent_name } ." , old_graph = old_graph
6363 )
6464
6565 def register_submodel (self , submodel , symbol = None ):
@@ -70,34 +70,47 @@ def register_submodel(self, submodel, symbol=None):
7070 )
7171 if symbol is None :
7272 symbol = submodel .name
73- if symbol in self .calc_obj ._parameters :
74- self .calc_obj ._remove_parameter (self .calc_obj ._parameters [symbol ])
73+ if symbol in self ._contribution ._parameters :
74+ self ._contribution ._remove_parameter (
75+ self ._contribution ._parameters [symbol ]
76+ )
7577 if isinstance (submodel , ParametricModelPDF ):
7678 if symbol != submodel .name :
7779 logger .warning (
7880 f"ParametricModelPDF's name ({ submodel .name } ) does "
7981 f"not match with the provided symbol ({ symbol } ) " ,
8082 )
81- self .calc_obj .add_profile_generator (submodel .calc_obj )
82- elif isinstance (submodel , ParametricModelEquation ):
83- self .calc_obj ._eqfactory .registerOperator (
84- symbol , submodel .calc_obj ._eq
83+ self ._contribution .add_profile_generator (submodel .calc_obj )
84+ elif isinstance (submodel , ParametricModelEquation ) or isinstance (
85+ submodel , ParametricModelFunction
86+ ):
87+ self ._contribution ._eqfactory .registerOperator (
88+ symbol , submodel ._contribution ._eq
8589 )
86- self .calc_obj .add_parameter_set (submodel .calc_obj )
90+ self ._contribution .add_parameter_set (submodel ._contribution )
8791 else :
8892 raise NotImplementedError (
89- "Only ParametricModelPDF and ParametricModelEquation "
93+ "Only ParametricModelPDF, ParametricModelEquation, "
94+ "and ParametricModelFunction "
9095 "instances are supported to be registered as submodels."
9196 )
9297 if self .equation_str is not None :
93- self .calc_obj .set_equation (self .equation_str )
98+ self ._contribution .set_equation (self .equation_str )
99+ submodel ._rebuild_graph ()
100+ if f"{ self .name } .{ submodel .name } " not in self ._graph .nodes :
101+ self ._graph .add_node (
102+ symbol ,
103+ parameter = None ,
104+ constrained_or_constant = False ,
105+ )
106+ self ._graph .add_edge (self .name , symbol )
107+ subgraph = submodel ._graph .copy ()
108+ mapping = {node : f"{ self .name } .{ node } " for node in subgraph .nodes }
109+ subgraph = nx .relabel_nodes (subgraph , mapping )
110+ self ._graph = nx .compose (self ._graph , subgraph )
94111 self ._submodels .append (submodel )
95112 self ._rebuild_graph ()
96113
97- def process_meta_data (self , meta ):
98- if hasattr (self .calc_obj , "process_meta_data" ):
99- self .calc_obj .process_meta_data (meta )
100-
101114 @property
102115 def parameters (self ):
103116 return {
@@ -131,17 +144,17 @@ def independent_parameters(self):
131144 }
132145
133146 def set_profile (self , profile ):
134- self .calc_obj .set_profile (profile )
147+ self ._contribution .set_profile (profile )
135148 for submodel in self ._submodels :
136149 if hasattr (submodel , "set_profile" ):
137150 submodel .set_profile (profile )
138151 self ._rebuild_graph ()
139152
140153 def _rebuild_graph (self ):
141154 old_graph = self ._graph
142- self ._graph . clear ()
155+ self ._graph = nx . DiGraph ()
143156 self ._construct_parameter_graph (
144- self .calc_obj , prefix = "" , old_graph = old_graph
157+ self ._contribution , prefix = "" , old_graph = old_graph
145158 )
146159
147160 def evaluate (self ):
@@ -156,39 +169,77 @@ def residual(self):
156169
157170
158171class ParametricModelEquation (ParametricModel ):
159- def __init__ (self , name , equation_str = None , from_model_name = None ):
172+ def __init__ (self , name , equation_str = None ):
160173 super ().__init__ (name = name )
161174 self .equation_str = None
162- if from_model_name is not None :
163- for name , obj in from_model_name .calc_obj .__dict__ .items ():
164- if name not in ["name" , "profile" , "_observers" ]:
165- setattr (self .calc_obj , name , obj )
166175 if equation_str :
167176 self .set_equation (equation_str )
168177
169- @property
170- def _contribution (self ):
171- return self .calc_obj
172-
173178 def set_equation (self , equation_str ):
174179 self .equation_str = equation_str
175- self .calc_obj .set_equation (equation_str )
180+ self ._contribution .set_equation (equation_str )
176181 self ._rebuild_graph ()
177182
178183 def get_equation (self ):
179184 return self .equation_str
180185
181186 def evaluate (self ):
182- yc = self .calc_obj ._eq ()
187+ yc = self ._contribution ._eq ()
183188 if (
184- hasattr (self .calc_obj , "profile" )
185- and self .calc_obj .profile is not None
189+ hasattr (self ._contribution , "profile" )
190+ and self ._contribution .profile is not None
186191 ):
187- self .calc_obj .profile .ycalc = yc
192+ self ._contribution .profile .ycalc = yc
188193 return yc
189194
190195 def residual (self ):
191- return self .calc_obj .residual ()
196+ return self ._contribution .residual ()
197+
198+
199+ class ParametricModelFunction (ParametricModel ):
200+ def __init__ (self , name , function , argnames = None ):
201+ """
202+ Initialize a ParametricModelFunction instance.
203+
204+ function can be either a callable or a string representing
205+ the pre-defined function.
206+ One and only one of func or characteristic_func_name must be provided.
207+ Allowed value for characteristic_func_name:
208+ "spherical_particle",
209+ "spheroidal_particle",
210+ "lognormal_spherical_particle",
211+ "sheet_particle",
212+ "shell_particle",
213+ "SASCF",
214+ "sphericalCF",
215+ "spheroidalCF",
216+ "spheroidalCF2",
217+ "lognormalSphericalCF",
218+ "sheetCF",
219+ "shellCF",
220+ "shellCF2",
221+ """
222+ super ().__init__ (name = name )
223+ if isinstance (function , str ):
224+ import diffpy .srfit .pdf .characteristicfunctions
225+
226+ function = getattr (
227+ diffpy .srfit .pdf .characteristicfunctions ,
228+ function ,
229+ )
230+ self ._contribution .register_function (function , argnames = argnames )
231+ self ._contribution .set_equation (function .__name__ )
232+ self .calc_obj = function
233+ self ._rebuild_graph ()
234+
235+ def set_profile (self , profile , xname = None , yname = None , dyname = None ):
236+ self ._contribution .set_profile (
237+ profile , xname = xname , yname = yname , dyname = dyname
238+ )
239+ # no submodel is allowed ParametricModelFunction
240+
241+ def evaluate (self ):
242+ return self ._contribution ._eq ()
192243
193244
194245class ParametricModelPDF (ParametricModel ):
@@ -199,6 +250,7 @@ def __init__(
199250 name ,
200251 structure_file_path = None ,
201252 from_model_name = None ,
253+ library = "Diffpy" ,
202254 ):
203255 super ().__init__ (name = name )
204256 self .calc_obj = PDFGenerator (name )
@@ -236,7 +288,7 @@ def __init__(
236288 )
237289 sg = getattr (stru_parser , "spacegroup" , None )
238290 self .space_group_symbol = sg .short_name if sg is not None else "P1"
239- if sg .number in DUAL_ORIGIN_SG_NUMBERS :
291+ if sg .number in DUAL_ORIGIN_SG_NUMBERS or library == "ObjCryst" :
240292 structure = loadCrystal (structure_file_path )
241293 self .calc_obj .setStructure (structure )
242294 else :
@@ -369,10 +421,18 @@ def constrain_symmetry(self, spacegroup_symbol=None, use_uiso=True):
369421 "constrained_or_constant"
370422 ] = True
371423
424+ def _rebuild_graph (self ):
425+ old_graph = self ._graph
426+ self ._graph = nx .DiGraph ()
427+ self ._construct_parameter_graph (
428+ # PDFGenerator itself holds parameters
429+ self .calc_obj ,
430+ prefix = "" ,
431+ old_graph = old_graph ,
432+ )
433+
372434 def set_profile (self , profile ):
373435 self .calc_obj .set_profile (profile )
374- self ._yname = self .calc_obj .profile .ypar .name
375- self ._dyname = self .calc_obj .profile .dypar .name
376436 # no submodel is allowed ParametricModelPDF
377437
378438 def evaluate (self ):
0 commit comments