Skip to content

Commit 58421db

Browse files
committed
feat: implement minimal refinement functionalities
1 parent 460d1df commit 58421db

6 files changed

Lines changed: 181 additions & 134 deletions

File tree

src/diffpy/apps/refinebase/parsers.py renamed to src/diffpy/apps/refinebase/refinable_model.py

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
11
from pathlib import Path
22

3+
from diffpy.srfit.fitbase import Profile
34
from diffpy.srfit.fitbase.parameter import Parameter
45
from diffpy.srfit.fitbase.parameterset import ParameterSet
56
from diffpy.srfit.pdf.pdfparser import PDFParser
67
from diffpy.srfit.structure import struToParameterSet
78
from diffpy.structure import Structure
89

910

11+
class RefinenableModel(ParameterSet):
12+
"""
13+
A RefinenableModel instance provides access to both the ParameterSet
14+
and the underlying model object.
15+
"""
16+
17+
def __init__(self, name):
18+
super().__init__(name=name)
19+
self.model = None
20+
self.parameters = None
21+
22+
1023
class ParameterParser:
1124
def __init__(self):
1225
pass
@@ -28,15 +41,22 @@ def _parse_pdf(
2841
pass
2942
parser = PDFParser()
3043
parser.parseFile(profile_file)
44+
profile = Profile()
45+
profile.loadParsedData(parser)
3146
parameter_values = parser.getData()
3247
parameter_names = ["x", "y", "dx", "dy"]
3348
parameter_set = _construct_parameterset(
3449
parameter_names, parameter_values, parset_name
3550
)
51+
profile_model = RefinenableModel(name=parset_name)
52+
profile_model.model = profile
53+
profile_model.parameters = parameter_set
3654
meta = dict(parser.getMetaData())
37-
return parameter_set, meta
55+
return profile_model, meta
3856

39-
def _parse_structure(self, structure_file: str):
57+
def _parse_structure(
58+
self, structure_file: str, parset_name: str = "structure_parameterset"
59+
):
4060
if not Path(structure_file).exists():
4161
raise FileNotFoundError(
4262
self.MISSING_FILE_WARNING.format(
@@ -45,35 +65,16 @@ def _parse_structure(self, structure_file: str):
4565
)
4666
stru = Structure()
4767
stru.read(structure_file)
48-
structure_parameterset = struToParameterSet(
49-
"structure_parameterset", stru
68+
stru_diffpy_parset = struToParameterSet(parset_name, stru)
69+
structure_mdel = RefinenableModel(name=parset_name)
70+
structure_mdel.model = stru
71+
structure_mdel.parameters = ParameterSet(name=parset_name)
72+
structure_mdel.parameters.addParameterSet(
73+
stru_diffpy_parset.getLattice()
5074
)
51-
return structure_parameterset
52-
53-
54-
class ParameterAdapter:
55-
"""
56-
A ParameterAdapter class contains methods to adapt parameters of
57-
variables format to the standard format used in diffpy
58-
"""
59-
60-
def __init__(self):
61-
pass
62-
63-
def _adapt_string(self, parameter: str):
64-
pass
65-
66-
def _adapt_code(self, function_code: str):
67-
pass
68-
69-
def _adapt_special(self, special_parameter: str):
70-
pass
71-
72-
def _adapt_structure_file(self, phase_file: str):
73-
pass
74-
75-
def _adapt_profile_file(self, profile_file: str):
76-
pass
75+
for atom_parset in stru_diffpy_parset.getScatterers():
76+
structure_mdel.parameters.addParameterSet(atom_parset)
77+
return structure_mdel
7778

7879

7980
def _construct_parameterset(names, values, parset_name):

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

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
from collections import OrderedDict
2+
3+
from scipy.optimize import leastsq
4+
5+
from diffpy.srfit.fitbase import FitContribution, FitRecipe, Profile
16
from diffpy.srfit.fitbase.parameterset import ParameterSet
27

38

4-
class RefinementSession(ParameterSet):
9+
class RefinementSession:
510
"""
611
A refinement session class that manages the refinement process.
712
@@ -25,23 +30,51 @@ class RefinementSession(ParameterSet):
2530
"""
2631

2732
def __init__(self):
28-
self.loss_functions = []
29-
self.loss_function = None
33+
self.main_parameter_set = ParameterSet(name="main_parameter_set")
34+
self.calculators = []
35+
self.contributions = OrderedDict()
36+
37+
def add_parameterSet(self, parset):
38+
"""Add a ParameterSet to the refinement session."""
39+
self.main_parameter_set.addParameterSet(parset)
3040

31-
def _prepare_variables(self):
32-
"""Prepare variables for refinement.
41+
def add_parameter(self, parameter):
42+
"""Add a Parameter to the refinement session."""
43+
self.main_parameter_set.addParameter(parameter)
3344

34-
Convert the literals into builders before make the equation"""
45+
def add_function(self, name, expression=None, ns={}):
3546
pass
3647

37-
def register_loss_function(self):
38-
"""Register the loss function for refinement."""
48+
def add_calculator(self, calculator):
3949
pass
4050

41-
def set_master_loss_function(self, name):
42-
"""set the master loss function for refinement."""
43-
self.master_loss_function = name
51+
def add_profile(self, name=None, profile=None, x=None, y=None, dy=None):
52+
if profile is None:
53+
profile = Profile()
54+
profile.x = x
55+
profile.y = y
56+
if dy is not None:
57+
profile.dy = dy
58+
contribution = FitContribution(name=name)
59+
contribution.setProfile(profile, xname=profile.x.name)
60+
self.contributions[name] = contribution
4461

45-
def refine(self):
46-
"""Perform the refinement."""
47-
pass
62+
def set_profile_equation(self, profile_name, expression):
63+
self.contributions[profile_name].setEquation(expression)
64+
65+
def set_profile_weights(self, names, weights):
66+
self.recipe = FitRecipe()
67+
for name, weight in zip(names, weights):
68+
self.recipe.addContribution(
69+
self.contributions[name], weight=weight
70+
)
71+
72+
def refine(self, var_names, initial_values):
73+
for name, value in zip(var_names, initial_values):
74+
self.main_parameter_set.parameters[name].setValue(value)
75+
for name in var_names:
76+
self.recipe.addVar(self.main_parameter_set.parameters[name])
77+
self.recipe.fix("all")
78+
for name in var_names:
79+
self.recipe.free(name)
80+
leastsq(self.recipe.residual, self.recipe.values)

‎tests/refinebase/test_parsers.py‎

Lines changed: 0 additions & 63 deletions
This file was deleted.

‎tests/refinebase/test_refinement_session.py‎

Lines changed: 0 additions & 27 deletions
This file was deleted.

‎tests/test_parsers.py‎

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from diffpy.apps.refinebase.refinable_model import (
2+
ParameterParser,
3+
)
4+
5+
6+
def test_parse_pdf():
7+
# C1: reading a valid .gr file
8+
# Expect return valid ParameterSet and meta data
9+
profile_path = "tests/data/Ni.gr"
10+
parser = ParameterParser()
11+
profile_model, meta = parser._parse_pdf(
12+
profile_path, parset_name="profile_parameterset"
13+
)
14+
expected_parameter_names = ["x", "y", "dx", "dy"]
15+
actual_parameter_names = [par.name for par in profile_model.parameters]
16+
assert set(actual_parameter_names) == set(expected_parameter_names)
17+
expected_meta = {
18+
"stype": "X",
19+
"qmin": 0.5,
20+
"qmax": 25.0,
21+
"filename": "tests/data/Ni.gr",
22+
"bank": 0,
23+
"nbanks": 1,
24+
}
25+
actual_meta = meta
26+
assert actual_meta == expected_meta
27+
28+
29+
def test_parse_structure():
30+
# C1: reading a valid .cif file
31+
# Expect return valid ParameterSet
32+
structure_path = "tests/data/Ni.cif"
33+
parser = ParameterParser()
34+
structure_model = parser._parse_structure(structure_path)
35+
expected_parameter_names = ["a", "b", "c", "alpha", "beta", "gamma"]
36+
actual_parameter_names = [
37+
name
38+
for name in structure_model.parameters._parsets[
39+
"lattice"
40+
]._parameters.keys()
41+
]
42+
assert set(actual_parameter_names) == set(expected_parameter_names)
43+
expected_atom_parameter_names = [
44+
"x",
45+
"y",
46+
"z",
47+
"Uiso",
48+
"Biso",
49+
"occ",
50+
"occupancy",
51+
]
52+
expected_atom_parameter_names += [
53+
f"U{j+1}{k+1}" for j in range(3) for k in range(3)
54+
]
55+
expected_atom_parameter_names += [
56+
f"B{j+1}{k+1}" for j in range(3) for k in range(3)
57+
]
58+
for i in range(len(structure_model.model)):
59+
actual_atom_parameter_names = [
60+
name
61+
for name in structure_model.parameters._parsets[
62+
f"Ni{i}"
63+
]._parameters.keys()
64+
]
65+
assert set(actual_atom_parameter_names) == set(
66+
expected_atom_parameter_names
67+
)

‎tests/test_refinement_session.py‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import numpy
2+
3+
from diffpy.apps.refinebase.refinement_session import RefinementSession
4+
5+
6+
def test_refinement_session():
7+
# C1: Refinement session without additional calculator or functions
8+
session = RefinementSession()
9+
xobs = numpy.linspace(-numpy.pi, numpy.pi, 100)
10+
yobs = numpy.sin(xobs) + 0.1 * numpy.random.normal(size=xobs.shape)
11+
session.add_profile(name="profile", x=xobs, y=yobs)
12+
session.add_parameter(name="x", delegates_to="profile.x")
13+
session.set_profile_equation(profile="profile", expression="A*sin(a*x)")
14+
session.set_profile_weights(["profile"], [1.0])
15+
session.refine(name=["A", "a"], initial_values=[1.0, 0.5])
16+
17+
# C2: Refinement session with one PDFCalculator
18+
# parser = ParameterParser()
19+
# profile_path = "tests/data/Ni.gr"
20+
# structure_path = "tests/data/Ni.cif"
21+
# profile_model, profile_meta = parser._parse_pdf(profile_path)
22+
# structure_model = parser._parse_structure(structure_path)
23+
24+
# session = RefinementSession()
25+
# session.addParameterSet(profile_model.parameters)
26+
# session.addParameterSet(structure_model.parameters)
27+
# session.addParameter(
28+
# ParameterAdapter(
29+
# name="g1",
30+
# callable=lambda: PDFCalculator(structure_model.model)[1]
31+
# )
32+
# )
33+
# session.addParameter(ParameterAdapter(name="s1", value=1.0))
34+
# session.register_loss_function(name="l1", expression="s1*g1")
35+
# session.set_master_loss_function(name="l1")
36+
# session.refine()

0 commit comments

Comments
 (0)