pdfbl.sequential 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pdfbl/__init__.py +14 -0
- pdfbl/sequential/__init__.py +23 -0
- pdfbl/sequential/pdfadapter.py +470 -0
- pdfbl/sequential/pdfbl_sequential_app.py +25 -0
- pdfbl/sequential/sequential_cmi_runner.py +556 -0
- pdfbl/sequential/version.py +26 -0
- pdfbl_sequential-0.1.0.dist-info/METADATA +190 -0
- pdfbl_sequential-0.1.0.dist-info/RECORD +13 -0
- pdfbl_sequential-0.1.0.dist-info/WHEEL +5 -0
- pdfbl_sequential-0.1.0.dist-info/entry_points.txt +2 -0
- pdfbl_sequential-0.1.0.dist-info/licenses/AUTHORS.rst +10 -0
- pdfbl_sequential-0.1.0.dist-info/licenses/LICENSE.rst +29 -0
- pdfbl_sequential-0.1.0.dist-info/top_level.txt +1 -0
pdfbl/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
##############################################################################
|
|
3
|
+
#
|
|
4
|
+
# (c) 2025 The Trustees of Columbia University in the City of New York.
|
|
5
|
+
# All rights reserved.
|
|
6
|
+
#
|
|
7
|
+
# File coded by: Billinge Group members and community contributors.
|
|
8
|
+
#
|
|
9
|
+
# See GitHub contributions for a more detailed list of contributors.
|
|
10
|
+
# https://github.com/pdf-bl/pdfbl.sequential/graphs/contributors
|
|
11
|
+
#
|
|
12
|
+
# See LICENSE.rst for license information.
|
|
13
|
+
#
|
|
14
|
+
##############################################################################
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
##############################################################################
|
|
3
|
+
#
|
|
4
|
+
# (c) 2025 Simon Billinge.
|
|
5
|
+
# All rights reserved.
|
|
6
|
+
#
|
|
7
|
+
# File coded by: members of the Billinge Group and PDF beamline at NSLS-II.
|
|
8
|
+
#
|
|
9
|
+
# See GitHub contributions for a more detailed list of contributors.
|
|
10
|
+
# https://github.com/pdf-bl/pdfbl.sequential/graphs/contributors
|
|
11
|
+
#
|
|
12
|
+
# See LICENSE.rst for license information.
|
|
13
|
+
#
|
|
14
|
+
##############################################################################
|
|
15
|
+
"""Automated sequential refinements of PDF data."""
|
|
16
|
+
|
|
17
|
+
# package version
|
|
18
|
+
from pdfbl.sequential.version import __version__ # noqa
|
|
19
|
+
|
|
20
|
+
# silence the pyflakes syntax checker
|
|
21
|
+
assert __version__ or True
|
|
22
|
+
|
|
23
|
+
# End of file
|
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import tempfile
|
|
3
|
+
import warnings
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from queue import Queue
|
|
6
|
+
from typing import Literal
|
|
7
|
+
|
|
8
|
+
import numpy
|
|
9
|
+
from diffpy.srfit.fitbase import (
|
|
10
|
+
FitContribution,
|
|
11
|
+
FitRecipe,
|
|
12
|
+
FitResults,
|
|
13
|
+
Profile,
|
|
14
|
+
)
|
|
15
|
+
from diffpy.srfit.pdf import PDFGenerator, PDFParser
|
|
16
|
+
from diffpy.srfit.structure import constrainAsSpaceGroup
|
|
17
|
+
from diffpy.structure.parsers import getParser
|
|
18
|
+
from scipy.optimize import least_squares
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PDFAdapter:
|
|
22
|
+
"""Adapter to expose PDF fitting interface. Designed to provide a
|
|
23
|
+
simplified PDF fitting interface for human users and AI agents.
|
|
24
|
+
|
|
25
|
+
Attributes
|
|
26
|
+
----------
|
|
27
|
+
recipe : FitRecipe
|
|
28
|
+
The FitRecipe object managing the fitting process.
|
|
29
|
+
|
|
30
|
+
Methods
|
|
31
|
+
-------
|
|
32
|
+
initialize_profile(profile_path, qmin=None, qmax=None, xmin=None, xmax=None, dx=None)
|
|
33
|
+
Load and initialize the PDF profile from the given file path with
|
|
34
|
+
some optional parameters.
|
|
35
|
+
initialize_structures(structure_paths : list[str], run_parallel=True)
|
|
36
|
+
Load and initialize the structures from the given file paths, and
|
|
37
|
+
generate corresponding PDFGenerator objects.
|
|
38
|
+
initialize_contribution(equation_string=None)
|
|
39
|
+
Initialize the FitContribution object combining the PDF generators and
|
|
40
|
+
the profile.
|
|
41
|
+
initialize_recipe()
|
|
42
|
+
Initialize the FitRecipe object for the fitting process.
|
|
43
|
+
set_initial_variable_values(variable_name_to_value : dict)
|
|
44
|
+
Update parameter values from the provided dictionary.
|
|
45
|
+
refine_variables(variable_names: list[str])
|
|
46
|
+
Refine the parameters specified in the list and in that order.
|
|
47
|
+
get_variable_names()
|
|
48
|
+
Get the names of all variables in the recipe.
|
|
49
|
+
save_results(mode: str, filename: str=None)
|
|
50
|
+
Save the fitting results.
|
|
51
|
+
""" # noqa: E501
|
|
52
|
+
|
|
53
|
+
def __init__(self):
|
|
54
|
+
self.intermediate_results = {}
|
|
55
|
+
self.iter_count = 0
|
|
56
|
+
|
|
57
|
+
def monitor_intermediate_results(
|
|
58
|
+
self, key: str, step: int = 10, queue: Queue = None
|
|
59
|
+
):
|
|
60
|
+
"""Store an intermediate result during the fitting process.
|
|
61
|
+
|
|
62
|
+
Parameters
|
|
63
|
+
----------
|
|
64
|
+
key : str
|
|
65
|
+
The key to identify the intermediate result.
|
|
66
|
+
step : int
|
|
67
|
+
The step interval to store the intermediate result.
|
|
68
|
+
queue : Queue
|
|
69
|
+
The queue to store the intermediate results.
|
|
70
|
+
"""
|
|
71
|
+
if queue is None:
|
|
72
|
+
queue = Queue()
|
|
73
|
+
self.intermediate_results[(key, step)] = queue
|
|
74
|
+
|
|
75
|
+
def initialize_profile(
|
|
76
|
+
self,
|
|
77
|
+
profile_path: str,
|
|
78
|
+
qmin=None,
|
|
79
|
+
qmax=None,
|
|
80
|
+
xmin=None,
|
|
81
|
+
xmax=None,
|
|
82
|
+
dx=None,
|
|
83
|
+
):
|
|
84
|
+
"""Load and initialize the PDF profile from the given file path
|
|
85
|
+
with some optional parameters.
|
|
86
|
+
|
|
87
|
+
The target output, FitRecipe, requires a profile object, multiple
|
|
88
|
+
PDFGenerator objects, and a FitContribution object combining them. This
|
|
89
|
+
method initializes the profile object.
|
|
90
|
+
|
|
91
|
+
Parameters
|
|
92
|
+
----------
|
|
93
|
+
profile_path : str
|
|
94
|
+
The path to the experimental PDF profile file.
|
|
95
|
+
qmin : float
|
|
96
|
+
The minimum Q value for PDF calculation. The default value is
|
|
97
|
+
the one parsed from the profile file.
|
|
98
|
+
qmax : float
|
|
99
|
+
The maximum Q value for PDF calculation. The default value is the
|
|
100
|
+
one parsed from the profile file.
|
|
101
|
+
xmin : float
|
|
102
|
+
The minimum r value for PDF calculation. The default value is the
|
|
103
|
+
one parsed from the profile file.
|
|
104
|
+
xmax : float
|
|
105
|
+
The maximum r value for PDF calculation. The default value is the
|
|
106
|
+
one parsed from the profile file.
|
|
107
|
+
dx : float
|
|
108
|
+
The r step size for PDF calculation. The default value is the
|
|
109
|
+
one parsed from the profile file.
|
|
110
|
+
"""
|
|
111
|
+
profile = Profile()
|
|
112
|
+
parser = PDFParser()
|
|
113
|
+
parser.parseString(Path(profile_path).read_text())
|
|
114
|
+
profile.loadParsedData(parser)
|
|
115
|
+
if qmin:
|
|
116
|
+
profile.meta["qmin"] = qmin
|
|
117
|
+
if qmax:
|
|
118
|
+
profile.meta["qmax"] = qmax
|
|
119
|
+
profile.setCalculationRange(xmin=xmin, xmax=xmax, dx=dx)
|
|
120
|
+
self.profile = profile
|
|
121
|
+
|
|
122
|
+
def initialize_structures(
|
|
123
|
+
self, structure_paths: list[str], run_parallel=True
|
|
124
|
+
):
|
|
125
|
+
"""Load and initialize the structures from the given file paths,
|
|
126
|
+
and generate corresponding PDFGenerator objects.
|
|
127
|
+
|
|
128
|
+
The target output, FitRecipe, requires a profile object, multiple
|
|
129
|
+
PDFGenerator objects, and a FitContribution object combining them. This
|
|
130
|
+
method creates the PDFGenerator objects from the structure files.
|
|
131
|
+
|
|
132
|
+
Must be called after initialize_profile.
|
|
133
|
+
|
|
134
|
+
Parameters
|
|
135
|
+
----------
|
|
136
|
+
structure_paths : list of str
|
|
137
|
+
The list of paths to the structure files (CIF format).
|
|
138
|
+
|
|
139
|
+
Notes
|
|
140
|
+
-----
|
|
141
|
+
Planned features:
|
|
142
|
+
- Support cif file manipulation.
|
|
143
|
+
- Add/Remove atoms.
|
|
144
|
+
- symmetry operations?
|
|
145
|
+
"""
|
|
146
|
+
if isinstance(structure_paths, str):
|
|
147
|
+
structure_paths = [structure_paths]
|
|
148
|
+
structures = []
|
|
149
|
+
spacegroups = []
|
|
150
|
+
pdfgenerators = []
|
|
151
|
+
if run_parallel:
|
|
152
|
+
try:
|
|
153
|
+
import multiprocessing
|
|
154
|
+
from multiprocessing import Pool
|
|
155
|
+
|
|
156
|
+
import psutil
|
|
157
|
+
|
|
158
|
+
syst_cores = multiprocessing.cpu_count()
|
|
159
|
+
cpu_percent = psutil.cpu_percent()
|
|
160
|
+
avail_cores = numpy.floor(
|
|
161
|
+
(100 - cpu_percent) / (100.0 / syst_cores)
|
|
162
|
+
)
|
|
163
|
+
ncpu = int(numpy.max([1, avail_cores]))
|
|
164
|
+
pool = Pool(processes=ncpu)
|
|
165
|
+
self.pool = pool
|
|
166
|
+
except ImportError:
|
|
167
|
+
warnings.warn(
|
|
168
|
+
"\nYou don't appear to have the necessary packages for "
|
|
169
|
+
"parallelization. Proceeding without parallelization."
|
|
170
|
+
)
|
|
171
|
+
run_parallel = False
|
|
172
|
+
for i, structure_path in enumerate(structure_paths):
|
|
173
|
+
stru_parser = getParser("cif")
|
|
174
|
+
structure = stru_parser.parse(Path(structure_path).read_text())
|
|
175
|
+
sg = getattr(stru_parser, "spacegroup", None)
|
|
176
|
+
spacegroup = sg.short_name if sg is not None else "P1"
|
|
177
|
+
structures.append(structure)
|
|
178
|
+
spacegroups.append(spacegroup)
|
|
179
|
+
pdfgenerator = PDFGenerator(f"G{i+1}")
|
|
180
|
+
pdfgenerator.setStructure(structure)
|
|
181
|
+
if run_parallel:
|
|
182
|
+
pdfgenerator.parallel(ncpu=ncpu, mapfunc=self.pool.map)
|
|
183
|
+
pdfgenerators.append(pdfgenerator)
|
|
184
|
+
self.spacegroups = spacegroups
|
|
185
|
+
self.pdfgenerators = pdfgenerators
|
|
186
|
+
|
|
187
|
+
def initialize_contribution(self, equation_string=None):
|
|
188
|
+
"""Initialize the FitContribution object combining the PDF
|
|
189
|
+
generators and the profile.
|
|
190
|
+
|
|
191
|
+
The target output, FitRecipe, requires a profile object, multiple
|
|
192
|
+
PDFGenerator objects, and a FitContribution object combining them. This
|
|
193
|
+
method creates the FitContribution object combining the profile and PDF
|
|
194
|
+
generators.
|
|
195
|
+
|
|
196
|
+
Must be called after initialize_profile and initialize_structures.
|
|
197
|
+
|
|
198
|
+
Parameters
|
|
199
|
+
----------
|
|
200
|
+
equation_string : str
|
|
201
|
+
The equation string defining the contribution. The default
|
|
202
|
+
equation will be generated based on the number of phases.
|
|
203
|
+
e.g.
|
|
204
|
+
for one phase: "s0*G1",
|
|
205
|
+
for two phases: "s0*(s1*G1+(1-s1)*G2)",
|
|
206
|
+
for three phases: "s0*(s1*G1+s2*G2+(1-(s1+s2))*G3)",
|
|
207
|
+
...
|
|
208
|
+
|
|
209
|
+
Notes
|
|
210
|
+
-----
|
|
211
|
+
Planned features:
|
|
212
|
+
- Support registerFunction for custom equations.
|
|
213
|
+
"""
|
|
214
|
+
contribution = FitContribution("pdfcontribution")
|
|
215
|
+
contribution.setProfile(self.profile)
|
|
216
|
+
for pdfgenerator in self.pdfgenerators:
|
|
217
|
+
contribution.addProfileGenerator(pdfgenerator)
|
|
218
|
+
number_of_phase = len(self.pdfgenerators)
|
|
219
|
+
if equation_string is None:
|
|
220
|
+
if number_of_phase == 1:
|
|
221
|
+
equation_string = "s0*G1"
|
|
222
|
+
else:
|
|
223
|
+
equation_string = (
|
|
224
|
+
"s0*("
|
|
225
|
+
+ "+".join(
|
|
226
|
+
[f"s{i+1}*G{i+1}" for i in range(number_of_phase - 1)]
|
|
227
|
+
)
|
|
228
|
+
+ f"+(1-({'+'.join([f's{i+1}' for i in range(1, number_of_phase)])}))*G{number_of_phase}" # noqa: E501
|
|
229
|
+
+ ")"
|
|
230
|
+
)
|
|
231
|
+
contribution.setEquation(equation_string)
|
|
232
|
+
self.contribution = contribution
|
|
233
|
+
return self.contribution
|
|
234
|
+
|
|
235
|
+
def initialize_recipe(
|
|
236
|
+
self,
|
|
237
|
+
):
|
|
238
|
+
"""Initialize the FitRecipe object for the fitting process.
|
|
239
|
+
|
|
240
|
+
The target output, FitRecipe, requires a profile object, multiple
|
|
241
|
+
PDFGenerator objects, and a FitContribution object combining them. This
|
|
242
|
+
method creates the FitRecipe object combining the profile, PDF
|
|
243
|
+
generators, and contribution.
|
|
244
|
+
|
|
245
|
+
Must be called after initialize_contribution.
|
|
246
|
+
|
|
247
|
+
Notes
|
|
248
|
+
-----
|
|
249
|
+
Planned features:
|
|
250
|
+
- support instructions to
|
|
251
|
+
- add variables
|
|
252
|
+
- constrain variables of the scatters
|
|
253
|
+
- change symmetry constraints
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
def modify_xyz_adp_name(parname, nth_phase):
|
|
257
|
+
parname, nth_atom = parname.split("_")
|
|
258
|
+
return f"{parname}_phase_{nth_phase+1}_atom_{int(nth_atom)+1}"
|
|
259
|
+
|
|
260
|
+
def modify_lat_delta_name(parname, nth_phase):
|
|
261
|
+
return f"{parname}_phase_{nth_phase+1}"
|
|
262
|
+
|
|
263
|
+
recipe = FitRecipe()
|
|
264
|
+
recipe.addContribution(self.contribution)
|
|
265
|
+
qdamp = recipe.newVar("qdamp", fixed=False, value=0.04)
|
|
266
|
+
qbroad = recipe.newVar("qbroad", fixed=False, value=0.02)
|
|
267
|
+
for i, (pdfgenerator, spacegroup) in enumerate(
|
|
268
|
+
zip(self.pdfgenerators, self.spacegroups)
|
|
269
|
+
):
|
|
270
|
+
for pname in [
|
|
271
|
+
"delta1",
|
|
272
|
+
"delta2",
|
|
273
|
+
]:
|
|
274
|
+
par = getattr(pdfgenerator, pname)
|
|
275
|
+
recipe.addVar(
|
|
276
|
+
par, name=modify_lat_delta_name(pname, i), fixed=False
|
|
277
|
+
)
|
|
278
|
+
if len(self.pdfgenerators) > 1:
|
|
279
|
+
recipe.addVar(
|
|
280
|
+
getattr(self.contribution, f"s{i+1}"),
|
|
281
|
+
name=f"s{i+1}",
|
|
282
|
+
fixed=False,
|
|
283
|
+
)
|
|
284
|
+
recipe.restrain(f"s{i+1}", lb=0.0, ub=1.0)
|
|
285
|
+
recipe.constrain(pdfgenerator.qdamp, qdamp)
|
|
286
|
+
recipe.constrain(pdfgenerator.qbroad, qbroad)
|
|
287
|
+
stru_parset = pdfgenerator.phase
|
|
288
|
+
spacegroupparams = constrainAsSpaceGroup(stru_parset, spacegroup)
|
|
289
|
+
for par in spacegroupparams.xyzpars:
|
|
290
|
+
recipe.addVar(
|
|
291
|
+
par, name=modify_xyz_adp_name(par.name, i), fixed=False
|
|
292
|
+
)
|
|
293
|
+
for par in spacegroupparams.latpars:
|
|
294
|
+
recipe.addVar(
|
|
295
|
+
par, name=modify_lat_delta_name(par.name, i), fixed=False
|
|
296
|
+
)
|
|
297
|
+
for par in spacegroupparams.adppars:
|
|
298
|
+
recipe.addVar(
|
|
299
|
+
par, name=modify_xyz_adp_name(par.name, i), fixed=False
|
|
300
|
+
)
|
|
301
|
+
recipe.addVar(self.contribution.s0, name="s0", fixed=False)
|
|
302
|
+
recipe.fix("all")
|
|
303
|
+
recipe.fithooks[0].verbose = 0
|
|
304
|
+
self.recipe = recipe
|
|
305
|
+
|
|
306
|
+
def set_initial_variable_values(self, variable_name_to_value: dict):
|
|
307
|
+
"""Update parameter values from the provided dictionary.
|
|
308
|
+
|
|
309
|
+
Parameters
|
|
310
|
+
----------
|
|
311
|
+
variable_name_to_value : dict
|
|
312
|
+
A dictionary mapping variable names to their new values.
|
|
313
|
+
"""
|
|
314
|
+
for vname, vvalue in variable_name_to_value.items():
|
|
315
|
+
self.recipe._parameters[vname].setValue(vvalue)
|
|
316
|
+
|
|
317
|
+
def residual(self, p=[]):
|
|
318
|
+
"""Wrapper for the recipe residual function to store
|
|
319
|
+
intermediate results if needed.
|
|
320
|
+
|
|
321
|
+
Parameters
|
|
322
|
+
----------
|
|
323
|
+
p : list
|
|
324
|
+
List of parameter values.
|
|
325
|
+
|
|
326
|
+
Returns
|
|
327
|
+
-------
|
|
328
|
+
numpy.ndarray
|
|
329
|
+
The residual array.
|
|
330
|
+
"""
|
|
331
|
+
residual = self.recipe.residual(p)
|
|
332
|
+
fitresults_dict = None
|
|
333
|
+
for (key, step), values in self.intermediate_results.items():
|
|
334
|
+
if (self.iter_count % step) == 0:
|
|
335
|
+
if fitresults_dict is None:
|
|
336
|
+
fitresults_dict = self.save_results(mode="dict")
|
|
337
|
+
value = fitresults_dict.get(key, None)
|
|
338
|
+
if value is None:
|
|
339
|
+
raise KeyError(
|
|
340
|
+
f"{key} is not found in the fit results. "
|
|
341
|
+
f"Available keys are: {list(fitresults_dict.keys())}"
|
|
342
|
+
)
|
|
343
|
+
values.put(value)
|
|
344
|
+
self.iter_count += 1
|
|
345
|
+
return residual
|
|
346
|
+
|
|
347
|
+
def refine_variables(self, variable_names: list[str]):
|
|
348
|
+
"""Refine the parameters specified in the list and in that
|
|
349
|
+
order. Must be called after initialize_recipe.
|
|
350
|
+
|
|
351
|
+
Parameters
|
|
352
|
+
----------
|
|
353
|
+
variable_names : list of str
|
|
354
|
+
The names of the variables to refine.
|
|
355
|
+
"""
|
|
356
|
+
for vname in variable_names:
|
|
357
|
+
if vname not in self.recipe._parameters:
|
|
358
|
+
raise ValueError(
|
|
359
|
+
f"Variable {vname} not found in the recipe. "
|
|
360
|
+
"Please choose from the existing variables: "
|
|
361
|
+
f"{list(self.recipe._parameters.keys())}"
|
|
362
|
+
)
|
|
363
|
+
for vname in variable_names:
|
|
364
|
+
self.recipe.free(vname)
|
|
365
|
+
least_squares(
|
|
366
|
+
self.residual,
|
|
367
|
+
self.recipe.values,
|
|
368
|
+
x_scale="jac",
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
def get_variable_names(self) -> list[str]:
|
|
372
|
+
"""Get the names of all variables in the recipe.
|
|
373
|
+
|
|
374
|
+
Returns
|
|
375
|
+
-------
|
|
376
|
+
list of str
|
|
377
|
+
A list of variable names.
|
|
378
|
+
"""
|
|
379
|
+
return list(self.recipe._parameters.keys())
|
|
380
|
+
|
|
381
|
+
def save_results(
|
|
382
|
+
self, mode: Literal["str", "dict"] = "str", filename=None
|
|
383
|
+
):
|
|
384
|
+
"""Save the fitting results. Must be called after
|
|
385
|
+
refine_variables.
|
|
386
|
+
|
|
387
|
+
Parameters
|
|
388
|
+
----------
|
|
389
|
+
mode : str
|
|
390
|
+
The format to save the results. Options are:
|
|
391
|
+
"str" - Save results as a formatted text string.
|
|
392
|
+
"dict" - Save results as a JSON-compatible dictionary.
|
|
393
|
+
filename : str
|
|
394
|
+
The path to the output file. If None, results will not be saved to
|
|
395
|
+
a file.
|
|
396
|
+
|
|
397
|
+
Returns
|
|
398
|
+
-------
|
|
399
|
+
str or dict
|
|
400
|
+
The fitting results in the specified format.
|
|
401
|
+
"""
|
|
402
|
+
fit_results = FitResults(self.recipe)
|
|
403
|
+
if mode == "str":
|
|
404
|
+
if filename is None:
|
|
405
|
+
tmp_directory = tempfile.TemporaryDirectory()
|
|
406
|
+
temp_file = Path(tmp_directory.name) / "data.txt"
|
|
407
|
+
filename = str(temp_file)
|
|
408
|
+
fit_results.saveResults(filename)
|
|
409
|
+
with open(filename, "r") as f:
|
|
410
|
+
results_str = f.read()
|
|
411
|
+
if filename is None:
|
|
412
|
+
tmp_directory.cleanup()
|
|
413
|
+
return results_str
|
|
414
|
+
|
|
415
|
+
elif mode == "dict":
|
|
416
|
+
results_dict = {}
|
|
417
|
+
results_dict["residual"] = fit_results.residual
|
|
418
|
+
results_dict["contributions"] = (
|
|
419
|
+
fit_results.residual - fit_results.penalty
|
|
420
|
+
)
|
|
421
|
+
results_dict["restraints"] = fit_results.penalty
|
|
422
|
+
results_dict["chi2"] = fit_results.chi2
|
|
423
|
+
results_dict["reduced_chi2"] = fit_results.rchi2
|
|
424
|
+
results_dict["rw"] = fit_results.rw
|
|
425
|
+
# variables
|
|
426
|
+
results_dict["variables"] = {}
|
|
427
|
+
for name, val, unc in zip(
|
|
428
|
+
fit_results.varnames, fit_results.varvals, fit_results.varunc
|
|
429
|
+
):
|
|
430
|
+
results_dict["variables"][name] = {
|
|
431
|
+
"value": val,
|
|
432
|
+
"uncertainty": unc,
|
|
433
|
+
}
|
|
434
|
+
# fixed variables
|
|
435
|
+
results_dict["fixed_variables"] = {}
|
|
436
|
+
if fit_results.fixednames is not None:
|
|
437
|
+
for name, val in zip(
|
|
438
|
+
fit_results.fixednames, fit_results.fixedvals
|
|
439
|
+
):
|
|
440
|
+
results_dict["fixed_variables"][name] = {"value": val}
|
|
441
|
+
# constraints
|
|
442
|
+
results_dict["constraints"] = {}
|
|
443
|
+
if fit_results.connames and fit_results.showcon:
|
|
444
|
+
for con in fit_results.conresults.values():
|
|
445
|
+
for i, loc in enumerate(con.conlocs):
|
|
446
|
+
names = [obj.name for obj in loc]
|
|
447
|
+
name = ".".join(names)
|
|
448
|
+
val = con.convals[i]
|
|
449
|
+
unc = con.conuncs[i]
|
|
450
|
+
results_dict["constraints"][name] = {
|
|
451
|
+
"value": val,
|
|
452
|
+
"uncertainty": unc,
|
|
453
|
+
}
|
|
454
|
+
# covariance matrix
|
|
455
|
+
results_dict["covariance_matrix"] = fit_results.cov.tolist()
|
|
456
|
+
# certainty
|
|
457
|
+
certain = True
|
|
458
|
+
for con in fit_results.conresults.values():
|
|
459
|
+
if (con.dy == 1).all():
|
|
460
|
+
certain = False
|
|
461
|
+
results_dict["certain"] = certain
|
|
462
|
+
if filename is not None:
|
|
463
|
+
with open(filename, "w") as f:
|
|
464
|
+
json.dump(results_dict, f, indent=2)
|
|
465
|
+
return results_dict
|
|
466
|
+
|
|
467
|
+
else:
|
|
468
|
+
raise ValueError(
|
|
469
|
+
f"Unsupported mode: {mode}. Please use 'json' or 'txt'."
|
|
470
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
from pdfbl.sequential import __version__
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def main():
|
|
7
|
+
"""Entry point for the pdfbl-cli.
|
|
8
|
+
|
|
9
|
+
Examples
|
|
10
|
+
--------
|
|
11
|
+
>>> pdfbl-cli --version
|
|
12
|
+
"""
|
|
13
|
+
parser = argparse.ArgumentParser(
|
|
14
|
+
description=(
|
|
15
|
+
"Scripts for running sequential PDF refinements "
|
|
16
|
+
"using diffpy.cmi automatically"
|
|
17
|
+
)
|
|
18
|
+
)
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
"--version",
|
|
21
|
+
action="version",
|
|
22
|
+
version=f"pdfbl.sequential {__version__}",
|
|
23
|
+
help="Show the version of pdfbl.sequential and exit.",
|
|
24
|
+
)
|
|
25
|
+
parser.parse_args()
|