Skip to content

Commit 484487e

Browse files
authored
Merge pull request #32 from ycexiao/multiple-contribution
feat: support multiple-contribution refinement
2 parents 28131c8 + 76ca2b3 commit 484487e

17 files changed

Lines changed: 29714 additions & 337 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: 161 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,171 @@ 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+
):
198203
super().__init__(name=name)
199204
self.calc_obj = PDFGenerator(name)
200-
self.calc_obj.setStructure(structure)
205+
# NOTE: Certain space groups require dual origin handling.
206+
DUAL_ORIGIN_SG_NUMBERS = {
207+
48,
208+
50,
209+
59,
210+
68,
211+
70,
212+
85,
213+
86,
214+
88,
215+
125,
216+
126,
217+
129,
218+
130,
219+
133,
220+
134,
221+
137,
222+
138,
223+
141,
224+
142,
225+
201,
226+
203,
227+
222,
228+
224,
229+
227,
230+
228,
231+
}
232+
if structure_file_path is not None:
233+
stru_parser = get_parser("auto")
234+
structure = stru_parser.parse(
235+
Path(structure_file_path).read_text()
236+
)
237+
sg = getattr(stru_parser, "spacegroup", None)
238+
self.space_group_symbol = sg.short_name if sg is not None else "P1"
239+
if sg.number in DUAL_ORIGIN_SG_NUMBERS:
240+
structure = loadCrystal(structure_file_path)
241+
self.calc_obj.setStructure(structure)
242+
else:
243+
self.calc_obj.setStructure(structure)
244+
elif from_model_name is not None:
245+
self.calc_obj.setPhase(from_model_name.calc_obj.phase)
246+
self.space_group_symbol = from_model_name.space_group_symbol
247+
else:
248+
raise ValueError(
249+
"Either structure_file or from_model must be provided."
250+
)
251+
self.sgpar_names = []
201252
self._rebuild_graph()
202-
self._hide_dependent_parameters()
203253

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-
]
254+
def _hide_dependent_parameters(self, use_uiso=True):
255+
if use_uiso:
256+
dependent_par_names = [
257+
r"\.U21$",
258+
r"\.U31$",
259+
r"\.U32$", # U21=U12, U31=U13, U32=U23
260+
r"\.Biso",
261+
r"\.B\d{2}", # Bij = Uij * 8 * pi^2
262+
r"\.occupancy$", # occupancy=oc
263+
]
264+
else:
265+
dependent_par_names = [
266+
r"\.B21$",
267+
r"\.B31$",
268+
r"\.B32$",
269+
r"\.Uiso",
270+
r"\.U\d{2}",
271+
r"\.occupancy$", # occupancy=oc
272+
]
213273
regex = re.compile("|".join(dependent_par_names))
214274
for par_name in self.parameters.keys():
215275
if regex.search(par_name):
216276
self._graph.nodes[par_name]["constrained_or_constant"] = True
217277

218-
def constrain_symmetry(self, spacegroup_symbol):
219-
space_group_parset = constrain_as_space_group(
220-
self.calc_obj.phase, spacegroup_symbol
221-
)
278+
def constrain_symmetry(self, spacegroup_symbol=None, use_uiso=True):
279+
if spacegroup_symbol is None:
280+
spacegroup_symbol = self.space_group_symbol
281+
if isinstance(self.calc_obj.phase, DiffpyStructureParSet):
282+
space_group_parset = constrain_as_space_group(
283+
self.calc_obj.phase, spacegroup_symbol
284+
)
285+
self._hide_dependent_parameters(use_uiso=use_uiso)
286+
elif isinstance(self.calc_obj.phase, ObjCrystCrystalParSet):
287+
if use_uiso is not False:
288+
use_uiso = False
289+
logger.warning(
290+
"ObjCrystCrystalParSet prefers using the letter B instead "
291+
"U for ADP parameters."
292+
)
293+
self._hide_dependent_parameters(use_uiso=use_uiso)
294+
else:
295+
raise ValueError(
296+
"Unsupported calculation object type."
297+
"Currently supported types are "
298+
"DiffpyStructureParSet and ObjCrystCrystalParSet."
299+
)
222300
# 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-
]
301+
if use_uiso:
302+
symmetry_par_names = [
303+
r"\.a$",
304+
r"\.b$",
305+
r"\.c$",
306+
r"\.alpha$",
307+
r"\.beta$",
308+
r"\.gamma$",
309+
r"\.x$",
310+
r"\.y$",
311+
r"\.z$",
312+
r"\.Uiso$",
313+
r"\.U11$",
314+
r"\.U22$",
315+
r"\.U33$",
316+
r"\.U12$",
317+
r"\.U13$",
318+
r"\.U23$",
319+
]
320+
else:
321+
symmetry_par_names = [
322+
r"\.a$",
323+
r"\.b$",
324+
r"\.c$",
325+
r"\.alpha$",
326+
r"\.beta$",
327+
r"\.gamma$",
328+
r"\.x$",
329+
r"\.y$",
330+
r"\.z$",
331+
r"\.Biso$",
332+
r"\.B11$",
333+
r"\.B22$",
334+
r"\.B33$",
335+
r"\.B12$",
336+
r"\.B13$",
337+
r"\.B23$",
338+
]
241339
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)
340+
if isinstance(self.calc_obj.phase, DiffpyStructureParSet):
341+
for latpar in space_group_parset.latpars:
342+
free_variables.append(latpar)
343+
for adpar in space_group_parset.adppars:
344+
free_variables.append(adpar)
345+
for xyzpar in space_group_parset.xyzpars:
346+
free_variables.append(xyzpar)
347+
elif isinstance(self.calc_obj.phase, ObjCrystCrystalParSet):
348+
for par in self.calc_obj.phase.sgpars:
349+
free_variables.append(par)
248350
for i in range(len(free_variables)):
249351
while isinstance(free_variables[i], ParameterProxy):
250352
free_variables[i] = free_variables[i].par
353+
organized_name = ".".join(
354+
[
355+
obj.name
356+
for obj in self.calc_obj._locate_managed_object(
357+
free_variables[i]
358+
)
359+
]
360+
)
361+
self.sgpar_names.append(organized_name)
251362
symmetry_par_regex = re.compile("|".join(symmetry_par_names))
252363
for par_name, par in self.parameters.items():
253364
if symmetry_par_regex.search(par_name):

0 commit comments

Comments
 (0)