Skip to content

Commit 3f69015

Browse files
committed
feat: support multiple-contribution refinement
1 parent 28131c8 commit 3f69015

16 files changed

Lines changed: 29440 additions & 298 deletions

‎news/multiple-contribution.rst‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
**Added:**
2+
3+
* No news added: Support multiple contribution refinement.
4+
5+
**Changed:**
6+
7+
* <news item>
8+
9+
**Deprecated:**
10+
11+
* <news item>
12+
13+
**Removed:**
14+
15+
* <news item>
16+
17+
**Fixed:**
18+
19+
* <news item>
20+
21+
**Security:**
22+
23+
* <news item>

‎src/diffpy/apps/refinebase/parametric_model.py‎

Lines changed: 140 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import logging
22
import re
3+
from pathlib import Path
34

45
import networkx as nx
6+
from pyobjcryst import loadCrystal
57

68
from diffpy.srfit.fitbase import FitContribution
79
from diffpy.srfit.fitbase.parameter import Parameter, ParameterProxy
810
from diffpy.srfit.pdf.pdfgenerator import PDFGenerator
911
from 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
1317
logger = logging.getLogger(__name__)
@@ -152,9 +156,13 @@ def residual(self):
152156

153157

154158
class 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):
194194
class 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

Comments
 (0)