11import logging
22import re
3+ from pathlib import Path
34
45import networkx as nx
6+ from pyobjcryst import loadCrystal
57
68from diffpy .srfit .fitbase import FitContribution
79from diffpy .srfit .fitbase .parameter import Parameter , ParameterProxy
810from diffpy .srfit .pdf .pdfgenerator import PDFGenerator
911from diffpy .srfit .structure import constrain_as_space_group
10- from diffpy .structure import Structure
12+ from diffpy .srfit .structure .diffpyparset import DiffpyStructureParSet
13+ from diffpy .srfit .structure .objcrystparset import ObjCrystCrystalParSet
14+ from diffpy .structure .parsers import get_parser
1115
1216# NOTE: MCP server prefers logging for output
1317logger = logging .getLogger (__name__ )
@@ -152,9 +156,13 @@ def residual(self):
152156
153157
154158class ParametricModelEquation (ParametricModel ):
155- def __init__ (self , name , equation_str = None ):
159+ def __init__ (self , name , equation_str = None , from_model_name = None ):
156160 super ().__init__ (name = name )
157161 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 )
158166 if equation_str :
159167 self .set_equation (equation_str )
160168
@@ -170,14 +178,6 @@ def set_equation(self, equation_str):
170178 def get_equation (self ):
171179 return self .equation_str
172180
173- def set_residual_equation (self , residual_equation_str ):
174- self .residual_equation = residual_equation_str
175- self .calc_obj .set_residual_equation (residual_equation_str )
176- self ._rebuild_graph ()
177-
178- def get_residual_equation (self ):
179- return self .residual_equation
180-
181181 def evaluate (self ):
182182 yc = self .calc_obj ._eq ()
183183 if (
@@ -194,60 +194,150 @@ def residual(self):
194194class ParametricModelPDF (ParametricModel ):
195195 # NOTE: qmin, qmax, stype(scattering type) are meta handled
196196 # throughout the loaded profile in the refinement session
197- def __init__ (self , name , structure : Structure ):
197+ def __init__ (
198+ self ,
199+ name ,
200+ structure_file_path = None ,
201+ from_model_name = None ,
202+ structure_lib = "Diffpy" ,
203+ ):
198204 super ().__init__ (name = name )
199205 self .calc_obj = PDFGenerator (name )
200- self .calc_obj .setStructure (structure )
206+ if structure_file_path is not None :
207+ stru_parser = get_parser ("auto" )
208+ structure = stru_parser .parse (
209+ Path (structure_file_path ).read_text ()
210+ )
211+ sg = getattr (stru_parser , "spacegroup" , None )
212+ self .space_group_symbol = sg .short_name if sg is not None else "P1"
213+ if structure_lib == "Diffpy" :
214+ self .calc_obj .setStructure (structure )
215+ elif structure_lib == "PyObjcryst" :
216+ structure = loadCrystal (structure_file_path )
217+ self .calc_obj .setStructure (structure )
218+ else :
219+ raise ValueError (
220+ f"Unsupported structure library: { structure_lib } "
221+ "Please use 'Diffpy' or 'PyObjcryst'."
222+ )
223+ elif from_model_name is not None :
224+ self .calc_obj .setPhase (from_model_name .calc_obj .phase )
225+ self .space_group_symbol = from_model_name .space_group_symbol
226+ else :
227+ raise ValueError (
228+ "Either structure_file or from_model must be provided."
229+ )
230+ self .sgpar_names = []
201231 self ._rebuild_graph ()
202- self ._hide_dependent_parameters ()
203232
204- def _hide_dependent_parameters (self ):
205- dependent_par_names = [
206- r"\.U21$" ,
207- r"\.U31$" ,
208- r"\.U32$" , # U21=U12, U31=U13, U32=U23
209- r"\.Biso" ,
210- r"\.B\d{2}" , # Bij = Uij * 8 * pi^2
211- r"\.occupancy$" , # occupancy=oc
212- ]
233+ def _hide_dependent_parameters (self , use_uiso = True ):
234+ if use_uiso :
235+ dependent_par_names = [
236+ r"\.U21$" ,
237+ r"\.U31$" ,
238+ r"\.U32$" , # U21=U12, U31=U13, U32=U23
239+ r"\.Biso" ,
240+ r"\.B\d{2}" , # Bij = Uij * 8 * pi^2
241+ r"\.occupancy$" , # occupancy=oc
242+ ]
243+ else :
244+ dependent_par_names = [
245+ r"\.B21$" ,
246+ r"\.B31$" ,
247+ r"\.B32$" ,
248+ r"\.Uiso" ,
249+ r"\.U\d{2}" ,
250+ r"\.occupancy$" , # occupancy=oc
251+ ]
213252 regex = re .compile ("|" .join (dependent_par_names ))
214253 for par_name in self .parameters .keys ():
215254 if regex .search (par_name ):
216255 self ._graph .nodes [par_name ]["constrained_or_constant" ] = True
217256
218- def constrain_symmetry (self , spacegroup_symbol ):
219- space_group_parset = constrain_as_space_group (
220- self .calc_obj .phase , spacegroup_symbol
221- )
257+ def constrain_symmetry (self , spacegroup_symbol = None , use_uiso = True ):
258+ if spacegroup_symbol is None :
259+ spacegroup_symbol = self .space_group_symbol
260+ if isinstance (self .calc_obj .phase , DiffpyStructureParSet ):
261+ space_group_parset = constrain_as_space_group (
262+ self .calc_obj .phase , spacegroup_symbol
263+ )
264+ self ._hide_dependent_parameters (use_uiso = use_uiso )
265+ elif isinstance (self .calc_obj .phase , ObjCrystCrystalParSet ):
266+ if use_uiso is not False :
267+ use_uiso = False
268+ logger .warning (
269+ "ObjCrystCrystalParSet prefers using the letter B instead "
270+ "U for ADP parameters."
271+ )
272+ self ._hide_dependent_parameters (use_uiso = use_uiso )
273+ else :
274+ raise ValueError (
275+ "Unsupported calculation object type."
276+ "Currently supported types are "
277+ "DiffpyStructureParSet and ObjCrystCrystalParSet."
278+ )
222279 # hide constrained parameters in the graph
223- symmetry_par_names = [
224- r"\.a$" ,
225- r"\.b$" ,
226- r"\.c$" ,
227- r"\.alpha$" ,
228- r"\.beta$" ,
229- r"\.gamma$" ,
230- r"\.x$" ,
231- r"\.y$" ,
232- r"\.z$" ,
233- r"\.Uiso$" ,
234- r"\.U11$" ,
235- r"\.U22$" ,
236- r"\.U33$" ,
237- r"\.U12$" ,
238- r"\.U13$" ,
239- r"\.U23$" ,
240- ]
280+ if use_uiso :
281+ symmetry_par_names = [
282+ r"\.a$" ,
283+ r"\.b$" ,
284+ r"\.c$" ,
285+ r"\.alpha$" ,
286+ r"\.beta$" ,
287+ r"\.gamma$" ,
288+ r"\.x$" ,
289+ r"\.y$" ,
290+ r"\.z$" ,
291+ r"\.Uiso$" ,
292+ r"\.U11$" ,
293+ r"\.U22$" ,
294+ r"\.U33$" ,
295+ r"\.U12$" ,
296+ r"\.U13$" ,
297+ r"\.U23$" ,
298+ ]
299+ else :
300+ symmetry_par_names = [
301+ r"\.a$" ,
302+ r"\.b$" ,
303+ r"\.c$" ,
304+ r"\.alpha$" ,
305+ r"\.beta$" ,
306+ r"\.gamma$" ,
307+ r"\.x$" ,
308+ r"\.y$" ,
309+ r"\.z$" ,
310+ r"\.Biso$" ,
311+ r"\.B11$" ,
312+ r"\.B22$" ,
313+ r"\.B33$" ,
314+ r"\.B12$" ,
315+ r"\.B13$" ,
316+ r"\.B23$" ,
317+ ]
241318 free_variables = []
242- for latpar in space_group_parset .latpars :
243- free_variables .append (latpar )
244- for adpar in space_group_parset .adppars :
245- free_variables .append (adpar )
246- for xyzpar in space_group_parset .xyzpars :
247- free_variables .append (xyzpar )
319+ if isinstance (self .calc_obj .phase , DiffpyStructureParSet ):
320+ for latpar in space_group_parset .latpars :
321+ free_variables .append (latpar )
322+ for adpar in space_group_parset .adppars :
323+ free_variables .append (adpar )
324+ for xyzpar in space_group_parset .xyzpars :
325+ free_variables .append (xyzpar )
326+ elif isinstance (self .calc_obj .phase , ObjCrystCrystalParSet ):
327+ for par in self .calc_obj .phase .sgpars :
328+ free_variables .append (par )
248329 for i in range (len (free_variables )):
249330 while isinstance (free_variables [i ], ParameterProxy ):
250331 free_variables [i ] = free_variables [i ].par
332+ organized_name = "." .join (
333+ [
334+ obj .name
335+ for obj in self .calc_obj ._locate_managed_object (
336+ free_variables [i ]
337+ )
338+ ]
339+ )
340+ self .sgpar_names .append (organized_name )
251341 symmetry_par_regex = re .compile ("|" .join (symmetry_par_names ))
252342 for par_name , par in self .parameters .items ():
253343 if symmetry_par_regex .search (par_name ):
0 commit comments