11import re
2- from collections import OrderedDict
32
43import networkx as nx
54
6- from diffpy .srfit .equation .literals import Operator , makeOperator
75from diffpy .srfit .fitbase import FitContribution
86from diffpy .srfit .fitbase .parameter import Parameter , ParameterProxy
97from diffpy .srfit .pdf .pdfgenerator import PDFGenerator
1412class ParametricModel :
1513 def __init__ (self , name ):
1614 self .name = name
17- self .description = ""
18- self ._contribution = FitContribution (name )
15+ self .calc_obj = FitContribution (name )
1916 self ._graph = nx .DiGraph ()
20- self .meta = OrderedDict ()
2117 # all submodels will share the same profile
2218 self ._submodels = []
2319
@@ -26,9 +22,7 @@ def _construct_parameter_graph(
2622 ):
2723 parent_name = f"{ prefix } { parameterset .name } "
2824 self ._graph .add_node (parent_name , parameter = parameterset )
29- for par in parameterset ._iter_local_parameters (
30- regexp = re .compile ("" ) # regex is a required argument
31- ):
25+ for par in parameterset ._iter_local_parameters (regexp = re .compile ("" )):
3226 par_node_id = f"{ parent_name } .{ par .name } "
3327 if not old_graph or par_node_id not in old_graph .nodes :
3428 self ._graph .add_node (
@@ -37,8 +31,6 @@ def _construct_parameter_graph(
3731 constrained_or_constant = False ,
3832 )
3933 else :
40- # apply stored constrained_or_constant information
41- # from the old graph
4234 self ._graph .add_node (
4335 par_node_id ,
4436 parameter = par ,
@@ -47,7 +39,6 @@ def _construct_parameter_graph(
4739 ],
4840 )
4941 self ._graph .add_edge (parent_name , par_node_id )
50-
5142 for obj in parameterset ._iter_managed_parameter_containers ():
5243 if hasattr (obj , "_iter_managed_parameter_containers" ):
5344 child_name = f"{ parent_name } .{ obj .name } "
@@ -63,29 +54,37 @@ def _construct_parameter_graph(
6354 prefix = f"{ parent_name } ." ,
6455 )
6556
66- def register_submodel (self , symbol , model ):
67- """Register a parametric model to the current model."""
68- op = model .evaluate
69- if not isinstance (model .evaluate , Operator ):
70- op = makeOperator (
71- name = model .name ,
72- symbol = symbol ,
73- operation = model .evaluate ,
74- nin = 0 , # NOTE: make this (nin, nout) work for all scenarios
75- nout = 1 ,
57+ def register_submodel (self , submodel , symbol = None ):
58+ if not isinstance (self , ParametricModelEquation ):
59+ raise ValueError (
60+ "Submodels can only be registered to "
61+ "ParametricModelEquation instance."
62+ )
63+ if symbol is None :
64+ symbol = submodel .name
65+ if symbol in self .calc_obj ._parameters :
66+ self .calc_obj ._remove_parameter (self .calc_obj ._parameters [symbol ])
67+ if isinstance (submodel , ParametricModelPDF ):
68+ self .calc_obj .add_profile_generator (submodel .calc_obj )
69+ elif isinstance (submodel , ParametricModelEquation ):
70+ self .calc_obj ._eqfactory .registerOperator (
71+ symbol , submodel .calc_obj ._eq
7672 )
77- # NOTE: registerOperator -> register_operator
78- # once updated in diffpy.srfit.
79- self ._contribution ._eqfactory .registerOperator (symbol , op )
80- # Replace parameter with the submodel's parameterset
81- if symbol in self ._contribution ._parameters :
82- self ._contribution ._remove_parameter (
83- self ._contribution ._parameters [symbol ]
73+ self .calc_obj .add_parameter_set (submodel .calc_obj )
74+ else :
75+ raise NotImplementedError (
76+ "Only ParametricModelPDF and ParametricModelEquation "
77+ "instances are supported to be registered as submodels."
8478 )
85- self ._contribution .add_parameter_set (model ._contribution )
86- self ._submodels .append (model )
79+ if self .equation_str is not None :
80+ self .calc_obj .set_equation (self .equation_str )
81+ self ._submodels .append (submodel )
8782 self ._rebuild_graph ()
8883
84+ def process_meta_data (self , meta ):
85+ if hasattr (self .calc_obj , "process_meta_data" ):
86+ self .calc_obj .process_meta_data (meta )
87+
8988 @property
9089 def parameters (self ):
9190 return {
@@ -119,114 +118,70 @@ def independent_parameters(self):
119118 }
120119
121120 def set_profile (self , profile ):
122- self ._contribution .set_profile (profile )
123- if hasattr (self , "pdf_generator" ):
124- self .pdf_generator .set_profile (profile )
121+ self .calc_obj .set_profile (profile )
125122 for submodel in self ._submodels :
126- submodel .set_profile (profile )
123+ if hasattr (submodel , "set_profile" ):
124+ submodel .set_profile (profile )
125+ self ._rebuild_graph ()
127126
128127 def _rebuild_graph (self ):
129- """
130- Rebuild the parameter graph based on the current contribution.
131- """
132128 old_graph = self ._graph
133129 self ._graph .clear ()
134130 self ._construct_parameter_graph (
135- self ._contribution , prefix = "" , old_graph = old_graph
131+ self .calc_obj , prefix = "" , old_graph = old_graph
136132 )
137133
138134 def evaluate (self ):
139- return self ._contribution ._eq ()
135+ return self .calc_obj ._eq ()
140136
141137
142138class ParametricModelEquation (ParametricModel ):
143- def __init__ (self , name , equation_str ):
139+ def __init__ (self , name , equation_str = None ):
144140 super ().__init__ (name = name )
145- self ._contribution .set_equation (equation_str )
146- self .description = f"ParametricModelEq: { equation_str } "
141+ self .equation_str = None
142+ if equation_str :
143+ self .equation_str = equation_str
144+ self .calc_obj .set_equation (equation_str )
145+ self ._rebuild_graph ()
146+
147+ @property
148+ def _contribution (self ):
149+ return self .calc_obj
150+
151+ def set_equation (self , equation_str ):
152+ self .equation_str = equation_str
153+ self .calc_obj .set_equation (equation_str )
147154 self ._rebuild_graph ()
148155
149156
150157class ParametricModelPDF (ParametricModel ):
151- def __init__ (self , name , structure : Structure , meta = None ):
158+ def __init__ (self , name , structure : Structure ):
152159 super ().__init__ (name = name )
153- self .description = f"ParametricModelPDF: { name } "
154- self ._initialize_pdf_generator (structure , meta )
160+ self .calc_obj = PDFGenerator ( name )
161+ self .calc_obj . setStructure (structure )
155162 self ._rebuild_graph ()
156- # Constrain aliased parameters
157- constrained_par_names = [
158- r"\.U21$" , # U21=U12
163+ self ._hide_dependent_parameters ()
164+
165+ def _hide_dependent_parameters (self ):
166+ dependent_par_names = [
167+ r"\.U21$" ,
159168 r"\.U31$" ,
160- r"\.U32$" ,
161- r"\.B21$" ,
162- r"\.B31$" ,
163- r"\.B32$" ,
169+ r"\.U32$" , # U21=U12, U31=U13, U32=U23
170+ r"\.Biso" ,
171+ r"\.B\d{2}" , # Bij = Uij * 8 * pi^2
164172 r"\.occupancy$" , # occupancy=oc
165173 ]
166- constrained_par_regex = re .compile ("|" .join (constrained_par_names ))
174+ regex = re .compile ("|" .join (dependent_par_names ))
167175 for par_name in self .parameters .keys ():
168- if constrained_par_regex .search (par_name ):
176+ if regex .search (par_name ):
169177 self ._graph .nodes [par_name ]["constrained_or_constant" ] = True
170178
171- if meta is not None :
172- self .process_metadata (meta )
173-
174- # Add a placeholder equation to pass the validation
175- old_validate = self ._contribution ._validate
176- self ._contribution ._validate = lambda : self .validate (old_validate )
177-
178- def validate (self , old_validate ):
179- if self ._contribution ._eq is None :
180- placeholder_equation = makeOperator (
181- name = self .pdf_generator .name ,
182- symbol = f"{ self .name } _eq_placeholder" ,
183- operation = self .pdf_generator .operation ,
184- nin = 0 ,
185- nout = 1 ,
186- )
187- self ._contribution .set_equation (
188- "g" , ns = {"g" : placeholder_equation }
189- )
190- old_validate ()
191-
192- def _initialize_pdf_generator (self , structure : Structure , meta : dict ):
193- self .pdf_generator = PDFGenerator (self .name )
194- # NOTE: setStructure -> set_structure
195- # once updated in diffpy.srfit.
196- self .pdf_generator .setStructure (structure )
197- # Get access to all parameters in the PDFGenerator
198- self ._contribution .add_parameter_set (self .pdf_generator )
199- self ._contribution ._RecipeContainer__managed = (
200- self .pdf_generator ._RecipeContainer__managed
201- )
202- self ._contribution ._parameters = self .pdf_generator ._parameters
203-
204- def process_metadata (self , meta : dict = None ):
205- self .pdf_generator .meta .update (meta )
206- self .pdf_generator ._process_metadata ()
207-
208- def evaluate (self ):
209- if self ._contribution .profile is None :
210- raise ValueError ("Profile is not set for the PDF model." )
211- return self .pdf_generator .operation ()
212-
213- def constrain_symmetry (self , space_group : str ):
179+ def constrain_symmetry (self , spacegroup_symbol ):
214180 space_group_parset = constrain_as_space_group (
215- self .pdf_generator .phase , space_group
181+ self .calc_obj .phase , spacegroup_symbol
216182 )
217- # Get the independent parameters from spacegroup symmetry
218- free_variables = []
219- for latpar in space_group_parset .latpars :
220- free_variables .append (latpar )
221- for adpar in space_group_parset .adppars :
222- free_variables .append (adpar )
223- for xyzpar in space_group_parset .xyzpars :
224- free_variables .append (xyzpar )
225- for i in range (len (free_variables )):
226- while isinstance (free_variables [i ], ParameterProxy ):
227- free_variables [i ] = free_variables [i ].par
228- # Add constraints information for dependent parameters
229- symmetry_parnames = [
183+ # hide constrained parameters in the graph
184+ symmetry_par_names = [
230185 r"\.a$" ,
231186 r"\.b$" ,
232187 r"\.c$" ,
@@ -243,22 +198,32 @@ def constrain_symmetry(self, space_group: str):
243198 r"\.U12$" ,
244199 r"\.U13$" ,
245200 r"\.U23$" ,
246- r"\.Biso$" ,
247- r"\.B11$" ,
248- r"\.B22$" ,
249- r"\.B33$" ,
250- r"\.B12$" ,
251- r"\.B13$" ,
252- r"\.B23$" ,
253201 ]
254- symmetry_par_regex = re .compile ("|" .join (symmetry_parnames ))
202+ free_variables = []
203+ for latpar in space_group_parset .latpars :
204+ free_variables .append (latpar )
205+ for adpar in space_group_parset .adppars :
206+ free_variables .append (adpar )
207+ for xyzpar in space_group_parset .xyzpars :
208+ free_variables .append (xyzpar )
209+ for i in range (len (free_variables )):
210+ while isinstance (free_variables [i ], ParameterProxy ):
211+ free_variables [i ] = free_variables [i ].par
212+ symmetry_par_regex = re .compile ("|" .join (symmetry_par_names ))
255213 for par_name , par in self .parameters .items ():
256- if par .constrained or par .const :
257- self ._graph .nodes [par_name ]["constrained_or_constant" ] = True
258- elif symmetry_par_regex .search (par_name ):
214+ if symmetry_par_regex .search (par_name ):
259215 while isinstance (par , ParameterProxy ):
260216 par = par .par
261217 if par not in free_variables :
262218 self ._graph .nodes [par_name ][
263219 "constrained_or_constant"
264220 ] = True
221+
222+ def set_qmin (self , qmin : float ):
223+ self .calc_obj .setQmin (qmin )
224+
225+ def set_qmax (self , qmax : float ):
226+ self .calc_obj .setQmax (qmax )
227+
228+ def set_scattering_type (self , scattering_type : str ):
229+ self .calc_obj .setScatteringType (scattering_type )
0 commit comments